import os
import shutil
import pytest
from collections import OrderedDict
from calculate.parameters.parameters import BaseParameter, Integer, String,\
                                            Description, Parameters,\
                                            ValidationError, Choice, Bool,\
                                            List, CyclicValidationError,\
                                            ParameterError, Table, TableValue
from calculate.variables.loader import Datavars


TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/parameters/testfiles')
GENTOO_PATH = os.path.join(TESTFILES_PATH, 'gentoo')


@pytest.mark.parameters
class TestParameters:
    def test_to_make_testfiles(self):
        shutil.copytree(os.path.join(TESTFILES_PATH, 'gentoo.backup'),
                        os.path.join(TESTFILES_PATH, 'gentoo'), symlinks=True)

    def test_if_some_parameters_are_created_using_classes_inheriting_the_BaseParameter_class_and_its_types_is_set_in_the_parameter_s_classes__the_parameters_can_be_created_using_this_classes_and_their_instances_types_are_types_from_their_classes(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))

        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class MyShinyParameter(BaseParameter):
            type = Integer()

            def validate(self, var):
                if var.value < 10:
                    raise ValidationError("The value must be greater than 10")

            def bind_method(self, var):
                return var.value, None

        class AnotherParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return 'default string', None

            def validate(self, value):
                if not value.startswith('/var/lib'):
                    raise ValidationError("The value must starts with a"
                                          " '/var/lib'")

        class OneMoreParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return 'default string', None

            def validate(self, value):
                available_set = {'mystery', 'horror', 'weird'}
                if value not in available_set:
                    raise ValidationError(f"The value '{value}' is not in"
                                          f" available. Available values:"
                                          f" {', '.join(available_set)}")

        # Создание параметров данных типов.
        PARAMS.add(
            MyShinyParameter('my-shiny', 'The Shiniest ones',
                             Description(
                                   short='The shiny parameter',
                                   full='The shiniest thing known to science.',
                                   usage='-S COUNT, --my-shiny COUNT'),
                             shortname='S').bind('os.linux.test_5'),
            AnotherParameter('my-other', 'The Others',
                             Description(short='Not from this world.'),
                             shortname='o'),
            OneMoreParameter('one-more', 'The Others',
                             Description(full='Plan 9 from outer space.')))

        class AnotherParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return 'default string', None

            def validate(self, value):
                if not value.startswith('/var/lib'):
                    raise ValidationError("The value must starts with a"
                                          " '/var/lib'")

        shiny_one, another_one, weird_one = PARAMS

        shiny_one.set(9)
        assert type(shiny_one._parameter_type) == Integer

        another_one.set('/var/lib/bla')
        assert type(another_one._parameter_type) == String

        weird_one.set('weird')
        assert type(weird_one._parameter_type) == String

    def test_if_parameter_is_created_with_bind_method_with_a_variable_in_its_arguments__the_default_parameter_value_is_calculated_using_this_method_and_a_variable_from_arguments_can_invalidate_the_parameter(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))

        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class TestParameter(BaseParameter):
            type = String()

            def bind_method(self, fullname):
                return fullname.value, None

        # Создание параметров данных типов.
        PARAMS.add(
               TestParameter('test', 'Test',
                             Description(
                                 short='Test parameter',
                                 full='Test parameters are needed for tests.'),
                             shortname='t').bind('os.linux.fullname'))

        assert PARAMS['test'] == PARAMS['t']
        assert PARAMS['test'].value == 'Calculate Linux Desktop'
        datavars.os.linux['fullname'].set('Gentoo Linux')
        assert PARAMS['test'].value == 'Gentoo Linux'

        PARAMS['test'].set('Arch Linux')
        assert PARAMS['test'].value == 'Arch Linux'

        datavars.os.linux['fullname'].set('Void Linux')
        assert PARAMS['test'].value == 'Arch Linux'

    def test_if_bind_method_is_set_for_parameter_and_then_the_set_method_is_used_to_change_value__the_parameters_value_is_changed_and_a_variable_from_a_bounded_variable_is_not_able_to_invalidate_parameter_s_value(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))

        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class TestParameter(BaseParameter):
            type = String()

            def bind_method(self, fullname):
                return fullname.value, None

        # Создание параметров данных типов.
        PARAMS.add(
               TestParameter('test', 'Test',
                             Description(
                                 short='Test parameter',
                                 full='Test parameters are needed for tests.'),
                             shortname='t').bind('os.linux.fullname'))

        assert PARAMS['test'].value == 'Calculate Linux Desktop'
        datavars.os.linux['fullname'].set('Gentoo Linux')
        assert PARAMS['test'].value == 'Gentoo Linux'

        PARAMS['test'].set('Arch Linux')
        assert PARAMS['test'].value == 'Arch Linux'

        datavars.os.linux['fullname'].set('Void Linux')
        assert PARAMS['test'].value == 'Arch Linux'

    def test_if_the_bind_method_is_set_for_two_variables__the_bind_method_calculates_parameter_s_default_value__variables_can_invalidate_parameter_s_value_before_the_set_method_is_used__the_set_parameter_can_change_value_of_the_parameter(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))

        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class TestParameter(BaseParameter):
            type = String()

            def bind_method(self, fullname, shortname):
                return f'{fullname.value} -> {shortname.value}', None

        # Создание параметров данных типов.
        PARAMS.add(
               TestParameter('test', 'Test',
                             Description(
                                 short='Test parameter',
                                 full='Test parameters are needed for tests.'),
                             shortname='t').bind('os.linux.fullname',
                                                 'os.linux.shortname')
                                           .to_set('os.linux.subname'))

        assert PARAMS['test'].value == 'Calculate Linux Desktop -> CLD'
        datavars.os.linux['fullname'].set('Gentoo Linux')
        assert PARAMS['test'].value == 'Gentoo Linux -> CLD'
        datavars.os.linux['shortname'].set('GL')
        assert PARAMS['test'].value == 'Gentoo Linux -> GL'

        # Переменные, указанные в to_set не меняются если используется
        # дефолтное значение, рассчитанное с помощью переменных.
        assert datavars.os.linux.subname == 'KDE'

        PARAMS['test'].set('Arch Linux -> AL')
        assert PARAMS['test'].value == 'Arch Linux -> AL'

        # Теперь меняются.
        assert datavars.os.linux.subname == 'Arch Linux -> AL'

        datavars.os.linux['fullname'].set('Void Linux')
        assert PARAMS['test'].value == 'Arch Linux -> AL'

    def test_if_hash_value_is_in_the_set_variables_list__the_parameter_is_able_to_change_that_hash_s_value(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))

        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class TestParameter(BaseParameter):
            type = String()

            def bind_method(self, fullname):
                return fullname.value, True

        # Создание параметров данных типов.
        PARAMS.add(
               TestParameter('test', 'Test',
                             Description(
                                 short='Test parameter',
                                 full='Test parameters are needed for tests.'),
                             shortname='t'
                             ).bind('os.linux.fullname').to_set(
                                                          'os.linux.shortname',
                                                          'os.hashvar.value2'))

        PARAMS['test'].set('CLD')
        assert datavars.os.linux.shortname == 'CLD'
        assert datavars.os.hashvar.get_hash() == {'value1': 'new1',
                                                  'value2': 'CLD'}

        PARAMS['test'].set('CLDX')
        assert datavars.os.linux.shortname == 'CLDX'
        assert datavars.os.hashvar.get_hash() == {'value1': 'new1',
                                                  'value2': 'CLDX'}

    def test_if_bind_method_is_set_for_parameter__the_method_can_return_as_second_value_of_the_return_tuple_disactivity_comment_witch_disactivates_the_parameter_even_if_parameters_values_is_set_by_user(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))

        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class BoolTestParameter(BaseParameter):
            type = Bool()

            def bind_method(self):
                return False, None

        class StringTestParameter(BaseParameter):
            type = String()

            def bind_method(self, string, boolean):
                disactivity_comment = None
                if boolean.value:
                    disactivity_comment = "Disactivated by test bool parameter"
                return string.value, disactivity_comment

        # Создание параметров данных типов.
        PARAMS.add(
               BoolTestParameter('test-bool', 'Booleans',
                                 Description(
                                        short='Test bool parameter',
                                        full=('Test bool parameter, that can'
                                              ' disactivate other parameter')),
                                 shortname='b').to_set('os.linux.test_2'),
               StringTestParameter('test-string', 'Strings',
                                   Description(
                                            short='Test string parameter',
                                            full=('Test string parameter, that'
                                                  ' can be disactivated')),
                                   shortname='s').bind('os.linux.test_3',
                                                       'os.linux.test_2'))

        assert PARAMS['test-bool'].value is False
        assert PARAMS['test-string'].value == 'test string'

        PARAMS['test-bool'].set(True)
        assert PARAMS['test-string'].disactivity_comment ==\
            "Disactivated by test bool parameter"

        PARAMS['test-bool'].set(False)
        PARAMS['test-string'].set("My own value")
        assert PARAMS['test-string'].value == "My own value"
        assert PARAMS['test-string'].disactivity_comment is None

        PARAMS['test-bool'].set(True)
        assert PARAMS['test-string'].value == "My own value"
        assert PARAMS['test-string'].disactivity_comment ==\
            "Disactivated by test bool parameter"

    def test_Choice_type_is_set_for_parameter_and_bind_method_is_set_too__the_bind_method_can_define_available_values_for_the_choice_parameter__choice_availables_is_invalidatable_even_if_the_value_is_set_by_user__choice_type_checks_if_the_new_value_is_the_availables_list(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class TestParameter(BaseParameter):
            type = Choice()

            def bind_method(self, choices_list):
                choices_list = choices_list.value
                for choice in choices_list:
                    self.choices.update({choice: f'Comment for {choice}'})
                return choices_list[0], None

        # Создание параметров данных типов.
        PARAMS.add(
               TestParameter('test', 'Test',
                             Description(
                                 short='Test parameter',
                                 full='Test parameters are needed for tests.'),
                             shortname='t').bind('os.linux.test_1'))

        assert PARAMS['test'].value == 'choice_1'
        PARAMS['test'].set(PARAMS['test'].check_value_type('choice_2'))
        assert PARAMS['test'].value == 'choice_2'

        with pytest.raises(ValidationError):
            PARAMS['test'].set(PARAMS['test'].check_value_type('unavailable'))

    def test_if_some_parameters_are_created_with_some_types_and_there_are_attempts_to_assign_some_values_which_type_is_not_correct_to_this_parameters__the_parameters_types_checks_this_values_and_raises_ValidationError_exception(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Choice()

            def bind_method(self, choices_list):
                choices_list = choices_list.value
                for choice in choices_list:
                    self.choices.update({choice: f'Comment for {choice}'})
                return choices_list[0], None

        class SecondTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        class ThirdTestParameter(BaseParameter):
            type = List()

            def bind_method(self):
                return [1, 2, 3, 4], None

        # Создание параметров данных типов.
        PARAMS.add(
                 FirstTestParameter('test_1', 'Test',
                                    Description(
                                           short='Test choice',
                                           full=('Test parameters are'
                                                 ' needed for tests.'))
                                    ).bind('os.linux.test_1'),
                 SecondTestParameter('test_2', 'Test',
                                     Description(short='Test choice',
                                                 full=('Test parameters are'
                                                       ' needed for tests.'))),
                 ThirdTestParameter('test_3', 'Test',
                                    Description(short='Test list',
                                                full=('Test parameters are'
                                                      ' needed for tests.'))))

        with pytest.raises(ValidationError):
            PARAMS.set_parameters({'test_1': 'unavailable'})

        with pytest.raises(ValidationError):
            PARAMS.set_parameters({'test_2': 123})

        with pytest.raises(ValidationError):
            PARAMS.set_parameters({'test_3': 'suspicious value'})

    def test_if_some_parameters_is_created_with_the_validate_methods__the_validate_methods_are_used_for_validating_all_values_that_is_set_to_the_parameter(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Choice()

            def bind_method(self, choices_list):
                choices_list = choices_list.value
                for choice in choices_list:
                    self.choices.update({choice: f'Comment for {choice}'})
                return choices_list[0], None

            def validate(self, parameters, datavars, value):
                if datavars.os.linux.test_2 and bool(int(value[-1]) % 2):
                    raise ValidationError('Choice must be with even number.')
                elif not datavars.os.linux.test_2 and not bool(
                                                           int(value[-1]) % 2):
                    raise ValidationError('Choice must be with odd number.')
                print(f'VALIDATED: {self._name}')

        class SecondTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

            def validate(self, parameters, datavars, value):
                if (bool(int(parameters['test-1'].value[-1]) % 2) and
                   value != 'odd'):
                    raise ValidationError('Value must be "odd".')
                elif (not bool(int(parameters['test-1'].value[-1]) % 2) and
                      value != 'even'):
                    raise ValidationError('Value must be "even".')
                elif value not in {'odd', 'even'}:
                    raise ValidationError('Value must be "odd" or "value"')
                print(f'VALIDATED: {self._name}')

        class ThirdTestParameter(BaseParameter):
            type = List()

            def bind_method(self):
                return ['/dev/sda1'], None

            def validate(self, parameters, datavars, value):
                prefix = datavars.os.linux.test_4
                for device in value:
                    if not device.startswith(prefix):
                        raise ValidationError("Partitions must be from the"
                                              " '{prefix}' device")
                print(f'VALIDATED: {self._name}')

        # Создание параметров данных типов.
        PARAMS.add(
                 FirstTestParameter('test-1', 'Test',
                                    Description(
                                           short='Test choice',
                                           full=('Test parameters are'
                                                 ' needed for tests.'))
                                    ).bind('os.linux.test_1'),
                 SecondTestParameter('test-2', 'Test',
                                     Description(short='Test choice',
                                                 full=('Test parameters are'
                                                       ' needed for tests.'))),
                 ThirdTestParameter('test-3', 'Test',
                                    Description(short='Test list',
                                                full=('Test parameters are'
                                                      ' needed for tests.'))))

        set_values = OrderedDict()
        set_values.update({'test-2': 'odd',
                           'test-1': 'choice_3',
                           'test-3': ['/dev/sda1', '/dev/sda2', '/dev/sda3']})
        PARAMS.set_parameters(set_values)
        assert PARAMS['test-1']._value == 'choice_3'
        assert PARAMS['test-2']._value == 'odd'
        assert PARAMS['test-3']._value == ['/dev/sda1',
                                           '/dev/sda2',
                                           '/dev/sda3']

        with pytest.raises(ValidationError):
            PARAMS.set_parameters({'test-3': ['/dev/sdb1', '/dev/sdb2']})

        with pytest.raises(ValidationError):
            PARAMS.set_parameters({'test-2': 'even'})

        with pytest.raises(ValidationError):
            PARAMS.set_parameters({'test-1': 'choice_5'})

    def test_if_some_parameters_are_created_with_validation_methods_that_use_parameters_forming_cyclic_validation_process__the_parameters_throw_the_CyclicValidationError_exception(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Choice()

            def bind_method(self, choices_list):
                choices_list = choices_list.value
                for choice in choices_list:
                    self.choices.update({choice: f'Comment for {choice}'})
                return choices_list[0], None

            def validate(self, parameters, datavars, value):
                if len(parameters['test-3'].value) < 5:
                    raise ValidationError('Too many devices.')
                if datavars.os.linux.test_2 and bool(int(value[-1]) % 2):
                    raise ValidationError('Choice must be with even number.')
                elif not datavars.os.linux.test_2 and not bool(
                                                           int(value[-1]) % 2):
                    raise ValidationError('Choice must be with odd number.')
                print(f'VALIDATED: {self._name}')

        class SecondTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

            def validate(self, parameters, datavars, value):
                if (bool(int(parameters['test-1'].value[-1]) % 2) and
                   value != 'odd'):
                    raise ValidationError('Value must be "odd".')
                elif (not bool(int(parameters['test-1'].value[-1]) % 2) and
                      value != 'even'):
                    raise ValidationError('Value must be "even".')
                elif value not in {'odd', 'even'}:
                    raise ValidationError('Value must be "odd" or "value"')
                print(f'VALIDATED: {self._name}')

        class ThirdTestParameter(BaseParameter):
            type = List()

            def bind_method(self):
                return ['/dev/sda1'], None

            def validate(self, parameters, datavars, value):
                if parameters['test-2'].value == 'even':
                    raise ValidationError("Must be odd lol")
                prefix = datavars.os.linux.test_4
                for device in value:
                    if not device.startswith(prefix):
                        raise ValidationError("Partitions must be from the"
                                              " '{prefix}' device")
                print(f'VALIDATED: {self._name}')

        # Создание параметров данных типов.
        PARAMS.add(
                 FirstTestParameter('test-1', 'Test',
                                    Description(
                                           short='Test choice',
                                           full=('Test parameters are'
                                                 ' needed for tests.'))
                                    ).bind('os.linux.test_1'),
                 SecondTestParameter('test-2', 'Test',
                                     Description(short='Test choice',
                                                 full=('Test parameters are'
                                                       ' needed for tests.'))),
                 ThirdTestParameter('test-3', 'Test',
                                    Description(short='Test list',
                                                full=('Test parameters are'
                                                      ' needed for tests.'))))
        assert PARAMS['test-1']._value is None
        PARAMS.order = ['test-1', 'test-2', 'test-3']

        set_values = OrderedDict()
        set_values.update({'test-2': 'odd',
                           'test-1': 'choice_3',
                           'test-3': ['/dev/sda1', '/dev/sda2', '/dev/sda3']})
        with pytest.raises(CyclicValidationError):
            PARAMS.set_parameters(set_values)

    def test_if_two_parameters_is_created_with_the_bind_methods_and_one_of_them_can_be_disactivated_depending_on_the_variable_and_other_one_can_change_this_variable_s_value__the_second_parameter_can_disactivate_the_first_one_through_variable(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Choice()

            def bind_method(self, choices_list, flag_variable):
                disactivity_comment = None
                if flag_variable.value:
                    disactivity_comment = "Disactive because of flag"
                choices_list = choices_list.value
                for choice in choices_list:
                    self.choices.update({choice: f'Comment for {choice}'})
                return choices_list[0], disactivity_comment

        class SecondTestParameter(BaseParameter):
            type = Bool()

            def bind_method(self):
                return True, None

        # Создание параметров данных типов.
        PARAMS.add(
                  FirstTestParameter('test-1', 'Test',
                                     Description(
                                            short='Test choice',
                                            full=('Test parameters are'
                                                  ' needed for tests.'))
                                     ).bind('os.linux.test_1',
                                            'os.linux.test_2'),
                  SecondTestParameter('test-2', 'Test',
                                      Description(short='Test bool',
                                                  full=('Test parameters are'
                                                        ' needed for tests.')
                                                  )).to_set('os.linux.test_2'))

        assert PARAMS['test-1'].disactivity_comment is None
        PARAMS.set_parameters({'test-1': 'choice_2', 'test-2': True})
        assert PARAMS['test-1'].disactivity_comment ==\
            "Disactive because of flag"
        assert PARAMS['test-1'].value == 'choice_2'
        PARAMS.set_parameters({'test-2': False})

    def test_if_there_is_an_attempt_to_add_in_the_parameters_container_new_parameter_but_the_container_already_has_parameter_with_the_same_fullname__the_parameters_container_throws_ParameterError_exception(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        class SecondTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        # Создание параметров данных типов.
        with pytest.raises(ParameterError):
            PARAMS.add(
                 FirstTestParameter('test', 'Test',
                                    Description(
                                           short='Test string',
                                           full=('Test parameters are'
                                                 ' needed for tests.'))),
                 SecondTestParameter('test', 'Test',
                                     Description(short='Test string',
                                                 full=('Test parameters are'
                                                       ' needed for tests.'))))

    def test_if_there_is_an_attempt_to_add_in_the_parameters_container_new_parameter_but_the_container_already_has_parameter_with_the_same_shortname__the_parameters_container_throws_ParameterError_exception(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        class SecondTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        # Создание параметров данных типов.
        with pytest.raises(ParameterError):
            PARAMS.add(
                  FirstTestParameter('test-1', 'Test',
                                     Description(
                                            short='Test string',
                                            full=('Test parameters are'
                                                  ' needed for tests.')),
                                     shortname='t'),
                  SecondTestParameter('test-2', 'Test',
                                      Description(short='Test string',
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t'))

    def test_if_some_parameters_are_created_with_different_argv_values__their_position_number_will_be_saved_in_the_parameters_container(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        class SecondTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        class ThirdTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        # Создание параметров данных типов.
        PARAMS.add(
              FirstTestParameter('test-a', 'Test 1',
                                 Description(
                                        short='Test string',
                                        full=('Test parameters are'
                                              ' needed for tests.')),
                                 shortname='a', argv=0),
              SecondTestParameter('test-b', 'Test 2',
                                  Description(short='Test string',
                                              full=('Test parameters are'
                                                    ' needed for tests.')),
                                  shortname='b', argv=3),
              SecondTestParameter('test-c', 'Test 3',
                                  Description(short='Test string',
                                              full=('Test parameters are'
                                                    ' needed for tests.')),
                                  shortname='c', argv=1))
        assert PARAMS._argvs == [PARAMS['test-a'], PARAMS['test-c'],
                                 None, PARAMS['test-b']]

    def test_if_there_is_an_attempt_to_add_in_the_parameters_container_new_parameter_but_the_container_already_has_parameter_with_the_same_argv_value__the_parameters_container_throws_ParameterError_exception(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        class SecondTestParameter(BaseParameter):
            type = String()

            def bind_method(self):
                return "default string", None

        # Создание параметров данных типов.
        with pytest.raises(ParameterError):
            PARAMS.add(
                 FirstTestParameter('test-1', 'Test',
                                    Description(
                                           short='Test string',
                                           full=('Test parameters are'
                                                 ' needed for tests.')),
                                    shortname='t', argv=0),
                 SecondTestParameter('test-2', 'Test',
                                     Description(short='Test string',
                                                 full=('Test parameters are'
                                                       ' needed for tests.')),
                                     shortname='t', argv=0))

    def test_if_parameter_is_created_with_the_bind_method__the_bind_method_can_get_access_to_the_current_parameter_value_during_the_value_calculation(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = String()

            def bind_method(self, test_var, test_comment):
                output = "/dev/sda"
                test_var_value = test_var.value
                if self.value is not None:
                    number = int(self.value[8:])
                    if test_var_value > number:
                        number += 1
                    elif number:
                        number -= 1
                    output = output + str(number)
                else:
                    output = output + '0'

                self._description.full = test_comment.value
                return output, None

        # Создание параметров данных типов.
        PARAMS.add(FirstTestParameter('test', 'Test',
                                      Description(short=('Test string'),
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t').bind('os.linux.test_5',
                                                          'os.linux.test_6'))

        assert PARAMS['test'].value == '/dev/sda0'
        datavars.os.linux['test_5'].set(6)
        assert PARAMS['test'].value == '/dev/sda1'
        datavars.os.linux['test_5'].set(4)
        assert PARAMS['test'].value == '/dev/sda2'
        datavars.os.linux['test_5'].set(2)
        assert PARAMS['test'].value == '/dev/sda1'

        assert PARAMS['test']._description.full == 'Comment'
        datavars.os.linux['test_6'].set("New comment")
        assert PARAMS['test']._description.full == 'New comment'

    def test_if_parameter_is_created_with_table_type_and_bind_method_is_set_for_this_parameter__the_bind_method_can_be_used_for_setting_of_the_table_fields_and_its_types(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Table()

            def bind_method(self):
                table = TableValue('dev', {'dev': String(),
                                           'mount': String()})
                table.set_comments('Device', 'Mount point')

                mount_points = ['/', '/var/calculate', '/tmp']
                for number in range(1, 4):
                    table.change(f'/dev/sdb{number}', mount_points[number - 1])

                return table, None

        # Создание параметров данных типов.
        PARAMS.add(FirstTestParameter('test', 'Test',
                                      Description(short=('Test table'),
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t'))
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/'},
             {'dev': '/dev/sdb2', 'mount': '/var/calculate'},
             {'dev': '/dev/sdb3', 'mount': '/tmp'}]

        with pytest.raises(ParameterError):
            PARAMS['test'].set(['/dev/sdb2', '/var', 'onemore'])

        with pytest.raises(ParameterError):
            PARAMS['test'].set(['/dev/sdb2'])

    def test_if_parameter_is_created_with_table_type_and_types_of_its_fields_is_set__this_parameter_uses_this_types_for_invalidating_of_all_fields_values(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Table()

            def bind_method(self):
                mounted_choices = Choice(choices=OrderedDict(
                                                    {'mounted': 'Mounted',
                                                     'umounted': 'Not mounted',
                                                     'auto': 'Autodetection'}))
                table = TableValue('id', {'id': Integer(),
                                          'dev': String(),
                                          'mount': String(),
                                          'status': mounted_choices})
                table.set_comments('ID',
                                   'Device',
                                   'Mount point',
                                   'Status')

                mount_points = ['/', '/var/calculate', '/tmp']
                states = ['mounted', 'umounted', 'auto']
                for number in range(1, 4):
                    table.change(number,
                                 f'/dev/sdb{number}',
                                 mount_points[number - 1],
                                 states[number - 1])

                return table, None

        # Создание параметров данных типов.
        PARAMS.add(FirstTestParameter('test', 'Test',
                                      Description(short=('Test table'),
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t'))

        assert PARAMS['test'].value.get_for_var() ==\
            [{'id': 1, 'dev': '/dev/sdb1',
              'mount': '/', 'status': 'mounted'},
             {'id': 2, 'dev': '/dev/sdb2',
              'mount': '/var/calculate', 'status': 'umounted'},
             {'id': 3, 'dev': '/dev/sdb3',
              'mount': '/tmp', 'status': 'auto'}]

        with pytest.raises(ValidationError):
            PARAMS['test'].set(['woops', '/dev/sdb4', '/home', 'mounted'])

        with pytest.raises(ValidationError):
            PARAMS['test'].set([3, 4, '/home', 'mounted'])

        with pytest.raises(ValidationError):
            PARAMS['test'].set([3, '/dev/sdb4', False, 'mounted'])

        with pytest.raises(ValidationError):
            PARAMS['test'].set([3, '/dev/sdb4', '/home', 'custom'])

    def test_if_parameter_is_created_with_table_type_and_expandable_flag_is_True__new_rows_can_be_added_to_the_created_table_and_existing_rows_can_be_modified(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Table(expandable=True)

            def bind_method(self):
                table = TableValue('dev', {'dev': String(),
                                           'mount': String()})
                table.set_comments('Device', 'Mount point')

                mount_points = ['/', '/var/calculate', '/tmp']
                for number in range(1, 4):
                    table.change(f'/dev/sdb{number}', mount_points[number - 1])

                return table, None

        # Создание параметров данных типов.
        PARAMS.add(FirstTestParameter('test', 'Test',
                                      Description(short=('Test table'),
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t'))
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/'},
             {'dev': '/dev/sdb2', 'mount': '/var/calculate'},
             {'dev': '/dev/sdb3', 'mount': '/tmp'}]

        PARAMS['test'].set(['/dev/sdb2', '/var'])
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/'},
             {'dev': '/dev/sdb2', 'mount': '/var'},
             {'dev': '/dev/sdb3', 'mount': '/tmp'}]

        PARAMS['test'].set(['/dev/sdb4', '/home'])
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/'},
             {'dev': '/dev/sdb2', 'mount': '/var'},
             {'dev': '/dev/sdb3', 'mount': '/tmp'},
             {'dev': '/dev/sdb4', 'mount': '/home'}]

    def test_if_parameter_is_created_with_table_type_and_expandable_flag_is_False__existing_rows_can_be_modified_but_attempt_to_add_new_will_call_set_error_method_that_can_be_set_for_table(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Table(expandable=False)

            def bind_method(self):
                table = TableValue('dev', {'dev': String(),
                                           'mount': String()})
                table.set_comments('Device', 'Mount point')

                mount_points = ['/', '/var/calculate', '/tmp']
                for number in range(1, 4):
                    table.change(f'/dev/sdb{number}', mount_points[number - 1])

                return table, None

        # Создание параметров данных типов.
        PARAMS.add(FirstTestParameter('test', 'Test',
                                      Description(short=('Test table'),
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t'))
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/'},
             {'dev': '/dev/sdb2', 'mount': '/var/calculate'},
             {'dev': '/dev/sdb3', 'mount': '/tmp'}]

        PARAMS['test'].set(['/dev/sdb2', '/var'])
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/'},
             {'dev': '/dev/sdb2', 'mount': '/var'},
             {'dev': '/dev/sdb3', 'mount': '/tmp'}]

        with pytest.raises(ParameterError,
                           match="Device '/dev/sdb4' is not found."):
            PARAMS['test'].set(['/dev/sdb4', '/home'])

        def set_error(value: str, field: str, available: list):
            raise ParameterError(
                                f"No such {field.lower()} '{value}' in table. "
                                f"Available: {', '.join(available)}.")
        PARAMS['test'].value.set_error = set_error

        with pytest.raises(ParameterError,
                           match="No such device '/dev/sdb4' in table. "
                                 "Available: /dev/sdb[123], /dev/sdb[123], "
                                 "/dev/sdb[123]."):
            PARAMS['test'].set(['/dev/sdb4', '/home'])

    def test_if_parameter_is_created_with_table_type_and_to_set_method_is_used_to_add_variables_to_set_value__the_table_parameter_will_set_its_value_to_the_variable_from_set_list(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Table(expandable=True)

            def bind_method(self):
                table = TableValue('dev', {'dev': String(),
                                           'mount': String()})
                table.set_comments('Device', 'Mount point')

                mount_points = ['/', '/var/calculate', '/tmp']
                for number in range(1, 4):
                    table.change(f'/dev/sdb{number}', mount_points[number - 1])

                return table, None

        # Создание параметров данных типов.
        PARAMS.add(FirstTestParameter('test', 'Test',
                                      Description(short=('Test table'),
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t').to_set('os.dev_table'))
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/'},
             {'dev': '/dev/sdb2', 'mount': '/var/calculate'},
             {'dev': '/dev/sdb3', 'mount': '/tmp'}]

        PARAMS['test'].set(['/dev/sdb2', '/var'])
        assert datavars.os.dev_table == [{'dev': '/dev/sdb1', 'mount': '/'},
                                         {'dev': '/dev/sdb2', 'mount': '/var'},
                                         {'dev': '/dev/sdb3', 'mount': '/tmp'}]

        PARAMS['test'].set(['/dev/sdb4', '/home'])
        assert datavars.os.dev_table == [{'dev': '/dev/sdb1', 'mount': '/'},
                                         {'dev': '/dev/sdb2', 'mount': '/var'},
                                         {'dev': '/dev/sdb3', 'mount': '/tmp'},
                                         {'dev': '/dev/sdb4', 'mount': '/home'}
                                         ]

    def test_if_parameter_is_created_with_table_type_and_fill_method_is_set__the_parameter_will_use_fill_method_to_fill_empty_fields_of_the_table(self):
        datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
                                                        'variables'))
        PARAMS = Parameters(datavars)

        # Описание классов параметров.
        class FirstTestParameter(BaseParameter):
            type = Table(expandable=True)

            def bind_method(self):
                mounted_choices = Choice(choices=OrderedDict(
                                                    {'mounted': 'Mounted',
                                                     'umounted': 'Not mounted',
                                                     'auto': 'Autodetection'}))
                table = TableValue('dev', {'dev': String(),
                                           'mount': String(),
                                           'name': String(),
                                           'status': mounted_choices})
                table.set_comments('Device', 'Mount point', 'Name', 'Status')

                def set_error(value: str, field: str, available: list):
                    raise ParameterError(
                                f"No such {field.lower()} '{value}' in table. "
                                f"Available: {', '.join(available)}.")
                table.set_error = set_error

                def fill(values):
                    for key, row in values.items():
                        for field, value in row.items():
                            if value is None:
                                if field == 'name':
                                    value = f'Device {int(row["dev"][8:])}'
                                elif field == 'status':
                                    value = 'auto'
                                row[field] = value
                    return values
                table.fill = fill

                mount_points = ['/', '/var/calculate', '/tmp']
                states = ['mounted', 'umounted', 'auto']
                names = ['Device 1', None, 'Device 3']
                for number in range(1, 4):
                    table.change(f'/dev/sdb{number}',
                                 mount_points[number - 1],
                                 names[number - 1],
                                 states[number - 1])

                return table, None

        # Создание параметров данных типов.
        PARAMS.add(FirstTestParameter('test', 'Test',
                                      Description(short=('Test table'),
                                                  full=('Test parameters are'
                                                        ' needed for tests.')),
                                      shortname='t').to_set('os.dev_table'))
        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/',
              'name': 'Device 1', 'status': 'mounted'},
             {'dev': '/dev/sdb2', 'mount': '/var/calculate',
              'name': 'Device 2', 'status': 'umounted'},
             {'dev': '/dev/sdb3', 'mount': '/tmp',
              'name': 'Device 3', 'status': 'auto'}]

        PARAMS['test'].set(['/dev/sdb4', '/home', None, None])

        assert PARAMS['test'].value.get_for_var() ==\
            [{'dev': '/dev/sdb1', 'mount': '/',
              'name': 'Device 1', 'status': 'mounted'},
             {'dev': '/dev/sdb2', 'mount': '/var/calculate',
              'name': 'Device 2', 'status': 'umounted'},
             {'dev': '/dev/sdb3', 'mount': '/tmp',
              'name': 'Device 3', 'status': 'auto'},
             {'dev': '/dev/sdb4', 'mount': '/home',
              'name': 'Device 4', 'status': 'auto'}]

    def test_gui_wrapper(self):
        pass

    def test_for_removing_testfiles(self):
        shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo'))
