#!/usr/bin/env python3
"""
Code-section validator for the Orbital decomp project.

Builds asm/all_stubs.s into a binary, extracts the code section
(0x080000C0..0x0801F30C), and compares it byte-for-byte against the
original ROM.

Outputs:
  - build/orbital_stub.elf
  - build/orbital_stub.bin
  - build/rom_compare.txt
  - stdout: diffs + accuracy percentage
"""

import os
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"
STUB = ROOT / "asm" / "all_stubs.s"
BUILD = ROOT / "build"

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")

ROM_BASE = 0x08000000
CODE_START_ADDR = 0x080000C0
CODE_END_ADDR = 0x0801F30C
CODE_START_OFF = CODE_START_ADDR - ROM_BASE  # 0xC0
CODE_END_OFF = CODE_END_ADDR - ROM_BASE       # 0x1F30C
CODE_SIZE = CODE_END_OFF - CODE_START_OFF      # 127564

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}")
    if not STUB.exists():
        sys.exit(f"Run tools/gen_stubs.py first to create {STUB}")

    rom = ROM.read_bytes()
    code_section = rom[CODE_START_OFF:CODE_END_OFF]
    assert len(code_section) == CODE_SIZE, f"Expected {CODE_SIZE}, got {len(code_section)}"

    o = BUILD / "orbital_stub.o"
    elf = BUILD / "orbital_stub.elf"
    rom_bin = BUILD / "orbital_stub.bin"

    rc, out, err = run([AS, "-mcpu=arm7tdmi", str(STUB), "-o", str(o)])
    if rc != 0:
        print(f"ASM FAIL:\n{err.strip()[:2000]}", file=sys.stderr)
        return 1

    rc, out, err = run([LD, "-T", str(LINKER), str(o), "-o", str(elf)])
    if rc != 0:
        print(f"LINK FAIL:\n{err.strip()[:2000]}", file=sys.stderr)
        return 1

    rc, out, err = run([OBJCOPY, "-O", "binary", "-j", ".text", str(elf), str(rom_bin)])
    if rc != 0:
        print(f"OBJCOPY FAIL:\n{err.strip()[:2000]}", file=sys.stderr)
        return 1

    bin_data = rom_bin.read_bytes()
    if len(bin_data) < CODE_SIZE:
        print(f"Binary too small ({len(bin_data)} < {CODE_SIZE}).", file=sys.stderr)
        return 1

    result_code = bin_data[:CODE_SIZE]
    diffs = sum(1 for a, b in zip(result_code, code_section) if a != b)
    accuracy = 100 - 100 * diffs / CODE_SIZE

    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: {CODE_SIZE}\n")
        f.write(f"Differences:       {diffs}\n")
        f.write(f"Accuracy:          {accuracy:.1f}%\n")

    print(f"stub binary: {len(bin_data)} bytes")
    print(f"code section: {CODE_SIZE} bytes")
    print(f"diffs: {diffs}")
    print(f"accuracy: {accuracy:.1f}%")
    return 0 if diffs == 0 else 1

if __name__ == "__main__":
    raise SystemExit(main())
