import os
import sys
from unittest import TestCase
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO  # doesn't accept 'str' in Py2

from .. import Options
from ..CmdLine import parse_command_line

from .Utils import backup_Options, restore_Options, check_global_options


class CmdLineParserTest(TestCase):
    def setUp(self):
        self._options_backup = backup_Options()

    def tearDown(self):
        restore_Options(self._options_backup)

    def check_default_global_options(self, white_list=[]):
        self.assertEqual(check_global_options(self._options_backup, white_list), "")

    def check_default_options(self, options, white_list=[]):
        default_options = Options.CompilationOptions(Options.default_options)
        no_value = object()
        for name in default_options.__dict__.keys():
            if name not in white_list:
               self.assertEqual(getattr(options, name, no_value), getattr(default_options, name), msg="error in option " + name)

    def test_short_options(self):
        options, sources = parse_command_line([
            '-V', '-l', '-+', '-t', '-v', '-v', '-v', '-p', '-D', '-a', '-3',
        ])
        self.assertFalse(sources)
        self.assertTrue(options.show_version)
        self.assertTrue(options.use_listing_file)
        self.assertTrue(options.cplus)
        self.assertTrue(options.timestamps)
        self.assertTrue(options.verbose >= 3)
        self.assertTrue(Options.embed_pos_in_docstring)
        self.assertFalse(Options.docstrings)
        self.assertTrue(Options.annotate)
        self.assertEqual(options.language_level, 3)

        options, sources = parse_command_line([
            '-f', '-2', 'source.pyx',
        ])
        self.assertTrue(sources)
        self.assertTrue(len(sources) == 1)
        self.assertFalse(options.timestamps)
        self.assertEqual(options.language_level, 2)

    def test_long_options(self):
        options, sources = parse_command_line([
            '--version', '--create-listing', '--cplus', '--embed', '--timestamps',
            '--verbose', '--verbose', '--verbose',
            '--embed-positions', '--no-docstrings', '--annotate', '--lenient',
        ])
        self.assertFalse(sources)
        self.assertTrue(options.show_version)
        self.assertTrue(options.use_listing_file)
        self.assertTrue(options.cplus)
        self.assertEqual(Options.embed, 'main')
        self.assertTrue(options.timestamps)
        self.assertTrue(options.verbose >= 3)
        self.assertTrue(Options.embed_pos_in_docstring)
        self.assertFalse(Options.docstrings)
        self.assertTrue(Options.annotate)
        self.assertFalse(Options.error_on_unknown_names)
        self.assertFalse(Options.error_on_uninitialized)

        options, sources = parse_command_line([
            '--force', 'source.pyx',
        ])
        self.assertTrue(sources)
        self.assertTrue(len(sources) == 1)
        self.assertFalse(options.timestamps)

    def test_options_with_values(self):
        options, sources = parse_command_line([
            '--embed=huhu',
            '-I/test/include/dir1', '--include-dir=/test/include/dir2',
            '--include-dir', '/test/include/dir3',
            '--working=/work/dir',
            'source.pyx',
            '--output-file=/output/dir',
            '--pre-import=/pre/import',
            '--cleanup=3',
            '--annotate-coverage=cov.xml',
            '--gdb-outdir=/gdb/outdir',
            '--directive=wraparound=false',
        ])
        self.assertEqual(sources, ['source.pyx'])
        self.assertEqual(Options.embed, 'huhu')
        self.assertEqual(options.include_path, ['/test/include/dir1', '/test/include/dir2', '/test/include/dir3'])
        self.assertEqual(options.working_path, '/work/dir')
        self.assertEqual(options.output_file, '/output/dir')
        self.assertEqual(Options.pre_import, '/pre/import')
        self.assertEqual(Options.generate_cleanup_code, 3)
        self.assertTrue(Options.annotate)
        self.assertEqual(Options.annotate_coverage_xml, 'cov.xml')
        self.assertTrue(options.gdb_debug)
        self.assertEqual(options.output_dir, '/gdb/outdir')
        self.assertEqual(options.compiler_directives['wraparound'], False)

    def test_embed_before_positional(self):
        options, sources = parse_command_line([
            '--embed',
            'source.pyx',
        ])
        self.assertEqual(sources, ['source.pyx'])
        self.assertEqual(Options.embed, 'main')

    def test_two_embeds(self):
        options, sources = parse_command_line([
            '--embed', '--embed=huhu',
            'source.pyx',
        ])
        self.assertEqual(sources, ['source.pyx'])
        self.assertEqual(Options.embed, 'huhu')

    def test_two_embeds2(self):
        options, sources = parse_command_line([
            '--embed=huhu', '--embed',
            'source.pyx',
        ])
        self.assertEqual(sources, ['source.pyx'])
        self.assertEqual(Options.embed, 'main')

    def test_no_annotate(self):
        options, sources = parse_command_line([
            '--embed=huhu', 'source.pyx'
        ])
        self.assertFalse(Options.annotate)

    def test_annotate_short(self):
        options, sources = parse_command_line([
            '-a',
            'source.pyx',
        ])
        self.assertEqual(Options.annotate, 'default')

    def test_annotate_long(self):
        options, sources = parse_command_line([
            '--annotate',
            'source.pyx',
        ])
        self.assertEqual(Options.annotate, 'default')

    def test_annotate_fullc(self):
        options, sources = parse_command_line([
            '--annotate-fullc',
            'source.pyx',
        ])
        self.assertEqual(Options.annotate, 'fullc')

    def test_short_w(self):
       options, sources = parse_command_line([
                '-w', 'my_working_path',
                'source.pyx'
       ])
       self.assertEqual(options.working_path, 'my_working_path')
       self.check_default_global_options()
       self.check_default_options(options, ['working_path'])

    def test_short_o(self):
       options, sources = parse_command_line([
                '-o', 'my_output',
                'source.pyx'
       ])
       self.assertEqual(options.output_file, 'my_output')
       self.check_default_global_options()
       self.check_default_options(options, ['output_file'])

    def test_short_z(self):
       options, sources = parse_command_line([
                '-z', 'my_preimport',
                'source.pyx'
       ])
       self.assertEqual(Options.pre_import, 'my_preimport')
       self.check_default_global_options(['pre_import'])
       self.check_default_options(options)

    def test_convert_range(self):
       options, sources = parse_command_line([
                '--convert-range',
                'source.pyx'
       ])
       self.assertEqual(Options.convert_range, True)
       self.check_default_global_options(['convert_range'])
       self.check_default_options(options)

    def test_line_directives(self):
       options, sources = parse_command_line([
                '--line-directives',
                'source.pyx'
       ])
       self.assertEqual(options.emit_linenums, True)
       self.check_default_global_options()
       self.check_default_options(options, ['emit_linenums'])

    def test_no_c_in_traceback(self):
       options, sources = parse_command_line([
                '--no-c-in-traceback',
                'source.pyx'
       ])
       self.assertEqual(options.c_line_in_traceback, False)
       self.check_default_global_options()
       self.check_default_options(options, ['c_line_in_traceback'])

    def test_gdb(self):
       options, sources = parse_command_line([
                '--gdb',
                'source.pyx'
       ])
       self.assertEqual(options.gdb_debug, True)
       self.assertEqual(options.output_dir, os.curdir)
       self.check_default_global_options()
       self.check_default_options(options, ['gdb_debug', 'output_dir'])

    def test_3str(self):
       options, sources = parse_command_line([
                '--3str',
                'source.pyx'
       ])
       self.assertEqual(options.language_level, '3str')
       self.check_default_global_options()
       self.check_default_options(options, ['language_level'])

    def test_capi_reexport_cincludes(self):
       options, sources = parse_command_line([
                '--capi-reexport-cincludes',
                'source.pyx'
       ])
       self.assertEqual(options.capi_reexport_cincludes, True)
       self.check_default_global_options()
       self.check_default_options(options, ['capi_reexport_cincludes'])

    def test_fast_fail(self):
       options, sources = parse_command_line([
                '--fast-fail',
                'source.pyx'
       ])
       self.assertEqual(Options.fast_fail, True)
       self.check_default_global_options(['fast_fail'])
       self.check_default_options(options)

    def test_cimport_from_pyx(self):
       options, sources = parse_command_line([
                '--cimport-from-pyx',
                'source.pyx'
       ])
       self.assertEqual(Options.cimport_from_pyx, True)
       self.check_default_global_options(['cimport_from_pyx'])
       self.check_default_options(options)

    def test_Werror(self):
       options, sources = parse_command_line([
                '-Werror',
                'source.pyx'
       ])
       self.assertEqual(Options.warning_errors, True)
       self.check_default_global_options(['warning_errors'])
       self.check_default_options(options)

    def test_warning_errors(self):
       options, sources = parse_command_line([
                '--warning-errors',
                'source.pyx'
       ])
       self.assertEqual(Options.warning_errors, True)
       self.check_default_global_options(['warning_errors'])
       self.check_default_options(options)

    def test_Wextra(self):
       options, sources = parse_command_line([
                '-Wextra',
                'source.pyx'
       ])
       self.assertEqual(options.compiler_directives, Options.extra_warnings)
       self.check_default_global_options()
       self.check_default_options(options, ['compiler_directives'])

    def test_warning_extra(self):
       options, sources = parse_command_line([
                '--warning-extra',
                'source.pyx'
       ])
       self.assertEqual(options.compiler_directives, Options.extra_warnings)
       self.check_default_global_options()
       self.check_default_options(options, ['compiler_directives'])

    def test_old_style_globals(self):
       options, sources = parse_command_line([
                '--old-style-globals',
                'source.pyx'
       ])
       self.assertEqual(Options.old_style_globals, True)
       self.check_default_global_options(['old_style_globals'])
       self.check_default_options(options)

    def test_directive_multiple(self):
        options, source = parse_command_line([
               '-X', 'cdivision=True',
               '-X', 'c_string_type=bytes',
               'source.pyx'
        ])
        self.assertEqual(options.compiler_directives['cdivision'], True)
        self.assertEqual(options.compiler_directives['c_string_type'], 'bytes')
        self.check_default_global_options()
        self.check_default_options(options, ['compiler_directives'])

    def test_directive_multiple_v2(self):
        options, source = parse_command_line([
               '-X', 'cdivision=True,c_string_type=bytes',
               'source.pyx'
        ])
        self.assertEqual(options.compiler_directives['cdivision'], True)
        self.assertEqual(options.compiler_directives['c_string_type'], 'bytes')
        self.check_default_global_options()
        self.check_default_options(options, ['compiler_directives'])

    def test_directive_value_yes(self):
        options, source = parse_command_line([
               '-X', 'cdivision=YeS',
               'source.pyx'
        ])
        self.assertEqual(options.compiler_directives['cdivision'], True)
        self.check_default_global_options()
        self.check_default_options(options, ['compiler_directives'])

    def test_directive_value_no(self):
        options, source = parse_command_line([
               '-X', 'cdivision=no',
               'source.pyx'
        ])
        self.assertEqual(options.compiler_directives['cdivision'], False)
        self.check_default_global_options()
        self.check_default_options(options, ['compiler_directives'])

    def test_directive_value_invalid(self):
        self.assertRaises(ValueError, parse_command_line, [
               '-X', 'cdivision=sadfasd',
               'source.pyx'
        ])

    def test_directive_key_invalid(self):
        self.assertRaises(ValueError, parse_command_line, [
               '-X', 'abracadabra',
               'source.pyx'
        ])

    def test_directive_no_value(self):
        self.assertRaises(ValueError, parse_command_line, [
               '-X', 'cdivision',
               'source.pyx'
        ])

    def test_compile_time_env_short(self):
        options, source = parse_command_line([
               '-E', 'MYSIZE=10',
               'source.pyx'
        ])
        self.assertEqual(options.compile_time_env['MYSIZE'], 10)
        self.check_default_global_options()
        self.check_default_options(options, ['compile_time_env'])

    def test_compile_time_env_long(self):
        options, source = parse_command_line([
               '--compile-time-env', 'MYSIZE=10',
               'source.pyx'
        ])
        self.assertEqual(options.compile_time_env['MYSIZE'], 10)
        self.check_default_global_options()
        self.check_default_options(options, ['compile_time_env'])

    def test_compile_time_env_multiple(self):
        options, source = parse_command_line([
               '-E', 'MYSIZE=10', '-E', 'ARRSIZE=11',
               'source.pyx'
        ])
        self.assertEqual(options.compile_time_env['MYSIZE'], 10)
        self.assertEqual(options.compile_time_env['ARRSIZE'], 11)
        self.check_default_global_options()
        self.check_default_options(options, ['compile_time_env'])

    def test_compile_time_env_multiple_v2(self):
        options, source = parse_command_line([
               '-E', 'MYSIZE=10,ARRSIZE=11',
               'source.pyx'
        ])
        self.assertEqual(options.compile_time_env['MYSIZE'], 10)
        self.assertEqual(options.compile_time_env['ARRSIZE'], 11)
        self.check_default_global_options()
        self.check_default_options(options, ['compile_time_env'])

    def test_option_first(self):
        options, sources = parse_command_line(['-V', 'file.pyx'])
        self.assertEqual(sources, ['file.pyx'])

    def test_file_inbetween(self):
        options, sources = parse_command_line(['-V', 'file.pyx', '-a'])
        self.assertEqual(sources, ['file.pyx'])

    def test_option_trailing(self):
        options, sources = parse_command_line(['file.pyx', '-V'])
        self.assertEqual(sources, ['file.pyx'])

    def test_multiple_files(self):
        options, sources = parse_command_line([
             'file1.pyx', '-V',
             'file2.pyx', '-a',
             'file3.pyx'
        ])
        self.assertEqual(sources, ['file1.pyx', 'file2.pyx', 'file3.pyx'])

    def test_debug_flags(self):
        options, sources = parse_command_line([
             '--debug-disposal-code', '--debug-coercion',
             'file3.pyx'
        ])
        from Cython.Compiler import DebugFlags
        for name in ['debug_disposal_code', 'debug_temp_alloc', 'debug_coercion']:
            self.assertEqual(getattr(DebugFlags, name), name in ['debug_disposal_code', 'debug_coercion'])
            setattr(DebugFlags, name, 0)  # restore original value

    def test_gdb_overwrites_gdb_outdir(self):
        options, sources = parse_command_line([
            '--gdb-outdir=my_dir', '--gdb',
            'file3.pyx'
        ])
        self.assertEqual(options.gdb_debug, True)
        self.assertEqual(options.output_dir, os.curdir)
        self.check_default_global_options()
        self.check_default_options(options, ['gdb_debug', 'output_dir'])

    def test_gdb_first(self):
        options, sources = parse_command_line([
            '--gdb', '--gdb-outdir=my_dir',
            'file3.pyx'
        ])
        self.assertEqual(options.gdb_debug, True)
        self.assertEqual(options.output_dir, 'my_dir')
        self.check_default_global_options()
        self.check_default_options(options, ['gdb_debug', 'output_dir'])

    def test_coverage_overwrites_annotation(self):
        options, sources = parse_command_line([
            '--annotate-fullc', '--annotate-coverage=my.xml',
            'file3.pyx'
        ])
        self.assertEqual(Options.annotate, True)
        self.assertEqual(Options.annotate_coverage_xml, 'my.xml')
        self.check_default_global_options(['annotate', 'annotate_coverage_xml'])
        self.check_default_options(options)

    def test_coverage_first(self):
        options, sources = parse_command_line([
            '--annotate-coverage=my.xml', '--annotate-fullc',
            'file3.pyx'
        ])
        self.assertEqual(Options.annotate, 'fullc')
        self.assertEqual(Options.annotate_coverage_xml, 'my.xml')
        self.check_default_global_options(['annotate', 'annotate_coverage_xml'])
        self.check_default_options(options)

    def test_annotate_first_fullc_second(self):
        options, sources = parse_command_line([
            '--annotate', '--annotate-fullc',
            'file3.pyx'
        ])
        self.assertEqual(Options.annotate, 'fullc')
        self.check_default_global_options(['annotate'])
        self.check_default_options(options)

    def test_annotate_fullc_first(self):
        options, sources = parse_command_line([
            '--annotate-fullc', '--annotate',
            'file3.pyx'
        ])
        self.assertEqual(Options.annotate, 'default')
        self.check_default_global_options(['annotate'])
        self.check_default_options(options)

    def test_warning_extra_dont_overwrite(self):
       options, sources = parse_command_line([
                '-X', 'cdivision=True',
                '--warning-extra',
                '-X', 'c_string_type=bytes',
                'source.pyx'
       ])
       self.assertTrue(len(options.compiler_directives), len(Options.extra_warnings) + 1)
       self.check_default_global_options()
       self.check_default_options(options, ['compiler_directives'])

    def test_errors(self):
        def error(*args):
            old_stderr = sys.stderr
            stderr = sys.stderr = StringIO()
            try:
                self.assertRaises(SystemExit, parse_command_line, list(args))
            finally:
                sys.stderr = old_stderr
            self.assertTrue(stderr.getvalue())

        error('-1')
        error('-I')
        error('--version=-a')
        error('--version=--annotate=true')
        error('--working')
        error('--verbose=1')
        error('--verbose=1')
        error('--cleanup')
        error('--debug-disposal-code-wrong-name', 'file3.pyx')
