from dataclasses import dataclass
from fnmatch import fnmatch
from sys import argv

from lxml.etree import XML

# List of <peri>.<register> patterns to mark as unsafe.
UNSAFE: list[str] = [
    "SIO.SPINLOCK[0-9]*",
    "ADC.FIFO",
    "SSI.*CR",
    "SIO.DIV_QUOTIENT",
    "SIO.DIV_REMAINDER",
    "SIO.FIFO_RD",
    "M0PLUS.SYST_CSR",
    "I2C*.IC_CLR*",
    "TIMER.TIME[HL]R",
    "PIO*.RXF*",
    "PPB.*",
    "XIP.*",
]


def is_unsafe(peri: str, reg: str) -> bool:
    for pattern in UNSAFE:
        if fnmatch(f"{peri}.{reg}", pattern):
            return True

    return False


with open(argv[1], "rb") as fp:
    xml = XML(fp.read())


@dataclass
class Register:
    name: str
    offset: int
    reset: int
    read: bool
    write: bool


@dataclass
class Peripheral:
    name: str
    base: int
    regs: list[Register]


peripherals: dict[str, Peripheral] = {}

for peri in xml.cssselect("peripheral"):
    if "derivedFrom" in peri.attrib:
        orig = peripherals[peri.attrib["derivedFrom"]]
        names = peri.cssselect("peripheral > name")
        assert len(names) == 1

        peripheral = Peripheral(name=names[0].text, base=orig.base, regs=orig.regs)
        continue

    names = peri.cssselect("peripheral > name")
    assert len(names) == 1

    bases = peri.cssselect("peripheral > baseAddress")
    assert len(bases) == 1

    base = int(bases[0].text, 16)
    peripheral = Peripheral(name=names[0].text, base=base, regs=[])

    for reg in peri.cssselect("peripheral > registers > register"):
        names = reg.cssselect("register > name")
        assert len(names) == 1

        offsets = reg.cssselect("register > addressOffset")
        assert len(offsets) == 1

        resets = reg.cssselect("register > resetValue")
        assert len(resets) == 1

        accesses = reg.cssselect("access")
        assert len(accesses) > 0

        write = read = False

        for access in accesses:
            match access.text:
                case "read-only":
                    read = True

                case "read-write":
                    write = read = True

                case "write-only":
                    write = True

                case mode:
                    assert False, f"unknown access mode {mode!r}"

        offset = int(offsets[0].text, 16)
        reset = int(resets[0].text, 16)
        register = Register(
            name=names[0].text,
            offset=offset,
            reset=reset,
            write=write,
            read=read,
        )
        peripheral.regs.append(register)

    peripherals[peripheral.name] = peripheral

print(
    """
#include "regmap.h"

struct reg_info regmap[] = {
""".strip()
)

for peripheral in peripherals.values():
    for register in peripheral.regs:
        print("{ ", end="")
        print(f"{hex(peripheral.base + register.offset)}, ", end="")
        print(f"{hex(register.reset)}, ", end="")
        print(f"{hex(register.reset)}, ", end="")
        print(f'"{peripheral.name}", ', end="")
        print(f'"{register.name}", ', end="")

        if register.read:
            print("1, ", end="")
        else:
            print("0, ", end="")

        if register.write:
            print("1, ", end="")
        else:
            print("0, ", end="")

        if is_unsafe(peripheral.name, register.name):
            print("1", end="")
        else:
            print("0", end="")

        print(" },")

print("{ 0, 0, 0, NULL, NULL, 0, 0, 0 },")
print("};")
