"""Test Python APIs for working with formatters"""

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


class SBFormattersAPITestCase(TestBase):
    SHARED_BUILD_TESTCASE = False
    NO_DEBUG_INFO_TESTCASE = True

    def setUp(self):
        # Call super's setUp().
        TestBase.setUp(self)
        self.line = line_number("main.cpp", "// Set break point at this line.")

    def test_formatters_api(self):
        """Test Python APIs for working with formatters"""
        self.build()
        self.setTearDownCleanup()

        """Test Python APIs for working with formatters"""
        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)

        lldbutil.run_break_set_by_file_and_line(
            self, "main.cpp", self.line, num_expected_locations=1, loc_exact=True
        )

        self.runCmd("run", RUN_SUCCEEDED)

        # The stop reason of the thread should be breakpoint.
        self.expect(
            "thread list",
            STOPPED_DUE_TO_BREAKPOINT,
            substrs=["stopped", "stop reason = breakpoint"],
        )

        # This is the function to remove the custom formats in order to have a
        # clean slate for the next test case.
        def cleanup():
            self.runCmd("type format clear", check=False)
            self.runCmd("type summary clear", check=False)
            self.runCmd("type filter clear", check=False)
            self.runCmd("type synthetic clear", check=False)
            self.runCmd("type category delete foobar", check=False)
            self.runCmd("type category delete JASSynth", check=False)
            self.runCmd("type category delete newbar", check=False)

        # Execute the cleanup function during test case tear down.
        self.addTearDownHook(cleanup)

        format = lldb.SBTypeFormat(lldb.eFormatHex)
        category = self.dbg.GetDefaultCategory()
        category.AddTypeFormat(lldb.SBTypeNameSpecifier("int"), format)

        self.expect("frame variable foo.A", substrs=["0x00000001"])
        self.expect("frame variable foo.E", matching=False, substrs=["b8cca70a"])

        category.AddTypeFormat(lldb.SBTypeNameSpecifier("long"), format)
        self.expect("frame variable foo.A", substrs=["0x00000001"])
        self.expect("frame variable foo.E", substrs=["b8cca70a"])

        format.SetFormat(lldb.eFormatOctal)
        category.AddTypeFormat(lldb.SBTypeNameSpecifier("int"), format)
        self.expect("frame variable foo.A", substrs=[" 01"])
        self.expect("frame variable foo.E", substrs=["b8cca70a"])

        category.DeleteTypeFormat(lldb.SBTypeNameSpecifier("int"))
        category.DeleteTypeFormat(lldb.SBTypeNameSpecifier("long"))
        self.expect("frame variable foo.A", matching=False, substrs=[" 01"])
        self.expect("frame variable foo.E", matching=False, substrs=["b8cca70a"])

        summary = lldb.SBTypeSummary.CreateWithSummaryString(
            "the hello world you'll never see"
        )
        summary.SetSummaryString("hello world")
        new_category = self.dbg.GetCategory("foobar")
        self.assertFalse(
            new_category.IsValid(), "getting a non-existing category worked"
        )
        new_category = self.dbg.CreateCategory("foobar")
        new_category.SetEnabled(True)
        new_category.AddTypeSummary(
            lldb.SBTypeNameSpecifier(
                "^.*t$",
                True,  # is_regexp
            ),
            summary,
        )

        self.expect("frame variable foo.A", substrs=["hello world"])
        self.expect("frame variable foo.E", matching=False, substrs=["hello world"])
        self.expect("frame variable foo.B", substrs=["hello world"])
        self.expect("frame variable foo.F", substrs=["hello world"])
        new_category.SetEnabled(False)
        self.expect("frame variable foo.A", matching=False, substrs=["hello world"])
        self.expect("frame variable foo.E", matching=False, substrs=["hello world"])
        self.expect("frame variable foo.B", matching=False, substrs=["hello world"])
        self.expect("frame variable foo.F", matching=False, substrs=["hello world"])
        self.dbg.DeleteCategory(new_category.GetName())
        self.expect("frame variable foo.A", matching=False, substrs=["hello world"])
        self.expect("frame variable foo.E", matching=False, substrs=["hello world"])
        self.expect("frame variable foo.B", matching=False, substrs=["hello world"])
        self.expect("frame variable foo.F", matching=False, substrs=["hello world"])

        filter = lldb.SBTypeFilter(0)
        filter.AppendExpressionPath("A")
        filter.AppendExpressionPath("D")
        self.assertEqual(
            filter.GetNumberOfExpressionPaths(),
            2,
            "filter with two items does not have two items",
        )

        category.AddTypeFilter(lldb.SBTypeNameSpecifier("JustAStruct"), filter)
        self.expect("frame variable foo", substrs=["A = 1", "D = 6.28"])
        self.expect(
            "frame variable foo",
            matching=False,
            substrs=["B = ", "C = ", "E = ", "F = "],
        )

        category.DeleteTypeFilter(lldb.SBTypeNameSpecifier("JustAStruct", True))
        self.expect("frame variable foo", substrs=["A = 1", "D = 6.28"])
        self.expect(
            "frame variable foo",
            matching=False,
            substrs=["B = ", "C = ", "E = ", "F = "],
        )

        category.DeleteTypeFilter(lldb.SBTypeNameSpecifier("JustAStruct", False))
        self.expect("frame variable foo", substrs=["A = 1", "D = 6.28"])
        self.expect(
            "frame variable foo",
            matching=True,
            substrs=["B = ", "C = ", "E = ", "F = "],
        )

        self.runCmd("command script import --allow-reload ./synth.py")

        self.expect("frame variable foo", matching=False, substrs=["X = 1"])

        self.dbg.GetCategory("JASSynth").SetEnabled(True)
        self.expect("frame variable foo", matching=True, substrs=["X = 1"])

        self.dbg.GetCategory("CCCSynth2").SetEnabled(True)
        self.expect(
            "frame variable ccc",
            matching=True,
            substrs=[
                "CCC object with leading synthetic value (int) b = 222",
                "a = 111",
                "b = 222",
                "c = 333",
            ],
        )
        self.dbg.GetCategory("CCCSynth2").SetEnabled(False)

        self.dbg.GetCategory("CCCSynth").SetEnabled(True)
        self.expect(
            "frame variable ccc",
            matching=True,
            substrs=[
                "CCC object with leading value (int) a = 111",
                "a = 111",
                "b = 222",
                "c = 333",
            ],
        )

        self.dbg.GetCategory("BarIntSynth").SetEnabled(True)
        self.expect(
            "frame variable bar_int",
            matching=True,
            substrs=[
                "(int) bar_int = 20 bar_int synthetic: No value",
            ],
        )

        foo_var = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
            .FindVariable("foo")
        )
        self.assertTrue(foo_var.IsValid(), "could not find foo")
        self.assertTrue(
            foo_var.GetDeclaration().IsValid(), "foo declaration is invalid"
        )

        self.assertEqual(
            foo_var.GetNumChildren(),
            2,
            "synthetic value has wrong number of child items (synth)",
        )
        self.assertEqual(
            foo_var.GetChildMemberWithName("X").GetValueAsUnsigned(),
            1,
            "foo_synth.X has wrong value (synth)",
        )
        self.assertFalse(
            foo_var.GetChildMemberWithName("B").IsValid(),
            "foo_synth.B is valid but should not (synth)",
        )

        self.dbg.GetCategory("JASSynth").SetEnabled(False)
        foo_var = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
            .FindVariable("foo")
        )
        self.assertTrue(foo_var.IsValid(), "could not find foo")

        self.assertNotEqual(foo_var.GetNumChildren(), 2, "still seeing synthetic value")

        filter = lldb.SBTypeFilter(0)
        filter.AppendExpressionPath("A")
        filter.AppendExpressionPath("D")
        category.AddTypeFilter(lldb.SBTypeNameSpecifier("JustAStruct"), filter)
        self.expect("frame variable foo", substrs=["A = 1", "D = 6.28"])

        foo_var = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
            .FindVariable("foo")
        )
        self.assertTrue(foo_var.IsValid(), "could not find foo")

        self.assertEqual(
            foo_var.GetNumChildren(),
            2,
            "synthetic value has wrong number of child items (filter)",
        )
        self.assertEqual(
            foo_var.GetChildMemberWithName("X").GetValueAsUnsigned(),
            0,
            "foo_synth.X has wrong value (filter)",
        )
        self.assertEqual(
            foo_var.GetChildMemberWithName("A").GetValueAsUnsigned(),
            1,
            "foo_synth.A has wrong value (filter)",
        )

        self.assertTrue(
            filter.ReplaceExpressionPathAtIndex(0, "C"),
            "failed to replace an expression path in filter",
        )
        self.expect("frame variable foo", substrs=["A = 1", "D = 6.28"])
        category.AddTypeFilter(lldb.SBTypeNameSpecifier("JustAStruct"), filter)
        self.expect("frame variable foo", substrs=["C = 'e'", "D = 6.28"])
        category.AddTypeFilter(lldb.SBTypeNameSpecifier("FooType"), filter)
        filter.ReplaceExpressionPathAtIndex(1, "F")
        self.expect("frame variable foo", substrs=["C = 'e'", "D = 6.28"])
        category.AddTypeFilter(lldb.SBTypeNameSpecifier("JustAStruct"), filter)
        self.expect("frame variable foo", substrs=["C = 'e'", "F = 0"])
        self.expect("frame variable bar", substrs=["C = 'e'", "D = 6.28"])

        foo_var = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
            .FindVariable("foo")
        )
        self.assertTrue(foo_var.IsValid(), "could not find foo")
        self.assertEqual(
            foo_var.GetChildMemberWithName("C").GetValueAsUnsigned(),
            ord("e"),
            "foo_synth.C has wrong value (filter)",
        )

        chosen = self.dbg.GetFilterForType(lldb.SBTypeNameSpecifier("JustAStruct"))
        self.assertEqual(chosen.count, 2, "wrong filter found for JustAStruct")
        self.assertEqual(
            chosen.GetExpressionPathAtIndex(0),
            "C",
            "wrong item at index 0 for JustAStruct",
        )
        self.assertEqual(
            chosen.GetExpressionPathAtIndex(1),
            "F",
            "wrong item at index 1 for JustAStruct",
        )

        self.assertFalse(
            category.DeleteTypeFilter(lldb.SBTypeNameSpecifier("NoSuchType")),
            "deleting a non-existing filter worked",
        )
        self.assertFalse(
            category.DeleteTypeSummary(lldb.SBTypeNameSpecifier("NoSuchType")),
            "deleting a non-existing summary worked",
        )
        self.assertFalse(
            category.DeleteTypeFormat(lldb.SBTypeNameSpecifier("NoSuchType")),
            "deleting a non-existing format worked",
        )
        self.assertFalse(
            category.DeleteTypeSynthetic(lldb.SBTypeNameSpecifier("NoSuchType")),
            "deleting a non-existing synthetic worked",
        )

        self.assertFalse(
            category.DeleteTypeFilter(lldb.SBTypeNameSpecifier("")),
            "deleting a filter for '' worked",
        )
        self.assertFalse(
            category.DeleteTypeSummary(lldb.SBTypeNameSpecifier("")),
            "deleting a summary for '' worked",
        )
        self.assertFalse(
            category.DeleteTypeFormat(lldb.SBTypeNameSpecifier("")),
            "deleting a format for '' worked",
        )
        self.assertFalse(
            category.DeleteTypeSynthetic(lldb.SBTypeNameSpecifier("")),
            "deleting a synthetic for '' worked",
        )

        try:
            self.assertFalse(
                category.AddTypeSummary(lldb.SBTypeNameSpecifier("NoneSuchType"), None),
                "adding a summary valued None worked",
            )
        except:
            pass
        else:
            self.assertFalse(True, "adding a summary valued None worked")

        try:
            self.assertFalse(
                category.AddTypeFilter(lldb.SBTypeNameSpecifier("NoneSuchType"), None),
                "adding a filter valued None worked",
            )
        except:
            pass
        else:
            self.assertFalse(True, "adding a filter valued None worked")

        try:
            self.assertFalse(
                category.AddTypeSynthetic(
                    lldb.SBTypeNameSpecifier("NoneSuchType"), None
                ),
                "adding a synthetic valued None worked",
            )
        except:
            pass
        else:
            self.assertFalse(True, "adding a synthetic valued None worked")

        try:
            self.assertFalse(
                category.AddTypeFormat(lldb.SBTypeNameSpecifier("NoneSuchType"), None),
                "adding a format valued None worked",
            )
        except:
            pass
        else:
            self.assertFalse(True, "adding a format valued None worked")

        self.assertFalse(
            category.AddTypeSummary(
                lldb.SBTypeNameSpecifier("EmptySuchType"), lldb.SBTypeSummary()
            ),
            "adding a summary without value worked",
        )
        self.assertFalse(
            category.AddTypeFilter(
                lldb.SBTypeNameSpecifier("EmptySuchType"), lldb.SBTypeFilter()
            ),
            "adding a filter without value worked",
        )
        self.assertFalse(
            category.AddTypeSynthetic(
                lldb.SBTypeNameSpecifier("EmptySuchType"), lldb.SBTypeSynthetic()
            ),
            "adding a synthetic without value worked",
        )
        self.assertFalse(
            category.AddTypeFormat(
                lldb.SBTypeNameSpecifier("EmptySuchType"), lldb.SBTypeFormat()
            ),
            "adding a format without value worked",
        )

        self.assertFalse(
            category.AddTypeSummary(
                lldb.SBTypeNameSpecifier(""),
                lldb.SBTypeSummary.CreateWithSummaryString(""),
            ),
            "adding a summary for an invalid type worked",
        )
        self.assertFalse(
            category.AddTypeFilter(lldb.SBTypeNameSpecifier(""), lldb.SBTypeFilter(0)),
            "adding a filter for an invalid type worked",
        )
        self.assertFalse(
            category.AddTypeSynthetic(
                lldb.SBTypeNameSpecifier(""),
                lldb.SBTypeSynthetic.CreateWithClassName(""),
            ),
            "adding a synthetic for an invalid type worked",
        )
        self.assertFalse(
            category.AddTypeFormat(
                lldb.SBTypeNameSpecifier(""), lldb.SBTypeFormat(lldb.eFormatHex)
            ),
            "adding a format for an invalid type worked",
        )

        new_category = self.dbg.CreateCategory("newbar")
        new_category.AddTypeSummary(
            lldb.SBTypeNameSpecifier("JustAStruct"),
            lldb.SBTypeSummary.CreateWithScriptCode("return 'hello scripted world';"),
        )
        self.expect(
            "frame variable foo", matching=False, substrs=["hello scripted world"]
        )
        new_category.SetEnabled(True)
        self.expect(
            "frame variable foo", matching=True, substrs=["hello scripted world"]
        )

        self.expect(
            "frame variable foo_ptr", matching=True, substrs=["hello scripted world"]
        )
        new_category.AddTypeSummary(
            lldb.SBTypeNameSpecifier("JustAStruct"),
            lldb.SBTypeSummary.CreateWithScriptCode(
                "return 'hello scripted world';", lldb.eTypeOptionSkipPointers
            ),
        )
        self.expect(
            "frame variable foo", matching=True, substrs=["hello scripted world"]
        )

        frame = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
        )
        foo_ptr = frame.FindVariable("foo_ptr")
        summary = foo_ptr.GetTypeSummary()

        self.assertFalse(
            summary.IsValid(), "summary found for foo* when none was planned"
        )

        self.expect(
            "frame variable foo_ptr", matching=False, substrs=["hello scripted world"]
        )

        new_category.AddTypeSummary(
            lldb.SBTypeNameSpecifier("JustAStruct"),
            lldb.SBTypeSummary.CreateWithSummaryString(
                "hello static world", lldb.eTypeOptionNone
            ),
        )

        summary = foo_ptr.GetTypeSummary()

        self.assertTrue(
            summary.IsValid(), "no summary found for foo* when one was in place"
        )
        self.assertEqual(
            summary.GetData(), "hello static world", "wrong summary found for foo*"
        )

        self.expect("frame variable e1", substrs=["I am an empty Empty1 {}"])
        self.expect("frame variable e2", substrs=["I am an empty Empty2"])
        self.expect(
            "frame variable e2", substrs=["I am an empty Empty2 {}"], matching=False
        )

        self.assertIsNotNone(
            self.dbg.GetCategory(lldb.eLanguageTypeObjC), "ObjC category is None"
        )

    def test_force_synth_off(self):
        """Test that one can have the public API return non-synthetic SBValues if desired"""
        self.build(dictionary={"EXE": "no_synth"})
        self.setTearDownCleanup()

        self.runCmd("file " + self.getBuildArtifact("no_synth"), CURRENT_EXECUTABLE_SET)

        lldbutil.run_break_set_by_file_and_line(
            self, "main.cpp", self.line, num_expected_locations=1, loc_exact=True
        )

        self.runCmd("run", RUN_SUCCEEDED)

        # The stop reason of the thread should be breakpoint.
        self.expect(
            "thread list",
            STOPPED_DUE_TO_BREAKPOINT,
            substrs=["stopped", "stop reason = breakpoint"],
        )

        # This is the function to remove the custom formats in order to have a
        # clean slate for the next test case.
        def cleanup():
            self.runCmd("type format clear", check=False)
            self.runCmd("type summary clear", check=False)
            self.runCmd("type filter clear", check=False)
            self.runCmd("type synthetic clear", check=False)
            self.runCmd("type category delete foobar", check=False)
            self.runCmd("type category delete JASSynth", check=False)
            self.runCmd("type category delete newbar", check=False)
            self.runCmd("settings set target.enable-synthetic-value true")

        # Execute the cleanup function during test case tear down.
        self.addTearDownHook(cleanup)

        frame = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
        )
        int_vector = frame.FindVariable("int_vector")
        if self.TraceOn():
            print(int_vector)
        self.assertEqual(int_vector.GetNumChildren(), 0, "synthetic vector is empty")

        self.runCmd("settings set target.enable-synthetic-value false")
        frame = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
        )
        int_vector = frame.FindVariable("int_vector")
        if self.TraceOn():
            print(int_vector)
        self.assertNotEqual(
            int_vector.GetNumChildren(), 0, '"physical" vector is not empty'
        )

        self.runCmd("settings set target.enable-synthetic-value true")
        frame = (
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
        )
        int_vector = frame.FindVariable("int_vector")
        if self.TraceOn():
            print(int_vector)
        self.assertEqual(
            int_vector.GetNumChildren(), 0, "synthetic vector is still empty"
        )
