"""The query's z rule on the sweep's descriptors (query only, no encode): "table" (inside at the two neighbouring layer
centres decides when they agree, else the per-part ring blend) against "nearest" (the ring set of the nearest layer
centre: one parity per part), per ring count NT: wrong answers beyond 0.1–0.5 % of the diagonal on 1M near-surface and
1M uniform points, GPU ns per point. Appends to ../report/rule_cmp.csv.

    env -u PYTHONPATH .venv/bin/python scripts/rule_cmp.py <layers> <N> [objects...]
"""
import csv
import sys
from pathlib import Path

import numpy as np

sys.path.insert(0, str(Path(__file__).parent))
from common import CACHE, REPORT, cuda_sync, cut, fcp, load_mesh, timeit  # noqa: E402
from sweep import EPS, points  # noqa: E402

import cupy as cp  # noqa: E402

from fcpq import FCPQuery  # noqa: E402

OBJS = ["mug", "bowl", "dino", "horse", "shoe", "dress", "pan_holes", "knot", "spring", "sponge", "gear"]


def main():
    layers, N = int(sys.argv[1]), int(sys.argv[2])
    objs = sys.argv[3:] or OBJS
    out = REPORT / "rule_cmp.csv"
    for name in objs:
        p = CACHE / f"sw_{name}_L{layers}_N{N}_s2048_ma0.fcp"
        if not p.exists():
            print(name, "no descriptor", p.name)
            continue
        d = cut(fcp.FCP.from_bytes(p.read_bytes()))
        sets = points(load_mesh(name))
        rows = []
        for nt in (256, 512, 1024):
            q = FCPQuery(d, nt=nt)
            for rule in ("table", "nearest"):
                row = dict(object=name, layers=layers, N=N, nt=nt, rule=rule, bytes=d.nbytes, parts=q.P)
                for k, (P, gt, dist) in sets.items():
                    Pg = cp.asarray(d.pose.to_canonical(P).astype(np.float32))
                    ins = q.inside_gpu(Pg, rule).get()
                    row[f"{k}_ns"] = timeit(lambda: q.inside_gpu(Pg, rule), runs=5, sync=cuda_sync) / len(P) * 1e9
                    wrong = ins != gt
                    for e in EPS:
                        row[f"{k}_n{e}"] = int((wrong & (dist > e)).sum())
                    row[f"{k}_wrong_pct"] = 100 * wrong.mean()
                rows.append(row)
                print(f"{name:9s} L{layers} N{N} NT{nt:5d} {rule:8s} | near >0.1:{row['near_n0.1']:5d} >0.2:{row['near_n0.2']:4d} "
                      f"all {row['near_wrong_pct']:.3f} % | uniform >0.1:{row['uniform_n0.1']:4d} | GPU {row['near_ns']:5.1f} / "
                      f"{row['uniform_ns']:5.1f} ns near / uniform", flush=True)
            del q
            cp.get_default_memory_pool().free_all_blocks()
        new = not out.exists()
        with out.open("a", newline="") as f:
            w = csv.DictWriter(f, list(rows[0]))
            if new:
                w.writeheader()
            w.writerows(rows)


if __name__ == "__main__":
    main()
