#!/usr/bin/env python3
"""Requested native menu defaults: actual renderer text, fresh formats and saved-choice compatibility."""
from pathlib import Path
import argparse,json,os,struct,subprocess,hashlib
ap=argparse.ArgumentParser();ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path);ap.add_argument('--seed',type=Path,required=True);ap.add_argument('--icing-default',type=int,choices=(0,1),required=True);a=ap.parse_args();h=Path(__file__).resolve().parent;o=a.output.resolve();o.mkdir(parents=True,exist_ok=True)
(o/'probe.s').write_text(''' .text
 .globl _start
_start:
 cmpi.w #14,%d7
 blt.s original
 cmpi.w #24,%d7
 bgt.s original
 move.w %sr,-(%sp)
 movem.l %d0/%a0,-(%sp)
 move.w %d7,%d0
 subi.w #14,%d0
 add.w %d0,%d0
 lea 0xFFF080,%a0
 move.l %a1,(%a0,%d0.w)
 movem.l (%sp)+,%d0/%a0
 move.w (%sp)+,%sr
original:
 jmp 0x11BA4
''')
subprocess.run([str(h.parent/'m68kasm.sh'),str(o/'probe.s'),'2FEA00',str(o/'probe.bin')],check=True,stdout=subprocess.DEVNULL);r=bytearray(a.rom.read_bytes());code=(o/'probe.bin').read_bytes();assert set(r[0x2fea00:0x2fea00+len(code)])=={255} and r[0xf7bf4:0xf7bfa]==bytes.fromhex('4ef900011ba4');r[0x2fea00:0x2fea00+len(code)]=code;r[0xf7bf4:0xf7bfa]=bytes.fromhex('4ef9002fea00');(o/'observed.bin').write_bytes(r)
(o/'hook.py').write_text('''from pathlib import Path
import json
p=Path(outdir);s=json.loads((p/'spec.json').read_text());rows=[]
def rw(a):return (wram[ram(a)]<<8)|wram[ram(a+1)]
def test_tick(f):
 if str(f) not in s['checks']:return
 want=s['checks'][str(f)];row=dict(frame=f,slot=rw(0xf06a),home=rw(0xd04c),difficulty=rw(0xf07a),replay=rw(0xf0de),crit=rw(0xf0ec),icing=rw(0xf0ee),lines=rw(0xd072),fighting=rw(0xf004),records=rw(0xd054))
 for key,v in want.items():assert row[key]==v,(s['name'],row,want)
 if s.get('render'):
  texts=[]
  for i in range(6):
   ptr=(rw(0xf080+4*i)<<16)|rw(0xf082+4*i);assert 0x2de900<=ptr<0x2def00,('value renderer missing',i,hex(ptr))
   assert romdata[ptr:ptr+2]==b'\\0\\x16';texts.append(romdata[ptr+2:ptr+22].decode('ascii').strip())
  assert texts==['Auto','Off','Hard','Off','Off','Off' if s['icing'] else 'On'],('native menu text',texts)
  row['rendered_values']=texts
 rows.append(row);(p/'results.json').write_text(json.dumps(rows,indent=2)+'\\n')
''')
def pack(b):
 for off in range(0,len(b),8192):b[off+0x1fff]=sum(b[off:off+0x1ffe])&255;b[off+0x1ffe]=b[off+0x1fff]^255
 r=bytearray(len(b)*2);r[1::2]=b;return r
reports=[]
def run(name,dev,battery,cmd,checks,render=False):
 p=o/name;p.mkdir(exist_ok=True);(p/'battery.srm').write_bytes(battery);(p/'commands.txt').write_text('\n'.join(cmd)+'\n');(p/'spec.json').write_text(json.dumps(dict(name=name,checks=checks,render=render,icing=a.icing_default)));env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env.update(GPGX_DEV=dev,GPGX_SRM=str(p/'battery.srm'),GPGX_HOOK=str(o/'hook.py'))
 with (p/'log.txt').open('w') as log:subprocess.run(['python3',str(h/'test_core.py'),str(o/'observed.bin'),str(p/'commands.txt'),str(p)],env=env,stdout=log,stderr=log,check=True,timeout=35)
 reports.append(dict(name=name,observations=json.loads((p/'results.json').read_text())));return (p/'battery.srm').read_bytes()
for dev in ('513','1281'):
 wanted=dict(home=10,difficulty=1,replay=1,crit=1,icing=a.icing_default,lines=2,fighting=0,records=1)
 cmd=[f'{900+70*i} press 1 Down 5' for i in range(12)]+['1850 snap','1901 exit'];run('fresh-visible-'+dev,dev,bytes(65536),cmd,{1800:wanted},True)
 cmd=[f'{900+70*i} press 1 Down 5' for i in range(9)]+['1600 press 1 Left 5']+[f'{1700+70*i} press 1 Up 5' for i in range(9)]+['2400 press 1 Right 5']+[f'{2500+70*i} press 1 Down 5' for i in range(9)]+['3200 press 1 Left 5']+[f'{3300+70*i} press 1 Up 5' for i in range(9)]+['4000 press 1 Right 5','4201 exit']
 run('new-formats-'+dev,dev,bytes(65536),cmd,{1650:dict(difficulty=0,slot=1),2450:dict(difficulty=1,slot=2),3250:dict(difficulty=0,slot=2),4100:dict(difficulty=1,slot=3)})
 # Previously saved Normal/On choices win over the new fresh defaults.
 b=bytearray(a.seed.read_bytes()[1:16384:2]);b[0xe20:0x1ffe]=bytes(0x11de);struct.pack_into('>4s6H',b,0x1000,b'SESN',1,11,1,84,0,3);struct.pack_into('>4s4H',b,0x1ea0,b'S3SV',1,1,7,1);b[0x1ef0:0x1ef6]=b'PC\x01\xfe\0\xff';b[0x1ff6:0x1ffd]=bytes([0x54,0,0,2,2,0,0x5a]);logical=bytearray(b)+b''.join([b]*3);logical[0x2e18:0x2e20]=b'RP\0\xffCT\0\xff'
 run('saved-choices-'+dev,dev,pack(logical),['1301 exit'],{1200:dict(difficulty=0,replay=0,crit=0,icing=0)})
(o/'results.json').write_text(json.dumps(dict(rom_sha256=hashlib.sha256(a.rom.read_bytes()).hexdigest(),icing_default=a.icing_default,cases=reports),indent=2)+'\n');print('PASS',len(reports),'native fresh defaults/render/new formats/saved-choice cases')
