"""Weapons: the physical object of each weapon (lines 65 and 71 of the tracker).

One GLB per weapon id, in assets/models/weapons/<id>.glb. Convention shared by
every piece, so the 3D bridge can hang any of them from a hand bone the same way:

- real size, in metres (the hero is about 1.75 m: 75 game units per metre);
- the grip, where the hand closes, sits at the origin;
- the main axis (handle to tip, stock to muzzle) runs along +Z in Blender,
  which the glTF export turns into +Y;
- the top of a gun (its carton, its tank) faces +Y in Blender, -Z in glTF.

Preview sheet for the author: add `--preview <png>` (Workbench, vertex colours).

Laid on the ground, the bridge rotates the piece flat; in hand, it applies the
grip rotation of the hand bone. Nothing here decides a collision.

Reuses the mesh helpers of crater_meadow_props.py; no ambient occlusion bake
(a baked ground would darken pieces that float in a hand).

Run headless from big-egg-3d/:
  blender --background --factory-startup --python tools/blender/weapon_props.py [-- --only id,id] [--preview out.png]
"""
import bpy, 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", "weapons")


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")


# ---------------------------------------------------------------- the pieces

def rolling_pin(name):
    """Gros rouleau a pate: a heavy beech barrel between two turned handles.
    The hand holds the lower handle."""
    wood = shade("#d9b98a", 0.08, 1)
    handle = shade("#a8743f", 0.1, 2)
    parts = [
        (cyl(0.016, 0.019, 0.11, 12, (0, 0, -0.06)), handle),
        (cyl(0.024, 0.024, 0.02, 14, (0, 0, 0.05)), handle),
        (cyl(0.052, 0.052, 0.40, 20, (0, 0, 0.07)), wood),
        (cyl(0.024, 0.024, 0.02, 14, (0, 0, 0.47)), handle),
        (cyl(0.019, 0.016, 0.10, 12, (0, 0, 0.49)), handle),
        (ico(0.022, 1, (0, 0, 0.60)), handle),
        (ico(0.02, 1, (0, 0, -0.065)), handle),
    ]
    return build(name, parts)


def egg_launcher(name):
    """Lance-oeufs: a stubby blue tube fed by an egg carton, pistol grip at the
    origin. The muzzle points along +Z."""
    metal = shade("#3f7f99", 0.06, 3, light="#7dd3fc")
    dark = shade("#1f2f3a", 0.05, 4)
    carton = shade("#c9b48a", 0.12, 5)
    shell = shade("#fbf3e4", 0.03, 6)
    parts = [
        # Grip and trigger guard, under the barrel.
        (box((0.035, 0.07, 0.10), (0, -0.01, -0.05), rot_x(-12)), dark),
        (bar((0, 0.035, 0.07), (0, -0.02, 0.07), 0.006), dark),
        # Barrel, stock to muzzle, above the hand.
        (cyl(0.055, 0.055, 0.52, 18, (0, 0.08, -0.16)), metal),
        (cyl(0.068, 0.068, 0.05, 18, (0, 0.08, 0.34)), dark),
        (cyl(0.06, 0.06, 0.04, 18, (0, 0.08, -0.18)), dark),
        # The egg carton on top, three eggs showing.
        (box((0.08, 0.06, 0.20), (0, 0.155, -0.02)), carton),
    ]
    for k in range(3):
        parts.append((ico(0.024, 2, (0, 0.19, 0.0 + k * 0.06), (1, 1, 1.25)), shell))
    return build(name, parts)


def frost_gun(name):
    """Fusil de givre: a long pale barrel with an ice tank and frost fins."""
    metal = shade("#8fa9b8", 0.05, 7, light="#e6f7ff")
    dark = shade("#26323d", 0.05, 8)
    ice = shade("#bfeeff", 0.1, 9, light="#ffffff")
    parts = [
        (box((0.035, 0.07, 0.11), (0, -0.01, -0.055), rot_x(-12)), dark),
        (box((0.04, 0.05, 0.28), (0, 0.06, -0.30)), dark),
        (cyl(0.032, 0.032, 0.66, 14, (0, 0.07, -0.05)), metal),
        (cyl(0.05, 0.05, 0.04, 14, (0, 0.07, 0.58)), ice),
        # The tank of ice beside the barrel.
        (cyl(0.042, 0.042, 0.20, 14, (0.06, 0.06, 0.02)), ice),
        (cyl(0.046, 0.046, 0.02, 14, (0.06, 0.06, 0.02)), dark),
        (cyl(0.046, 0.046, 0.02, 14, (0.06, 0.06, 0.20)), dark),
    ]
    for k in range(3):
        z = 0.36 + k * 0.07
        parts.append((box((0.11, 0.012, 0.03), (0, 0.07, z)), ice))
    return build(name, parts)


def horseshoe(name):
    """Fer a cheval: a flat polished U, held by one heel (the origin); the arch
    points along +Z."""
    steel = shade("#c0c6cf", 0.06, 10, light="#f2f5f8")
    holes = shade("#3b4048", 0.0, 11)
    radius, thick = 0.065, 0.014
    points, radii = [], []
    steps = 22
    for k in range(steps + 1):
        # From one heel, over the arch, to the other heel: 244 degrees.
        a = math.radians(-122 + k * (244 / steps))
        points.append((radius * math.sin(a), 0.0, radius * math.cos(a)))
        radii.append(thick)
    shoe = tube(points, radii, 8)
    # Flatten across Y: a horseshoe is a plate, not a ring.
    for v in shoe.verts:
        v.co.y *= 0.45
    heel = Vector(points[0])
    for v in shoe.verts:
        v.co -= heel
    parts = [(shoe, steel)]
    for k in (4, 8, 14, 18):
        p = Vector(points[k]) - heel
        parts.append((box((0.008, 0.012, 0.008), (p.x, 0, p.z - 0.004)), holes))
    return build(name, parts)


def berimbau(name):
    """Berimbau: the Saule's musical bow, wire and gourd. Held at the gourd tie."""
    wood = shade("#8a6b48", 0.12, 12, light="#b08a5e")
    gourd = shade("#c8a05a", 0.1, 13, light="#e3c27f")
    wire = shade("#9aa3ab", 0.0, 14)
    length, bow = 1.35, 0.06
    stick_points, stick_radii = [], []
    for k in range(13):
        t = k / 12
        stick_points.append((0, -bow * math.sin(math.pi * t), -0.25 + t * length))
        stick_radii.append(0.014 if 0.05 < t < 0.95 else 0.011)
    parts = [
        (tube(stick_points, stick_radii, 8), wood),
        (bar((0, 0.002, -0.24), (0, 0.002, length - 0.26), 0.002, 4), wire),
        # The gourd sits against the bow, its open mouth toward the player.
        (ico(0.10, 2, (0, 0.11, -0.08), (1, 1, 0.92)), gourd),
        (cyl(0.055, 0.055, 0.012, 16, (0, 0.20, -0.08), rot_x(-90)), shade("#3d2b1a")),
        (bar((0, 0.0, -0.02), (0, 0.06, -0.05), 0.004, 4), wire),
    ]
    return build(name, parts)


def master_fork(name):
    """Fourchette maitresse: an old chef's grand fork, silver with gold bands.
    Held mid-handle; the tines point along +Z."""
    silver = shade("#d7dce2", 0.05, 15, light="#ffffff")
    gold = shade("#d4a63a", 0.08, 16, light="#f6d77a")
    dark = shade("#5a4630", 0.1, 17)
    parts = [
        (cyl(0.016, 0.019, 0.24, 12, (0, 0, -0.12)), dark),
        (cyl(0.022, 0.022, 0.02, 12, (0, 0, -0.13)), gold),
        (ico(0.02, 1, (0, 0, -0.14)), gold),
        (cyl(0.022, 0.022, 0.025, 12, (0, 0, 0.12)), gold),
        (cyl(0.014, 0.01, 0.10, 10, (0, 0, 0.145)), silver),
        (box((0.07, 0.012, 0.03), (0, 0, 0.24)), silver),
    ]
    for k in range(4):
        x = -0.027 + k * 0.018
        parts.append((cyl(0.0055, 0.002, 0.13, 6, (x, 0, 0.268)), silver))
    return build(name, parts)


def chef_knife(name):
    """Couteau de chef: a black riveted handle and a broad steel blade, spine up
    (+Y). Held mid-handle; the point runs along +Z."""
    steel = shade("#cfd6dd", 0.04, 18, light="#ffffff")
    edge = shade("#eef2f5", 0.02, 19)
    handle = shade("#22252b", 0.06, 20)
    rivet = shade("#b9c0c8", 0.0, 21)
    blade = []
    # The blade: a tapered slab, widest at the heel, curving to the point.
    for k in range(6):
        z = 0.07 + k * 0.042
        width = 0.048 * (1.0 - (k / 6.0) ** 1.6)
        blade.append((box((0.004, width, 0.044), (0, 0.006 + width / 2 - 0.024, z)), steel))
    parts = [
        (box((0.022, 0.026, 0.13), (0, 0, -0.065)), handle),
        (box((0.024, 0.03, 0.012), (0, 0, 0.062)), rivet),
    ]
    for z in (-0.04, 0.0, 0.035):
        parts.append((cyl(0.004, 0.004, 0.024, 6, (-0.012, 0, z), rot_y(90)), rivet))
    parts += blade
    parts.append((box((0.003, 0.004, 0.25), (0, -0.0185, 0.07)), edge))
    return build(name, parts)


def cast_iron_pan(name):
    """Poele en fonte: a heavy black skillet. Held by the end of its handle; the
    pan sits flat at the tip (its face toward +Y, the top), along +Z."""
    iron = shade("#2b2b2e", 0.08, 22, light="#55565c")
    inner = shade("#1a1a1c", 0.05, 23)
    parts = [
        (box((0.03, 0.022, 0.2), (0, 0, -0.04)), iron),
        (cyl(0.016, 0.016, 0.02, 10, (0, 0, -0.06), rot_x(90)), inner),
        # The pan: a shallow cylinder lying in XZ, centred ahead of the handle.
        (cyl(0.13, 0.12, 0.045, 24, (0, -0.02, 0.29), rot_x(-90)), iron),
        (cyl(0.112, 0.112, 0.006, 24, (0, 0.022, 0.29), rot_x(-90)), inner),
        (box((0.035, 0.03, 0.03), (0, -0.005, 0.15)), iron),
    ]
    return build(name, parts)


def pepper_mill(name):
    """Moulin a poivre: a tall turned-wood mill fired like a gun; the grinder
    mouth points along +Z, the crank knob behind, a grip under it."""
    wood = shade("#6b3f22", 0.12, 24, light="#9a6238")
    dark = shade("#2a1a10", 0.08, 25)
    metal = shade("#b8bec6", 0.04, 26)
    pepper = shade("#151210", 0.2, 27)
    parts = [
        (box((0.032, 0.06, 0.09), (0, -0.02, -0.045), rot_x(-12)), dark),
        (cyl(0.04, 0.032, 0.32, 16, (0, 0.06, -0.1)), wood),
        (cyl(0.044, 0.044, 0.025, 16, (0, 0.06, 0.06)), dark),
        (cyl(0.03, 0.022, 0.05, 12, (0, 0.06, 0.22)), metal),
        (bar((0, 0.06, -0.12), (0, 0.06, -0.16), 0.006, 6), metal),
        (bar((0, 0.06, -0.16), (0, 0.12, -0.16), 0.005, 6), metal),
        (ico(0.016, 1, (0, 0.13, -0.16)), dark),
    ]
    for k in range(3):
        parts.append((ico(0.009, 1, (0.012 * (k - 1), 0.06, 0.28 + 0.02 * k)), pepper))
    return build(name, parts)


def kitchen_torch(name):
    """Chalumeau de cuisine: a creme-brulee torch, red head on a butane can,
    pistol grip at the origin; the brass nozzle points along +Z."""
    red = shade("#c8372d", 0.08, 28, light="#f06a55")
    dark = shade("#1d1d22", 0.05, 29)
    can = shade("#e8e2d4", 0.06, 30, light="#ffffff")
    brass = shade("#c99a3b", 0.06, 31, light="#f2cd72")
    blue = shade("#3b82f6", 0.0, 32, light="#93c5fd")
    parts = [
        (box((0.034, 0.065, 0.095), (0, -0.015, -0.05), rot_x(-12)), dark),
        (bar((0, 0.03, 0.055), (0, -0.015, 0.06), 0.006), dark),
        # The butane can lies behind the head, the head above the hand.
        (cyl(0.034, 0.034, 0.17, 16, (0, 0.07, -0.21)), can),
        (cyl(0.036, 0.036, 0.015, 16, (0, 0.07, -0.05)), dark),
        (box((0.05, 0.065, 0.11), (0, 0.04, -0.035)), red),
        (cyl(0.012, 0.012, 0.03, 10, (0.028, 0.08, 0.0), rot_y(90)), dark),
        (cyl(0.016, 0.012, 0.13, 12, (0, 0.075, 0.075)), brass),
        (cyl(0.019, 0.019, 0.012, 12, (0, 0.075, 0.2)), dark),
        (ico(0.008, 1, (0, 0.075, 0.214)), blue),
    ]
    return build(name, parts)


def hand_mixer(name):
    """Batteur electrique: a white motor housing gripped from behind, two wire
    beaters reaching forward along +Z. The grip sits at the origin."""
    shell = shade("#f1f0ea", 0.04, 33, light="#ffffff")
    accent = shade("#2f9e8f", 0.06, 34, light="#5fd3c3")
    dark = shade("#26282d", 0.05, 35)
    wire = shade("#c7ced6", 0.0, 36, light="#ffffff")
    parts = [
        (box((0.036, 0.05, 0.13), (0, 0, -0.07)), dark),
        (box((0.07, 0.09, 0.15), (0, 0.01, 0.055)), shell),
        (box((0.072, 0.02, 0.15), (0, 0.06, 0.055)), accent),
        (box((0.02, 0.012, 0.03), (0, 0.075, 0.1)), dark),
    ]
    for side in (-1, 1):
        x = side * 0.018
        parts.append((bar((x, 0.0, 0.2), (x, 0.0, 0.26), 0.004, 6), wire))
        # Each beater: two wire loops crossed, a balloon at the end of a shaft.
        for turn in (0.0, 90.0):
            loop = []
            for k in range(13):
                a = math.radians(k * 30.0)
                loop.append((x + 0.016 * math.sin(a) * math.cos(math.radians(turn)),
                    0.016 * math.sin(a) * math.sin(math.radians(turn)),
                    0.26 + 0.045 * (1.0 - math.cos(a))))
            parts.append((tube(loop, [0.0022] * len(loop), 5), wire))
    return build(name, parts)


def flour_sack(name):
    """Sac de farine: a plump paper-and-burlap sack tied at the neck. The hand
    holds the knot (the origin); the sack swings along +Z when thrown."""
    burlap = shade("#d8c8a4", 0.14, 37, light="#efe3c4")
    band = shade("#3d6fb6", 0.06, 38)
    string = shade("#8a6b48", 0.08, 39)
    flour = shade("#fbfaf6", 0.04, 40)
    parts = [
        (ico(0.012, 1, (0, 0, -0.01)), string),
        (cyl(0.022, 0.03, 0.04, 12, (0, 0, -0.02)), burlap),
        (torus(0.026, 0.006, 16, 6, (0, 0, 0.015)), string),
        (ico(0.085, 2, (0, 0, 0.13), (0.95, 0.95, 1.3), bumps=0.04, seed=41), burlap),
        (cyl(0.0815, 0.0815, 0.035, 20, (0, 0, 0.112), None, False), band),
        (ico(0.022, 1, (0.02, 0.03, -0.035), (1.2, 1, 0.8)), flour),
    ]
    return build(name, parts)


def meat_cleaver(name):
    """Hachoir de boucher: a short wooden handle and a broad square blade hung
    below it, spine up (+Y). Held mid-handle; the blade runs along +Z."""
    steel = shade("#b8c0c8", 0.05, 42, light="#eef2f5")
    edge = shade("#eef2f5", 0.02, 43)
    wood = shade("#7a4a28", 0.12, 44, light="#a8683a")
    rivet = shade("#d4d8dd", 0.0, 45)
    hole = shade("#2a2d33", 0.0, 46)
    parts = [
        (box((0.026, 0.032, 0.13), (0, 0, -0.07)), wood),
        (box((0.028, 0.034, 0.014), (0, 0, 0.058)), steel),
        (box((0.006, 0.12, 0.2), (0, -0.045, 0.07)), steel),
        (box((0.007, 0.008, 0.2), (0, -0.101, 0.07)), edge),
        (cyl(0.011, 0.011, 0.008, 12, (-0.004, -0.0, 0.235), rot_y(90)), hole),
    ]
    for z in (-0.045, 0.0, 0.035):
        parts.append((cyl(0.0045, 0.0045, 0.03, 6, (-0.015, 0, z), rot_y(90)), rivet))
    return build(name, parts)


def vinegar_bottle(name):
    """Bouteille de vinaigre: a squeeze bottle of pale vinegar with a red pointed
    spout. Held around the body (the origin); the spout points along +Z."""
    liquid = shade("#e9dc9a", 0.06, 47, light="#fff6c8")
    label = shade("#fdfaf0", 0.04, 48)
    stripe = shade("#7a1f2b", 0.06, 49)
    cap = shade("#c62828", 0.06, 50, light="#ef5350")
    parts = [
        (cyl(0.038, 0.038, 0.17, 18, (0, 0, -0.09)), liquid),
        (cyl(0.0395, 0.0395, 0.07, 18, (0, 0, -0.04), None, False), label),
        (cyl(0.0398, 0.0398, 0.012, 18, (0, 0, -0.005), None, False), stripe),
        (cyl(0.038, 0.016, 0.04, 18, (0, 0, 0.08)), liquid),
        (cyl(0.019, 0.019, 0.025, 14, (0, 0, 0.115)), cap),
        (cyl(0.014, 0.003, 0.06, 12, (0, 0, 0.14)), cap),
    ]
    return build(name, parts)


# name: maker. The keys are weapon ids of the master data; the master fork is
# still a key item (master_fork) and becomes a weapon in the weapons project.
PROPS = {
    "restaurant_fork": rolling_pin,
    "egg_launcher": egg_launcher,
    "frost_gun": frost_gun,
    "horseshoe": horseshoe,
    "berimbau": berimbau,
    "master_fork": master_fork,
    "chef_knife": chef_knife,
    "cast_iron_pan": cast_iron_pan,
    "pepper_mill": pepper_mill,
    "kitchen_torch": kitchen_torch,
    "hand_mixer": hand_mixer,
    "flour_sack": flour_sack,
    "meat_cleaver": meat_cleaver,
    "vinegar_bottle": vinegar_bottle,
}


def preview(keys, path):
    """One contact sheet, every piece standing 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.12
    cam_data = bpy.data.cameras.new("Cam")
    cam_data.type = "ORTHO"
    cam_data.ortho_scale = max(x, tallest * 1600 / 700) * 1.1
    cam = bpy.data.objects.new("Cam", cam_data)
    scene.collection.objects.link(cam)
    # A three-quarter view from +Y (the top of a gun), a little from above: the
    # carton, the tank and the gourd sit on that side and must show.
    target = Vector((x / 2, 0, tallest / 2))
    cam.location = target + Vector((1.2, 3.0, 1.0))
    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 = 700
    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)
