"""Generate synthesized schematics (SVG) for the BBA design via yosys + graphviz.

Run from the workspace root:
    python -m exi_bba.diagram            # render the standard set into diagrams/
    python -m exi_bba.diagram <name>...  # render only the named module(s)

For each module we emit RTLIL (`amaranth.back.rtlil`), run yosys to elaborate
RTL processes (`proc`/`opt`), and `show` it to SVG through graphviz `dot`.

A schematic of the *whole* synthesised BBATop is a ~2300-LUT hairball, so we
render readable **per-module RTL** views (real muxes/adders/FFs, not anonymous
LUTs), plus a top-level **block diagram** (BBATop's submodules as boxes).
"""

import os
import shutil
import subprocess
import sys

from amaranth import Signal
from amaranth.back import rtlil

from exi_bba.spi_mode3_slave      import SPIMode3Slave
from exi_bba.tx_frame_drain       import TXFrameDrain
from exi_bba.status_panel         import StatusPanel
from exi_bba.spram_arbiter        import SPRAMArbiter
from exi_bba.rx_frame_assembler   import RXFrameAssembler
from exi_bba.w5100_parallel_master import W5100ParallelMaster
from exi_bba.bba_top              import BBATop

OUT = "diagrams"
_YOSYS = shutil.which("yosys")


def _ports(obj):
    """Top-level interface = all Signal attributes of the instance."""
    return [v for v in vars(obj).values() if isinstance(v, Signal)]


def render(name, obj, *, block=False):
    """Emit RTLIL for `obj` and render an SVG schematic into diagrams/.

    block=True  → keep the hierarchy and show submodules as boxes (top-level
                  block diagram).  block=False → flatten one module to RTL cells.
    """
    if _YOSYS is None:
        sys.exit("yosys not found on PATH (install it, or use amaranth_yosys).")
    os.makedirs(OUT, exist_ok=True)
    il_path = os.path.join(OUT, f"{name}.il")
    with open(il_path, "w") as f:
        f.write(rtlil.convert(obj, name=name, ports=_ports(obj)))

    prefix = os.path.join(OUT, name)
    if block:
        # Keep the hierarchy; show only the top module (submodules → boxes).
        script = (f"read_rtlil {il_path}; hierarchy -top {name}; proc; "
                  f"show -format svg -prefix {prefix} -notitle {name}")
    else:
        # Flatten tiny CDC sub-cells in, convert processes, render RTL cells.
        # `delete t:$meminit_v2` drops the (cosmetic) memory-init constant — a
        # 64K-word all-zero vector that overflows graphviz's string limit.
        script = (f"read_rtlil {il_path}; hierarchy -top {name}; "
                  f"delete t:$meminit_v2; proc; flatten; opt -purge; "
                  f"show -format svg -prefix {prefix} -notitle -colors 1")
    subprocess.run([_YOSYS, "-q", "-p", script], check=True)
    for junk in (il_path, f"{prefix}.dot"):     # keep only the .svg
        if os.path.exists(junk):
            os.remove(junk)
    print(f"  ✓ {prefix}.svg")


# name → (factory, block?)
TARGETS = {
    "spi_mode3_slave": (lambda: SPIMode3Slave(domain="sync"),        False),
    "tx_frame_drain":  (lambda: TXFrameDrain(),                      False),
    "status_panel":    (lambda: StatusPanel(),                       False),
    "spram_arbiter":   (lambda: SPRAMArbiter(),                      False),
    "rx_frame_assembler": (lambda: RXFrameAssembler(),               False),
    "w5100_master":    (lambda: W5100ParallelMaster(),               False),
    "bbatop_blocks":   (lambda: BBATop(eth="w5100"),                 True),
}


if __name__ == "__main__":
    want = sys.argv[1:] or list(TARGETS)
    for name in want:
        if name not in TARGETS:
            print(f"  ? unknown target '{name}' (have: {', '.join(TARGETS)})")
            continue
        factory, block = TARGETS[name]
        print(f"rendering {name} …")
        try:
            render(name, factory(), block=block)
        except Exception as exc:
            print(f"  ✗ {name} failed: {exc}")
    print(f"\nDone → {OUT}/")
