from __future__ import annotations

from dataclasses import dataclass, field

from .constants import (
    HEARTBEAT_FRAME,
    SCI1_BRR,
    SCI1_RDR,
    SCI1_SCR,
    SCI1_SMR,
    SCI1_SSR,
    SCI1_TDR,
    SCI_SCR_TE,
    SCI_SSR_FER,
    SCI_SSR_ORER,
    SCI_SSR_PER,
    SCI_SSR_RDRF,
    SCI_SSR_TDRE,
)
from .uart import UartTiming


@dataclass
class SciTxEvent:
    address: int
    value: int
    scr: int
    ssr: int
    emitted: bool


@dataclass
class SCI1:
    """Small SCI1 model for the H8/536 serial path.

    Manual anchors:
    - RDR/TDR/SCR/SSR live at H'FEDD/H'FEDB/H'FEDA/H'FEDC for SCI1.
    - SCR bit 7 TIE, bit 6 RIE, bit 5 TE, bit 4 RE.
    - SSR bit 7 TDRE, bit 6 RDRF, bits 5..3 ORER/FER/PER.
    - Software normally writes TDR after TDRE=1, then clears SSR.TDRE.
    - SSR status flags are R/(W)*: writing 0 clears a latched flag, while
      writing 1 leaves the hardware-owned flag unchanged.
    """

    smr: int = 0x00
    brr: int = 0xFF
    scr: int = 0x0C
    tdr: int = 0xFF
    ssr: int = 0x87
    rdr: int = 0x00
    tx_bytes: list[int] = field(default_factory=list)
    tx_events: list[SciTxEvent] = field(default_factory=list)
    tx_frames: list[bytes] = field(default_factory=list)
    _frame_buffer: bytearray = field(default_factory=bytearray)
    tx_ready_delay: int = 0
    tx_ready_ticks: int = 2
    clock_hz: int = 10_000_000
    tx_timing: UartTiming | None = None
    _tx_ready_pending: bool = False

    def read(self, address: int) -> int:
        if address == SCI1_SMR:
            return self.smr
        if address == SCI1_BRR:
            return self.brr
        if address == SCI1_SCR:
            return self.scr
        if address == SCI1_TDR:
            return self.tdr
        if address == SCI1_SSR:
            return self.ssr
        if address == SCI1_RDR:
            return self.rdr
        raise KeyError(address)

    def write(self, address: int, value: int) -> None:
        value &= 0xFF
        if address == SCI1_SMR:
            self.smr = value
        elif address == SCI1_BRR:
            self.brr = value
        elif address == SCI1_SCR:
            self.scr = value
        elif address == SCI1_TDR:
            self.tdr = value
            self._write_tdr(value)
        elif address == SCI1_SSR:
            self._write_ssr(value)
        elif address == SCI1_RDR:
            self.rdr = value
        else:
            raise KeyError(address)

    def _write_tdr(self, value: int) -> None:
        emitted = bool(self.scr & SCI_SCR_TE)
        if emitted:
            self.tx_bytes.append(value)
            self._frame_buffer.append(value)
            if len(self._frame_buffer) == len(HEARTBEAT_FRAME):
                self.tx_frames.append(bytes(self._frame_buffer))
                self._frame_buffer.clear()
        self.tx_ready_delay = self._tx_ready_delay()
        self._tx_ready_pending = True
        self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted))

    def _write_ssr(self, value: int) -> None:
        writable_zero_flags = SCI_SSR_TDRE | SCI_SSR_RDRF | SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER
        self.ssr = (self.ssr & (writable_zero_flags & value)) | (value & ~writable_zero_flags)

    def inject_rx(self, value: int) -> None:
        if self.ssr & SCI_SSR_ORER:
            return
        if self.ssr & SCI_SSR_RDRF:
            self.ssr |= SCI_SSR_ORER
            return
        self.rdr = value & 0xFF
        self.ssr |= SCI_SSR_RDRF

    def saw_heartbeat(self) -> bool:
        return HEARTBEAT_FRAME in self.tx_frames

    def configure_tx_timing(self, timing: UartTiming | None, *, clock_hz: int | None = None) -> None:
        self.tx_timing = timing
        if clock_hz is not None:
            self.clock_hz = max(1, clock_hz)

    def tx_busy(self) -> bool:
        return self._tx_ready_pending and self.tx_ready_delay > 0

    def tick(self, cycles: int = 1) -> None:
        if self._tx_ready_pending and self.tx_ready_delay:
            self.tx_ready_delay = max(0, self.tx_ready_delay - max(1, cycles))
        if self._tx_ready_pending and self.tx_ready_delay == 0 and not (self.ssr & SCI_SSR_TDRE):
            self.ssr |= SCI_SSR_TDRE
            self._tx_ready_pending = False

    def _tx_ready_delay(self) -> int:
        if self.tx_timing is None:
            return max(0, self.tx_ready_ticks)
        return self.tx_timing.cycles_per_character(self.clock_hz)
