"""Equipment: the object a piece of gear shows when it lies on the ground (line 71
of the tracker, steps 8 and 9). One GLB per equipment id, in
assets/models/equipment/<id>.glb. Same convention as weapon_props.py, except
that gear stands instead of lying down:
- real size, in metres (the hero is about 1.75 m);
- the piece stands on the origin, +Z up in Blender (+Y in glTF);
- vertex colours, no texture, no ambient occlusion bake.
The bridge scales it to the hero and centres it on the loot. Nothing here
decides a collision.
Run headless from big-egg-3d/:
  blender --background --factory-startup --python tools/blender/equipment_props.py [-- --only id,id] [--preview out.png]
"""
import bpy, bmesh, math, os, sys
from mathutils import Matrix, Vector

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import crater_meadow_props as kit  # noqa: E402
from crater_meadow_props import srgb, paint, ico, cyl, bar, box, tube, torus, build  # noqa: E402

kit.BAKE_AO = False
OUT = os.path.join(kit.PROJECT, "assets", "models", "equipment")


def shade(hexa, vary=0.0, seed=0, light=None, dark=None):
    return paint(srgb(hexa), srgb(light) if light else None, srgb(dark) if dark else None, vary, seed, 0.5)


def rot_x(degrees):
    return Matrix.Rotation(math.radians(degrees), 3, "X")


def rot_y(degrees):
    return Matrix.Rotation(math.radians(degrees), 3, "Y")


def moved(mesh, matrix):
    bmesh.ops.transform(mesh, matrix=matrix, verts=mesh.verts)
    return mesh


def dome(radius, subdiv, at, scale):
    """The upper half of a squashed sphere: a hat crown, a lid."""
    mesh = ico(radius, subdiv, at, scale)
    geom = list(mesh.verts) + list(mesh.edges) + list(mesh.faces)
    bmesh.ops.bisect_plane(mesh, geom=geom, plane_co=Vector(at), plane_no=(0, 0, 1), clear_inner=True)
    return mesh


def boot(x, upper, sole, height=0.26, extra=()):
    """One rubber boot standing on the origin plane, toe toward +Y."""
    parts = [
        (box((0.09, 0.2, 0.035), (x, 0.03, 0.0)), sole),
        (box((0.085, 0.17, 0.07), (x, 0.035, 0.03)), upper),
        (cyl(0.048, 0.052, height, 16, (x, -0.02, 0.05)), upper),
        (cyl(0.054, 0.054, 0.02, 16, (x, -0.02, 0.05 + height - 0.02)), sole),
    ]
    return parts + list(extra)


# ---------------------------------------------------------------- the pieces

def oven_mitts(name):
    """Gants de four: two quilted red mitts standing side by side, cuffs down."""
    red = shade("#c0392b", 0.12, 1, light="#e8644f")
    cuff = shade("#efe6d2", 0.06, 2)
    parts = []
    for side in (-1, 1):
        x = side * 0.075
        parts += [
            (cyl(0.05, 0.05, 0.06, 14, (x, 0, 0.0)), cuff),
            (ico(0.06, 2, (x, 0, 0.14), (0.85, 0.5, 1.45)), red),
            (ico(0.024, 1, (x + side * 0.05, 0, 0.12), (1, 0.8, 1.6)), red),
        ]
    return build(name, parts)


def non_slip_boots(name):
    """Bottes antiderapantes: a pair of green rubber boots on thick black soles."""
    green = shade("#3f7d3a", 0.08, 3, light="#6fb35f")
    sole = shade("#1c1c1f", 0.04, 4)
    parts = boot(-0.06, green, sole) + boot(0.06, green, sole)
    for k in range(4):
        for x in (-0.06, 0.06):
            parts.append((box((0.092, 0.02, 0.01), (x, -0.05 + k * 0.05, -0.008)), sole))
    return build(name, parts)


def broth_flask(name):
    """Gourde de bouillon: a round dented canteen with a cork and a strap."""
    tin = shade("#8a9aa6", 0.06, 5, light="#c9d4dc")
    felt = shade("#6b5a3e", 0.1, 6)
    cork = shade("#b88a55", 0.1, 7)
    strap = shade("#4a3826", 0.08, 8)
    parts = [
        (ico(0.11, 2, (0, 0, 0.115), (1, 0.45, 1)), tin),
        (cyl(0.112, 0.112, 0.04, 22, (0, -0.02, 0.115), rot_x(-90), False), felt),
        (cyl(0.02, 0.018, 0.04, 10, (0, 0, 0.215)), tin),
        (cyl(0.017, 0.015, 0.03, 10, (0, 0, 0.245)), cork),
        (torus(0.13, 0.006, 24, 6, (0, 0, 0.115), rot_x(90)), strap),
    ]
    return build(name, parts)


def string_bag(name):
    """Filet a provisions: a string net bag with two handles and a few
    vegetables showing through the mesh."""
    net = shade("#d9c9a3", 0.08, 9)
    onion = shade("#d08a3c", 0.1, 10, light="#f0b26a")
    leek = shade("#7fb069", 0.1, 11)
    parts = [
        (ico(0.05, 2, (-0.03, 0.0, 0.07)), onion),
        (ico(0.045, 2, (0.04, 0.02, 0.06)), onion),
        (cyl(0.018, 0.016, 0.24, 8, (0.0, -0.02, 0.05), rot_y(12)), leek),
    ]
    # The net: meridians and parallels over a squat sphere.
    for k in range(10):
        a = math.radians(k * 36.0)
        points = []
        for j in range(9):
            t = math.radians(-80 + j * 20)
            points.append((0.1 * math.cos(t) * math.cos(a), 0.1 * math.cos(t) * math.sin(a), 0.1 + 0.1 * math.sin(t)))
        parts.append((tube(points, [0.0025] * len(points), 4), net))
    for z in (0.05, 0.1, 0.15):
        r = math.sqrt(max(0.0, 0.01 - (z - 0.1) ** 2))
        parts.append((torus(r, 0.0025, 24, 4, (0, 0, z)), net))
    for side in (-1, 1):
        parts.append((torus(0.05, 0.004, 16, 5, (side * 0.03, 0, 0.24), rot_y(90)), net))
    return build(name, parts)


def hard_hat(name):
    """Casque de chantier: a yellow dome with a brim and a ridge."""
    yellow = shade("#f2c40f", 0.06, 12, light="#ffe066")
    band = shade("#2b2b2e", 0.04, 13)
    parts = [
        (cyl(0.15, 0.15, 0.012, 28, (0, 0.015, 0.0)), yellow),
        (dome(0.12, 3, (0, 0, 0.012), (1, 1.12, 0.95)), yellow),
        (box((0.03, 0.15, 0.03), (0, 0, 0.09)), yellow),
        (cyl(0.122, 0.122, 0.018, 28, (0, 0, 0.012), None, False), band),
    ]
    return build(name, parts)


def spring_boots(name):
    """Bottes a ressort: red boots perched on steel coil springs."""
    red = shade("#b8322a", 0.08, 14, light="#e45b4f")
    sole = shade("#26262b", 0.04, 15)
    steel = shade("#c9d1d9", 0.04, 16, light="#ffffff")
    parts = []
    lift = 0.08
    for x in (-0.06, 0.06):
        coil = []
        for k in range(49):
            a = k * math.radians(30)
            coil.append((x + 0.03 * math.cos(a), 0.03 + 0.03 * math.sin(a), lift * k / 48.0))
        parts.append((tube(coil, [0.004] * len(coil), 5), steel))
        for mesh, mat in boot(x, red, sole, 0.2):
            moved(mesh, Matrix.Translation((0, 0, lift)))
            parts.append((mesh, mat))
    return build(name, parts)


def party_balloon(name):
    """Ballon de fete de l'Abominable: a big pink balloon on a ribbon, tied to
    a little weight that stands on the ground."""
    pink = shade("#ff5fa2", 0.05, 17, light="#ffb3d1")
    ribbon = shade("#ffd166", 0.04, 18)
    weight = shade("#3a3a44", 0.06, 19)
    parts = [
        (cyl(0.035, 0.03, 0.04, 12, (0, 0, 0.0)), weight),
        (bar((0, 0, 0.04), (0.02, 0.0, 0.42), 0.003, 4), ribbon),
        (cyl(0.012, 0.0, 0.025, 8, (0.02, 0.0, 0.42)), pink),
        (ico(0.15, 3, (0.02, 0.0, 0.59), (1, 1, 1.18)), pink),
        (ico(0.03, 1, (-0.05, -0.08, 0.66)), shade("#ffe3ef", 0.0, 20)),
    ]
    return build(name, parts)


def siren_goggles(name):
    """Lunettes de la Sirene: teal swim goggles with a shell clip, standing on
    their strap."""
    teal = shade("#1fb5a8", 0.06, 21, light="#6ee7dd")
    lens = shade("#bff4ff", 0.02, 22, light="#ffffff")
    strap = shade("#20435a", 0.06, 23)
    shell = shade("#f7c6b5", 0.08, 24)
    parts = [
        (torus(0.075, 0.008, 28, 6, (0, 0, 0.075), rot_x(90), 0.0), strap),
    ]
    for side in (-1, 1):
        x = side * 0.04
        parts += [
            (torus(0.026, 0.008, 18, 6, (x, -0.075, 0.08), rot_x(90)), teal),
            (cyl(0.024, 0.024, 0.006, 18, (x, -0.078, 0.08), rot_x(90)), lens),
        ]
    parts += [
        (box((0.02, 0.012, 0.01), (0, -0.078, 0.076)), teal),
        (ico(0.016, 1, (0, -0.07, 0.12), (1.3, 0.5, 1)), shell),
    ]
    return build(name, parts)


def pot_lid(name):
    """Couvercle de marmite: a dented steel lid lying dome up, black knob."""
    steel = shade("#aeb7c0", 0.06, 25, light="#f0f4f8")
    rim = shade("#7d8790", 0.04, 26)
    knob = shade("#1f1f24", 0.05, 27)
    parts = [
        (cyl(0.15, 0.15, 0.012, 32, (0, 0, 0.0)), rim),
        (dome(0.145, 3, (0, 0, 0.006), (1, 1, 0.28)), steel),
        (cyl(0.012, 0.012, 0.03, 10, (0, 0, 0.035)), knob),
        (ico(0.022, 1, (0, 0, 0.07), (1.4, 1.4, 0.7)), knob),
    ]
    return build(name, parts)


def jay_soles(name):
    """Semelles de Jay: a pair of golden insoles with a lightning stripe, lying
    flat and slightly splayed."""
    gold = shade("#f2d94e", 0.06, 28, light="#fff3a3")
    stripe = shade("#3b82f6", 0.04, 29)
    parts = []
    for side in (-1, 1):
        x = side * 0.055
        turn = Matrix.Rotation(math.radians(-side * 8), 3, "Z")
        for k in range(5):
            y = -0.1 + k * 0.05
            width = 0.07 if 0 < k < 4 else 0.055
            mesh = box((width, 0.052, 0.012), (0, y, 0.0))
            moved(mesh, Matrix.Translation((x, 0, 0)) @ turn.to_4x4())
            parts.append((mesh, gold))
        bolt = box((0.012, 0.16, 0.004), (0, -0.01, 0.012))
        moved(bolt, Matrix.Translation((x, 0, 0)) @ turn.to_4x4())
        parts.append((bolt, stripe))
    return build(name, parts)


# name: maker. The keys are equipment ids of the master data.
PROPS = {
    "oven_mitts": oven_mitts,
    "non_slip_boots": non_slip_boots,
    "broth_flask": broth_flask,
    "string_bag": string_bag,
    "hard_hat": hard_hat,
    "spring_boots": spring_boots,
    "party_balloon": party_balloon,
    "siren_goggles": siren_goggles,
    "pot_lid": pot_lid,
    "jay_soles": jay_soles,
}


def preview(keys, path):
    """One contact sheet, every piece side by side, for the author."""
    bpy.ops.wm.read_factory_settings(use_empty=True)
    scene = bpy.context.scene
    x = 0.0
    tallest = 0.0
    for key in keys:
        obj = PROPS[key](key)
        scene.collection.objects.link(obj)
        zmin = min(v.co.z for v in obj.data.vertices)
        xs = [v.co.x for v in obj.data.vertices]
        obj.location = (x - min(xs), 0, -zmin)
        tallest = max(tallest, max(v.co.z for v in obj.data.vertices) - zmin)
        x += (max(xs) - min(xs)) + 0.08
    cam_data = bpy.data.cameras.new("Cam")
    cam_data.type = "ORTHO"
    cam_data.ortho_scale = max(x, tallest * 1600 / 600) * 1.1
    cam = bpy.data.objects.new("Cam", cam_data)
    scene.collection.objects.link(cam)
    target = Vector((x / 2, 0, tallest / 2))
    cam.location = target + Vector((0.8, -3.0, 1.4))
    cam.rotation_euler = (target - cam.location).to_track_quat("-Z", "Y").to_euler()
    scene.camera = cam
    scene.render.engine = "BLENDER_WORKBENCH"
    scene.display.shading.light = "STUDIO"
    scene.display.shading.color_type = "VERTEX"
    scene.display.shading.background_type = "VIEWPORT"
    scene.display.shading.background_color = (0.08, 0.08, 0.1)
    scene.render.resolution_x = 1600
    scene.render.resolution_y = 600
    scene.render.filepath = path
    bpy.ops.render.render(write_still=True)
    print("PREVIEW", path)


if __name__ == "__main__":
    args = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
    only = args[args.index("--only") + 1].split(",") if "--only" in args else list(PROPS)
    if "--preview" in args:
        preview(only, args[args.index("--preview") + 1])
        sys.exit(0)
    bpy.ops.wm.read_factory_settings(use_empty=True)
    os.makedirs(OUT, exist_ok=True)
    for key in only:
        kit.export(PROPS[key](key), [], folder=OUT)
