"""Native SRAM/ABI/delta regression for real roster goalies, including index2 and playoffs."""
from pathlib import Path
import argparse,struct,subprocess,os,json,hashlib
ap=argparse.ArgumentParser();ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);a=ap.parse_args();here=Path(__file__).resolve().parent;tools=here.parent;out=a.output.resolve();out.mkdir(parents=True,exist_ok=True);rom=a.rom.read_bytes()
folder=tools/'season';command='m68k-elf-as -m68000 -o /tmp/s.o season.s && m68k-elf-ld -Ttext=0x2F8000 -e _start -o /tmp/s.elf /tmp/s.o && m68k-elf-nm /tmp/s.elf';r=subprocess.run(['docker','run','--rm','-v',str(folder)+':/w','-w','/w','--entrypoint','sh','ghcr.io/stephane-d/sgdk','-c',command],capture_output=True,text=True,check=True);sym={l.split()[2]:int(l.split()[0],16) for l in r.stdout.splitlines() if len(l.split())==3};assert rom[sym['gkreal']:sym['gkreal']+6]==bytes.fromhex('4ef9002d8c80'),'wrong accounting build/source'
variants={
 'starters':([(2,9,800),(0,0,0),(0,0,0)],0,[(3,11,800),(0,0,0)],0),
 'reserves':([(0,0,0),(2,9,800),(0,0,0)],1,[(0,0,0),(3,11,800)],1),
 'third':([(0,0,0),(0,0,0),(2,9,800)],2,[(0,0,0),(3,11,800)],1),
 'split':([(1,3,100),(1,6,700),(0,0,0)],1,[(0,2,100),(3,9,700)],1),
 'pulled-third':([(1,3,100),(0,0,0),(1,6,700)],0xff02,[(0,0,0),(3,11,800)],1),
 'no-shots-cameo':([(0,0,100),(2,9,700),(0,0,0)],1,[(0,0,0),(3,11,800)],1)}
reports=[]
for playoff in [False,True]:
 for name,(home,hs,away,aws) in variants.items():
  p=out/(('playoff-' if playoff else 'league-')+name);p.mkdir(exist_ok=True);body=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xFFF800,%sp',' lea 0xFFFFB000,%a0',' move.w #0x4800/4-1,%d0','zero: clr.l (%a0)+',' dbra %d0,zero',' move.b #1,0xA130F1',' lea 0x200001,%a0',' move.w #8191,%d0','nvzero: clr.b (%a0)',' addq.l #2,%a0',' dbra %d0,nvzero',f' move.b #{int(playoff)},0xFFF0D2',' move.b #10,0xFFF6C0',' move.b #5,0xFFF6C1',' move.w #10,0xFFC330',' move.w #5,0xFFC332',' move.w #3,0xFFC6DA',' move.w #2,0xFFCA3E']
  for side,(gks,sel,count) in enumerate([(home,hs,3),(away,aws,2)]):
   base=0xffffc6ce+side*0x364;body += [f' move.w #{sel},0x{base+0x26:X}',f' move.b #1,0x{base+0x16a:X}']
   for i,(ga,sa,ice) in enumerate(gks):body += [f' move.b #{ga},0x{base+0xb4+i:X}',f' move.b #{sa},0x{base+0xe8+i:X}',f' move.w #{ice},0x{base+0x136+2*i:X}']
   for i,(g,ass,shots) in enumerate([(2,1,6),(1,1,3)] if side==0 else [(2,1,5),(0,1,2)]):
    slot=count+i;body += [f' move.b #{g},0x{base+0xb4+slot:X}',f' move.b #{ass},0x{base+0xce+slot:X}',f' move.b #{shots},0x{base+0xe8+slot:X}']
  for reg in range(8):body.append(f' move.l #0x{0x10203040+0x1010101*reg:X},%d{reg}')
  for reg in range(7):body.append(f' movea.l #0x{0xffe100+32*reg:X},%a{reg}')
  body += [f' jsr 0x{sym["addreal"]:X}',f' jsr 0x{sym["gkreal"]:X}',' movem.l %d0-%d7/%a0-%a6,0xFF4000',' move.w #0x4143,0xFF3FFC','done: bra.s done'];(p/'driver.s').write_text('\n'.join(body)+'\n');subprocess.run([str(tools/'m68kasm.sh'),str(p/'driver.s'),'2D9000',str(p/'driver.bin')],check=True,stdout=subprocess.DEVNULL);driver=(p/'driver.bin').read_bytes();assert len(driver)<0x3000;b=bytearray(rom);assert b[0x2d9000:0x2d9000+len(driver)]==b'\xff'*len(driver);b[0x2d9000:0x2d9000+len(driver)]=driver;struct.pack_into('>I',b,4,0x2d9000);(p/'rom.bin').write_bytes(b);(p/'battery.srm').write_bytes(bytes(65536));(p/'commands.txt').write_text('500 dump ff3ffc 2 done\n500 dump ff4000 60 regs\n500 dump fff0f0 1 delta\n501 exit\n');env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env.update(GPGX_DEV='513',GPGX_SRM=str(p/'battery.srm'))
  with (p/'log.txt').open('w') as log:subprocess.run(['nice','-n','19','python3',str(here/'test_core.py'),str(p/'rom.bin'),str(p/'commands.txt'),str(p)],env=env,check=True,stdout=log,stderr=log,timeout=60)
  rr={l.split()[1]:bytes.fromhex(' '.join(l.split()[2:])) for l in (p/'commands.out').read_text().splitlines()};assert rr['done']==b'AC',(p,'native driver incomplete');regs=struct.unpack('>15I',rr['regs'])
  for reg in range(8):assert regs[reg]==0x10203040+0x1010101*reg,(p,'D'+str(reg),hex(regs[reg]))
  for reg in range(7):assert regs[8+reg]==0xffe100+32*reg,(p,'A'+str(reg),hex(regs[8+reg]))
  nv=(p/'battery.srm').read_bytes()[1:16384:2];assert rr['delta'][0]==sum(nv)%256,(p,'checksum delta',rr['delta'].hex(),sum(nv)%256)
  for side,(t,gks,sel,count) in enumerate([(10,home,hs,3),(5,away,aws,2)]):
   player_off=(0x10+100*side) if playoff else 0x1220+100*t;keeper_off=(0x6b4+15*side) if playoff else 0xe20+15*t
   for i,(ga,sa,ice) in enumerate(gks):
    rec=nv[keeper_off+5*i:keeper_off+5*i+5];played=int(bool(ice or ga or sa or i==(sel&255)));want=bytes([played,int(side==0 and i==(sel&255)),ga>>4,((ga&15)<<4)|(sa>>8),sa&255]);assert rec==want,(p,t,i,rec.hex(),want.hex())
   for i in range(1,count):assert nv[player_off+4*(i-1):player_off+4*i]==bytes(4),(p,'keeper in skater records',i)
   for i,stats in enumerate([(2,1,6),(1,1,3)] if side==0 else [(2,1,5),(0,1,2)]):assert struct.unpack_from('>BBH',nv,player_off+4*(count+i-1))==stats,(p,'actual skater stats')
  reports.append(dict(case=p.name,passed=True,register_abi=True,individual_keeper_stats=True,keeper_excluded_from_skaters=True,checksum_delta=True));print('PASS',p.name,flush=True)
(out/'results.json').write_text(json.dumps(dict(status='passed',rom_sha256=hashlib.sha256(rom).hexdigest(),cases=reports),indent=2)+'\n')
