""" Test all things related to the ``jedi.api_classes`` module.
"""

from textwrap import dedent
from inspect import cleandoc

import pytest

import jedi
from jedi import __doc__ as jedi_doc, names
from ..helpers import cwd_at
from ..helpers import TestCase


def test_is_keyword(Script):
    #results = Script('import ', 1, 1, None).goto_definitions()
    #assert len(results) == 1 and results[0].is_keyword is True
    results = Script('str', 1, 1, None).goto_definitions()
    assert len(results) == 1 and results[0].is_keyword is False


def test_basedefinition_type(Script, environment):
    def make_definitions():
        """
        Return a list of definitions for parametrized tests.

        :rtype: [jedi.api_classes.BaseDefinition]
        """
        source = dedent("""
        import sys

        class C:
            pass

        x = C()

        def f():
            pass

        def g():
            yield

        h = lambda: None
        """)

        definitions = []
        definitions += names(source, environment=environment)

        source += dedent("""
        variable = sys or C or x or f or g or g() or h""")
        lines = source.splitlines()
        script = Script(source, len(lines), len('variable'), None)
        definitions += script.goto_definitions()

        script2 = Script(source, 4, len('class C'), None)
        definitions += script2.usages()

        source_param = "def f(a): return a"
        script_param = Script(source_param, 1, len(source_param), None)
        definitions += script_param.goto_assignments()

        return definitions

    for definition in make_definitions():
        assert definition.type in ('module', 'class', 'instance', 'function',
                                   'generator', 'statement', 'import', 'param')


def test_basedefinition_type_import(Script):
    def get_types(source, **kwargs):
        return {t.type for t in Script(source, **kwargs).completions()}

    # import one level
    assert get_types('import t') == {'module'}
    assert get_types('import ') == {'module'}
    assert get_types('import datetime; datetime') == {'module'}

    # from
    assert get_types('from datetime import timedelta') == {'class'}
    assert get_types('from datetime import timedelta; timedelta') == {'class'}
    assert get_types('from json import tool') == {'module'}
    assert get_types('from json import tool; tool') == {'module'}

    # import two levels
    assert get_types('import json.tool; json') == {'module'}
    assert get_types('import json.tool; json.tool') == {'module'}
    assert get_types('import json.tool; json.tool.main') == {'function'}
    assert get_types('import json.tool') == {'module'}
    assert get_types('import json.tool', column=9) == {'module'}


def test_function_call_signature_in_doc(Script):
    defs = Script("""
    def f(x, y=1, z='a'):
        pass
    f""").goto_definitions()
    doc = defs[0].docstring()
    assert "f(x, y=1, z='a')" in str(doc)


def test_param_docstring():
    param = jedi.names("def test(parameter): pass")[1]
    assert param.name == 'parameter'
    assert param.docstring() == ''


def test_class_call_signature(Script):
    defs = Script("""
    class Foo:
        def __init__(self, x, y=1, z='a'):
            pass
    Foo""").goto_definitions()
    doc = defs[0].docstring()
    assert "Foo(self, x, y=1, z='a')" in str(doc)


def test_position_none_if_builtin(Script):
    gotos = Script('import sys; sys.path').goto_assignments()
    assert gotos[0].line is None
    assert gotos[0].column is None


def test_completion_docstring(Script, jedi_path):
    """
    Jedi should follow imports in certain conditions
    """
    def docstr(src, result):
        c = Script(src, sys_path=[jedi_path]).completions()[0]
        assert c.docstring(raw=True, fast=False) == cleandoc(result)

    c = Script('import jedi\njed', sys_path=[jedi_path]).completions()[0]
    assert c.docstring(fast=False) == cleandoc(jedi_doc)

    docstr('import jedi\njedi.Scr', cleandoc(jedi.Script.__doc__))

    docstr('abcd=3;abcd', '')
    docstr('"hello"\nabcd=3\nabcd', '')
    docstr(dedent('''
        def x():
            "hello"
            0
        x'''),
        'hello'
    )
    docstr(dedent('''
        def x():
            "hello";0
        x'''),
        'hello'
    )
    # Shouldn't work with a tuple.
    docstr(dedent('''
        def x():
            "hello",0
        x'''),
        ''
    )
    # Should also not work if we rename something.
    docstr(dedent('''
        def x():
            "hello"
        y = x
        y'''),
        ''
    )


def test_completion_params(Script):
    c = Script('import string; string.capwords').completions()[0]
    assert [p.name for p in c.params] == ['s', 'sep']


def test_hashlib_params(Script, environment):
    if environment.version_info < (3,):
        pytest.skip()

    script = Script(source='from hashlib import ', line=1, column=20)
    c = script.completions()
    assert c[2].params


def test_signature_params(Script):
    def check(defs):
        params = defs[0].params
        assert len(params) == 1
        assert params[0].name == 'bar'

    s = dedent('''
    def foo(bar):
        pass
    foo''')

    check(Script(s).goto_definitions())

    check(Script(s).goto_assignments())
    check(Script(s + '\nbar=foo\nbar').goto_assignments())


def test_param_endings(Script):
    """
    Params should be represented without the comma and whitespace they have
    around them.
    """
    sig = Script('def x(a, b=5, c=""): pass\n x(').call_signatures()[0]
    assert [p.description for p in sig.params] == ['param a', 'param b=5', 'param c=""']


class TestIsDefinition(TestCase):
    @pytest.fixture(autouse=True)
    def init(self, environment):
        self.environment = environment

    def _def(self, source, index=-1):
        return names(
            dedent(source),
            references=True,
            all_scopes=True,
            environment=self.environment
        )[index]

    def _bool_is_definitions(self, source):
        ns = names(dedent(source), references=True, all_scopes=True)
        # Assure that names are definitely sorted.
        ns = sorted(ns, key=lambda name: (name.line, name.column))
        return [name.is_definition() for name in ns]

    def test_name(self):
        d = self._def('name')
        assert d.name == 'name'
        assert not d.is_definition()

    def test_stmt(self):
        src = 'a = f(x)'
        d = self._def(src, 0)
        assert d.name == 'a'
        assert d.is_definition()
        d = self._def(src, 1)
        assert d.name == 'f'
        assert not d.is_definition()
        d = self._def(src)
        assert d.name == 'x'
        assert not d.is_definition()

    def test_import(self):
        assert self._bool_is_definitions('import x as a') == [False, True]
        assert self._bool_is_definitions('from x import y') == [False, True]
        assert self._bool_is_definitions('from x.z import y') == [False, False, True]


class TestParent(TestCase):
    @pytest.fixture(autouse=True)
    def init(self, Script):
        self.Script = Script

    def _parent(self, source, line=None, column=None):
        def_, = self.Script(dedent(source), line, column).goto_assignments()
        return def_.parent()

    def test_parent(self):
        parent = self._parent('foo=1\nfoo')
        assert parent.type == 'module'

        parent = self._parent('''
            def spam():
                if 1:
                    y=1
                    y''')
        assert parent.name == 'spam'
        assert parent.parent().type == 'module'

    def test_on_function(self):
        parent = self._parent('''\
            def spam():
                pass''', 1, len('def spam'))
        assert parent.name == ''
        assert parent.type == 'module'


def test_parent_on_completion(Script):
    parent = Script(dedent('''\
        class Foo():
            def bar(): pass
        Foo().bar''')).completions()[0].parent()
    assert parent.name == 'Foo'
    assert parent.type == 'class'

    parent = Script('str.join').completions()[0].parent()
    assert parent.name == 'str'
    assert parent.type == 'class'


def test_type(Script):
    for c in Script('a = [str()]; a[0].').completions():
        if c.name == '__class__':
            assert c.type == 'class'
        else:
            assert c.type in ('function', 'instance')

    for c in Script('list.').completions():
        assert c.type

    # Github issue #397, type should never raise an error.
    for c in Script('import os; os.path.').completions():
        assert c.type


def test_type_II(Script):
    """
    GitHub Issue #833, `keyword`s are seen as `module`s
    """
    for c in Script('f').completions():
        if c.name == 'for':
            assert c.type == 'keyword'


"""
This tests the BaseDefinition.goto_assignments function, not the jedi
function. They are not really different in functionality, but really
different as an implementation.
"""


def test_goto_assignment_repetition(environment):
    defs = names('a = 1; a', references=True, definitions=False, environment=environment)
    # Repeat on the same variable. Shouldn't change once we're on a
    # definition.
    for _ in range(3):
        assert len(defs) == 1
        ass = defs[0].goto_assignments()
        assert ass[0].description == 'a = 1'


def test_goto_assignments_named_params(environment):
    src = """\
            def foo(a=1, bar=2):
                pass
            foo(bar=1)
          """
    bar = names(dedent(src), references=True, environment=environment)[-1]
    param = bar.goto_assignments()[0]
    assert (param.line, param.column) == (1, 13)
    assert param.type == 'param'


def test_class_call(environment):
    src = 'from threading import Thread; Thread(group=1)'
    n = names(src, references=True, environment=environment)[-1]
    assert n.name == 'group'
    param_def = n.goto_assignments()[0]
    assert param_def.name == 'group'
    assert param_def.type == 'param'


def test_parentheses(environment):
    n = names('("").upper', references=True, environment=environment)[-1]
    assert n.goto_assignments()[0].name == 'upper'


def test_import(environment):
    nms = names('from json import load', references=True, environment=environment)
    assert nms[0].name == 'json'
    assert nms[0].type == 'module'
    n = nms[0].goto_assignments()[0]
    assert n.name == 'json'
    assert n.type == 'module'

    assert nms[1].name == 'load'
    assert nms[1].type == 'function'
    n = nms[1].goto_assignments()[0]
    assert n.name == 'load'
    assert n.type == 'function'

    nms = names('import os; os.path', references=True, environment=environment)
    assert nms[0].name == 'os'
    assert nms[0].type == 'module'
    n = nms[0].goto_assignments()[0]
    assert n.name == 'os'
    assert n.type == 'module'

    n = nms[2].goto_assignments()[0]
    assert n.name == 'path'
    assert n.type == 'module'

    nms = names('import os.path', references=True, environment=environment)
    n = nms[0].goto_assignments()[0]
    assert n.name == 'os'
    assert n.type == 'module'
    n = nms[1].goto_assignments()[0]
    # This is very special, normally the name doesn't chance, but since
    # os.path is a sys.modules hack, it does.
    assert n.name in ('ntpath', 'posixpath', 'os2emxpath')
    assert n.type == 'module'


def test_import_alias(environment):
    nms = names('import json as foo', references=True, environment=environment)
    assert nms[0].name == 'json'
    assert nms[0].type == 'module'
    assert nms[0]._name.tree_name.parent.type == 'dotted_as_name'
    n = nms[0].goto_assignments()[0]
    assert n.name == 'json'
    assert n.type == 'module'
    assert n._name._context.tree_node.type == 'file_input'

    assert nms[1].name == 'foo'
    assert nms[1].type == 'module'
    assert nms[1]._name.tree_name.parent.type == 'dotted_as_name'
    ass = nms[1].goto_assignments()
    assert len(ass) == 1
    assert ass[0].name == 'json'
    assert ass[0].type == 'module'
    assert ass[0]._name._context.tree_node.type == 'file_input'


def test_added_equals_to_params(Script):
    def run(rest_source):
        source = dedent("""
        def foo(bar, baz):
            pass
        """)
        results = Script(source + rest_source).completions()
        assert len(results) == 1
        return results[0]

    assert run('foo(bar').name_with_symbols == 'bar='
    assert run('foo(bar').complete == '='
    assert run('foo(bar, baz').complete == '='
    assert run('    bar').name_with_symbols == 'bar'
    assert run('    bar').complete == ''
    x = run('foo(bar=isins').name_with_symbols
    assert x == 'isinstance'
