#!/usr/bin/env python3
"""Capture original in-flight preferences policy and complete PRF_ALRT rasters.
Physical sound/display callbacks are boundaries in the policy test. All option
arithmetic, resource painters and selected preference writes execute original
instructions; the controls child has separate reference coverage.
"""
import json,struct,zlib,random
from pathlib import Path
from capture_cockpit_dialogs import DialogNative
from native_scene import NativeScene
from native_reference import ROOT
from unicorn import UC_HOOK_CODE
def setup():
 n=DialogNative();scene=NativeScene(n);scene.dismiss_pause()
 p=n.call(0x47578,0,0);return n,scene,p
def policies(output):
 records=[];rows=[]
 for case in range(24):
  n,scene,p=setup();rng=random.Random(0x47578+case)
  initial=bytes([case%2,case//2%2,case%3,case//4%2,case*11%256,255-case*9%256,case*13%256,3,1,0,0,1,1,1,2,0,0])
  for a in (0xbd562,0xbd574,0xbd586):n.cpu.mem_write(a,initial)
  n.paths['data/prefs.cfg']=initial;n.put(0x956bf,'B',case%2)
  n.put(0xbd598,'17I',*[0x1024000+16*i for i in range(17)])
  n.cpu.hook_add(UC_HOOK_CODE,lambda *a:n.return_value(0,1),begin=0x1024000,end=0x102410f)
  packet=n.allocate_fixture(64);states=[];index=0
  actions=[(i,0)for i in range(7)]+[(i,1)for i in range(7)]+[(rng.randrange(7),rng.randrange(2))for _ in range(48)]+[(8,0)]
  def snapshot():
   return b''.join(bytes(n.cpu.mem_read(a,17))for a in (0xbd562,0xbd574,0xbd586))+n.paths['data/prefs.cfg']
  def poll(*unused):
   if n.get(0x94d78,'I')[0]:states.append(snapshot())
   n.return_value(packet,0)
  def dispatch(*unused):
   nonlocal index
   action,reverse=actions[index];index+=1;n.put(p+0x340,'i',action);n.put(p+0x2e7,'B',reverse);n.return_value(1,2)
  n.cpu.hook_add(UC_HOOK_CODE,poll,begin=0x4cb14,end=0x4cb14)
  n.cpu.hook_add(UC_HOOK_CODE,dispatch,begin=0x48076,end=0x48076)
  n.call(0x47a88,p,instruction_limit=100000000);states.append(snapshot())
  assert index==len(actions) and len(states)==len(actions)+1
  records.append(initial+struct.pack('<BH',case%2,len(actions))+states[0]+b''.join(bytes(a)+s for a,s in zip(actions,states[1:])))
  rows.append(dict(case=case,initial=list(initial),radio_available=bool(case%2),actions=actions,states=[list(s)for s in states]))
 output.write_bytes(b'ESPRFP01'+struct.pack('<I',len(records))+b''.join(records))
 (ROOT/'work/research/cockpits/preferences/policy.json').write_text(json.dumps(dict(scope=__doc__,cases=rows),separators=(',',':'))+'\n')
 print(sum(len(r['actions'])for r in rows),'original in-flight preference actions',flush=True)
def rasters(output):
 n,s,p=setup();n.call(0x45f00,p);n.call(n.get(0x9df50,'I')[0])
 # The host presents the mouse separately from these dialog artwork pixels.
 for a in n.get(0x9df4c,'2I'):n.cpu.hook_add(UC_HOOK_CODE,lambda *args:n.return_value(0,0),begin=a,end=a)
 records=[];rows=[]
 for i in range(96):
  values=bytes([i%2,i//2%2,i%3,i//4%2,[0,1,32,64,127,128,224,255][i%8],(i*33)%256,255-(i*37)%256])
  n.cpu.mem_write(0xbd562,values)
  faces=bytes([i%3 if j==i%9 else 0 for j in range(9)])
  for j,face in enumerate(faces):
   child=n.get(p+0x28d+j*4,'I')[0];n.put(child+0x17,'h',face)
  # Each complete repaint initializes the bar's previous position, as it is
  # freshly constructed when the real dialog opens.
  for j in range(3):
   bar=n.get(p+0x334+j*4,'I')[0];n.put(bar+0x1c,'i',n.get(bar+0x14,'i')[0])
  s.vga.clear(37);n.call(0x47e8d,p)
  image=s.hardware_page();raw=image.tobytes();crc=zlib.crc32(raw)
  records.append(values+faces+struct.pack('<I',crc))
  rows.append(dict(case=i,values=list(values),faces=list(faces),crc32=f'{crc:08x}'))
  if i<9:
   image.save(ROOT/f'work/research/cockpits/preferences/{i:02d}.png')
   (ROOT/f'work/research/cockpits/preferences/{i:02d}.raw').write_bytes(raw)
 output.write_bytes(b'ESPRFR01'+struct.pack('<I',len(records))+b''.join(records))
 (ROOT/'work/research/cockpits/preferences/rasters.json').write_text(json.dumps(dict(scope=__doc__,frames=rows),indent=2)+'\n')
 print(len(rows),'original in-flight preferences rasters',flush=True)
if __name__=='__main__':
 import argparse
 p=argparse.ArgumentParser();p.add_argument('--output',type=Path,required=True);p.add_argument('--rasters',action='store_true');a=p.parse_args()
 (ROOT/'work/research/cockpits/preferences').mkdir(exist_ok=True)
 (rasters if a.rasters else policies)(a.output)
