#!/usr/bin/env python3
"""
ROM-validation harness for the Orbital decomp project.

Builds a ROM image from the current function stubs, extracts the code
section, and compares it against the original ROM.  A perfect match
would show 0 differences.

This script does NOT claim anything about C accuracy; it verifies that
the assembly-level function layout is byte-identical to the original ROM
once placed at the correct addresses.

Outputs:
  - build/orbital_stub.elf
  - build/orbital_stub.bin
  - build/rom_compare.txt
"""
import os
import re
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
ROM = ROOT / "bit Generations - Orbital (Japan) (En).gba"
LINKER = ROOT / "gba.ld"
BUILD = ROOT / "build"

CC = str(ROOT / "agbcc" / "agbcc")
TB = str(ROOT / "arm-gnu-toolchain-13.3.rel1-darwin-arm64-arm-none-eabi" / "bin")
AS = os.path.join(TB, "arm-none-eabi-as")
LD = os.path.join(TB, "arm-none-eabi-ld")
OBJCOPY = os.path.join(TB, "arm-none-eabi-objcopy")
READELF = os.path.join(TB, "arm-none-eabi-readelf")

CODE_START = 0x080000C0
CODE_END = 0x0801F30C

def run(cmd, **kw):
    r = subprocess.run(cmd, capture_output=True, text=True, **kw)
    return r.returncode, r.stdout, r.stderr

def main():
    BUILD.mkdir(exist_ok=True)
    if not ROM.exists():
        sys.exit(f"ROM not found: {ROM}")

    rom = ROM.read_bytes()
    code_section = rom[CODE_START:CODE_END]

    # Step 1: assemble each .s stub under src/ into .o
    stubs = sorted((ROOT / "src").glob("*.s"))
    if not stubs:
        sys.exit("No .s stubs found in src/")
    o_files = []
    for s in stubs:
        o = BUILD / (s.stem + ".o")
        rc, out, err = run([AS, "-mcpu=arm7tdmi", str(s), "-o", str(o)])
        if rc != 0:
            print(f"ASM FAIL: {s.name}\n{err.strip()}", file=sys.stderr)
            continue
        o_files.append(o)

    # Step 2: link into ELF
    elf = BUILD / "orbital_stub.elf"
    cmd = [LD, "-T", str(LINKER), "-o", str(elf)] + [str(o) for o in o_files]
    rc, out, err = run(cmd)
    if rc != 0:
        sys.exit(f"LINK FAIL:\n{err.strip()}")

    # Step 3: extract raw binary
    rom_bin = BUILD / "orbital_stub.bin"
    rc, out, err = run([OBJCOPY, "-O", "binary", str(elf), str(rom_bin)])
    if rc != 0:
        sys.exit(f"OBJCOPY FAIL:\n{err.strip()}")

    bin_data = rom_bin.read_bytes()
    if len(bin_data) < CODE_END - CODE_START:
        sys.exit(f"Binary too small: {len(bin_data)} bytes < {CODE_END - CODE_START}")

    result_code = bin_data[:CODE_END - CODE_START]
    diffs = sum(1 for a, b in zip(result_code, code_section) if a != b)
    total = len(code_section)

    with open(BUILD / "rom_compare.txt", "w") as f:
        f.write(f"Stub binary size:  {len(bin_data)}\n")
        f.write(f"Code section size: {total}\n")
        f.write(f"Differences:       {diffs}\n")
        f.write(f"Accuracy:          {100 - 100*diffs/total:.1f}%\n")

    print(f"stub bytes: {len(bin_data)}")
    print(f"diffs: {diffs} / {total}")
    print(f"accuracy: {100 - 100*diffs/total:.1f}%")

if __name__ == "__main__":
    main()
