#!/usr/bin/env python3
"""Native momentum/events/rating regression: Hard == Normal; Easy fixed skills."""
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();here=Path(__file__).resolve().parent;out=a.output.resolve();out.mkdir(parents=True,exist_ok=True)
events=[(0,0,0,0,290),(2,0,0,0,290),(2,4,0,0,290),(2,4,8,0,290),(2,4,8,0,289),(2,4,8,5,289),(0,0,0,0,289)]
def run(spec):
 diff,layout=spec;p=out/f'd{diff}-h{layout}';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']
 for pad in range(4):b.append(f' move.w #{(1 if layout&1 else 2 if layout&2 else 0) if pad==0 else (2 if layout&2 else 0) if pad==1 else 0},0x{0xffc328+2*pad:X}')
 for i in range(12):
  o=0xffb04a+128*i;human=(i==0 and layout&1) or (i==6 and layout&2)
  b +=[f' move.w #1,0x{o+0x34:X}',f' move.b #{(64 if i>=6 else 0)+(8 if human else 0)},0x{o+0x62:X}',f' move.b #1,0x{0xfff71c+i:X}']
  for off in range(16):b +=[f' move.b #20,0x{o+0x66+off:X}',f' move.b #20,0x{0xfff740+16*i+off:X}']
 for hs,hc,aw,ac,clock in events:
  for addr,val in [(0xffc6ce,hs),(0xffc6de,hc),(0xffca32,aw),(0xffca42,ac),(0xffc468,clock)]:b.append(f' move.w #{val},0x{addr:X}')
  b+=[' jsr 0x2E0000']
  for addr in [0xfff702,0xfff70e,0xfff72a,0xfff72c,0xfff72e]:b.append(f' move.w 0x{addr:X},(%a4)+')
  for addr in [0xffb04a+0x69,0xffb34a+0x69]:b +=[' clr.w (%a4)',f' move.b 0x{addr:X},1(%a4)',' addq.l #2,%a4']
 b+=[' move.l #0x4D4F4D54,0xFF3FF0','end: bra.s end'];(p/'driver.s').write_text('\n'.join(b)+'\n');subprocess.run([str(here.parent/'m68kasm.sh'),str(p/'driver.s'),'2D9000',str(p/'code.bin')],check=True,stdout=subprocess.DEVNULL)
 data=bytearray(a.rom.read_bytes());c=(p/'code.bin').read_bytes();assert data[0x2d9000:0x2d9000+len(c)]==b'\xff'*len(c);data[0x2d9000:0x2d9000+len(c)]=c;struct.pack_into('>I',data,4,0x2d9000)
 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(events)*14} 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(here/'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'MOMT';return dict(difficulty=diff,humans=layout,rows=list(struct.iter_unpack('>7h',rows['result'])))
with ThreadPoolExecutor(max_workers=3) as pool:reports=list(pool.map(run,[(d,h) for h in [0,1,2,3] for d in [0,1,2]]))
for h in [0,1,2,3]:
 n,hard,easy=[next(x for x in reports if x['difficulty']==d and x['humans']==h) for d in [0,1,2]];assert n['rows']==hard['rows'],('Hard/Normal mismatch',h,n,hard)
 assert [r[1:3] for r in hard['rows']][:4]==[(0,0),(4,-4),(10,-10),(-6,6)],hard
 assert all(r[1:3]==(0,0) and r[3:5]==(1000 if h&1 else -1000,1000 if h&2 else -1000) and r[5:7]==(30 if h&1 else 6,30 if h&2 else 6) for r in easy['rows']),easy
 assert hard['rows'][-1][0:5]==(0,0,0,0,0),hard
 assert hard['rows'][2][5:7]==(22,18),hard
(out/'results.json').write_text(json.dumps(reports,indent=2)+'\n');print('PASS 12 native crowd profiles /',len(events)*12,'event/rating observations; Hard=Normal, Easy fixed, reset/decay/caps')
