from __future__ import annotations

import json
import math
import re
import sys
from collections import Counter
from pathlib import Path

ROOT = Path(__file__).resolve().parent
OUTPUT = ROOT / "40-afstandhouder-oefening.stl"
REPORT = ROOT / "40-afstandhouder-verification.json"
OUTER_RADIUS = 10.0
INNER_RADIUS = 5.0
HEIGHT = 6.0
SEGMENTS = 64


def ring(radius: float, z: float):
    return [(radius * math.cos(2 * math.pi * i / SEGMENTS), radius * math.sin(2 * math.pi * i / SEGMENTS), z) for i in range(SEGMENTS)]


outer_bottom, outer_top = ring(OUTER_RADIUS, 0), ring(OUTER_RADIUS, HEIGHT)
inner_bottom, inner_top = ring(INNER_RADIUS, 0), ring(INNER_RADIUS, HEIGHT)
triangles = []
for i in range(SEGMENTS):
    j = (i + 1) % SEGMENTS
    triangles.extend([
        (outer_bottom[i], outer_bottom[j], outer_top[j]), (outer_bottom[i], outer_top[j], outer_top[i]),
        (inner_bottom[i], inner_top[j], inner_bottom[j]), (inner_bottom[i], inner_top[i], inner_top[j]),
        (outer_top[i], outer_top[j], inner_top[j]), (outer_top[i], inner_top[j], inner_top[i]),
        (outer_bottom[i], inner_bottom[j], outer_bottom[j]), (outer_bottom[i], inner_bottom[i], inner_bottom[j]),
    ])
lines = ["solid afstandhouder_oefening"]
for triangle in triangles:
    lines.extend(["  facet normal 0 0 0", "    outer loop"])
    lines.extend(f"      vertex {x:.9f} {y:.9f} {z:.9f}" for x, y, z in triangle)
    lines.extend(["    endloop", "  endfacet"])
lines.append("endsolid afstandhouder_oefening")
OUTPUT.write_text("\n".join(lines) + "\n", encoding="ascii")
parsed_vertices = [
    tuple(map(float, match.groups()))
    for match in re.finditer(r"^\s*vertex\s+([\-\d.]+)\s+([\-\d.]+)\s+([\-\d.]+)$", OUTPUT.read_text(encoding="ascii"), re.MULTILINE)
]
parsed_triangles = [tuple(parsed_vertices[index:index + 3]) for index in range(0, len(parsed_vertices), 3)]
axis_bounds = [
    min(point[axis] for point in parsed_vertices) if bound == "min" else max(point[axis] for point in parsed_vertices)
    for axis in range(3)
    for bound in ("min", "max")
]
radial_values = sorted({round(math.hypot(x, y), 6) for x, y, _ in parsed_vertices})
edges = Counter()
directed_edges = Counter()
for triangle in parsed_triangles:
    for start, end in zip(triangle, triangle[1:] + triangle[:1]):
        edges[tuple(sorted((start, end)))] += 1
        directed_edges[(start, end)] += 1
boundary_edges = sum(count == 1 for count in edges.values())
non_manifold_edges = sum(count > 2 for count in edges.values())
winding_mismatches = sum(
    count != directed_edges[(end, start)]
    for (start, end), count in directed_edges.items()
    if start < end
)
inner_perimeter_edges = [
    (start, end)
    for start, end in edges
    if math.isclose(math.hypot(start[0], start[1]), INNER_RADIUS, abs_tol=1e-6)
    and math.isclose(math.hypot(end[0], end[1]), INNER_RADIUS, abs_tol=1e-6)
    and math.isclose(start[2], end[2], abs_tol=1e-6)
]


def xy_segment_distance_to_origin(start, end):
    delta_x = end[0] - start[0]
    delta_y = end[1] - start[1]
    length_squared = delta_x * delta_x + delta_y * delta_y
    projection = -(start[0] * delta_x + start[1] * delta_y) / length_squared
    projection = max(0.0, min(1.0, projection))
    nearest_x = start[0] + projection * delta_x
    nearest_y = start[1] + projection * delta_y
    return math.hypot(nearest_x, nearest_y)


minimum_clear_hole_diameter = 2 * min(
    xy_segment_distance_to_origin(start, end) for start, end in inner_perimeter_edges
)
expected_clear_hole_diameter = 2 * INNER_RADIUS * math.cos(math.pi / SEGMENTS)
expected_bounds = [-OUTER_RADIUS, OUTER_RADIUS, -OUTER_RADIUS, OUTER_RADIUS, 0, HEIGHT]
checks = {
    "facetCount": len(parsed_triangles) == SEGMENTS * 8,
    "outerDiameter": radial_values[-1] == OUTER_RADIUS,
    "innerDiameter": radial_values[0] == INNER_RADIUS,
    "polygonClearHoleDiameter": math.isclose(minimum_clear_hole_diameter, expected_clear_hole_diameter, abs_tol=1e-6),
    "height": axis_bounds[4:] == [0, HEIGHT],
    "axisBounds": axis_bounds == expected_bounds,
    "closedTwoManifold": boundary_edges == 0 and non_manifold_edges == 0 and winding_mismatches == 0,
}
report = {"schemaVersion": 1, "checkedOn": "2026-09-06", "method": "Generated a closed non-load-bearing ring spacer from explicit dimensions, reparsed every output vertex and triangle, and checked vertex radii, polygon clearance, height, bounds and edge topology. No CAD GUI, slicer, or physical print was used.", "output": OUTPUT.name, "inputs": {"outerDiameterMm": OUTER_RADIUS * 2, "innerDiameterMm": INNER_RADIUS * 2, "heightMm": HEIGHT, "segments": SEGMENTS}, "result": {"facetCount": len(parsed_triangles), "expectedFacetCount": SEGMENTS * 8, "outerVertexDiameterMm": radial_values[-1] * 2, "innerVertexDiameterMm": radial_values[0] * 2, "minimumPolygonClearHoleDiameterMm": minimum_clear_hole_diameter, "heightMm": axis_bounds[5] - axis_bounds[4], "axisBoundsMm": axis_bounds, "boundaryEdges": boundary_edges, "nonManifoldEdges": non_manifold_edges, "windingMismatches": winding_mismatches}, "checks": checks, "pass": all(checks.values())}
REPORT.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
print(json.dumps(report, indent=2))
sys.exit(0 if report["pass"] else 1)
