"""Inspect the pinned WaterBottle glTF and its ORM PNG; no rendering or network calls.

Requires Python 3 and Pillow. Download the two inputs from the URLs in SOURCES,
then run: python inspect-orm.py WaterBottle.gltf WaterBottle_occlusionRoughnessMetallic.png
Standard output is the reproducible material/channel report. No files are modified.
"""
import argparse
import hashlib
import json
from pathlib import Path
from PIL import Image

REVISION = "edc7c9e67c639d230715049ee31f9a96a6babbbe"
BASE = "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/" + REVISION + "/Models/WaterBottle/"
SOURCES = {
    "gltf": BASE + "glTF/WaterBottle.gltf",
    "texture": BASE + "glTF/WaterBottle_occlusionRoughnessMetallic.png",
    "license": BASE + "LICENSE.md",
    "specification": "https://raw.githubusercontent.com/KhronosGroup/glTF/8e798b02d254cea97659a333cfcb20875b62bdd4/specification/2.0/Specification.adoc",
    "factor_schema": "https://raw.githubusercontent.com/KhronosGroup/glTF/8e798b02d254cea97659a333cfcb20875b62bdd4/specification/2.0/schema/material.pbrMetallicRoughness.schema.json",
    "uv_schema": "https://raw.githubusercontent.com/KhronosGroup/glTF/8e798b02d254cea97659a333cfcb20875b62bdd4/specification/2.0/schema/textureInfo.schema.json",
    "occlusion_schema": "https://raw.githubusercontent.com/KhronosGroup/glTF/8e798b02d254cea97659a333cfcb20875b62bdd4/specification/2.0/schema/material.occlusionTextureInfo.schema.json",
}

def git_blob_sha(data):
    return hashlib.sha1(b"blob " + str(len(data)).encode("ascii") + b"\0" + data).hexdigest()

def inspect(gltf_path, texture_path):
    doc_bytes = Path(gltf_path).read_bytes()
    texture_bytes = Path(texture_path).read_bytes()
    doc = json.loads(doc_bytes)
    expected = {"gltf": "9f23f3add06376ba56326a9e3a99223a304c60a5", "texture": "0999b4fd02a582785c39055e86fed916eb5df486"}
    observed = {"gltf": git_blob_sha(doc_bytes), "texture": git_blob_sha(texture_bytes)}
    if observed != expected:
        raise ValueError("Input bytes do not match the pinned sample; use the exact source URLs.")
    material = doc["materials"][0]
    pbr = material["pbrMetallicRoughness"]
    mr = pbr["metallicRoughnessTexture"]
    ao = material["occlusionTexture"]
    bindings = []
    for role, info in [("metallicRoughnessTexture", mr), ("occlusionTexture", ao)]:
        texture = doc["textures"][info["index"]]
        bindings.append({"role": role, "texture_index": info["index"], "image_index": texture["source"], "image_uri": doc["images"][texture["source"]]["uri"], "texCoord_explicit": info.get("texCoord"), "texCoord_effective": info.get("texCoord", 0)})
    with Image.open(texture_path) as image:
        if image.mode != "RGB":
            raise ValueError("Expected the original RGB texture")
        points = [(0, 0), (512, 512), (1024, 1024), (1536, 1536)]
        samples = []
        for x, y in points:
            r, g, b = image.getpixel((x, y))
            samples.append({"x": x, "y": y, "RGB_8bit": [r, g, b], "occlusion_R": round(r / 255, 6), "roughness_G": round(g / 255, 6), "metallic_B": round(b / 255, 6)})
        texture_record = {"width": image.width, "height": image.height, "mode": image.mode, "file_bytes": len(texture_bytes), "sha256": hashlib.sha256(texture_bytes).hexdigest(), "channel_extrema": dict(zip("RGB", image.getextrema())), "samples": samples}
    return {
        "title": "WaterBottle ORM material and texel inspection",
        "source_revision": REVISION,
        "sources": SOURCES,
        "asset_credit": "WaterBottle sample: Microsoft; hosted by Khronos Group. Model-associated files are CC0-1.0; repository metadata has separate terms. No source texture or mesh is redistributed in this report.",
        "git_blob_sha1": observed,
        "material_name": material["name"],
        "bindings": bindings,
        "factors": {"metallicFactor_explicit": pbr.get("metallicFactor"), "metallicFactor_effective": pbr.get("metallicFactor", 1), "roughnessFactor_explicit": pbr.get("roughnessFactor"), "roughnessFactor_effective": pbr.get("roughnessFactor", 1), "occlusionStrength_explicit": ao.get("strength"), "occlusionStrength_effective": ao.get("strength", 1)},
        "primitive_texcoord_attributes": sorted(k for k in doc["meshes"][0]["primitives"][0]["attributes"] if k.startswith("TEXCOORD_")),
        "texture": texture_record,
        "synthetic_factor_example": {"not_a_sample_measurement": True, "input_RGB_linear": [0.2, 0.6, 0.8], "occlusionStrength": 0.5, "roughnessFactor": 0.5, "metallicFactor": 0.25, "effective_occlusion": round(1 + 0.5 * (0.2 - 1), 6), "effective_roughness": 0.6 * 0.5, "effective_metallic": 0.8 * 0.25},
        "method": {"pixel_coordinates": "Integer coordinates from top-left; x increases right, y increases down. Raw stored RGB samples are read without color conversion, filtering, or UV lookup.", "normalized_values": "Each 8-bit channel divided by 255; six decimal places in report. Effective glTF defaults resolved from specification.", "reproduction": "Download the exact pinned glTF and PNG, install Pillow if needed, then run the companion inspect-orm.py with those two local input files. The script rejects different source bytes."},
        "limits": ["File/material inspection, not a rendered scene, importer validation, or engine test.", "Individual atlas texels are not identified as visible surface points; no UV, filtering, mip, or lighting evaluation was performed.", "Channel extrema include the whole image, including atlas padding; they are not a visual-quality score.", "No performance, compression, memory, or generated-asset quality measurement is provided."],
    }

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("gltf")
    parser.add_argument("texture")
    args = parser.parse_args()
    print(json.dumps(inspect(args.gltf, args.texture), indent=2) + "\n", end="")
