"""Native gameplay measurement of actual accepted-pass plans and catch releases."""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse, hashlib, json, os, 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)
ap.add_argument('--state',type=Path,required=True)
ap.add_argument('--tools',type=Path,default=Path(__file__).resolve().parent)
a=ap.parse_args();h=a.tools.resolve();o=a.output.resolve();o.mkdir(parents=True,exist_ok=True)
source=(h/'ai_playbook_gameplay_hook.py').read_text()
source=source.replace('put(own+5,0,-246);put(opp+5,0,246)',"put(own+5,0,-246);put(opp+5,0,246)")
source=source.replace('px,depth=110,180;put(carrier,px,depth)',"px,depth=64*s.get('sign',1),220;put(carrier,px,depth)")
source=source.replace("ww(0xf060,0);ww(0xf002,0)","ww(0xf060,0);ww(0xf002,0);ww(0xf08c,0);ww(0xf08e,0)")
source=source.replace('put(own,0,220)',"put(own,-48*s.get('sign',1),240)")
source+='''
base_tick=test_tick
def test_tick(f):
 base_tick(f)
 if f>108:
  rows[-1]['pass_plan_pending']=rw(0xf08c)
  rows[-1]['pass_plan_ttl']=rw(0xf08e)
 if f==708:(p/'trajectory.json').write_text(json.dumps(rows,indent=2)+'\\n')
'''
(o/'hook.py').write_text(source)
specs=[dict(kind='finish',team=t,direction=d,sign=x,difficulty=1) for t in (0,1) for d in (-1,1) for x in (-1,1)]
specs += [dict(kind='finish',team=0,direction=d,sign=1,difficulty=level) for d in (-1,1) for level in (0,2)]
def run(profile,rom,index,s):
 p=o/(profile+'-'+str(index));p.mkdir(exist_ok=True);(p/'spec.json').write_text(json.dumps(s));(p/'commands.txt').write_text(f'100 load {a.state.resolve()}\n148 snap\n188 snap\n248 snap\n708 snap\n709 exit\n')
 env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env.update(GPGX_DEV='513',GPGX_HOOK=str(o/'hook.py'))
 with (p/'log.txt').open('w') as log:subprocess.run(['nice','-n','19','python3',str(h/'test_core.py'),str(rom.resolve()),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=60)
 rr=json.loads((p/'trajectory.json').read_text());assert len(rr)==600 and 0<rr[-1]['clock']<rr[0]['clock'] and all(x['sprite_bytes']<=0x280 for x in rr)
 assert all(x['controller_teams']==rr[0]['controller_teams'] for x in rr[:120]),('controller ownership changed',s)
 pending=[x for x in rr if x['pass_plan_pending']]
 if s['difficulty']!=1:assert not pending,('plan incorrectly active on Normal/Easy',s)
 releases=[]
 for before,now in zip(rr,rr[1:]):
  target=before['cpu_pending']-1
  if 0<=target<12 and now['cpu_pending']==0 and now['animations'][target]==0x7fc and (now['shooter']==target or now['owner']==target):releases.append(dict(frame=now['frame'],actor=target))
 return dict(planned_targets=sorted({x['pass_plan_pending']-1 for x in pending}),plan_frames=[x['frame'] for x in pending],native_buffered_release_frames=releases,final_score=rr[-1]['score'])
def pair(item):
 i,s=item;return dict(spec=s,baseline=run('old',a.baseline,i,s),candidate=run('new',a.rom,i,s))
with ThreadPoolExecutor(max_workers=3) as pool:rows=list(pool.map(pair,enumerate(specs)))
summary=dict(scenarios=len(rows),hard_cases_with_new_plan=sum(bool(x['candidate']['plan_frames']) for x in rows if x['spec']['difficulty']==1),hard_cases_with_native_release=sum(bool(x['candidate']['native_buffered_release_frames']) for x in rows if x['spec']['difficulty']==1),normal_easy_excluded=4)
(o/'results.json').write_text(json.dumps(dict(rom_sha256=hashlib.sha256(a.rom.read_bytes()).hexdigest(),baseline_sha256=hashlib.sha256(a.baseline.read_bytes()).hexdigest(),summary=summary,cases=rows),indent=2)+'\n')
print(json.dumps(summary),flush=True)

early=lambda name:sum(any(r['frame']<=60 for r in x[name]['native_buffered_release_frames']) for x in rows if x['spec']['difficulty']==1)
assert early('candidate')>early('baseline'),('actual early cross-catch release did not improve',early('candidate'),early('baseline'))
for i,spec in enumerate(specs):
 if spec['difficulty']!=1:assert (o/('old-'+str(i))/'trajectory.json').read_bytes()==(o/('new-'+str(i))/'trajectory.json').read_bytes(),('Normal/Easy trajectories changed',spec)
assert any(x['candidate']['plan_frames'] and any(r['actor'] in x['candidate']['planned_targets'] for r in x['candidate']['native_buffered_release_frames']) for x in rows if x['spec']['difficulty']==1),'no actual accepted-pass plan led to native catch release'
print('PASS actual centered-keeper native cross pass/catch/release improvement',early('baseline'),'->',early('candidate'),'with exact Normal/Easy trajectories')
