#!/usr/bin/env python3
"""Native event/ratings regression: consume post-whistle stats without momentum."""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse,json,os,struct,subprocess
ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);a=ap.parse_args();h=Path(__file__).resolve().parent;o=a.output.resolve();o.mkdir(parents=True,exist_ok=True)
# hs,hc,as,ac,stopped,pause,faceoff,replay,expected M. Clock stays fixed.
steps=[
 ('neutral',0,0,0,0,0,0,0,0,0),
 ('live-shot',1,0,0,0,0,0,0,0,200),
 ('whistle-home-check',1,1,0,0,1,0,0,0,200),
 ('whistle-away-check',1,1,0,1,1,0,0,0,200),
 ('resume-no-banked-credit',1,1,0,1,0,0,0,0,200),
 ('live-check',1,2,0,1,0,0,0,0,350),
 ('paused-shot',2,2,0,1,0,1,0,0,350),
 ('foreign-view-shot',2,2,1,1,0,128,0,0,350),
 ('faceoff-check',2,2,1,2,0,0,4,0,350),
 ('highlight-check',2,3,1,2,16,0,0,0,350),
 ('gameover-shot',2,3,2,2,64,0,0,0,350),
 ('replay-shot',3,3,2,2,0,0,0,0xa502,350),
 ('stars-shot',4,3,2,2,0,0,0,0xa507,350),
 ('resume-again-no-credit',4,3,2,2,0,0,0,0,350),
 ('live-away-shot',4,3,3,2,0,0,0,0,150),
 ('live-away-check',4,3,3,3,0,0,0,0,0),
 ('many-blocked-events',44,43,33,33,1,0,0,0,0),
 ('resume-many-no-credit',44,43,33,33,0,0,0,0,0),
 ('live-after-many',45,43,33,33,0,0,0,0,200),
 ('new-game-reset',0,0,0,0,0,0,0,0,0)]
stride=10+8+192

def run(diff):
 p=o/f'd{diff}';p.mkdir(exist_ok=True)
 b=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xFFF800,%sp',' lea 0xFFF700,%a0',' move.w #127,%d0','clear: clr.l (%a0)+',' dbra %d0,clear',' move.w #0x4352,0xFFF700',f' move.w #{diff},0xFFF07A',' move.w #290,0xFFF70C',' lea 0xFF4000,%a4']
 b += [' move.w #1,0xFFC328',' clr.w 0xFFC32A',' clr.w 0xFFC32C',' clr.w 0xFFC32E']
 for i in range(12):
  obj=0xffb04a+128*i
  b +=[f' move.w #1,0x{obj+0x34:X}',f' move.b #{(64 if i>=6 else 0)+(8 if i==0 else 0)},0x{obj+0x62:X}',f' move.b #1,0x{0xfff71c+i:X}']
  for off in range(16):b +=[f' move.b #20,0x{obj+0x66+off:X}',f' move.b #20,0x{0xfff740+16*i+off:X}']
 for n,(name,hs,hc,aw,ac,stop,pause,faceoff,replay,mom) in enumerate(steps):
  for addr,val in [(0xffc6ce,hs),(0xffc6de,hc),(0xffca32,aw),(0xffca42,ac),(0xffc468,290),(0xfff738,replay)]:b.append(f' move.w #{val},0x{addr:X}')
  for addr,val in [(0xffc2ea,stop),(0xffc2ec,pause),(0xffc2f2,faceoff)]:b.append(f' move.b #{val},0x{addr:X}')
  b+=[' jsr 0x2E0000']
  for addr in [0xfff702,0xfff70e,0xfff72a,0xfff72c,0xfff72e,0xfff704,0xfff706,0xfff708,0xfff70a]:b.append(f' move.w 0x{addr:X},(%a4)+')
  b += [' lea 0xFFFFB0B0,%a0',' moveq #11,%d5',f'actors{n}: moveq #15,%d0',f'ratings{n}: move.b (%a0)+,(%a4)+',f' dbra %d0,ratings{n}',' adda.w #112,%a0',f' dbra %d5,actors{n}']
 b+=[' move.l #0x57484D54,0xFF3FF0','end: bra.s end'];(p/'driver.s').write_text('\n'.join(b)+'\n')
 subprocess.run([str(h.parent/'m68kasm.sh'),str(p/'driver.s'),'2D5000',str(p/'code.bin')],check=True,stdout=subprocess.DEVNULL)
 data=bytearray(a.rom.read_bytes());code=(p/'code.bin').read_bytes();assert len(code)<=0x2000, len(code)
 assert data[0x2d5000:0x2d5000+len(code)]==b'\xff'*len(code);data[0x2d5000:0x2d5000+len(code)]=code;struct.pack_into('>I',data,4,0x2d5000)
 for addr in [0x2e4004,0x2e5000,0x2ed000]:data[addr:addr+2]=bytes.fromhex('4e75')
 (p/'driver.bin').write_bytes(data);(p/'commands.txt').write_text(f'500 dump ff3ff0 4 done\n500 dump ff4000 {len(steps)*stride} result\n501 exit\n')
 env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env['GPGX_DEV']='513'
 with (p/'log.txt').open('w') as log:subprocess.run(['nice','-n','19','python3',str(h/'test_core.py'),str(p/'driver.bin'),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=30)
 rows={l.split()[1]:bytes.fromhex(' '.join(l.split()[2:])) for l in (p/'commands.out').read_text().splitlines()};assert rows['done']==b'WHMT';raw=rows['result'];reports=[]
 for n,(name,hs,hc,aw,ac,stop,pause,faceoff,replay,mom) in enumerate(steps):
  r=raw[n*stride:(n+1)*stride];values=struct.unpack('>9h',r[:18]);pct=(0,0) if diff==2 else (int(mom/100),-int(mom/100));sides=(1000,-1000) if diff==2 else (mom,-mom)
  reports.append(dict(step=name,values=values,ratings=r[18:].hex()))
  (p/'observations.json').write_text(json.dumps(reports,indent=2)+'\n')
  assert values[:5]==(mom,*pct,*sides),(diff,name,'momentum/percent changed',values,mom,pct)
  assert values[5:]==(hs,hc,aw,ac),(diff,name,'blocked counters not consumed',values)
  if stop or pause or faceoff or replay:
   assert r[18:]==raw[(n-1)*stride+18:n*stride],(diff,name,'ratings changed after whistle')
 return dict(difficulty=diff,steps=reports)
with ThreadPoolExecutor(max_workers=3) as pool:result=list(pool.map(run,[0,1,2]))
(o/'results.json').write_text(json.dumps(dict(profiles=result,observations=len(steps)*3,live_scoring=True,whistle_credit=False,deferred_credit=False,stats_consumed=True,all12_ratings_checked=True),indent=2)+'\n');print('PASS 60 native momentum observations: both teams, whistle/pause/faceoff/replay/Stars, no banked credit, live resume, all12 ratings; Normal/Hard/Easy')
