#!/usr/bin/env python3
# Provenance generator for the DERIVED glTF test fixtures in this directory.
#
# This script is NOT part of the build or test path. The fixtures it produces are
# committed as static files and read directly by the Rust tests (mirroring the
# existing STL fixture tests that read public/models/*.stl). It exists only so the
# derived binary/minified fixtures have clear, reproducible provenance.
#
# ── Canonical base fixtures (downloaded once, committed as-is) ────────────────
#   box.glb, box.gltf, box.bin   <- Khronos glTF-Sample-Assets "Box"
#   box_textured.glb             <- Khronos glTF-Sample-Assets "BoxTextured"
# Fetched from:
#   https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Box/glTF-Binary/Box.glb
#   https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Box/glTF/Box.gltf
#   https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Box/glTF/Box0.bin   (saved as box.bin; box.gltf buffer uri patched Box0.bin -> box.bin)
#   https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/BoxTextured/glTF-Binary/BoxTextured.glb
#
# ── Derived fixtures this script (re)generates ────────────────────────────────
#   box_translated.glb   Box + node x-translation(10) composed into the existing
#                        rotation matrix, AND its single primitive duplicated ->
#                        2 primitives. Covers AC.3a: non-identity, nonzero-
#                        determinant transform (translation makes baking
#                        observable via the AABB x-shift) + multi-primitive
#                        flattening with per-primitive index offsets.
#   box_embedded.gltf    box.gltf with its external buffer inlined as a base64
#                        data URL. Covers AC.3b (embedded data-URL buffer).
#   box_textured.gltf    box_textured.glb decomposed into separate companions:
#     + box_textured.bin   the geometry buffer (external .bin)
#     + box_textured.png   the embedded image extracted to an external PNG
#                        Covers AC.3d (external texture companion, PNG decoded
#                        then discarded) and multi-companion gathering.
#
# Re-run anytime with:  python3 public/models/gen-derived-fixtures.py

import base64
import json
import struct


def read_glb(path):
    data = open(path, "rb").read()
    assert data[:4] == b"glTF", path
    total = struct.unpack("<I", data[8:12])[0]
    off = 12
    jlen = struct.unpack("<I", data[off : off + 4])[0]
    off += 8
    jstr = data[off : off + jlen]
    off += jlen
    blen = struct.unpack("<I", data[off : off + 4])[0]
    off += 8
    binb = data[off : off + blen]
    return json.loads(jstr), binb


def write_glb(path, doc, binb):
    jstr = json.dumps(doc, separators=(",", ":")).encode()
    while len(jstr) % 4:
        jstr += b" "
    b = bytearray(binb)
    while len(b) % 4:
        b.append(0)
    total = 12 + 8 + len(jstr) + 8 + len(b)
    out = bytearray()
    out += b"glTF" + struct.pack("<I", 2) + struct.pack("<I", total)
    out += struct.pack("<I", len(jstr)) + b"JSON" + jstr
    out += struct.pack("<I", len(b)) + b"BIN\x00" + b
    open(path, "wb").write(out)


def main():
    # 1) box_translated.glb: keep Box rotation, compose translation[10,0,0],
    #    duplicate the primitive.
    doc, binb = read_glb("box.glb")
    node = doc["nodes"][0]
    matrix = node["matrix"]
    matrix[12] = 10.0  # column-major: element 12 is the x-translation
    node["matrix"] = matrix
    prim = doc["meshes"][0]["primitives"][0]
    doc["meshes"][0]["primitives"] = [prim, dict(prim)]
    write_glb("box_translated.glb", doc, binb)

    # 2) box_embedded.gltf: inline box.bin as a base64 data URL.
    doc = json.load(open("box.gltf"))
    binb = open("box.bin", "rb").read()
    doc["buffers"][0]["uri"] = (
        "data:application/octet-stream;base64," + base64.b64encode(binb).decode()
    )
    json.dump(doc, open("box_embedded.gltf", "w"), separators=(",", ":"))

    # 3) box_textured.gltf + box_textured.bin + box_textured.png: decompose
    #    box_textured.glb into external companions.
    doc, binb = read_glb("box_textured.glb")
    doc["buffers"][0]["uri"] = "box_textured.bin"
    img = doc["images"][0]
    if "bufferView" in img:
        view = doc["bufferViews"][img["bufferView"]]
        png = binb[view["byteOffset"] : view["byteOffset"] + view["byteLength"]]
        open("box_textured.png", "wb").write(png)
        img.clear()
        img["uri"] = "box_textured.png"
    elif img.get("uri", "").startswith("data:"):
        raw = base64.b64decode(img["uri"].partition(",")[2])
        open("box_textured.png", "wb").write(raw)
        img["uri"] = "box_textured.png"
    open("box_textured.bin", "wb").write(binb)
    json.dump(doc, open("box_textured.gltf", "w"), separators=(",", ":"))


if __name__ == "__main__":
    main()
