#!/usr/bin/env python3
"""Exercise all832 native card lookups and native3bpp decoder, headlessly.

Uses an isolated reset-vector probe ROM, never installed. The production ROM
has only pointer/image/checksum changes, with no new code/RAM/save format.
"""
from pathlib import Path
import argparse,hashlib,json,os,struct,subprocess
from player_cards import ASSETS,identity,rosters

ap=argparse.ArgumentParser(description=__doc__)
ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path)
a=ap.parse_args();out=a.output.resolve();out.mkdir(parents=True,exist_ok=True)
here=Path(__file__).resolve().parent;data=a.rom.read_bytes()
manifest=json.loads((ASSETS/'manifest.json').read_text());expected=[]
photos={};pos=0x2b0000
for key,r in sorted(manifest['players'].items(),key=lambda item:item[1]['nhl_id']):
    photos[key]=r['pointer'] if r['kind']=='original' else pos
    if r['kind']!='original':pos+=874
for team,row in enumerate(rosters(data)):
    for slot,name in enumerate(row):
        r=manifest['players'][identity(name)];ptr=photos[identity(name)]
        assert hashlib.sha256(data[ptr:ptr+874]).hexdigest()==r['sha256']
        body=data[ptr+10:ptr+874];native=bytearray()
        for i in range(0,len(body),3):
            bits=int.from_bytes(body[i:i+3],'big')
            native+=sum((((bits>>(21-3*x))&7)+5)<<(28-4*x) for x in range(8)).to_bytes(4,'big')
        expected.append(dict(team=team,slot=slot,name=name,nhl_id=r['nhl_id'],kind=r['kind'],pointer=ptr,decoded_sha256=hashlib.sha256(native).hexdigest()))
(out/'expected.json').write_text(json.dumps(expected,indent=2)+'\n')
source=out/'native-probe.s';source.write_text('''    .text
    .globl _start
_start:
    moveq #0,%d1
team:
    moveq #0,%d0
slot:
    jsr 0xFAE26
    move.l %a0,0xFFFFF0B8
    lea 8(%a0),%a2
    jsr 0xFE98A
    move.w %d0,0xFFFFF0B4
    move.w %d1,0xFFFFF0B6
    move.w #1,0xFFFFF0B0
wait:
    tst.w 0xFFFFF0B0
    bne.s wait
    addq.w #1,%d0
    cmpi.w #26,%d0
    bcs.s slot
    addq.w #1,%d1
    cmpi.w #32,%d1
    bcs.s team
    move.w #2,0xFFFFF0B0
done:
    bra.s done
''')
subprocess.run([str(here.parent/'m68kasm.sh'),str(source),'2FEA00',str(out/'native-probe.bin')],check=True,stdout=subprocess.DEVNULL)
code=(out/'native-probe.bin').read_bytes();d=bytearray(data)
assert d[0x2fea00:0x2fea00+len(code)]==b'\xff'*len(code)
d[4:8]=struct.pack('>I',0x2fea00);d[0x2fea00:0x2fea00+len(code)]=code
probe=out/'probe.bin';probe.write_bytes(d)
hook=out/'native-hook.py';hook.write_text('''from pathlib import Path
import hashlib,json
p=Path(outdir);expected=json.loads((p/'expected.json').read_text());checked=[]
def rw(a):return (wram[ram(a)]<<8)|wram[ram(a+1)]
def raw(a,n):return bytes(wram[ram(a+i)] for i in range(n))
def test_tick(f):
    if rw(0xf0b0)==1:
        r=expected[len(checked)];team=rw(0xf0b6);slot=rw(0xf0b4);ptr=int.from_bytes(raw(0xf0b8,4),'big')
        assert [team,slot,ptr]==[r['team'],r['slot'],r['pointer']],(r,team,slot,hex(ptr))
        assert rw(0xda1e)==36
        digest=hashlib.sha256(raw(0xda20,1152)).hexdigest()
        assert digest==r['decoded_sha256'],('native decoder disagrees',r,digest)
        checked.append(r)
        wram[ram(0xf0b0)]=0;wram[ram(0xf0b1)]=0
    if rw(0xf0b0)==2 and len(checked)==832:
        (p/'results.json').write_text(json.dumps(dict(native_lookups=len(checked),native_decodes=len(checked),players=checked),indent=2)+'\\n')
        held.clear()
''')
cmd=out/'commands.txt';cmd.write_text('12000 exit\n')
env=dict(os.environ,GPGX_DEV='513',GPGX_HOOK=str(hook))
with (out/'log.txt').open('w') as log:
    subprocess.run(['python3',str(here/'test_core.py'),str(probe),str(cmd),str(out)],env=env,check=True,stdout=log,stderr=log)
results=json.loads((out/'results.json').read_text())
assert results['native_lookups']==results['native_decodes']==832
print('PASS832 native portrait lookups and832 native3bpp decodes')
