"""
Test lldb-dap completions request
"""

import lldbdap_testcase
import dap_server
from lldbsuite.test import lldbutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *

session_completion = {
    "text": "session",
    "label": "session -- Commands controlling LLDB session.",
}
settings_completion = {
    "text": "settings",
    "label": "settings -- Commands for managing LLDB settings.",
}
memory_completion = {
    "text": "memory",
    "label": "memory -- Commands for operating on memory in the current target process.",
}
command_var_completion = {
    "text": "var",
    "label": "var -- Show variables for the current stack frame. Defaults to all arguments and local variables in scope. Names of argument, local, file static and file global variables can be specified.",
}
variable_var_completion = {
    "text": "var",
    "label": "var -- vector<baz> &",
}
variable_var1_completion = {"text": "var1", "label": "var1 -- int &"}
variable_var2_completion = {"text": "var2", "label": "var2 -- int &"}


# Older version of libcxx produce slightly different typename strings for
# templates like vector.
@skipIf(compiler="clang", compiler_version=["<", "16.0"])
class TestDAP_completions(lldbdap_testcase.DAPTestCaseBase):
    def verify_completions(self, actual_list, expected_list, not_expected_list=[]):
        for expected_item in expected_list:
            self.assertIn(expected_item, actual_list)

        for not_expected_item in not_expected_list:
            self.assertNotIn(not_expected_item, actual_list)

    def setup_debuggee(self):
        program = self.getBuildArtifact("a.out")
        source = "main.cpp"
        self.build_and_launch(program)
        self.set_source_breakpoints(
            source,
            [
                line_number(source, "// breakpoint 1"),
                line_number(source, "// breakpoint 2"),
            ],
        )

    def test_command_completions(self):
        """
        Tests completion requests for lldb commands, within "repl-mode=command"
        """
        self.setup_debuggee()
        self.continue_to_next_stop()

        res = self.dap_server.request_evaluate(
            "`lldb-dap repl-mode command", context="repl"
        )
        self.assertTrue(res["success"])

        # Provides completion for top-level commands
        self.verify_completions(
            self.dap_server.get_completions("se"),
            [session_completion, settings_completion],
        )

        # Provides completions for sub-commands
        self.verify_completions(
            self.dap_server.get_completions("memory "),
            [
                {
                    "text": "read",
                    "label": "read -- Read from the memory of the current target process.",
                },
                {
                    "text": "region",
                    "label": "region -- Get information on the memory region containing an address in the current target process.",
                },
            ],
        )

        # Provides completions for parameter values of commands
        self.verify_completions(
            self.dap_server.get_completions("`log enable  "),
            [{"text": "gdb-remote", "label": "gdb-remote"}],
        )

        # Also works if the escape prefix is used
        self.verify_completions(
            self.dap_server.get_completions("`mem"), [memory_completion]
        )

        self.verify_completions(
            self.dap_server.get_completions("`"),
            [session_completion, settings_completion, memory_completion],
        )

        # Completes an incomplete quoted token
        self.verify_completions(
            self.dap_server.get_completions('setting "se'),
            [
                {
                    "text": "set",
                    "label": "set -- Set the value of the specified debugger setting.",
                }
            ],
        )

        # Completes an incomplete quoted token
        self.verify_completions(
            self.dap_server.get_completions("'mem"),
            [memory_completion],
        )

        # Completes expressions with quotes inside
        self.verify_completions(
            self.dap_server.get_completions('expr " "; typed'),
            [{"text": "typedef", "label": "typedef"}],
        )

        # Provides completions for commands, but not variables
        self.verify_completions(
            self.dap_server.get_completions("var"),
            [command_var_completion],
            [variable_var_completion],
        )

    def test_variable_completions(self):
        """
        Tests completion requests in "repl-mode=variable"
        """
        self.setup_debuggee()
        self.continue_to_next_stop()

        res = self.dap_server.request_evaluate(
            "`lldb-dap repl-mode variable", context="repl"
        )
        self.assertTrue(res["success"])

        # Provides completions for varibles, but not command
        self.verify_completions(
            self.dap_server.get_completions("var"),
            [variable_var_completion],
            [command_var_completion],
        )

        # We stopped inside `fun`, so we shouldn't see variables from main
        self.verify_completions(
            self.dap_server.get_completions("var"),
            [variable_var_completion],
            [
                variable_var1_completion,
                variable_var2_completion,
            ],
        )

        # We should see global keywords but not variables inside main
        self.verify_completions(
            self.dap_server.get_completions("str"),
            [{"text": "struct", "label": "struct"}],
            [{"text": "str1", "label": "str1 -- std::string &"}],
        )

        self.continue_to_next_stop()

        # We stopped in `main`, so we should see variables from main but
        # not from the other function
        self.verify_completions(
            self.dap_server.get_completions("var"),
            [
                variable_var1_completion,
                variable_var2_completion,
            ],
            [
                variable_var_completion,
            ],
        )

        self.verify_completions(
            self.dap_server.get_completions("str"),
            [
                {"text": "struct", "label": "struct"},
                {"text": "str1", "label": "str1 -- string &"},
            ],
        )

        # Completion also works for more complex expressions
        self.verify_completions(
            self.dap_server.get_completions("foo1.v"),
            [{"text": "var1", "label": "foo1.var1 -- int"}],
        )

        self.verify_completions(
            self.dap_server.get_completions("foo1.my_bar_object.v"),
            [{"text": "var1", "label": "foo1.my_bar_object.var1 -- int"}],
        )

        self.verify_completions(
            self.dap_server.get_completions("foo1.var1 + foo1.v"),
            [{"text": "var1", "label": "foo1.var1 -- int"}],
        )

        self.verify_completions(
            self.dap_server.get_completions("foo1.var1 + v"),
            [{"text": "var1", "label": "var1 -- int &"}],
        )

        # should correctly handle spaces between objects and member operators
        self.verify_completions(
            self.dap_server.get_completions("foo1 .v"),
            [{"text": "var1", "label": ".var1 -- int"}],
            [{"text": "var2", "label": ".var2 -- int"}],
        )

        self.verify_completions(
            self.dap_server.get_completions("foo1 . v"),
            [{"text": "var1", "label": "var1 -- int"}],
            [{"text": "var2", "label": "var2 -- int"}],
        )

        # Even in variable mode, we can still use the escape prefix
        self.verify_completions(
            self.dap_server.get_completions("`mem"), [memory_completion]
        )

    def test_auto_completions(self):
        """
        Tests completion requests in "repl-mode=auto"
        """
        self.setup_debuggee()

        res = self.dap_server.request_evaluate(
            "`lldb-dap repl-mode auto", context="repl"
        )
        self.assertTrue(res["success"])

        self.continue_to_next_stop()
        self.continue_to_next_stop()

        # We are stopped inside `main`. Variables `var1` and `var2` are in scope.
        # Make sure, we offer all completions
        self.verify_completions(
            self.dap_server.get_completions("va"),
            [
                command_var_completion,
                variable_var1_completion,
                variable_var2_completion,
            ],
        )

        # If we are using the escape prefix, only commands are suggested, but no variables
        self.verify_completions(
            self.dap_server.get_completions("`va"),
            [
                command_var_completion,
            ],
            [
                variable_var1_completion,
                variable_var2_completion,
            ],
        )
