from __future__ import annotations

from dataclasses import dataclass


@dataclass(frozen=True)
class UartTiming:
    baud: int = 38_400
    data_bits: int = 8
    parity: str = "N"
    stop_bits: int = 1
    start_bits: int = 1

    def __post_init__(self) -> None:
        parity = self.parity.upper()
        if parity not in {"N", "E", "O"}:
            raise ValueError("parity must be N, E, or O")
        object.__setattr__(self, "parity", parity)

    @property
    def parity_bits(self) -> int:
        return 0 if self.parity == "N" else 1

    @property
    def bits_per_character(self) -> int:
        return self.start_bits + self.data_bits + self.parity_bits + self.stop_bits

    def seconds_per_character(self) -> float:
        return self.bits_per_character / max(1, self.baud)

    def micros_per_character(self) -> float:
        return 1_000_000.0 * self.seconds_per_character()

    def cycles_per_character(self, clock_hz: int) -> int:
        return max(1, round(max(1, clock_hz) * self.seconds_per_character()))

    def summary(self, clock_hz: int) -> str:
        return (
            f"uart_{self.data_bits}{self.parity}{self.stop_bits} "
            f"baud={self.baud} byte_us={self.micros_per_character():.3f} "
            f"byte_cycles={self.cycles_per_character(clock_hz)}"
        )

    @classmethod
    def from_format(cls, text: str, *, baud: int = 38_400) -> "UartTiming":
        normalized = text.strip().upper()
        if len(normalized) != 3 or normalized[0] not in "78" or normalized[1] not in "NEO" or normalized[2] not in "12":
            raise ValueError(f"unsupported UART format {text!r}; expected 8E1, 8N1, 8O1, etc.")
        return cls(baud=baud, data_bits=int(normalized[0]), parity=normalized[1], stop_bits=int(normalized[2]))

    @classmethod
    def from_sci_smr(cls, smr: int, *, baud: int = 38_400) -> "UartTiming":
        data_bits = 7 if smr & 0x40 else 8
        if smr & 0x20:
            parity = "O" if smr & 0x10 else "E"
        else:
            parity = "N"
        stop_bits = 2 if smr & 0x08 else 1
        return cls(baud=baud, data_bits=data_bits, parity=parity, stop_bits=stop_bits)


__all__ = ["UartTiming"]
