from __future__ import absolute_import, division, print_function
import sys
import os
import py
import pytest
from _pytest import config as parseopt


@pytest.fixture
def parser():
    return parseopt.Parser()


class TestParser(object):
    def test_no_help_by_default(self, capsys):
        parser = parseopt.Parser(usage="xyz")
        pytest.raises(SystemExit, lambda: parser.parse(["-h"]))
        out, err = capsys.readouterr()
        assert err.find("error: unrecognized arguments") != -1

    def test_argument(self):
        with pytest.raises(parseopt.ArgumentError):
            # need a short or long option
            argument = parseopt.Argument()
        argument = parseopt.Argument('-t')
        assert argument._short_opts == ['-t']
        assert argument._long_opts == []
        assert argument.dest == 't'
        argument = parseopt.Argument('-t', '--test')
        assert argument._short_opts == ['-t']
        assert argument._long_opts == ['--test']
        assert argument.dest == 'test'
        argument = parseopt.Argument('-t', '--test', dest='abc')
        assert argument.dest == 'abc'
        assert str(argument) == (
            "Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')"
        )

    def test_argument_type(self):
        argument = parseopt.Argument('-t', dest='abc', type=int)
        assert argument.type is int
        argument = parseopt.Argument('-t', dest='abc', type=str)
        assert argument.type is str
        argument = parseopt.Argument('-t', dest='abc', type=float)
        assert argument.type is float
        with pytest.warns(DeprecationWarning):
            with pytest.raises(KeyError):
                argument = parseopt.Argument('-t', dest='abc', type='choice')
        argument = parseopt.Argument('-t', dest='abc', type=str,
                                     choices=['red', 'blue'])
        assert argument.type is str

    def test_argument_processopt(self):
        argument = parseopt.Argument('-t', type=int)
        argument.default = 42
        argument.dest = 'abc'
        res = argument.attrs()
        assert res['default'] == 42
        assert res['dest'] == 'abc'

    def test_group_add_and_get(self, parser):
        group = parser.getgroup("hello", description="desc")
        assert group.name == "hello"
        assert group.description == "desc"

    def test_getgroup_simple(self, parser):
        group = parser.getgroup("hello", description="desc")
        assert group.name == "hello"
        assert group.description == "desc"
        group2 = parser.getgroup("hello")
        assert group2 is group

    def test_group_ordering(self, parser):
        parser.getgroup("1")
        parser.getgroup("2")
        parser.getgroup("3", after="1")
        groups = parser._groups
        groups_names = [x.name for x in groups]
        assert groups_names == list("132")

    def test_group_addoption(self):
        group = parseopt.OptionGroup("hello")
        group.addoption("--option1", action="store_true")
        assert len(group.options) == 1
        assert isinstance(group.options[0], parseopt.Argument)

    def test_group_addoption_conflict(self):
        group = parseopt.OptionGroup("hello again")
        group.addoption("--option1", "--option-1", action="store_true")
        with pytest.raises(ValueError) as err:
            group.addoption("--option1", "--option-one", action="store_true")
        assert str(set(["--option1"])) in str(err.value)

    def test_group_shortopt_lowercase(self, parser):
        group = parser.getgroup("hello")
        pytest.raises(ValueError, """
            group.addoption("-x", action="store_true")
        """)
        assert len(group.options) == 0
        group._addoption("-x", action="store_true")
        assert len(group.options) == 1

    def test_parser_addoption(self, parser):
        group = parser.getgroup("custom options")
        assert len(group.options) == 0
        group.addoption("--option1", action="store_true")
        assert len(group.options) == 1

    def test_parse(self, parser):
        parser.addoption("--hello", dest="hello", action="store")
        args = parser.parse(['--hello', 'world'])
        assert args.hello == "world"
        assert not getattr(args, parseopt.FILE_OR_DIR)

    def test_parse2(self, parser):
        args = parser.parse([py.path.local()])
        assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local()

    def test_parse_known_args(self, parser):
        parser.parse_known_args([py.path.local()])
        parser.addoption("--hello", action="store_true")
        ns = parser.parse_known_args(["x", "--y", "--hello", "this"])
        assert ns.hello
        assert ns.file_or_dir == ['x']

    def test_parse_known_and_unknown_args(self, parser):
        parser.addoption("--hello", action="store_true")
        ns, unknown = parser.parse_known_and_unknown_args(["x", "--y",
                                                           "--hello", "this"])
        assert ns.hello
        assert ns.file_or_dir == ['x']
        assert unknown == ['--y', 'this']

    def test_parse_will_set_default(self, parser):
        parser.addoption("--hello", dest="hello", default="x", action="store")
        option = parser.parse([])
        assert option.hello == "x"
        del option.hello
        parser.parse_setoption([], option)
        assert option.hello == "x"

    def test_parse_setoption(self, parser):
        parser.addoption("--hello", dest="hello", action="store")
        parser.addoption("--world", dest="world", default=42)

        class A(object):
            pass

        option = A()
        args = parser.parse_setoption(['--hello', 'world'], option)
        assert option.hello == "world"
        assert option.world == 42
        assert not args

    def test_parse_special_destination(self, parser):
        parser.addoption("--ultimate-answer", type=int)
        args = parser.parse(['--ultimate-answer', '42'])
        assert args.ultimate_answer == 42

    def test_parse_split_positional_arguments(self, parser):
        parser.addoption("-R", action='store_true')
        parser.addoption("-S", action='store_false')
        args = parser.parse(['-R', '4', '2', '-S'])
        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
        args = parser.parse(['-R', '-S', '4', '2', '-R'])
        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
        assert args.R is True
        assert args.S is False
        args = parser.parse(['-R', '4', '-S', '2'])
        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
        assert args.R is True
        assert args.S is False

    def test_parse_defaultgetter(self):
        def defaultget(option):
            if not hasattr(option, 'type'):
                return
            if option.type is int:
                option.default = 42
            elif option.type is str:
                option.default = "world"
        parser = parseopt.Parser(processopt=defaultget)
        parser.addoption("--this", dest="this", type=int, action="store")
        parser.addoption("--hello", dest="hello", type=str, action="store")
        parser.addoption("--no", dest="no", action="store_true")
        option = parser.parse([])
        assert option.hello == "world"
        assert option.this == 42
        assert option.no is False

    def test_drop_short_helper(self):
        parser = py.std.argparse.ArgumentParser(formatter_class=parseopt.DropShorterLongHelpFormatter)
        parser.add_argument('-t', '--twoword', '--duo', '--two-word', '--two',
                            help='foo').map_long_option = {'two': 'two-word'}
        # throws error on --deux only!
        parser.add_argument('-d', '--deuxmots', '--deux-mots',
                            action='store_true', help='foo').map_long_option = {'deux': 'deux-mots'}
        parser.add_argument('-s', action='store_true', help='single short')
        parser.add_argument('--abc', '-a',
                            action='store_true', help='bar')
        parser.add_argument('--klm', '-k', '--kl-m',
                            action='store_true', help='bar')
        parser.add_argument('-P', '--pq-r', '-p', '--pqr',
                            action='store_true', help='bar')
        parser.add_argument('--zwei-wort', '--zweiwort', '--zweiwort',
                            action='store_true', help='bar')
        parser.add_argument('-x', '--exit-on-first', '--exitfirst',
                            action='store_true', help='spam').map_long_option = {'exitfirst': 'exit-on-first'}
        parser.add_argument('files_and_dirs', nargs='*')
        args = parser.parse_args(['-k', '--duo', 'hallo', '--exitfirst'])
        assert args.twoword == 'hallo'
        assert args.klm is True
        assert args.zwei_wort is False
        assert args.exit_on_first is True
        assert args.s is False
        args = parser.parse_args(['--deux-mots'])
        with pytest.raises(AttributeError):
            assert args.deux_mots is True
        assert args.deuxmots is True
        args = parser.parse_args(['file', 'dir'])
        assert '|'.join(args.files_and_dirs) == 'file|dir'

    def test_drop_short_0(self, parser):
        parser.addoption('--funcarg', '--func-arg', action='store_true')
        parser.addoption('--abc-def', '--abc-def', action='store_true')
        parser.addoption('--klm-hij', action='store_true')
        args = parser.parse(['--funcarg', '--k'])
        assert args.funcarg is True
        assert args.abc_def is False
        assert args.klm_hij is True

    def test_drop_short_2(self, parser):
        parser.addoption('--func-arg', '--doit', action='store_true')
        args = parser.parse(['--doit'])
        assert args.func_arg is True

    def test_drop_short_3(self, parser):
        parser.addoption('--func-arg', '--funcarg', '--doit', action='store_true')
        args = parser.parse(['abcd'])
        assert args.func_arg is False
        assert args.file_or_dir == ['abcd']

    def test_drop_short_help0(self, parser, capsys):
        parser.addoption('--func-args', '--doit', help='foo',
                         action='store_true')
        parser.parse([])
        help = parser.optparser.format_help()
        assert '--func-args, --doit  foo' in help

    # testing would be more helpful with all help generated
    def test_drop_short_help1(self, parser, capsys):
        group = parser.getgroup("general")
        group.addoption('--doit', '--func-args', action='store_true', help='foo')
        group._addoption("-h", "--help", action="store_true", dest="help",
                         help="show help message and configuration info")
        parser.parse(['-h'])
        help = parser.optparser.format_help()
        assert '-doit, --func-args  foo' in help

    def test_multiple_metavar_help(self, parser):
        """
        Help text for options with a metavar tuple should display help
        in the form "--preferences=value1 value2 value3" (#2004).
        """
        group = parser.getgroup("general")
        group.addoption('--preferences', metavar=('value1', 'value2', 'value3'), nargs=3)
        group._addoption("-h", "--help", action="store_true", dest="help")
        parser.parse(['-h'])
        help = parser.optparser.format_help()
        assert '--preferences=value1 value2 value3' in help


def test_argcomplete(testdir, monkeypatch):
    if not py.path.local.sysfind('bash'):
        pytest.skip("bash not available")
    script = str(testdir.tmpdir.join("test_argcomplete"))
    pytest_bin = sys.argv[0]
    if "pytest" not in os.path.basename(pytest_bin):
        pytest.skip("need to be run with pytest executable, not %s" % (pytest_bin,))

    with open(str(script), 'w') as fp:
        # redirect output from argcomplete to stdin and stderr is not trivial
        # http://stackoverflow.com/q/12589419/1307905
        # so we use bash
        fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" %s 8>&1 9>&2' % pytest_bin)
    # alternative would be exteneded Testdir.{run(),_run(),popen()} to be able
    # to handle a keyword argument env that replaces os.environ in popen or
    # extends the copy, advantage: could not forget to restore
    monkeypatch.setenv('_ARGCOMPLETE', "1")
    monkeypatch.setenv('_ARGCOMPLETE_IFS', "\x0b")
    monkeypatch.setenv('COMP_WORDBREAKS', ' \\t\\n"\\\'><=;|&(:')

    arg = '--fu'
    monkeypatch.setenv('COMP_LINE', "pytest " + arg)
    monkeypatch.setenv('COMP_POINT', str(len("pytest " + arg)))
    result = testdir.run('bash', str(script), arg)
    if result.ret == 255:
        # argcomplete not found
        pytest.skip("argcomplete not available")
    elif not result.stdout.str():
        pytest.skip("bash provided no output, argcomplete not available?")
    else:
        result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
    os.mkdir('test_argcomplete.d')
    arg = 'test_argc'
    monkeypatch.setenv('COMP_LINE', "pytest " + arg)
    monkeypatch.setenv('COMP_POINT', str(len('pytest ' + arg)))
    result = testdir.run('bash', str(script), arg)
    result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"])
