"""
Test lldb-dap logpoints feature.
"""


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


class TestDAP_logpoints(lldbdap_testcase.DAPTestCaseBase):
    def setUp(self):
        lldbdap_testcase.DAPTestCaseBase.setUp(self)

        self.main_basename = "main-copy.cpp"
        self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename))

    @skipIfWindows
    def test_logmessage_basic(self):
        """Tests breakpoint logmessage basic functionality."""
        before_loop_line = line_number("main.cpp", "// before loop")
        loop_line = line_number("main.cpp", "// break loop")
        after_loop_line = line_number("main.cpp", "// after loop")

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

        # Set a breakpoint at a line before loop
        before_loop_breakpoint_ids = self.set_source_breakpoints(
            self.main_path, [before_loop_line]
        )
        self.assertEqual(len(before_loop_breakpoint_ids), 1, "expect one breakpoint")

        self.dap_server.request_continue()

        # Verify we hit the breakpoint before loop line
        self.verify_breakpoint_hit(before_loop_breakpoint_ids)

        # Swallow old console output
        self.get_console()

        # Set two breakpoints:
        # 1. First at the loop line with logMessage
        # 2. Second guard breakpoint at a line after loop
        logMessage_prefix = "This is log message for { -- "
        logMessage = logMessage_prefix + "{i + 3}, {message}"
        [loop_breakpoint_id, post_loop_breakpoint_id] = self.set_source_breakpoints(
            self.main_path,
            [loop_line, after_loop_line],
            [{"logMessage": logMessage}, {}],
        )

        # Continue to trigger the breakpoint with log messages
        self.dap_server.request_continue()

        # Verify we hit the breakpoint after loop line
        self.verify_breakpoint_hit([post_loop_breakpoint_id])

        output = self.get_console()
        lines = output.splitlines()
        logMessage_output = []
        for line in lines:
            if line.startswith(logMessage_prefix):
                logMessage_output.append(line)

        # Verify logMessage count
        loop_count = 10
        self.assertEqual(len(logMessage_output), loop_count)

        message_addr_pattern = r"\b0x[0-9A-Fa-f]+\b"
        message_content = '"Hello from main!"'
        # Verify log message match
        for idx, logMessage_line in enumerate(logMessage_output):
            result = idx + 3
            reg_str = (
                f"{logMessage_prefix}{result}, {message_addr_pattern} {message_content}"
            )
            self.assertRegex(logMessage_line, reg_str)

    @skipIfWindows
    def test_logmessage_advanced(self):
        """Tests breakpoint logmessage functionality for complex expression."""
        before_loop_line = line_number("main.cpp", "// before loop")
        loop_line = line_number("main.cpp", "// break loop")
        after_loop_line = line_number("main.cpp", "// after loop")

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

        # Set a breakpoint at a line before loop
        before_loop_breakpoint_ids = self.set_source_breakpoints(
            self.main_path, [before_loop_line]
        )
        self.assertEqual(len(before_loop_breakpoint_ids), 1, "expect one breakpoint")

        self.dap_server.request_continue()

        # Verify we hit the breakpoint before loop line
        self.verify_breakpoint_hit(before_loop_breakpoint_ids)

        # Swallow old console output
        self.get_console()

        # Set two breakpoints:
        # 1. First at the loop line with logMessage
        # 2. Second guard breakpoint at a line after loop
        logMessage_prefix = "This is log message for { -- "
        logMessage = (
            logMessage_prefix
            + "{int y = 0; if (i % 3 == 0) { y = i + 3;} else {y = i * 3;} y}"
        )
        [loop_breakpoint_id, post_loop_breakpoint_id] = self.set_source_breakpoints(
            self.main_path,
            [loop_line, after_loop_line],
            [{"logMessage": logMessage}, {}],
        )

        # Continue to trigger the breakpoint with log messages
        self.dap_server.request_continue()

        # Verify we hit the breakpoint after loop line
        self.verify_breakpoint_hit([post_loop_breakpoint_id])

        output = self.get_console()
        lines = output.splitlines()
        logMessage_output = []
        for line in lines:
            if line.startswith(logMessage_prefix):
                logMessage_output.append(line)

        # Verify logMessage count
        loop_count = 10
        self.assertEqual(len(logMessage_output), loop_count)

        # Verify log message match
        for idx, logMessage_line in enumerate(logMessage_output):
            result = idx + 3 if idx % 3 == 0 else idx * 3
            self.assertEqual(logMessage_line, logMessage_prefix + str(result))

    @skipIfWindows
    def test_logmessage_format(self):
        """
        Tests breakpoint logmessage functionality with format.
        """
        before_loop_line = line_number("main.cpp", "// before loop")
        loop_line = line_number("main.cpp", "// break loop")
        after_loop_line = line_number("main.cpp", "// after loop")

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

        # Set a breakpoint at a line before loop
        before_loop_breakpoint_ids = self.set_source_breakpoints(
            self.main_path, [before_loop_line]
        )
        self.assertEqual(len(before_loop_breakpoint_ids), 1, "expect one breakpoint")

        self.dap_server.request_continue()

        # Verify we hit the breakpoint before loop line
        self.verify_breakpoint_hit(before_loop_breakpoint_ids)

        # Swallow old console output
        self.get_console()

        # Set two breakpoints:
        # 1. First at the loop line with logMessage
        # 2. Second guard breakpoint at a line after loop
        logMessage_prefix = "This is log message for -- "
        logMessage_with_format = "part1\tpart2\bpart3\x64part4"
        logMessage_with_format_raw = r"part1\tpart2\bpart3\x64part4"
        logMessage = logMessage_prefix + logMessage_with_format_raw + "{i - 1}"
        [loop_breakpoint_id, post_loop_breakpoint_id] = self.set_source_breakpoints(
            self.main_path,
            [loop_line, after_loop_line],
            [{"logMessage": logMessage}, {}],
        )

        # Continue to trigger the breakpoint with log messages
        self.dap_server.request_continue()

        # Verify we hit the breakpoint after loop line
        self.verify_breakpoint_hit([post_loop_breakpoint_id])

        output = self.get_console()
        lines = output.splitlines()
        logMessage_output = []
        for line in lines:
            if line.startswith(logMessage_prefix):
                logMessage_output.append(line)

        # Verify logMessage count
        loop_count = 10
        self.assertEqual(len(logMessage_output), loop_count)

        # Verify log message match
        for idx, logMessage_line in enumerate(logMessage_output):
            result = idx - 1
            self.assertEqual(
                logMessage_line,
                logMessage_prefix + logMessage_with_format + str(result),
            )

    @skipIfWindows
    def test_logmessage_format_failure(self):
        """
        Tests breakpoint logmessage format with parsing failure.
        """
        before_loop_line = line_number("main.cpp", "// before loop")
        loop_line = line_number("main.cpp", "// break loop")
        after_loop_line = line_number("main.cpp", "// after loop")

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

        # Set a breakpoint at a line before loop
        before_loop_breakpoint_ids = self.set_source_breakpoints(
            self.main_path, [before_loop_line]
        )
        self.assertEqual(len(before_loop_breakpoint_ids), 1, "expect one breakpoint")

        self.dap_server.request_continue()

        # Verify we hit the breakpoint before loop line
        self.verify_breakpoint_hit(before_loop_breakpoint_ids)

        # Swallow old console output
        self.get_console()

        # Set two breakpoints:
        # 1. First at the loop line with logMessage
        # 2. Second guard breakpoint at a line after loop
        logMessage_prefix = "This is log message for -- "
        # log message missing hex number.
        logMessage_with_format_raw = r"part1\x"
        logMessage = logMessage_prefix + logMessage_with_format_raw
        [loop_breakpoint_id, post_loop_breakpoint_id] = self.set_source_breakpoints(
            self.main_path,
            [loop_line, after_loop_line],
            [{"logMessage": logMessage}, {}],
        )

        # Continue to trigger the breakpoint with log messages
        self.dap_server.request_continue()

        # Verify we hit logpoint breakpoint if it's format has error.
        self.verify_breakpoint_hit([loop_breakpoint_id])

        output = self.get_console()
        lines = output.splitlines()

        failure_prefix = "Log message has error:"
        logMessage_output = []
        logMessage_failure_output = []
        for line in lines:
            if line.startswith(logMessage_prefix):
                logMessage_output.append(line)
            elif line.startswith(failure_prefix):
                logMessage_failure_output.append(line)

        # Verify logMessage failure message
        self.assertEqual(len(logMessage_failure_output), 1)
        self.assertEqual(
            logMessage_failure_output[0].strip(),
            failure_prefix + " missing hex number following '\\x'",
        )
