#!/usr/bin/env python3
"""retroDE_ps2 — Ch341 Brick 2: faithful texture downscale + boot-fixture generation.

Reads the LOCAL extracted cube texture (256x256 PSMCT32, from gs_texture.py --extract), box-downsamples
it to 64x64 PSMCT32 (a declared, reported linear transform — NOT a GS-feature approximation; the same
class as the Ch340 viewport fit), and emits the 64x64 texels as a $readmemh .mem the boot setup payload
uploads to a VRAM-resident TBP. Also runs Codex's gate: hash the dump's N texture uploads and report
whether they are byte-identical (so one preload suffices). Only aggregate facts/hashes are committable;
the texel .mem stays LOCAL (derived from the dump).

Declared transform: source 256x256 -> 64x64 (factor 4) => translator must scale UVs by /4.

Usage:
  gs_bake_texture.py <dump.gs[.xz|.zst]> [--blob extracted/tex0_blob.bin] [--out outdir] [--report r.txt]
"""
import sys, os, struct, hashlib
sys.path.insert(0, os.path.dirname(__file__))
import gs_parse, gs_texture

SRC=256; DST=64; FACTOR=SRC//DST   # 4

def upload_identity_gate(path):
    """Hash every IMAGE upload payload; report if all byte-identical."""
    d = gs_parse.read_dump_bytes(path)
    h, events = gs_parse.parse_dump(path)
    hashes=[]
    for e in events:
        if e.kind=="IMAGE":
            blob = d[e.byte_off:e.byte_off+e.info.get("bytes",0)]
            hashes.append(hashlib.sha256(blob).hexdigest())
    return hashes

def downsample_psmct32(blob):
    """256x256 ABGR words -> 64x64 ABGR words, box filter (average each 4x4 source block per channel)."""
    assert len(blob) == SRC*SRC*4, f"expected {SRC*SRC*4} bytes, got {len(blob)}"
    src = struct.unpack(f"<{SRC*SRC}I", blob)
    out = []
    for oy in range(DST):
        for ox in range(DST):
            ar=ag=ab=aa=0
            for dy in range(FACTOR):
                for dx in range(FACTOR):
                    w = src[(oy*FACTOR+dy)*SRC + (ox*FACTOR+dx)]
                    ar += w & 0xFF; ag += (w>>8)&0xFF; ab += (w>>16)&0xFF; aa += (w>>24)&0xFF
            n = FACTOR*FACTOR
            out.append((ar//n) | ((ag//n)<<8) | ((ab//n)<<16) | ((aa//n)<<24))
    return out   # 64*64 ABGR words

def main(argv):
    if len(argv)<2: print(__doc__); return 2
    path=argv[1]
    def opt(n,d=None): return argv[argv.index(n)+1] if n in argv else d
    blobp = opt("--blob", os.path.join(os.path.dirname(path),"extracted","tex0_blob.bin"))
    outdir = opt("--out", os.path.join(os.path.dirname(path),"extracted"))

    R=[f"# Ch341 Brick 2 texture bake  (source {os.path.basename(path)}; aggregate facts/hashes only)"]
    # --- gate: are the uploads byte-identical? (auditable: list every distinct hash + its count) ---
    hh = upload_identity_gate(path)
    from collections import Counter
    cnt = Counter(hh)
    R.append(f"texture uploads: {len(hh)}  distinct payloads: {len(cnt)}  "
             + ("ALL BYTE-IDENTICAL -> one preload suffices" if len(cnt)==1 else "DIFFER -> translator must not let a subsegment cross a re-upload to its TBP"))
    for i,(hsh,c) in enumerate(sorted(cnt.items(), key=lambda x:-x[1])):
        R.append(f"  upload payload #{i}: sha256[0..15]={hsh[:16]}  count={c}")

    if not os.path.exists(blobp):
        R.append(f"!! no extracted blob at {blobp} (run gs_texture.py --extract first)")
        print("\n".join(R)); return 1
    blob = open(blobp,"rb").read()
    src_hash = hashlib.sha256(blob).hexdigest()
    out = downsample_psmct32(blob)
    out_bytes = struct.pack(f"<{len(out)}I", *out)
    out_hash = hashlib.sha256(out_bytes).hexdigest()
    R.append("")
    R.append(f"downsample: {SRC}x{SRC} PSMCT32 -> {DST}x{DST} PSMCT32  (box filter /{FACTOR}; UV scale /{FACTOR})")
    R.append(f"  src sha256[0..15]={src_hash[:16]}  dst sha256[0..15]={out_hash[:16]}  dst_bytes={len(out_bytes)} (= {DST}*{DST}*4)")

    os.makedirs(outdir, exist_ok=True)
    # boot IMAGE upload packs 4 ABGR words / 128-bit qword -> $readmemh as 64-bit? bake.py packs the
    # IMAGE payload as 64-bit words (lane pairs). Emit one 32-bit ABGR texel per line (the bake helper
    # will pack); also emit a raw .bin for reuse. LOCAL only.
    memp = os.path.join(outdir, "cube_tex_64.mem")
    with open(memp,"w") as f:
        for w in out: f.write(f"{w & 0xFFFFFFFF:08x}\n")
    binp = os.path.join(outdir, "cube_tex_64.bin")
    open(binp,"wb").write(out_bytes)
    R.append(f"  wrote LOCAL fixture: {os.path.basename(memp)} ({len(out)} texels) + {os.path.basename(binp)}")

    report="\n".join(R)+"\n"
    print(report)
    if opt("--report"): open(opt("--report"),"w").write(report); print(f"[wrote report -> {opt('--report')}]")
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))
