import csv
import json
import sys
from pathlib import Path

import bpy
from bpy_extras.object_utils import world_to_camera_view
from mathutils import Matrix, Vector

out = Path(sys.argv[sys.argv.index("--") + 1])
out.mkdir(parents=True, exist_ok=True)
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
s = bpy.context.scene
s.render.engine = "CYCLES"
s.cycles.samples = 32
s.cycles.use_denoising = True
s.render.resolution_x = 800
s.render.resolution_y = 500
s.render.resolution_percentage = 100
s.render.image_settings.file_format = "PNG"
s.render.image_settings.color_mode = "RGBA"
s.view_settings.view_transform = "Standard"
s.view_settings.look = "None"
s.world.color = (0.035, 0.045, 0.065)


def material(name, color, emission=False):
    m = bpy.data.materials.new(name)
    m.use_nodes = True
    n = m.node_tree.nodes
    if emission:
        n.clear()
        e = n.new("ShaderNodeEmission")
        e.inputs["Color"].default_value = (*color, 1)
        o = n.new("ShaderNodeOutputMaterial")
        m.node_tree.links.new(e.outputs[0], o.inputs[0])
    else:
        p = n.get("Principled BSDF")
        p.inputs["Base Color"].default_value = (*color, 1)
        p.inputs["Roughness"].default_value = 0.45
    return m


objects = []
for name, center, radius, color in [
    ("Near sphere", (-0.8, 0, -4), 0.4, (0.8, 0.34, 0.09)),
    ("Far sphere", (2.4, 0, -12), 1.2, (0.12, 0.5, 0.7)),
]:
    bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=radius, location=center)
    o = bpy.context.object
    o.name = name
    o.data.materials.append(material(name, color))
    objects.append(o)
    for p in o.data.polygons:
        p.use_smooth = True
for loc, power, size in [((-3, 5, 1), 650, 6), ((6, 3, -7), 750, 7)]:
    bpy.ops.object.light_add(type="AREA", location=loc)
    light = bpy.context.object
    light.data.energy = power
    light.data.shape = "DISK"
    light.data.size = size
    light.rotation_euler = (Vector((0, 0, -8)) - light.location).to_track_quat("-Z", "Y").to_euler()
cameras = []
for name, loc, target, lens in [
    ("locked", (0, 0, 0), (0, 0, -8), 35),
    ("reveal", (10, 0, -3), (0.8, 0, -8), 35),
]:
    bpy.ops.object.camera_add(location=loc)
    c = bpy.context.object
    c.name = name
    back = (c.location - Vector(target)).normalized()
    right = Vector((0, 1, 0)).cross(back).normalized()
    up = back.cross(right)
    c.rotation_euler = Matrix((right, up, back)).transposed().to_euler()
    c.data.lens = lens
    c.data.sensor_width = 36
    c.data.sensor_fit = "HORIZONTAL"
    c.data.dof.use_dof = False
    cameras.append(c)
bpy.context.view_layer.update()
rows = []
for c in cameras:
    for o in objects:
        points = [world_to_camera_view(s, c, o.matrix_world @ v.co) for v in o.data.vertices]
        row = {
            "camera": c.name,
            "object": o.name,
            "minX": min(p.x for p in points) * 800,
            "maxX": max(p.x for p in points) * 800,
            "minY": (1 - max(p.y for p in points)) * 500,
            "maxY": (1 - min(p.y for p in points)) * 500,
        }
        row["projectedVertexHeight"] = row["maxY"] - row["minY"]
        rows.append(row)
assert abs(rows[0]["projectedVertexHeight"] - rows[1]["projectedVertexHeight"]) < 0.001
for row in rows:
    assert 0 <= row["minX"] < row["maxX"] <= 800 and 0 <= row["minY"] < row["maxY"] <= 500, row
for c in cameras:
    s.camera = c
    s.render.filepath = str(out / f"forced-perspective-{c.name}.png")
    bpy.ops.render.render(write_still=True)
s.camera = cameras[0]
s.render.filepath = "//forced-perspective-preview.png"
bpy.ops.wm.save_as_mainfile(filepath=str(out / "forced-perspective-original-scene.blend"))
for o, color in zip(objects, [(1, 0, 0), (0, 1, 0)]):
    o.data.materials.clear()
    o.data.materials.append(material(o.name + " ID", color, True))
s.world.color = (0, 0, 0)
for c in cameras:
    s.camera = c
    s.render.filepath = str(out / f"forced-perspective-{c.name}-id.png")
    bpy.ops.render.render(write_still=True)
record = {
    "scope": "Original native Blender scene and ID mask renders No AI generation or measured camera test",
    "blender": bpy.app.version_string,
    "width": 800,
    "height": 500,
    "cameraWorldUp": "Y",
    "frontLensMM": 35,
    "sensorWidthMM": 36,
    "geometry": [
        {"name": objects[0].name, "height": 0.8, "center": [-0.8, 0, -4]},
        {"name": objects[1].name, "height": 2.4, "center": [2.4, 0, -12]},
    ],
    "centerPlaneEstimatePixels": 800 * 35 / 36 * 0.8 / 4,
    "projectedVertexBounds": rows,
    "depthOfField": False,
    "meaning": "Vertex projected bounds for curved spheres differ slightly from planar H over Z estimate Image masks are checked separately",
}
(out / "forced-perspective-camera-record.json").write_text(json.dumps(record, indent=2) + "\n")
with (out / "forced-perspective-bounds.csv").open("w") as f:
    w = csv.DictWriter(f, fieldnames=rows[0].keys(), lineterminator="\n")
    w.writeheader()
    w.writerows(rows)
print(json.dumps(record))
