"""
Test the OverridesResolver feature of scripted breakpoints
"""

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


class TestOverridesResolver(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528")
    def test_overrides_resolver_resolver_python(self):
        """Use facade breakpoints to emulate hitting some locations"""
        self.build()
        self.do_test(True)

    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528")
    def test_overrides_resolver_resolver_cmd(self):
        """Use facade breakpoints to emulate hitting some locations"""
        self.build()
        self.do_test(False)

    def test_bad_values(self):
        self.build()
        self.do_test_bad_values()

    def make_target_and_import(self):
        self.target = lldbutil.run_to_breakpoint_make_target(self)
        self.import_resolver_script()
        return self.target

    def import_resolver_script(self):
        interp = self.dbg.GetCommandInterpreter()
        error = lldb.SBError()

        script_name = os.path.join(self.getSourceDir(), "bkpt_resolver.py")

        command = "command script import " + script_name
        self.runCmd(command)

    def calculate_mask_desc(self, mask):
        mask_str = ""
        if mask & lldb.eResolverFileAndLine:
            mask_str += "F"
        if mask & lldb.eResolverName:
            mask_str += "N"
        return mask_str

    def add_override_python(
        self,
        help_text,
        class_name,
        mask,
        key,
        value,
        expect_error=False,
        error_string="",
    ):
        extra_args = lldb.SBStructuredData()
        json_str = '{"' + str(key) + '":"' + str(value) + '"}'
        extra_args.SetFromJSON(json_str)
        error = lldb.SBError()
        override_id = self.target.AddBreakpointOverride(
            class_name, help_text, mask, extra_args, error
        )
        if not expect_error:
            self.assertSuccess(error, "Made the override successfully")
        else:
            self.assertFailure(error, error_string)
            return 0

        # Check the override listing, make sure our new entry is present:
        mask_str = self.calculate_mask_desc(mask)

        self.expect(
            "breakpoint override list", substrs=[str(override_id), mask_str, help_text]
        )

        return override_id

    def add_override_cmd(
        self,
        help_text,
        class_name,
        mask_args,
        mask_desc,
        key,
        value,
        expect_error=False,
        error_string="",
    ):
        result = lldb.SBCommandReturnObject()
        self.ci.HandleCommand(
            f"breakpoint override add -P {class_name} -k {key} -v {value} -d '{help_text}' {mask_args}",
            result,
        )
        if not expect_error:
            self.assertCommandReturn(result, "breakpoint override worked")
            override_id = int(result.GetOutput())
        else:
            self.assertFalse(result.Succeeded(), "We expected this to fail")
            return 0

        # Check the override listing, make sure our new entry is present:
        self.expect(
            "breakpoint override list", substrs=[str(override_id), mask_desc, help_text]
        )

        return override_id

    def do_test(self, use_cmd):
        """This reads in a python file and sets a breakpoint using it."""
        alternate_location = "stop_here_instead"
        self.make_target_and_import()
        # Add our trivial one first so we test more than one list element:

        trivial_help = "Trivial help text"
        trivial_id = 0
        useful_id = 0

        if use_cmd:
            trivial_id = self.add_override_cmd(
                trivial_help,
                "bkpt_resolver.TrivialExample",
                "-m name",
                "N",
                "test_key",
                "test_value",
            )

            useful_help = "SOME HELP TEXT"
            useful_id = self.add_override_cmd(
                useful_help,
                "bkpt_resolver.OverrideExample",
                "-m file_and_line",
                "F",
                "symbol",
                "stop_here_instead",
            )
        else:
            trivial_id = self.add_override_python(
                trivial_help,
                "bkpt_resolver.TrivialExample",
                lldb.eResolverName,
                "test_key",
                "test_value",
            )

            useful_help = "SOME HELP TEXT"
            useful_id = self.add_override_python(
                useful_help,
                "bkpt_resolver.OverrideExample",
                lldb.eResolverFileAndLine,
                "symbol",
                "stop_here_instead",
            )

        # Now exercise the list command by id:
        self.expect(
            f"breakpoint override list {trivial_id}",
            substrs=[str(useful_id), useful_help],
            matching=False,
        )
        self.expect(
            f"breakpoint override list {trivial_id}",
            substrs=[str(trivial_id), trivial_help],
        )
        self.expect(
            f"breakpoint override list {useful_id}",
            substrs=[str(trivial_id), trivial_help],
            matching=False,
        )
        self.expect(
            f"breakpoint override list {useful_id}",
            substrs=[str(useful_id), useful_help],
        )

        # Now make a breakpoint by file and line:
        bkpt = self.target.BreakpointCreateByLocation(
            "main.c", line_number("main.c", "I am in the stop symbol")
        )
        self.assertEqual(bkpt.GetNumLocations(), 1, "We make one location")
        # Make sure that the override was called but trivial was not:
        self.expect("checker override", startstr="1")
        self.expect("checker trivial", startstr="0")

        # Now continue and we'll hit this breakpoint but not in the
        # right place:
        (_, process, thread, bkpt) = lldbutil.run_to_breakpoint_do_run(
            self, self.target, bkpt
        )
        # This location should be bkpt_no.1:
        self.assertEqual(
            thread.stop_reason_data[0], bkpt.GetID(), "Hit the right breakpoint"
        )
        self.assertEqual(thread.stop_reason_data[1], 1, "First location hit is 1")
        func_name = thread.frames[0].name
        self.assertEqual(
            func_name, alternate_location, "Stopped at overridden location"
        )

        # Now set a symbol name breakpoint, that should not get overridden, and
        # when we continue we should hit it:
        name_bkpt = self.target.BreakpointCreateByName("change_him")
        self.assertGreater(name_bkpt.GetNumLocations(), 0, "Found locations")
        # Now we've made one by name and one file and line breakpoint so both
        # override functions should have been called.
        self.expect("checker trivial", startstr="1")
        self.expect("checker override", startstr="1")

        threads = lldbutil.continue_to_breakpoint(process, name_bkpt)
        self.assertEqual(len(threads), 1, "Hit our name breakpoint")
        func_name = threads[0].frames[0].name
        self.assertEqual(func_name, "change_him", "Stopped in the right place")

        # Now delete the override and make sure we hit newly set
        # source breakpoints:
        if use_cmd:
            self.runCmd(f"breakpoint override delete {useful_id}")
        else:
            self.assertTrue(
                self.target.RemoveBreakpointOverride(useful_id), "Delete the right one"
            )

        # Make sure it's gone from the listings:
        self.expect(
            "breakpoint override list",
            substrs=[str(useful_id), useful_help],
            matching=False,
        )
        # And that listing it is an error:
        self.expect(f"breakpoint override list {useful_id}", error=True)

        new_bkpt = self.target.BreakpointCreateByLocation(
            "main.c", line_number("main.c", "return 0")
        )
        # Neither override should have been called for this breakpoint
        # so the counts should still be at 1 each:
        self.expect("checker trivial", startstr="1")
        self.expect("checker override", startstr="1")

        self.assertEqual(new_bkpt.num_locations, 1, "Made breakpoint")
        threads = lldbutil.continue_to_breakpoint(process, new_bkpt)
        self.assertEqual(len(threads), 1, "Hit our new breakpoint")
        func_name = threads[0].frames[0].name
        self.assertEqual(func_name, "main", "Stopped in unchanged location")

        # Finally, make sure neither of the overrides was called with a
        # type that was not part of the mask.
        self.expect("checker trivial_not_name", startstr="0")
        self.expect("checker override_not_file", startstr="0")

    def do_test_bad_values(self):
        self.make_target_and_import()
        # Add our trivial one with a bad mask:

        trivial_help = "Trivial help text"
        bad_type_mask = lldb.BreakpointResolverAllResolversMask + 1
        trivial_id = self.add_override_python(
            trivial_help,
            "bkpt_resolver.TrivialExample",
            bad_type_mask,
            "test_key",
            "test_value",
            expect_error=True,
            error_string="invalid breakpoint type mask: 64, should be composed of the elements of the BreakpointResolverType enum.",
        )

        # Now try with an empty class name:
        trivial_id = self.add_override_python(
            trivial_help,
            "",
            lldb.eResolverName,
            "test_key",
            "test_value",
            expect_error=True,
            error_string="empty class name",
        )
        # FIXME: We aren't currently returning a correct
        # error when the class name doesn't exist.
        # And with a class name that doesn't exist:
        # trivial_id = self.add_override_python(
        #    trivial_help,
        #    "NoModuleOfThisName.NoClassOfThisName",
        #    lldb.eResolverName,
        #    "test_key",
        #    "test_value",
        #    expect_error=True,
        #    error_string=""
        # )

        trivial_id = self.add_override_cmd(
            trivial_help,
            "bkpt_resolver.TrivialExample",
            "-m  no_such_resolver",
            "",
            "test_key",
            "test_value",
            expect_error=True,
            error_string="",
        )
