"""
Test dwim-print with variables, variable paths, and expressions.
"""

import re
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil


class TestCase(TestBase):
    def _run_cmd(self, cmd: str) -> str:
        """Run the given lldb command and return its output."""
        result = lldb.SBCommandReturnObject()
        self.ci.HandleCommand(cmd, result)
        return result.GetOutput().rstrip()

    VAR_IDENT = re.compile(r"(?:\$\d+|\w+) = ")

    def _strip_result_var(self, string: str) -> str:
        """
        Strip (persistent) result variables (ex '$0 = ', or 'someVar = ', etc).

        This allows for using the output of `expression`/`frame variable`, to
        compare it to `dwim-print` output, which disables result variables.
        """
        return self.VAR_IDENT.subn("", string, 1)[0]

    def _expect_cmd(
        self,
        dwim_cmd: str,
        actual_cmd: str,
    ) -> None:
        """Run dwim-print and verify the output against the expected command."""
        # Resolve the dwim-print command to either `expression` or `frame variable`.
        substitute_cmd = dwim_cmd.replace("dwim-print", actual_cmd, 1)
        interp = self.dbg.GetCommandInterpreter()
        result = lldb.SBCommandReturnObject()
        interp.ResolveCommand(substitute_cmd, result)
        self.assertTrue(result.Succeeded(), result.GetError())

        resolved_cmd = result.GetOutput()
        if actual_cmd == "frame variable":
            resolved_cmd = resolved_cmd.replace(" -- ", " ", 1)

        resolved_cmd_output = self._run_cmd(resolved_cmd)
        dwim_cmd_output = self._strip_result_var(resolved_cmd_output)

        # Verify dwim-print chose the expected command.
        self.runCmd("settings set dwim-print-verbosity full")

        self.expect(
            dwim_cmd,
            substrs=[
                f"note: ran `{resolved_cmd}`",
                dwim_cmd_output,
            ],
        )

    def test_variables(self):
        """Test dwim-print with variables."""
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        vars = ("argc", "argv")
        for var in vars:
            self._expect_cmd(f"dwim-print {var}", "frame variable")

    def test_variable_paths(self):
        """Test dwim-print with variable path expressions."""
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        exprs = ("&argc", "*argv", "argv[0]")
        for expr in exprs:
            self._expect_cmd(f"dwim-print {expr}", "expression")

    def test_expressions(self):
        """Test dwim-print with expressions."""
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        exprs = ("argc + 1", "(void)argc", "(int)abs(argc)")
        for expr in exprs:
            self._expect_cmd(f"dwim-print {expr}", "expression")

    def test_dummy_target_expressions(self):
        """Test dwim-print's ability to evaluate expressions without a target."""
        self._expect_cmd("dwim-print 1 + 2", "expression")

    def test_gdb_format(self):
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        self._expect_cmd(f"dwim-print/x argc", "frame variable")
        self._expect_cmd(f"dwim-print/x argc + 1", "expression")

    def test_format_flags(self):
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        self._expect_cmd(f"dwim-print -fx -- argc", "frame variable")
        self._expect_cmd(f"dwim-print -fx -- argc + 1", "expression")

    def test_display_flags(self):
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        self._expect_cmd(f"dwim-print -T -- argc", "frame variable")
        self._expect_cmd(f"dwim-print -T -- argc + 1", "expression")

    def test_expression_language(self):
        """Test that the language flag doesn't affect the choice of command."""
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        self._expect_cmd(f"dwim-print -l c++ -- argc", "frame variable")
        self._expect_cmd(f"dwim-print -l c++ -- argc + 1", "expression")

    def test_empty_expression(self):
        self.build()
        lldbutil.run_to_name_breakpoint(self, "main")
        error_msg = "error: 'dwim-print' takes a variable or expression"
        self.expect(f"dwim-print", error=True, startstr=error_msg)
        self.expect(f"dwim-print -- ", error=True, startstr=error_msg)

    def test_nested_values(self):
        """Test dwim-print with nested values (structs, etc)."""
        self.build()
        lldbutil.run_to_source_breakpoint(
            self, "break here", lldb.SBFileSpec("main.c")
        )
        self.runCmd("settings set auto-one-line-summaries false")
        self._expect_cmd(f"dwim-print s", "frame variable")
        self._expect_cmd(f"dwim-print (struct Structure)s", "expression")

    def test_summary_strings(self):
        """Test dwim-print with nested values (structs, etc)."""
        self.build()
        lldbutil.run_to_source_breakpoint(
            self, "break here", lldb.SBFileSpec("main.c")
        )
        self.runCmd("settings set auto-one-line-summaries false")
        self.runCmd("type summary add -e -s 'stub summary' Structure")
        self._expect_cmd(f"dwim-print s", "frame variable")
        self._expect_cmd(f"dwim-print (struct Structure)s", "expression")

    def test_void_result(self):
        """Test dwim-print does not surface an error message for void expressions."""
        self.build()
        lldbutil.run_to_source_breakpoint(
            self, "break here", lldb.SBFileSpec("main.c")
        )
        self.expect("dwim-print (void)15", matching=False, patterns=["(?i)error"])

    def test_preserves_persistent_variables(self):
        """Test dwim-print does not delete persistent variables."""
        self.build()
        lldbutil.run_to_source_breakpoint(
            self, "break here", lldb.SBFileSpec("main.c")
        )
        self.expect("dwim-print int $i = 15")
        # Run the same expression twice and verify success. This ensures the
        # first command does not delete the persistent variable.
        for _ in range(2):
            self.expect("dwim-print $i", startstr="(int) 15")

    def test_missing_type(self):
        """The expected output of po opaque is its address (no error)"""
        self.build()
        lldbutil.run_to_source_breakpoint(
            self, "break here", lldb.SBFileSpec("main.c")
        )
        self.expect("dwim-print -O -- opaque", substrs=['0x'])
