#!/usr/bin/env python3
"""Exact native defender targets, guards, pair ownership and register/RNG contract."""
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);ap.add_argument('--baseline',type=Path,required=True);a=ap.parse_args();here=Path(__file__).resolve().parent;out=a.output.resolve();out.mkdir(parents=True,exist_ok=True)
def run(rom,name,s,arrival_callback=False):
 p=out/name;p.mkdir(exist_ok=True);team=s.get('team',0);orient=s.get('orient',1);sign=s.get('sign',1);own=team*6;opp=(1-team)*6;actor=own+s.get('local',3);carrier=s.get('owner',opp+2);role=s.get('role',2 if actor==own+3 else 1)
 body=[' .text',' .globl _start','_start:',' move.w #0x2700,%sr',' movea.l #0xFFF800,%sp',' lea 0xFFB04A,%a0',' move.w #383,%d0','clear: clr.l (%a0)+',' dbra %d0,clear']
 def word(i,off,v):body.append(f' move.w #{v},0x{0xffb04a+128*i+off:X}')
 def byte(i,off,v):body.append(f' move.b #{v},0x{0xffb04a+128*i+off:X}')
 for i in range(12):word(i,0x34,-1);word(i,0x52,i)
 for i,x,y,r in [(own+3,sign*60,-190,2),(own+4,-sign*60,-210,1),(opp+2,sign*104,-140,4)]:
  word(i,0,x);word(i,0x14,y*orient);word(i,0x34,r)
 if s.get('partner_absent'):word(own+3 if actor==own+4 else own+4,0x34,-1)
 if s.get('partner_human'):byte(own+3 if actor==own+4 else own+4,0x62,8)
 for n,t in enumerate(s.get('threats',[dict(x=-sign*40,y=-216)])):
  i=opp+n;word(i,0,t['x']);word(i,0x14,t['y']*orient);word(i,0x34,t.get('role',5));byte(i,0x63,t.get('unavailable',0))
 word(actor,0x34,role);word(actor,0x44,65);word(actor,0x46,-220*orient);byte(actor,0x62,(128 if orient==1 else 0)+(64 if team else 0)+s.get('flags',0));byte(actor,0x63,s.get('unavailable',0))
 body += [f' lea 0x{0xffb04a+128*actor:X},%a3',f' move.w #{s.get("difficulty",1)},0xFFF07A',f' move.w #{carrier},0xFFB7AA',f' move.w #{s.get("px",sign*104)},0xFFB74A',f' move.w #{s.get("depth",-140)*orient},0xFFB75E']
 for addr,v in [(0xc2ea,s.get('whistle',0)),(0xc2ec,s.get('view',0)),(0xc2ee,s.get('offside',0)),(0xc2f2,s.get('penalty',0)),(0xc2fa,s.get('special',0))]:body.append(f' move.b #{v},0x{0xff0000+addr:X}')
 body += [' move.l #0x12345678,0xFFD066',' move.l #0x11223344,%d2',' move.l #0x22334455,%d3',' move.l #0x33445566,%d4',' move.l #0x44556677,%d5',' move.l #0x55667788,%d6',' move.l #0x66778899,%d7',' movea.l #0xFF1234,%a1',f' lea 0x{0xffc6ce+team*0x364:X},%a2',' movea.l #0xFFCC00,%a6',' jsr 0xCF82',' lea 0xFF4000,%a4',' move.w %d0,(%a4)+',' move.w %d1,(%a4)+',' movem.l %d2-%d7/%a0-%a3,(%a4)',' move.l %a6,0xFF4030',' move.l #0x47415041,0xFF3FF0','end: bra.s end']
 (p/'driver.s').write_text('\n'.join(body)+'\n');subprocess.run([str(here.parent/'m68kasm.sh'),str(p/'driver.s'),'2DA000',str(p/'code.bin')],check=True,stdout=subprocess.DEVNULL)
 b=bytearray(rom.read_bytes());c=(p/'code.bin').read_bytes();assert len(c)<0x2000 and b[0x2da000:0x2da000+len(c)]==b'\xff'*len(c);b[0x2da000:0x2da000+len(c)]=c;struct.pack_into('>I',b,4,0x2da000)
 for pc in (0x103c8,0xeace):b[pc:pc+2]=bytes.fromhex('4e75')
 (p/'driver.bin').write_bytes(b);(p/'commands.txt').write_text('500 dump ff3ff0 4 done\n500 dump ff4000 44 result\n500 dump ff4030 4 a6\n500 dump ffd066 4 rng\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)
 d={line.split()[1]:bytes.fromhex(' '.join(line.split()[2:])) for line in (p/'commands.out').read_text().splitlines()};assert d['done']==b'GAPA' and d['rng']==bytes.fromhex('12345678') and d['a6']==bytes.fromhex('00ffcc00')
 regs=list(struct.unpack_from('>10I',d['result'],4));assert regs==[0x11223344,0x22334455,0x33445566,0x44556677,0x55667788,0x66778899,(0x228040 if arrival_callback else 0xe594),0xff1234,0xffc6ce+team*0x364,0xffb04a+128*actor],(name,regs)
 return list(struct.unpack_from('>2h',d['result']))
cases=[]
for team in (0,1):
 for orient in (-1,1):
  for sign in (-1,1):
   for local in (3,4):cases.append((f'pair-{team}-{orient}-{sign}-{local}',dict(team=team,orient=orient,sign=sign,local=local),[sign*91,-160*orient] if local==3 else [-sign*30,-228*orient]))
for name,change in [('Normal',dict(difficulty=0)),('Easy',dict(difficulty=2)),('human',dict(flags=8)),('locked',dict(flags=32)),('unavailable',dict(unavailable=4)),('whistle',dict(whistle=1)),('replay',dict(view=128)),('offside',dict(offside=128)),('penalty',dict(penalty=4)),('shootout',dict(special=1)),('penalty-shot',dict(special=4)),('teammate',dict(owner=0)),('loose',dict(owner=65535)),('invalid',dict(owner=12)),('upice',dict(depth=-79)),('behind-goal',dict(depth=-265)),('wide-left',dict(px=-145)),('wide-right',dict(px=145)),('goalie',dict(role=0)),('forward',dict(role=4))]:cases.append(('guard-'+name,change,None))
cases += [('single-back',dict(partner_absent=True),[91,-160]),('human-partner',dict(local=4,partner_human=True),[-30,-228]),('no-receiver',dict(local=4,threats=[]),[-30,-172]),('boxed-receiver',dict(local=4,threats=[dict(x=-40,y=-216,role=-1)]),[-30,-172]),('unavailable-receiver',dict(local=4,threats=[dict(x=-40,y=-216,unavailable=4)]),[-30,-172]),('dangerous-receiver',dict(local=4,threats=[dict(x=-80,y=-150),dict(x=24,y=-224)]),[18,-236]),('depth-limit',dict(depth=-264),[91,-238]),('depth-entry',dict(depth=-80),[91,-100])]
def pair(case):
 name,s,expected=case;old=run(a.baseline,'old-'+name,s);new=run(a.rom,'new-'+name,s,expected is not None and s.get('local',3)==3 and a.rom.read_bytes()[0x228040:0x228044]==bytes.fromhex('0c400009'))
 assert new==(old if expected is None else expected),(name,old,new,expected)
 return dict(case=name,spec=s,baseline=old,fixed=new,guard=expected is None)
with ThreadPoolExecutor(max_workers=3) as pool:results=list(pool.map(pair,cases))
(out/'results.json').write_text(json.dumps(results,indent=2)+'\n');print('PASS',len(results),'paired native defensive-pair/guard/register/RNG cases')

if a.rom.read_bytes()[0x228040:0x228044]==bytes.fromhex('0c400009'):
 from defensive_brake_checks import verify
 verify(a.rom,here,out/'defensive-brake')
