#!/usr/bin/env python3
"""Native a2touchpuck rule: both ends/teams, actual event buffer, exemptions and exact On ABI."""
from pathlib import Path
import argparse,json,struct,subprocess,os,hashlib
ap=argparse.ArgumentParser();ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);ap.add_argument('--baseline',type=Path,required=True);a=ap.parse_args();h=Path(__file__).resolve().parent;o=a.output.resolve();o.mkdir(parents=True,exist_ok=True)
cases=[]
for team in (0,1):
 for up in (False,True):
  for kind in ('icing','goalie','attacker','not-crossed','no-candidate','short-handed','even-strength'):
   for off in (0,1):cases.append(dict(team=team,up=up,kind=kind,off=off))
results=[]
for batch in range((len(cases)+7)//8):
 ss=cases[batch*8:batch*8+8];p=o/f'batch-{batch}';p.mkdir(exist_ok=True);body=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xfff800,%sp']
 for i,s in enumerate(ss):
  team=s['team'];up=s['up'];kind=s['kind'];toucher=team*6 if kind=='attacker' else (1-team)*6;g=0xffb04a+128*toucher;offender=team*6;flags=(64 if toucher//6 else 0)+(128 if (up if kind=='attacker' else not up) else 0);armed=kind not in ('no-candidate','short-handed','even-strength');iflags=(4+(1 if kind!='not-crossed' else 0)+(2 if up else 0)) if armed else 0
  body += [' lea 0xffb000,%a0',' move.w #0x1600/4-1,%d0',f'clear{i}: clr.l (%a0)+',f' dbra %d0,clear{i}',f' move.w #{s["off"]},0xfff0ee',f' move.b #{iflags},0xffbf80',f' move.b #{offender},0xffbf81',f' move.w #{toucher},0x{g+0x52:x}',f' move.w #{0 if kind=="goalie" else 4},0x{g+0x34:x}',f' move.b #{flags},0x{g+0x62:x}',f' move.w #{-100 if flags&128 else 100},0xffb75e',' move.w #5,0xffc6f2',' move.w #5,0xffca56']
  if kind=='short-handed':body += [f' move.w #4,0xff{0xca56 if toucher//6 else 0xc6f2:x}']
  for j in range(8):body.append(f' move.l #0x112233{j:02x},%d{j}')
  for j in range(7):body.append(f' movea.l #0xff{0x1000+j*256:x},%a{j}')
  at=0xff4000+i*192;body += [f' movea.l #0x{g:x},%a2',' movea.l #0xffb24a,%a3',' move.w #0x271f,%sr',' jsr 0x1013e',f' movem.l %d0-%d7/%a0-%a6,0x{at:x}',f' move.w %sr,0x{at+60:x}',f' move.l %sp,0x{at+64:x}',f' move.w 0xffbf80,0x{at+68:x}',f' lea 0xffc3a4,%a0',f' lea 0x{at+72:x},%a1',' moveq #15,%d0',f'copy{i}: move.l (%a0)+,(%a1)+',f' dbra %d0,copy{i}']
 body += [' move.l #0x49434535,0xff3ff0','done: bra.s done'];(p/'driver.s').write_text('\n'.join(body)+'\n');subprocess.run([str(h.parent/'m68kasm.sh'),str(p/'driver.s'),'2D9000',str(p/'code.bin')],check=True,stdout=subprocess.DEVNULL);code=(p/'code.bin').read_bytes();assert len(code)<0x3000
 profiles={}
 for profile,rom in [('baseline',a.baseline),('current',a.rom)]:
  q=p/profile;q.mkdir(exist_ok=True);r=bytearray(rom.read_bytes());assert set(r[0x2d9000:0x2d9000+len(code)])=={255};r[0x2d9000:0x2d9000+len(code)]=code;struct.pack_into('>I',r,4,0x2d9000);(q/'driver.bin').write_bytes(r);(q/'commands.txt').write_text(f'500 dump ff3ff0 4 done\n500 dump ff4000 {192*len(ss)} result\n501 exit\n');env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')}
  with (q/'log.txt').open('w') as log:subprocess.run(['python3',str(h/'test_core.py'),str(q/'driver.bin'),str(q/'commands.txt'),str(q)],env=env,stdout=log,stderr=log,check=True,timeout=30)
  raw={line.split()[1]:bytes.fromhex(' '.join(line.split()[2:])) for line in (q/'commands.out').read_text().splitlines()};assert raw['done']==b'ICE5';profiles[profile]=raw['result']
 for i,s in enumerate(ss):
  raw=profiles['current'][192*i:192*(i+1)];old=profiles['baseline'][192*i:192*(i+1)];events=[int.from_bytes(raw[j:j+2],'big') for j in range(72,136,2)];icing=any(e>>8==12 for e in events);expected=s['kind']=='icing' and s['off']==0;assert icing==expected,('wrong native icing award',s,events)
  assert int.from_bytes(raw[64:68],'big')==0xfff800,('stack',s)
  assert int.from_bytes(raw[40:44],'big')==0xffb04a+128*(s['team']*6 if s['kind']=='attacker' else (1-s['team'])*6) and int.from_bytes(raw[44:48],'big')==0xffb24a,('native actor pointers',s)
  if s['off']==0:assert raw==old,('Icing On native ABI/event/flag mismatch',s,raw.hex(),old.hex())
  if s['kind'] in ('short-handed','even-strength'):assert bool(raw[68]&4)==(s['kind']=='even-strength'),('native manpower exemption',s,raw[68])
  results.append(dict(**s,icing_awarded=icing,events=events,native_on_exact=s['off']==0))
(o/'results.json').write_text(json.dumps(dict(rom_sha256=hashlib.sha256(a.rom.read_bytes()).hexdigest(),cases=results,scope='Synthetic native rule fixtures, not independent matches: calls real a2touchpuck and native AddPenalty; compares native registers/CCR/stack/event buffer for Icing On.'),indent=2)+'\n');print('PASS',len(results),'native touch-icing/end/team/exemption cases; On ABI exact to r4')
