"""Normal error of the decoded mesh for local, hybrid+pointtest, gwn, package on 5 clouds per object:
../report/normals_cmp.csv. Normal error: points sampled on the decoded mesh, the angle (degrees) between their face
normal and the normal of the closest clean-mesh face. Chamfer as in ablation.py (stats).

    env -u PYTHONPATH PYTHONWARNINGS=ignore .venv/bin/python ~/code/data/meshes/fcp_2710_tests/scripts/normals_cmp.py
"""
import csv
import sys
from pathlib import Path

import numpy as np
import trimesh

sys.path.insert(0, str(Path(__file__).parent))
import ablation as ab  # noqa: E402
from ablation import CONFIGS, CTX, PN, label  # noqa: E402
import fcp  # noqa: E402
from parts_report import OBJ, REPORT, ROOT, UP, stats  # noqa: E402

CLOUDS = ["clean_10000", "pn0p6_10000", "dgrad_10000", "noise0p6_10000", "clean_2048"]
CFG = ["package", "local", "hybrid+pointtest", "gwn"]
OUT = REPORT / "normals_cmp.csv"


def normal_err(g, m, n=20000):
    q, fi = trimesh.sample.sample_surface(m, n, seed=3)
    _, _, tri = trimesh.proximity.closest_point(g, q)
    c = np.einsum("ij,ij->i", m.face_normals[fi], g.face_normals[tri]).clip(-1, 1)
    a = np.degrees(np.arccos(c))
    return a.mean(), np.median(a), np.percentile(a, 95), (a > 90).mean()


def main():
    with OUT.open("w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["object", "cloud", "config", "parts", "chamfer", "n_mean", "n_median", "n_p95", "flipped"])
        for name in OBJ:
            g = trimesh.load(OBJ[name], force="mesh")
            ref = np.asarray(g.sample(50_000, seed=1))
            for c in CLOUDS:
                tag = c.rsplit("_", 1)[0]
                CTX["sigma"] = ({f"pn{label(v)}": v for v in PN}.get(tag, 0.0)) * float(np.linalg.norm(g.extents))
                P = np.asarray(trimesh.load(ROOT / "pointclouds" / f"{name}_{c}.ply").vertices)
                for k in CFG:
                    d = CONFIGS[k](P, UP.get(name, (0, 0, 1)))
                    m = fcp.decode(d, device="cuda")
                    w.writerow([name, c, k, len(d.parts), stats(d, ref, m)[0], *normal_err(g, m)])
                    f.flush()
                print(name, c, flush=True)


if __name__ == "__main__":
    main()
