import unittest

from exactly_lib.processing.preprocessor import PreprocessorViaExternalProgram
from exactly_lib.section_document.element_parsers.instruction_parser_exceptions import \
    SingleInstructionInvalidArgumentException
from exactly_lib.test_suite.instruction_set.sections.configuration import preprocessor as sut
from exactly_lib.test_suite.instruction_set.sections.configuration.instruction_definition import \
    ConfigurationSectionInstruction
from exactly_lib_test.common.help.test_resources.check_documentation import suite_for_instruction_documentation
from exactly_lib_test.impls.types.parse.test_resources.single_line_source_instruction_utils import \
    equivalent_source_variants__with_source_check__consume_last_line
from exactly_lib_test.section_document.test_resources.misc import ARBITRARY_FS_LOCATION_INFO
from exactly_lib_test.test_resources.argument_renderer import CustomOptionArgument
from exactly_lib_test.test_suite.instruction_set.sections.configuration.test_resources import \
    configuration_section_environment


def suite() -> unittest.TestSuite:
    return unittest.TestSuite([
        unittest.makeSuite(TestFailingParse),
        unittest.makeSuite(TestSuccessfulParseAndInstructionExecution),
        suite_for_instruction_documentation(sut.TheInstructionDocumentation('instruction mame')),
    ])


class TestFailingParse(unittest.TestCase):
    def test_fail_when_invalid_syntax(self):
        test_cases = [
            '   ',
            '  =  ',
            '= "argument-2',
            'argument-1 "argument-2',
        ]
        parser = sut.Parser()
        for instruction_argument in test_cases:
            with self.subTest(msg='instruction argument=' + repr(instruction_argument)):
                for source in equivalent_source_variants__with_source_check__consume_last_line(self,
                                                                                               instruction_argument):
                    with self.assertRaises(SingleInstructionInvalidArgumentException) as cm:
                        parser.parse(ARBITRARY_FS_LOCATION_INFO, source)

                    self.assertIsInstance(cm.exception.error_message, str)


class TestSuccessfulParseAndInstructionExecution(unittest.TestCase):
    def _check(self,
               instruction_argument_source: str,
               expected_command_and_arguments: list):
        for source in equivalent_source_variants__with_source_check__consume_last_line(self,
                                                                                       instruction_argument_source):
            # ARRANGE #
            instruction = sut.Parser().parse(ARBITRARY_FS_LOCATION_INFO, source)
            assert isinstance(instruction, ConfigurationSectionInstruction)
            environment = configuration_section_environment()
            # ACT #
            instruction.execute(environment)
            # ASSERT #
            actual_preprocessor = environment.preprocessor
            self.assertIsInstance(actual_preprocessor, PreprocessorViaExternalProgram)
            assert isinstance(actual_preprocessor, PreprocessorViaExternalProgram)
            self.assertEqual(expected_command_and_arguments,
                             actual_preprocessor.external_program)

    def test_single_command(self):
        self._check('= executable', ['executable'])

    def test_command_with_arguments(self):
        self._check(' = executable arg1 {}'.format(CustomOptionArgument('arg2')),
                    ['executable', 'arg1', str(CustomOptionArgument('arg2'))])

    def test_quoting(self):
        self._check("= 'executable with space' arg2 \"arg 3\"",
                    ['executable with space', 'arg2', 'arg 3'])


if __name__ == '__main__':
    unittest.TextTestRunner().run(suite())
