"""Capture AC7020C range spectra, check simulation equality, and estimate distance."""
import argparse
import json
from pathlib import Path
import sys
import time
import numpy as np
import serial
from serial.tools import list_ports
from check_spectrum import check_frame, load_reference, read_simulation


def main():
    root = Path(__file__).resolve().parent
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--port")
    parser.add_argument("--frames", type=int, default=2)
    parser.add_argument("--timeout", type=float, default=60)
    parser.add_argument("--reference", type=Path, default=root / "results" / "reference.npz")
    parser.add_argument("--simulation", type=Path, default=root / "results" / "fft_sim.csv")
    args = parser.parse_args()
    if args.frames < 2 or args.timeout <= 0:
        parser.error("At least two frames and a positive timeout are required")
    if args.port is None:
        ports = [p.device for p in list_ports.comports() if (p.vid, p.pid) == (0x10c4, 0xea60)]
        if len(ports) != 1:
            parser.error(f"Expected one CP210x, found {ports}; specify --port")
        args.port = ports[0]
    reference, config = load_reference(args.reference)
    simulated = read_simulation(args.simulation)
    capture = {"port": args.port, "baud": 115200, "config": config, "verified": False, "frames": []}
    seen_cases = set()
    out = root / "results" / "serial_capture.json"
    rows = None
    header = None
    try:
        with serial.Serial(args.port, 115200, timeout=0.5) as port:
            print(f"Listening on {args.port}, 115200 8N1", flush=True)
            deadline = time.monotonic() + args.timeout
            while time.monotonic() < deadline:
                line = port.readline().decode("ascii", errors="replace").strip()
                fields = line.split(",")
                if fields[0] == "ERROR":
                    raise RuntimeError(line)
                if fields[0] == "RANGE" and len(fields) == 8:
                    header = list(map(int, fields[1:])); rows = []
                    print(line, flush=True)
                elif fields[0] == "BIN" and len(fields) == 5 and rows is not None:
                    rows.append(list(map(int, fields[1:])))
                elif fields[0] == "END" and len(fields) == 2 and rows is not None:
                    frame, case_id, peak, power, inputs, outputs, errors = header
                    if int(fields[1]) != frame or inputs != 512 or outputs != 512 or errors != 0:
                        raise ValueError(f"Invalid frame metadata: {header}, {line}")
                    if case_id not in (0, 1):
                        raise ValueError(f"Unknown ROM bank {case_id}")
                    checked = check_frame(rows, reference[case_id], config, case_id)
                    if not np.array_equal(np.asarray(rows), np.asarray(simulated[case_id + 1])):
                        raise ValueError("Hardware spectrum is not bit-for-bit equal to RTL simulation")
                    checked['exact_simulation_match'] = True
                    if peak != checked["peak_bin"] or power != checked["peak_power_q30"]:
                        raise ValueError("FPGA peak finder disagrees with full spectrum")
                    if capture["frames"] and frame <= capture["frames"][-1]["frame"]:
                        raise ValueError("Frame counter failed to advance")
                    capture["frames"].append({"frame": frame, **checked, "spectrum": rows})
                    seen_cases.add(case_id)
                    print(f"RANGE_PASS: frame={frame}, case={case_id}, truth={checked['true_range_m']:.2f} m, peak={peak}, estimate={checked['estimated_range_m']:.8f} m, error={checked['range_error_m']:+.8f} m, simulation=EXACT", flush=True)
                    rows = None
                    if len(capture["frames"]) >= args.frames and seen_cases == {0, 1}:
                        capture["verified"] = True
                        print("RANGE_SERIAL_PASS: both single-target cases verified", flush=True)
                        return
            raise TimeoutError("Timed out before receiving all verified FFT frames")
    finally:
        out.parent.mkdir(parents=True, exist_ok=True)
        out.write_text(json.dumps(capture, ensure_ascii=False, indent=2), encoding="utf-8")
        print(f"Capture: {out}", flush=True)


if __name__ == "__main__":
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8")
    main()
