"""
Test lldb-dap stackTrace request with an extended backtrace thread.
"""

import os

import lldbdap_testcase
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test.lldbplatformutil import *


class TestDAP_extendedStackTrace(lldbdap_testcase.DAPTestCaseBase):
    def build_and_run(self, displayExtendedBacktrace=True):
        backtrace_recording_lib = findBacktraceRecordingDylib()
        if not backtrace_recording_lib:
            self.skipTest(
                "Skipped because libBacktraceRecording.dylib was present on the system."
            )

        if not os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"):
            self.skipTest(
                "Skipped because introspection libdispatch dylib is not present."
            )

        program = self.getBuildArtifact("a.out")

        self.build_and_launch(
            program,
            env=[
                "DYLD_LIBRARY_PATH=/usr/lib/system/introspection",
                "DYLD_INSERT_LIBRARIES=" + backtrace_recording_lib,
            ],
            displayExtendedBacktrace=displayExtendedBacktrace,
        )
        source = "main.m"
        breakpoint = line_number(source, "breakpoint 1")
        lines = [breakpoint]

        breakpoint_ids = self.set_source_breakpoints(source, lines)
        self.assertEqual(
            len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
        )

    @skipUnlessDarwin
    def test_stackTrace(self):
        """
        Tests the 'stackTrace' packet on a thread with an extended backtrace.
        """
        self.build_and_run()
        events = self.continue_to_next_stop()

        stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
            threadId=events[0]["body"]["threadId"]
        )
        self.assertGreaterEqual(len(stackFrames), 3, "expect >= 3 frames")
        self.assertEqual(len(stackFrames), totalFrames)
        self.assertEqual(stackFrames[0]["name"], "one")
        self.assertEqual(stackFrames[1]["name"], "two")
        self.assertEqual(stackFrames[2]["name"], "three")

        stackLabels = [
            (i, frame)
            for i, frame in enumerate(stackFrames)
            if frame.get("presentationHint", "") == "label"
        ]
        self.assertEqual(len(stackLabels), 2, "expected two label stack frames")
        self.assertRegex(
            stackLabels[0][1]["name"],
            r"Enqueued from com.apple.root.default-qos \(Thread \d\)",
        )
        self.assertRegex(
            stackLabels[1][1]["name"],
            r"Enqueued from com.apple.main-thread \(Thread \d\)",
        )

        for i, frame in stackLabels:
            # Ensure requesting startFrame+levels across thread backtraces works as expected.
            stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
                threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=3
            )
            self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
            self.assertGreaterEqual(
                totalFrames, i + 3, "total frames should include a pagination offset"
            )
            self.assertEqual(stackFrames[1], frame)

            # Ensure requesting startFrame+levels at the beginning of a thread backtraces works as expected.
            stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
                threadId=events[0]["body"]["threadId"], startFrame=i, levels=3
            )
            self.assertEqual(len(stackFrames), 3, "expected 3 frames with levels=3")
            self.assertGreaterEqual(
                totalFrames, i + 3, "total frames should include a pagination offset"
            )
            self.assertEqual(stackFrames[0], frame)

            # Ensure requests with startFrame+levels that end precisely on the last frame includes the totalFrames pagination offset.
            stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
                threadId=events[0]["body"]["threadId"], startFrame=i - 1, levels=1
            )
            self.assertEqual(len(stackFrames), 1, "expected 1 frames with levels=1")
            self.assertGreaterEqual(
                totalFrames, i, "total frames should include a pagination offset"
            )

    @skipUnlessDarwin
    def test_stackTraceWithFormat(self):
        """
        Tests the 'stackTrace' packet on a thread with an extended backtrace using stack trace formats.
        """
        self.build_and_run(displayExtendedBacktrace=False)
        events = self.continue_to_next_stop()

        stackFrames, _ = self.get_stackFrames_and_totalFramesCount(
            threadId=events[0]["body"]["threadId"], format={"includeAll": True}
        )

        stackLabels = [
            (i, frame)
            for i, frame in enumerate(stackFrames)
            if frame.get("presentationHint", "") == "label"
        ]

        self.assertEqual(len(stackLabels), 2, "expected two label stack frames")
