"""Solid properties table (volume, convex hull) from the fcp_3010_hull study's outputs; writes section.html."""
from pathlib import Path
import pandas as pd

H = Path.home() / "code/data/meshes/fcp_3010_hull/out"
R = Path(__file__).resolve().parent
a, s = pd.read_csv(H / "volume_atoms.csv"), pd.read_csv(H / "volume_sota.csv")
vd = pd.read_csv(H / "volume.csv")
h = pd.read_csv(R.parent / "hull/hull_objects.csv")


def rng(x, f="{:.1f}"):
    return f"{f.format(x.median())} ({f.format(x.min())}–{f.format(x.max())})"


def worst(e):
    return e.loc[e.abs().idxmax()]


rows = []
for (L, A), g in a.merge(s, on="obj").groupby(["layers", "atoms_req"]):
    e = g.err_pct
    d = vd[(vd.layers == L) & (vd.atoms_req == A)]
    rows.append(["volume (encoded from the mesh)", L, A, "–", rng(g.us_fast / 1e3, "{:.3f}"), "exact mesh volume (divergence theorem, numba)",
                 rng(g.nb_ms, "{:.3f}"), f"{d.ms_decoded.median():.0f}", f"{(g.us_fast < g.nb_ms * 1e3).sum()} / {len(g)}",
                 f"{e.median():+.2f} ({worst(e):+.2f})", "–"])
o = pd.read_csv(R.parent / "hull/volume_objects.csv")  # the finished volume run: 128 layers, 2048 atoms
# from the mesh at 6144 atoms the rows above (volume_atoms.csv); from points at every setting here
for pre, src, lab, L, A in (("fcp", "mesh", "volume (encoded from the mesh)", 128, 2048),
                            ("fcp", "points", "volume (encoded from 20k points)", 128, 2048),
                            ("fcp6144", "points", "volume (encoded from 20k points)", 128, 6144),
                            ("fcpL256", "points", "volume (encoded from 20k points)", 256, 6144)):
    e = o[f"{pre}_{src}_err"]
    rows.append([lab, L, A, "–", rng(o[f"{pre}_{src}_vol_us"] / 1e3, "{:.3f}"),
                 "exact mesh volume (tetrahedron sum, numba)", rng(o.exact_us / 1e3, "{:.3f}"), "–",
                 f"{(o[f'{pre}_{src}_vol_us'] < o.exact_us).sum()} / {len(o)}", f"{e.median():+.2f} ({worst(e):+.2f})", "–"])
for (L, A, M), g in h.groupby(["layers", "atoms_req", "M"]):
    e = 100 * (g.vol_ratio - 1)
    rows.append(["convex hull", L, A, M, rng(g.fcp_ms), "qhull on the mesh's vertices", rng(g.qhull_ms),
                 f"{g.decode_qhull_ms.median():.0f}", f"{g.faster.sum()} / {len(g)}",
                 f"{e.median():+.2f} ({worst(e):+.2f})",
                 f"{g.gap_true_to_fcp_mean.median():.3f} ({g.gap_true_to_fcp_max.max():.2f})"])

# bounds: the fast method (accuracy bounds_fast_eval, idle times bounds_fast); references on the mesh, best route per object
ref = pd.read_csv(H / "bounds.csv").drop_duplicates("obj")[["obj", "mesh_obb_ms", "mesh_sphere_exact_ms"]]
b = (pd.read_csv(H / "bounds_fast_eval.csv").drop(columns=["fast_ms", "fast_aabb_sphere_ms"])
     .merge(pd.read_csv(H / "bounds_fast.csv")[["obj", "layers", "atoms", "fast_ms", "fast_aabb_sphere_ms"]], on=["obj", "layers", "atoms"])
     .merge(pd.read_csv(H / "bounds_o3d.csv"), on="obj").merge(ref, on="obj"))
b["obb_ref_ms"] = b[["mesh_obb_ms", "o3d_min_ms"]].min(axis=1)
b["obb_ratio"] = b.obb_vol_ratio / b.o3d_min_vol_ratio.clip(upper=1)
for lab, ref_lab, ms, rms, vol, out in (
        ("axis-aligned box", "Open3D box of the mesh vertices", "fast_aabb_sphere_ms", "o3d_aabb_ms", lambda g: g.aabb_vol_ratio, "aabb_out_pct"),
        ("oriented box", "minimal box: the smaller of trimesh's and Open3D's; the faster one's time", "fast_ms", "obb_ref_ms", lambda g: g.obb_ratio, "obb_out_pct"),
        ("smallest sphere", "exact smallest ball (qhull, then the minimum ball)", "fast_aabb_sphere_ms", "mesh_sphere_exact_ms", lambda g: g.sphere_r_ratio ** 3, "sphere_out_pct")):
    for (L, A), g in b.groupby(["layers", "atoms"]):
        e = 100 * (vol(g) - 1)
        tf = "{:.3f}" if rms == "o3d_aabb_ms" else "{:.1f}"
        rows.append([lab, L, A, 64, rng(g[ms], "{:.2f}"), ref_lab, rng(g[rms], tf), "–", f"{(g[ms] < g[rms]).sum()} / {len(g)}",
                     f"{e.median():+.2f} ({worst(e):+.2f})", "–", f"{g[out].max():.2f}"])
for r in rows:
    if len(r) == 11:
        r.append("–")
order = ["volume (encoded from the mesh)", "volume (encoded from 20k points)", "convex hull", "axis-aligned box", "oriented box", "smallest sphere"]
rows.sort(key=lambda r: (order.index(r[0]), r[1], r[2], 0 if r[3] == "–" else r[3]))
head = ["property", "layers", "atoms", "directions M", "FCP, ms median (range)", "reference on the source mesh",
        "reference, ms median (range)", "decode + reference, ms median", "FCP faster than the reference",
        "volume error, % median (worst)", "hull surface gap, % diag: median of means (max)",
        "farthest mesh vertex outside the bound, % diag (max)"]
td = lambda r: "".join(f"<td style='text-align:left'>{c}</td>" if i in (0, 5) else f"<td>{c}</td>" for i, c in enumerate(r))
html = f"""<h2>Solid properties straight from the descriptor</h2>
<p>The 12 objects of the collision section; CPU: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz, one thread for the volume and the hull; the boxes and the sphere on numba's 12 threads, the references as their libraries run. Volume from the stored atoms in one contraction (no rings, no grid); convex hull from the rings (prototype, not in the package). Volume error against the source mesh's volume (volume rows), its convex hull's volume (hull rows), or the reference bound's volume (box and sphere rows; the sphere's from its radius; trimesh's minimum sphere is up to 0.4 % too large and slower, so the exact ball is the reference); the boxes and the sphere come from candidate hull points of the rings (no hull surface; 64 directions); the oriented box's time is that of all three bounds, the axis-aligned box and the sphere share one pass; the hull gap from 20k points on the true hull to the descriptor's hull. The 6144-atom runs from points ran at a load average of about 7: their times are upper bounds.</p>
<div class="tablewrap"><table>
<thead><tr>{''.join(f"<th>{c}</th>" for c in head)}</tr></thead>
<tbody>
{chr(10).join(f"<tr>{td(r)}</tr>" for r in rows)}
</tbody>
</table></div>"""
(R / "section.html").write_text(html + "\n")
pd.DataFrame(rows, columns=head).to_csv(R / "data.csv", index=False)
print(html)
