from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from .cpu import mask, sign_bit


LOC_BFE0_TRANSFER_WRAPPER = 0xBFE0
LOC_BFFE_TRANSFER_WRAPPER = 0xBFFE
LOC_C08B_P9_WRITE_BYTE = 0xC08B
LOC_C0DB_P9_READ_BYTE = 0xC0DB
LOC_C10C_P9_MASTER_ACK = 0xC10C
LOC_C10C_P9_MARKER = 0xC10C
LOC_C121_P9_START = 0xC121
LOC_C121_P9_MARKER = LOC_C121_P9_START
LOC_C142_P9_STOP = 0xC142
LOC_C142_P9_MARKER = LOC_C142_P9_STOP


@dataclass(frozen=True)
class P9FastPathConfig:
    """Configuration for optional ROM P9 transfer shortcuts.

    The helper assumes the CPU PC is exactly at a known routine entry. It feeds
    those byte/marker/wrapper operations into the modeled P9/X24164 bus and
    returns via RTS. Integration should keep this disabled unless the runner
    intentionally opts into skipping these ROM routines.
    """

    enabled: bool = False
    write_byte_pc: int = LOC_C08B_P9_WRITE_BYTE
    read_byte_pc: int = LOC_C0DB_P9_READ_BYTE
    marker_pcs: frozenset[int] = frozenset(
        {
            LOC_C10C_P9_MARKER,
            LOC_C121_P9_MARKER,
            LOC_C142_P9_MARKER,
        }
    )
    wrapper_pcs: frozenset[int] = frozenset(
        {
            LOC_BFE0_TRANSFER_WRAPPER,
            LOC_BFFE_TRANSFER_WRAPPER,
        }
    )
    default_input_byte: int = 0xFF
    account_step: bool = True
    cycles_per_hit: int = 0


@dataclass(frozen=True)
class P9FastPathEvent:
    kind: str
    pc: int
    value: int | None = None
    source: str | None = None
    queue_depth: int | None = None
    success: bool | None = None

    def line(self) -> str:
        value = "" if self.value is None else f" value={self.value:02X}"
        success = "" if self.success is None else f" success={int(self.success)}"
        source = "" if self.source is None else f" source={self.source}"
        queue_depth = "" if self.queue_depth is None else f" queued={self.queue_depth}"
        return f"{self.kind} pc={self.pc:04X}{value}{success}{source}{queue_depth}"


@dataclass
class P9FastPath:
    """Optional fast-path scaffold for ROM P9 bit-transfer routines."""

    config: P9FastPathConfig = field(default_factory=P9FastPathConfig)
    input_bytes: list[int] = field(default_factory=list)
    input_sources: list[str] = field(default_factory=list)
    output_bytes: list[int] = field(default_factory=list)
    events: list[P9FastPathEvent] = field(default_factory=list)

    def __post_init__(self) -> None:
        if len(self.input_sources) < len(self.input_bytes):
            self.input_sources.extend(["initial"] * (len(self.input_bytes) - len(self.input_sources)))
        elif len(self.input_sources) > len(self.input_bytes):
            del self.input_sources[len(self.input_bytes) :]

    def queue_input(self, *values: int, source: str = "queued") -> None:
        self.input_bytes.extend(value & 0xFF for value in values)
        self.input_sources.extend(source for _ in values)

    def queue_input_script(self, name: str, values: list[int] | tuple[int, ...]) -> None:
        self.queue_input(*values, source=f"script:{name}")

    def trace_lines(self, limit: int | None = None) -> list[str]:
        events = self.events if limit is None else self.events[-limit:]
        return [event.line() for event in events]

    def try_handle(self, emulator: Any) -> bool:
        if not self.config.enabled:
            return False

        pc = emulator.cpu.pc & 0xFFFF
        if pc == (self.config.write_byte_pc & 0xFFFF):
            self._handle_write_byte(emulator)
        elif pc == (self.config.read_byte_pc & 0xFFFF):
            self._handle_read_byte(emulator)
        elif pc in self.config.marker_pcs:
            self._handle_marker(emulator, pc)
        elif pc in self.config.wrapper_pcs:
            self._handle_wrapper(emulator)
        else:
            return False

        if self.config.account_step:
            emulator.cpu.steps += 1
            emulator.cpu.cycles += self.config.cycles_per_hit
        return True

    def _handle_write_byte(self, emulator: Any) -> None:
        pc = emulator.cpu.pc & 0xFFFF
        value = emulator.cpu.regs[0] & 0xFF
        self.output_bytes.append(value)
        success = emulator.memory.p9_bus.fast_write_byte(value)
        self.events.append(P9FastPathEvent("write_byte", pc, value, source="x24164", success=success))

        result = 1 if success else 0
        emulator.cpu.regs[0] = result
        self._set_logic_flags(emulator.cpu, result, 1)
        self._return_from_subroutine(emulator)

    def _handle_read_byte(self, emulator: Any) -> None:
        pc = emulator.cpu.pc & 0xFFFF
        if self.input_bytes:
            value = self.input_bytes.pop(0)
            source = self.input_sources.pop(0) if self.input_sources else "queued"
        elif (x24164_value := emulator.memory.p9_bus.fast_read_byte()) is not None:
            value = x24164_value
            source = "x24164"
        else:
            value = self.config.default_input_byte
            source = "default_input_byte"
        value &= 0xFF
        self.events.append(P9FastPathEvent("read_byte", pc, value, source, len(self.input_bytes)))

        # The ROM-side read routine yields a byte in R5. Model that as a byte
        # register write so the existing high byte is not accidentally clobbered.
        emulator.cpu.regs[5] = (emulator.cpu.regs[5] & 0xFF00) | value
        self._set_logic_flags(emulator.cpu, value, 1)
        self._return_from_subroutine(emulator)

    def _handle_wrapper(self, emulator: Any) -> None:
        pc = emulator.cpu.pc & 0xFFFF
        success: bool
        source: str
        queue_depth: int | None
        if pc == (LOC_BFE0_TRANSFER_WRAPPER & 0xFFFF):
            address = emulator.cpu.regs[4] & 0x0FFF
            value = emulator.cpu.regs[5] & 0xFFFF
            write_success = emulator.memory.p9_bus.fast_write_word(address, value)
            read_success, readback = emulator.memory.p9_bus.fast_read_word(address)
            success = write_success and read_success and readback == value
            source = "x24164_write_verify"
            queue_depth = None
        elif pc == (LOC_BFFE_TRANSFER_WRAPPER & 0xFFFF):
            address = emulator.cpu.regs[4] & 0x0FFF
            read_success, value = emulator.memory.p9_bus.fast_read_word(address)
            if read_success:
                emulator.cpu.regs[5] = value & 0xFFFF
            success = read_success
            source = "x24164_read_word"
            queue_depth = None
        else:
            success, source, queue_depth = emulator.memory.p9_bus.consume_wrapper_result()
        value = 1 if success else 0
        self.events.append(
            P9FastPathEvent(
                "wrapper_success" if success else "wrapper_timeout",
                pc,
                value=value,
                source=source,
                queue_depth=queue_depth,
                success=success,
            )
        )

        emulator.cpu.regs[0] = value
        self._set_logic_flags(emulator.cpu, value, 1)
        self._return_from_subroutine(emulator)

    def _return_from_subroutine(self, emulator: Any) -> None:
        sp = emulator.cpu.regs[7] & 0xFFFF
        emulator.cpu.pc = emulator.memory.read16(sp) & 0xFFFF
        emulator.cpu.regs[7] = (sp + 2) & 0xFFFF

    def _handle_marker(self, emulator: Any, pc: int) -> None:
        if pc == (LOC_C121_P9_START & 0xFFFF):
            emulator.memory.p9_bus.fast_start()
            self.events.append(P9FastPathEvent("start", pc, source="x24164"))
        elif pc == (LOC_C10C_P9_MASTER_ACK & 0xFFFF):
            emulator.memory.p9_bus.fast_master_ack(True)
            self.events.append(P9FastPathEvent("master_ack", pc, source="x24164"))
        elif pc == (LOC_C142_P9_STOP & 0xFFFF):
            emulator.memory.p9_bus.fast_stop()
            self.events.append(P9FastPathEvent("stop", pc, source="x24164"))
        else:
            self.events.append(P9FastPathEvent("marker", pc))
        self._return_from_subroutine(emulator)

    def _set_logic_flags(self, cpu: Any, value: int, size: int) -> None:
        value &= mask(size)
        cpu.z = value == 0
        cpu.n = bool(value & sign_bit(size))
        cpu.v = False
