#!/usr/bin/env python3
"""Author a small CONTENT-CLEAN synthetic PCSX2 .gs dump for Ch340 byte-exact parser tests.

No copyrighted content — every byte is hand-authored here. Exercises the container header + each
packet type + each GIF mode the parser must handle: PACKED A+D, PACKED PRE-prim, PACKED RGBAQ/XYZ2,
an IMAGE upload, and a VSync frame boundary. Writes captures/gs/synthetic/mini.gs (whitelisted).
"""
import struct, os

OUT = os.path.join(os.path.dirname(__file__), "..", "captures", "gs", "synthetic", "mini.gs")

def giftag(nloop, eop, flg, nreg, regs, pre=0, prim=0):
    lo = (nloop & 0x7FFF) | ((eop & 1) << 15) | ((pre & 1) << 46) | ((prim & 0x7FF) << 47) \
       | ((flg & 3) << 58) | ((nreg & 0xF) << 60)
    return (lo | ((regs & ((1 << 64) - 1)) << 64)).to_bytes(16, "little")

def ad(addr, data):                       # PACKED A+D qword: data[63:0], addr[72:64]
    return ((addr & 0xFF) << 64 | (data & ((1 << 64) - 1))).to_bytes(16, "little")

def packed_rgbaq(r, g, b, a):             # R[7:0] G[39:32] B[71:64] A[103:96]
    return (r | (g << 32) | (b << 64) | (a << 96)).to_bytes(16, "little")

def packed_xyz2(x, y, z, adc=0):          # X[15:0] Y[47:32] Z[95:64] ADC[111]
    return ((x & 0xFFFF) | ((y & 0xFFFF) << 32) | ((z & 0xFFFFFFFF) << 64) | (adc << 111)).to_bytes(16, "little")

def transfer(path, gifdata):
    return bytes([0]) + bytes([path]) + struct.pack("<I", len(gifdata)) + gifdata

def vsync(field):     return bytes([1, field])

# ---- GIF payloads ----
# P1: one A+D write to FRAME_1 (0x4C) = 0x000000000C001807 (an arbitrary recognizable value).
p1 = giftag(1, 0, 0, 1, 0xE) + ad(0x4C, 0x0000_0000_0C00_1807)
# P2: a TRIANGLE (prim type 3, IIP/TME bits) via PRE, PACKED, nreg=2 (RGBAQ=1, XYZ2=5), nloop=3.
prim_val = 3 | (1 << 4) | (1 << 6)        # TRIANGLE + TME + ABE
regs2 = 0x1 | (0x5 << 4)                   # desc0=RGBAQ, desc1=XYZ2
verts = [(100, 50, 0x5000, 0xFF, 0x00, 0x00), (200, 50, 0x5000, 0x00, 0xFF, 0x00),
         (100, 150, 0x5000, 0x00, 0x00, 0xFF)]
p2 = giftag(3, 1, 0, 2, regs2, pre=1, prim=prim_val)
for (x, y, z, r, g, b) in verts:
    p2 += packed_rgbaq(r, g, b, 0x80) + packed_xyz2(x << 4, y << 4, z)
# P3: an IMAGE upload, 2 qwords of dummy texture bytes.
p3 = giftag(2, 1, 2, 0, 0) + bytes(32)

packets = transfer(3, p1) + transfer(3, p2) + transfer(3, p3) + vsync(0)

def build_container(serial, packets, state_size=16):
    state = bytes(state_size); regs_block = bytes(8192)
    hdr = struct.pack("<9I", 9, len(state), 36, len(serial), 0x12345678, 0, 0, 36 + len(serial), 0)
    header_block = hdr + serial
    return (struct.pack("<I", 0xFFFFFFFF) + struct.pack("<I", len(header_block)) + header_block
            + state + regs_block + packets)

def write(path, data):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "wb") as f: f.write(data)
    print(f"wrote {path} ({len(data)} bytes)")

# mini.gs — mixed fixture for the byte-exact PARSER test (P2's triangle is TME=1 = unsupported).
write(OUT, build_container(b"SYNTH001", packets))

# mini_supported.gs — two NON-textured opaque triangles for the TRANSLATOR test (TME=0, ABE=0, flat).
OUT2 = os.path.join(os.path.dirname(__file__), "..", "captures", "gs", "synthetic", "mini_supported.gs")
def flat_tri(prim_val, tris):
    out = b""
    for (vs, rgb) in tris:                 # vs = 3x(x,y,z) ; flat color rgb
        out += giftag(3, 1, 0, 2, 0x1 | (0x5 << 4), pre=1, prim=prim_val)
        for (x, y, z) in vs:
            out += packed_rgbaq(rgb[0], rgb[1], rgb[2], 0x80) + packed_xyz2(x << 4, y << 4, z)
    return out
sup = transfer(3, giftag(1, 0, 0, 1, 0xE) + ad(0x18, 0x0000_0000_0000_0000))   # XYOFFSET_1 = 0
sup += transfer(3, flat_tri(3, [                                               # PRIM=TRIANGLE only (TME=0,ABE=0)
    ([(10, 10, 0x6000), (50, 10, 0x6000), (10, 50, 0x6000)], (0xFF, 0x00, 0x00)),   # red
    ([(50, 50, 0x5000), (14, 50, 0x5000), (50, 14, 0x5000)], (0x00, 0x00, 0xFF)),   # blue
]))
sup += vsync(0)
write(OUT2, build_container(b"SYNTHSUP", sup))

# mini_st.gs — Ch342 regression: a PACKED ST write. The GS routes the ST qword's Q lane (bits [95:64])
# to RGBAQ.Q; the parser must expose it as info["q_stq"]. Guards the easy-to-reintroduce drop-Q bug.
OUT3 = os.path.join(os.path.dirname(__file__), "..", "captures", "gs", "synthetic", "mini_st.gs")
def packed_st(s, t, q):                  # PACKED ST: S[31:0], T[63:32], Q[95:64]
    return (s | (t << 32) | (q << 64)).to_bytes(16, "little")
p_st = giftag(1, 1, 0, 1, 0x2) + packed_st(0x11111111, 0x22222222, 0x33333333)   # descriptor 0x2 = ST
write(OUT3, build_container(b"SYNTHST0", transfer(3, p_st) + vsync(0)))
