import unittest

from h8536.model import Instruction
from h8536.sci_protocol import (
    analyze_sci_protocol,
    sci_protocol_comment_for_instruction,
    sci_protocol_json_payload,
    sci_protocol_metadata_for_instruction,
)


def ins(
    address: int,
    mnemonic: str,
    operands: str = "",
    references: list[int] | None = None,
    kind: str = "normal",
    targets: list[int] | None = None,
) -> Instruction:
    return Instruction(
        address,
        b"",
        mnemonic,
        operands,
        kind=kind,
        targets=targets or [],
        references=references or [],
    )


class SciProtocolTest(unittest.TestCase):
    def test_tdre_polling_loop_is_wait_for_transmit_data_register_empty(self):
        instructions = {
            0x2000: ins(0x2000, "BTST.B", "#7, @SCI1_SSR", references=[0xFEDC]),
            0x2004: ins(0x2004, "BEQ", "loc_2000", kind="branch", targets=[0x2000]),
        }

        analysis = analyze_sci_protocol(instructions)

        self.assertIn(
            "wait for SCI1 transmit data register empty (TDRE=1)",
            sci_protocol_comment_for_instruction(analysis, 0x2000),
        )
        self.assertIn(
            "repeat SCI1 transmit-empty wait while TDRE=0",
            sci_protocol_comment_for_instruction(analysis, 0x2004),
        )
        metadata = sci_protocol_metadata_for_instruction(analysis, 0x2000)[0]
        self.assertEqual(metadata["action"], "wait_for_tdre")
        self.assertEqual(metadata["branch_address"], 0x2004)

    def test_write_to_tdr_is_rs232_sci_transmit_byte(self):
        instructions = {
            0x2100: ins(0x2100, "MOV:G.B", "R0, @SCI1_TDR", references=[0xFEDB]),
        }

        analysis = analyze_sci_protocol(instructions)

        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2100),
            "write RS232/SCI byte to SCI1 TDR for transmission",
        )
        self.assertEqual(analysis["channels"]["SCI1"]["events"][0]["register"], "TDR")

    def test_scr_tie_bit_set_and_clear_enable_disable_tx_interrupt(self):
        instructions = {
            0x2200: ins(0x2200, "BSET.B", "#7, @SCI1_SCR", references=[0xFEDA]),
            0x2204: ins(0x2204, "BCLR.B", "#7, @SCI1_SCR", references=[0xFEDA]),
        }

        analysis = analyze_sci_protocol(instructions)

        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2200),
            "enable SCI1 TX interrupt (TIE); gates TXI when hardware sets TDRE",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2204),
            "disable SCI1 TX interrupt (TIE); gates TXI when hardware sets TDRE",
        )

    def test_sci1_transmit_path_comments_tdr_tdre_and_tie_timing(self):
        instructions = {
            0xBA72: ins(0xBA72, "MOV:G.B", "R0, @SCI1_TDR", references=[0xFEDB]),
            0xBA7B: ins(0xBA7B, "BCLR.B", "#7, @SCI1_SSR", references=[0xFEDC]),
            0xBA7F: ins(0xBA7F, "BSET.B", "#7, @SCI1_SCR", references=[0xFEDA]),
            0xBAB5: ins(0xBAB5, "MOV:G.B", "R1, @SCI1_TDR", references=[0xFEDB]),
            0xBABB: ins(0xBABB, "BCLR.B", "#7, @SCI1_SSR", references=[0xFEDC]),
        }

        analysis = analyze_sci_protocol(instructions)

        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0xBA72),
            "write RS232/SCI byte to SCI1 TDR for transmission",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0xBA7B),
            "clear SCI1 TDRE after TDR write; TXI can fire again when hardware reasserts TDRE",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0xBA7F),
            "enable SCI1 TX interrupt (TIE); gates TXI when hardware sets TDRE",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0xBAB5),
            "write RS232/SCI byte to SCI1 TDR for transmission",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0xBABB),
            "clear SCI1 TDRE after TDR write; TXI can fire again when hardware reasserts TDRE",
        )

    def test_receive_path_clears_rdrf_then_reads_received_byte(self):
        instructions = {
            0x2300: ins(0x2300, "BCLR.B", "#6, @SCI1_SSR", references=[0xFEDC]),
            0x2304: ins(0x2304, "MOV:G.B", "@SCI1_RDR, R0", references=[0xFEDD]),
        }

        analysis = analyze_sci_protocol(instructions)

        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2300),
            "clear SCI1 RDRF with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2304),
            "read SCI1 received byte from RDR",
        )

    def test_receive_error_handler_clears_overrun_framing_and_parity_errors(self):
        instructions = {
            0x2400: ins(0x2400, "BCLR.B", "#5, @SCI1_SSR", references=[0xFEDC]),
            0x2404: ins(0x2404, "BCLR.B", "#4, @SCI1_SSR", references=[0xFEDC]),
            0x2408: ins(0x2408, "BCLR.B", "#3, @SCI1_SSR", references=[0xFEDC]),
        }

        analysis = analyze_sci_protocol(instructions)

        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2400),
            "clear SCI1 ORER with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2404),
            "clear SCI1 FER with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state",
        )
        self.assertEqual(
            sci_protocol_comment_for_instruction(analysis, 0x2408),
            "clear SCI1 PER with SSR R/(W)* semantics: write 0 clears latched hardware flag, write 1 preserves hardware-owned state",
        )

    def test_immediate_scr_write_reports_protocol_control_bits(self):
        instructions = {
            0x2500: ins(0x2500, "MOV:G.B", "#H'B0, @SCI2_SCR", references=[0xFEF2]),
        }

        analysis = analyze_sci_protocol(instructions)
        comment = sci_protocol_comment_for_instruction(analysis, 0x2500)

        self.assertIn(
            "enable SCI2 TX interrupt (TIE); gates TXI when hardware sets TDRE",
            comment,
        )
        self.assertIn("disable SCI2 receive and receive-error interrupts (RIE)", comment)
        self.assertIn("enable SCI2 transmitter (TE)", comment)
        self.assertIn("enable SCI2 receiver (RE)", comment)

    def test_json_payload_keeps_events_without_instruction_index(self):
        instructions = {
            0x2600: ins(0x2600, "MOV:G.B", "R0, @SCI2_TDR", references=[0xFEF3]),
        }
        analysis = analyze_sci_protocol(instructions)

        payload = sci_protocol_json_payload(analysis)

        self.assertIn("manual_references", payload)
        self.assertEqual(payload["events"][0]["action"], "write_tdr")
        self.assertEqual(payload["channels"]["SCI2"]["events"][0]["register"], "TDR")


if __name__ == "__main__":
    unittest.main()
