"""Author an original cup inspection fixture, not a Hi3D generation."""

import json
import math
from pathlib import Path
import sys

import bmesh
import bpy
from mathutils import Vector

out = Path(sys.argv[sys.argv.index("--") + 1]) if "--" in sys.argv else Path(".")
out.mkdir(parents=True, exist_ok=True)
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.context.preferences.filepaths.save_version = 0
scene = bpy.context.scene
scene.render.engine = "CYCLES"
scene.cycles.device = "CPU"
scene.cycles.samples = 24
scene.cycles.seed = 27
scene.cycles.use_denoising = True
scene.render.resolution_x = 1024
scene.render.resolution_y = 1024
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = "PNG"
scene.view_settings.view_transform = "AgX"
scene.world.color = (0.65, 0.65, 0.65)


def material(name, color, roughness=0.4):
    mat = bpy.data.materials.new(name)
    mat.use_nodes = True
    shader = mat.node_tree.nodes.get("Principled BSDF")
    shader.inputs["Base Color"].default_value = (*color, 1)
    shader.inputs["Roughness"].default_value = roughness
    return mat


coral = material("Coral ceramic", (0.68, 0.13, 0.08), 0.28)
teal = material("Teal handle", (0.018, 0.20, 0.23), 0.28)
cream = material("Cream stage", (0.82, 0.74, 0.60), 0.8)
segments = 96
profile = [
    (0.82, 0.12),
    (0.90, 0.22),
    (0.99, 2.32),
    (1.01, 2.46),
    (0.87, 2.46),
    (0.84, 2.32),
    (0.72, 0.33),
]
vertices = [
    (r * math.cos(2 * math.pi * j / segments), r * math.sin(2 * math.pi * j / segments), z)
    for r, z in profile
    for j in range(segments)
]
faces = []
for k in range(len(profile) - 1):
    for j in range(segments):
        n = (j + 1) % segments
        faces.append(
            (k * segments + j, k * segments + n, (k + 1) * segments + n, (k + 1) * segments + j)
        )
outer_center = len(vertices)
vertices.append((0, 0, profile[0][1]))
inner_center = len(vertices)
vertices.append((0, 0, profile[-1][1]))
for j in range(segments):
    n = (j + 1) % segments
    faces.append((outer_center, n, j))
    faces.append(
        (inner_center, (len(profile) - 1) * segments + j, (len(profile) - 1) * segments + n)
    )
mesh = bpy.data.meshes.new("Cup hollow body mesh")
mesh.from_pydata(vertices, [], faces)
mesh.update()
cup = bpy.data.objects.new("Original hollow cup", mesh)
scene.collection.objects.link(cup)
cup.data.materials.append(coral)
cup.data.materials.append(teal)
bpy.context.view_layer.objects.active = cup
cup.select_set(True)
curve = bpy.data.curves.new("C handle path", "CURVE")
curve.dimensions = "3D"
curve.resolution_u = 16
curve.bevel_depth = 0.15
curve.bevel_resolution = 5
curve.use_fill_caps = True
spline = curve.splines.new("BEZIER")
spline.bezier_points.add(3)
for p, co in zip(
    spline.bezier_points, [(0.94, 0, 2.08), (1.82, 0, 1.88), (1.82, 0, 0.76), (0.88, 0, 0.59)]
):
    p.co = co
    p.handle_left_type = "AUTO"
    p.handle_right_type = "AUTO"
handle = bpy.data.objects.new("Handle union input", curve)
scene.collection.objects.link(handle)
handle.data.materials.append(teal)
bpy.ops.object.select_all(action="DESELECT")
handle.select_set(True)
bpy.context.view_layer.objects.active = handle
bpy.ops.object.convert(target="MESH")
handle = bpy.context.object
handle.data.materials.clear()
handle.data.materials.append(coral)
handle.data.materials.append(teal)
for polygon in handle.data.polygons:
    polygon.material_index = 1
bpy.ops.object.select_all(action="DESELECT")
cup.select_set(True)
bpy.context.view_layer.objects.active = cup
union = cup.modifiers.new("Attach handle", "BOOLEAN")
union.operation = "UNION"
union.solver = "EXACT"
union.object = handle
bpy.ops.object.modifier_apply(modifier=union.name)
bpy.data.objects.remove(handle, do_unlink=True)
bm = bmesh.new()
bm.from_mesh(cup.data)
bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces))
bm.to_mesh(cup.data)
bm.free()
for p in cup.data.polygons:
    p.use_smooth = True
cup.data.update()
bpy.ops.object.select_all(action="DESELECT")
cup.select_set(True)
bpy.context.view_layer.objects.active = cup
bpy.ops.export_scene.gltf(
    filepath=str(out / "hi3d-original-cup.glb"),
    export_format="GLB",
    use_selection=True,
    export_apply=True,
)
bpy.ops.wm.stl_export(filepath=str(out / "hi3d-original-cup.stl"), export_selected_objects=True)
bm = bmesh.new()
bm.from_mesh(cup.data)
unseen = set(bm.verts)
components = 0
while unseen:
    components += 1
    queue = [unseen.pop()]
    while queue:
        v = queue.pop()
        for e in v.link_edges:
            n = e.other_vert(v)
            if n in unseen:
                unseen.remove(n)
                queue.append(n)
cup.data.calc_loop_triangles()
audit = {
    "authorship": "Native Blender fixture not Hi3D output",
    "blenderVersion": bpy.app.version_string,
    "vertices": len(cup.data.vertices),
    "polygons": len(cup.data.polygons),
    "triangles": len(cup.data.loop_triangles),
    "connectedComponents": components,
    "boundaryEdges": sum(e.is_boundary for e in bm.edges),
    "nonManifoldEdges": sum(not e.is_manifold for e in bm.edges),
    "signedVolumeCoordinateUnits": bm.calc_volume(signed=True),
    "boundsCoordinateUnits": {
        axis: float(cup.dimensions[i]) for i, axis in enumerate(["x", "y", "z"])
    },
    "stlUnits": "Unitless coordinates no printer scale validated",
    "notTested": "Hi3D generation refinement self-intersection analysis wall thickness suitability slicer or physical printing",
}
bm.free()
(out / "hi3d-cup-audit.json").write_text(json.dumps(audit, indent=2) + "\n")
bpy.ops.mesh.primitive_plane_add(size=200, location=(0, 0, 0.10))
floor = bpy.context.object
floor.name = "Render stage not exported"
floor.data.materials.append(cream)


def aim(obj, target):
    obj.rotation_euler = (Vector(target) - obj.location).to_track_quat("-Z", "Y").to_euler()


for name, location, power, size in [
    ("Key", (3, -4, 6), 600, 4),
    ("Fill", (-3, 1, 4), 400, 3),
    ("Under", (2, -3, -2), 180, 3),
]:
    light = bpy.data.lights.new(name, "AREA")
    light.energy = power
    light.shape = "DISK"
    light.size = size
    obj = bpy.data.objects.new(name, light)
    scene.collection.objects.link(obj)
    obj.location = location
    aim(obj, (0.4, 0, 1.2))
cam_data = bpy.data.cameras.new("Inspection camera")
cam_data.type = "ORTHO"
cam_data.ortho_scale = 4.9
cam = bpy.data.objects.new("Inspection camera", cam_data)
scene.collection.objects.link(cam)
scene.camera = cam
views = {"front": (4, -6, 4.1), "rear": (-4, 6, 3.8), "underside": (3, -4, -3.5)}
for name, location in views.items():
    floor.hide_render = name == "underside"
    cam.location = location
    aim(cam, (0.4, 0, 1.25))
    scene.render.filepath = str(out / f"hi3d-cup-{name}.png")
    bpy.ops.render.render(write_still=True)
floor.hide_render = False
cam.location = views["front"]
aim(cam, (0.4, 0, 1.25))
bpy.ops.wm.save_as_mainfile(filepath=str(out / "hi3d-original-cup.blend"))
print(json.dumps(audit))
