"""
Test the lldb command line completion mechanism for the 'expr' command.
"""


import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbplatform
from lldbsuite.test import lldbutil


class CommandLineExprCompletionTestCase(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    def test_expr_completion(self):
        self.build()
        self.main_source = "main.cpp"
        self.main_source_spec = lldb.SBFileSpec(self.main_source)
        self.createTestTarget()

        # Try the completion before we have a context to complete on.
        self.assume_no_completions("expr some_expr")
        self.assume_no_completions("expr ")
        self.assume_no_completions("expr f")

        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
            self, "// Break here", self.main_source_spec
        )

        # Completing member functions
        self.complete_from_to(
            "expr some_expr.FooNoArgs", "expr some_expr.FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr some_expr.FooWithArgs", "expr some_expr.FooWithArgsBar("
        )
        self.complete_from_to(
            "expr some_expr.FooWithMultipleArgs",
            "expr some_expr.FooWithMultipleArgsBar(",
        )
        self.complete_from_to(
            "expr some_expr.FooUnderscore", "expr some_expr.FooUnderscoreBar_()"
        )
        self.complete_from_to(
            "expr some_expr.FooNumbers", "expr some_expr.FooNumbersBar1()"
        )
        self.complete_from_to(
            "expr some_expr.StaticMemberMethod",
            "expr some_expr.StaticMemberMethodBar()",
        )

        # Completing static functions
        self.complete_from_to(
            "expr Expr::StaticMemberMethod", "expr Expr::StaticMemberMethodBar()"
        )

        # Completing member variables
        self.complete_from_to(
            "expr some_expr.MemberVariab", "expr some_expr.MemberVariableBar"
        )

        # Multiple completions
        self.completions_contain(
            "expr some_expr.",
            [
                "some_expr.FooNumbersBar1()",
                "some_expr.FooUnderscoreBar_()",
                "some_expr.FooWithArgsBar(",
                "some_expr.MemberVariableBar",
            ],
        )

        self.completions_contain(
            "expr some_expr.Foo",
            [
                "some_expr.FooNumbersBar1()",
                "some_expr.FooUnderscoreBar_()",
                "some_expr.FooWithArgsBar(",
            ],
        )

        self.completions_contain(
            "expr ", ["static_cast", "reinterpret_cast", "dynamic_cast"]
        )

        self.completions_contain(
            "expr 1 + ", ["static_cast", "reinterpret_cast", "dynamic_cast"]
        )

        # Completion expr without spaces
        # This is a bit awkward looking for the user, but that's how
        # the completion API works at the moment.
        self.completions_contain("expr 1+", ["1+some_expr", "1+static_cast"])

        # Test with spaces
        self.complete_from_to(
            "expr   some_expr .FooNoArgs", "expr   some_expr .FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr  some_expr .FooNoArgs", "expr  some_expr .FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr some_expr .FooNoArgs", "expr some_expr .FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr some_expr. FooNoArgs", "expr some_expr. FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr some_expr . FooNoArgs", "expr some_expr . FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr Expr :: StaticMemberMethod", "expr Expr :: StaticMemberMethodBar()"
        )
        self.complete_from_to(
            "expr Expr ::StaticMemberMethod", "expr Expr ::StaticMemberMethodBar()"
        )
        self.complete_from_to(
            "expr Expr:: StaticMemberMethod", "expr Expr:: StaticMemberMethodBar()"
        )

        # Test that string literals don't break our parsing logic.
        self.complete_from_to(
            'expr const char *cstr = "some_e"; char c = *cst',
            'expr const char *cstr = "some_e"; char c = *cstr',
        )
        self.complete_from_to(
            'expr const char *cstr = "some_e" ; char c = *cst',
            'expr const char *cstr = "some_e" ; char c = *cstr',
        )
        # Requesting completions inside an incomplete string doesn't provide any
        # completions.
        self.complete_from_to(
            'expr const char *cstr = "some_e', 'expr const char *cstr = "some_e'
        )

        # Completing inside double dash should do nothing
        self.assume_no_completions("expr -i0 -- some_expr.", 10)
        self.assume_no_completions("expr -i0 -- some_expr.", 11)

        # Test with expr arguments
        self.complete_from_to(
            "expr -i0 -- some_expr .FooNoArgs", "expr -i0 -- some_expr .FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr  -i0 -- some_expr .FooNoArgs",
            "expr  -i0 -- some_expr .FooNoArgsBar()",
        )

        # Addrof and deref
        self.complete_from_to(
            "expr (*(&some_expr)).FooNoArgs", "expr (*(&some_expr)).FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr (*(&some_expr)) .FooNoArgs", "expr (*(&some_expr)) .FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr (* (&some_expr)) .FooNoArgs", "expr (* (&some_expr)) .FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr (* (& some_expr)) .FooNoArgs",
            "expr (* (& some_expr)) .FooNoArgsBar()",
        )

        # Addrof and deref (part 2)
        self.complete_from_to(
            "expr (&some_expr)->FooNoArgs", "expr (&some_expr)->FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr (&some_expr) ->FooNoArgs", "expr (&some_expr) ->FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr (&some_expr) -> FooNoArgs", "expr (&some_expr) -> FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr (&some_expr)-> FooNoArgs", "expr (&some_expr)-> FooNoArgsBar()"
        )

        # Builtin arg
        self.complete_from_to("expr static_ca", "expr static_cast")

        # From other files
        self.complete_from_to(
            "expr fwd_decl_ptr->Hidden", "expr fwd_decl_ptr->HiddenMember"
        )

        # Types
        self.complete_from_to("expr LongClassNa", "expr LongClassName")
        self.complete_from_to(
            "expr LongNamespaceName::NestedCla", "expr LongNamespaceName::NestedClass"
        )

        # Namespaces
        self.complete_from_to("expr LongNamespaceNa", "expr LongNamespaceName::")

        # Multiple arguments
        self.complete_from_to(
            "expr &some_expr + &some_e", "expr &some_expr + &some_expr"
        )
        self.complete_from_to(
            "expr SomeLongVarNameWithCapitals + SomeLongVarName",
            "expr SomeLongVarNameWithCapitals + SomeLongVarNameWithCapitals",
        )
        self.complete_from_to(
            "expr SomeIntVar + SomeIntV", "expr SomeIntVar + SomeIntVar"
        )

        # Multiple statements
        self.complete_from_to(
            "expr long LocalVariable = 0; LocalVaria",
            "expr long LocalVariable = 0; LocalVariable",
        )

        # Custom Decls
        self.complete_from_to(
            "expr auto l = [](int LeftHandSide, int bx){ return LeftHandS",
            "expr auto l = [](int LeftHandSide, int bx){ return LeftHandSide",
        )
        self.complete_from_to(
            "expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.Mem",
            "expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.MemberName",
        )

        # Completing function call arguments
        self.complete_from_to(
            "expr some_expr.FooWithArgsBar(some_exp",
            "expr some_expr.FooWithArgsBar(some_expr",
        )
        self.complete_from_to(
            "expr some_expr.FooWithArgsBar(SomeIntV",
            "expr some_expr.FooWithArgsBar(SomeIntVar",
        )
        self.complete_from_to(
            "expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVa",
            "expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVar",
        )

        # Function return values
        self.complete_from_to(
            "expr some_expr.Self().FooNoArgs", "expr some_expr.Self().FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr some_expr.Self() .FooNoArgs", "expr some_expr.Self() .FooNoArgsBar()"
        )
        self.complete_from_to(
            "expr some_expr.Self(). FooNoArgs", "expr some_expr.Self(). FooNoArgsBar()"
        )

        self.complete_from_to("expr myVec.__f", "expr myVec.__func()")
        self.complete_from_to("expr myVec._F", "expr myVec._Func()")
        self.complete_from_to("expr myVec.__m", "expr myVec.__mem")
        self.complete_from_to("expr myVec._M", "expr myVec._Mem")

    def test_expr_completion_with_descriptions(self):
        self.build()
        self.main_source = "main.cpp"
        self.main_source_spec = lldb.SBFileSpec(self.main_source)
        self.createTestTarget()

        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
            self, "// Break here", self.main_source_spec
        )

        self.check_completion_with_desc(
            "expr ",
            [
                # builtin types have no description.
                ["int", ""],
                ["float", ""],
                # VarDecls have their type as description.
                ["some_expr", "Expr &"],
            ],
            enforce_order=True,
        )
        self.check_completion_with_desc(
            "expr some_expr.",
            [
                # Functions have their signature as description.
                ["some_expr.~Expr()", "inline ~Expr()"],
                ["some_expr.operator=(", "inline Expr &operator=(const Expr &)"],
                # FieldDecls have their type as description.
                ["some_expr.MemberVariableBar", "int"],
                [
                    "some_expr.StaticMemberMethodBar()",
                    "static int StaticMemberMethodBar()",
                ],
                ["some_expr.Self()", "Expr &Self()"],
                ["some_expr.FooNoArgsBar()", "int FooNoArgsBar()"],
                ["some_expr.FooWithArgsBar(", "int FooWithArgsBar(int)"],
                ["some_expr.FooNumbersBar1()", "int FooNumbersBar1()"],
                ["some_expr.FooUnderscoreBar_()", "int FooUnderscoreBar_()"],
                [
                    "some_expr.FooWithMultipleArgsBar(",
                    "int FooWithMultipleArgsBar(int, int)",
                ],
            ],
            enforce_order=True,
        )

    def assume_no_completions(self, str_input, cursor_pos=None):
        interp = self.dbg.GetCommandInterpreter()
        match_strings = lldb.SBStringList()
        if cursor_pos is None:
            cursor_pos = len(str_input)
        num_matches = interp.HandleCompletion(
            str_input, cursor_pos, 0, -1, match_strings
        )

        available_completions = []
        for m in match_strings:
            available_completions.append(m)

        self.assertEqual(
            num_matches,
            0,
            "Got matches, but didn't expect any: " + str(available_completions),
        )

    def completions_contain(self, str_input, items):
        interp = self.dbg.GetCommandInterpreter()
        match_strings = lldb.SBStringList()
        num_matches = interp.HandleCompletion(
            str_input, len(str_input), 0, -1, match_strings
        )
        common_match = match_strings.GetStringAtIndex(0)

        for item in items:
            found = False
            for m in match_strings:
                if m == item:
                    found = True
            if not found:
                # Transform match_strings to a python list with strings
                available_completions = []
                for m in match_strings:
                    available_completions.append(m)
                self.assertTrue(
                    found,
                    "Couldn't find completion "
                    + item
                    + " in completions "
                    + str(available_completions),
                )
