from __future__ import annotations

from typing import TypedDict

from .formatting import h16
from .memory import region_for
from .rom import Rom
from .tables import IO_REGISTERS


DTC_REGISTER_INFO_SIZE = 8
DTC_RESERVED_MODE_MASK = 0x1FFF


class DtcEndpointInfo(TypedDict):
    address: int
    text: str
    name: str | None
    region: str
    increment: bool
    increment_step: int


class DtcModeInfo(TypedDict):
    raw: int
    size: str
    bytes_per_transfer: int
    source_increment: bool
    destination_increment: bool
    source_increment_step: int
    destination_increment_step: int
    reserved: int
    reserved_set: bool


class DtcCountInfo(TypedDict):
    raw: int
    transfers: int
    bytes: int
    zero_means_65536: bool


class DtcRegisterInfo(TypedDict, total=False):
    address: int
    valid: bool
    error: str
    dtmr: int
    dtsr: int
    dtdr: int
    dtcr: int
    mode: DtcModeInfo
    source: DtcEndpointInfo
    destination: DtcEndpointInfo
    count: DtcCountInfo


def _endpoint(address: int, increment: bool, increment_step: int) -> DtcEndpointInfo:
    name = IO_REGISTERS.get(address)
    return {
        "address": address,
        "text": name or h16(address),
        "name": name,
        "region": region_for(address).name,
        "increment": increment,
        "increment_step": increment_step if increment else 0,
    }


def _mode(dtmr: int) -> DtcModeInfo:
    size = "word" if dtmr & 0x8000 else "byte"
    bytes_per_transfer = 2 if size == "word" else 1
    source_increment = bool(dtmr & 0x4000)
    destination_increment = bool(dtmr & 0x2000)
    source_step = bytes_per_transfer if source_increment else 0
    destination_step = bytes_per_transfer if destination_increment else 0
    reserved = dtmr & DTC_RESERVED_MODE_MASK
    return {
        "raw": dtmr,
        "size": size,
        "bytes_per_transfer": bytes_per_transfer,
        "source_increment": source_increment,
        "destination_increment": destination_increment,
        "source_increment_step": source_step,
        "destination_increment_step": destination_step,
        "reserved": reserved,
        "reserved_set": reserved != 0,
    }


def decode_dtc_register_info(rom: Rom, address: int) -> DtcRegisterInfo:
    """Decode the four-word DTMR/DTSR/DTDR/DTCR block pointed to by a DTC vector."""
    end = address + DTC_REGISTER_INFO_SIZE - 1
    if end > 0xFFFF:
        return {
            "address": address,
            "valid": False,
            "error": f"register information block {h16(address)}+{DTC_REGISTER_INFO_SIZE} exceeds page 0",
        }
    if not rom.contains(address, DTC_REGISTER_INFO_SIZE):
        return {
            "address": address,
            "valid": False,
            "error": f"register information block {h16(address)}-{h16(end)} is outside ROM image",
        }

    dtmr = rom.u16(address)
    dtsr = rom.u16(address + 2)
    dtdr = rom.u16(address + 4)
    dtcr = rom.u16(address + 6)
    mode = _mode(dtmr)
    transfers = 0x10000 if dtcr == 0 else dtcr

    return {
        "address": address,
        "valid": True,
        "dtmr": dtmr,
        "dtsr": dtsr,
        "dtdr": dtdr,
        "dtcr": dtcr,
        "mode": mode,
        "source": _endpoint(dtsr, mode["source_increment"], mode["source_increment_step"]),
        "destination": _endpoint(dtdr, mode["destination_increment"], mode["destination_increment_step"]),
        "count": {
            "raw": dtcr,
            "transfers": transfers,
            "bytes": transfers * mode["bytes_per_transfer"],
            "zero_means_65536": dtcr == 0,
        },
    }
