"""Auto Replays/Crit preferences plus a per-season Icing row; all default On."""
import struct
ORIGIN=0x2DE000
LABELS=0x2DE800
VALUES=0x2DE900
COUNTS=0x2DEF00
ADAPTER=0x2DEF20

def legacy_install(data,assemble,patch):
    code=assemble('replaymenu',ORIGIN);assert len(code)<0x800
    patch(ORIGIN,b'\xff'*len(code),code)
    labels=bytes(data[0x20915A:0x20915A+160])+b'\x00\x10Auto Replays \x00'
    assert len(labels)==176
    patch(LABELS,b'\xff'*len(labels),labels)
    # Keep the old relative string offsets valid by copying their whole span,
    # inserting the new offset word and adjusting existing offsets by two.
    old=bytes(data[0x2091FE:0x209742]);values=bytearray(old[:20]+b'\x00\x00'+old[20:])
    for i in range(10):struct.pack_into('>H',values,2*i,struct.unpack_from('>H',old,2*i)[0]+2)
    struct.pack_into('>H',values,20,len(values))
    values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'On',b'Off'))
    if with_refinements:
        ai=struct.unpack_from('>H',values,46)[0]
        momentum=struct.unpack_from('>H',values,50)[0]
        glass=struct.unpack_from('>H',values,24)[0] # existing On/Off order
        struct.pack_into('>4H',values,44,ai,momentum,unit_pointer,glass)
    assert VALUES+len(values)<COUNTS
    patch(VALUES,b'\xff'*len(values),values)
    for oldaddr,newaddr in [(0x2095AE,COUNTS),(0x2095C2,ADAPTER)]:
        counts=bytes(data[oldaddr:oldaddr+20])+b'\x00\x02'
        patch(newaddr,b'\xff'*len(counts),counts)
    for operand,oldaddr,newaddr in [(0xF807C,0x20915A,LABELS),(0x209638,0x2091FE,VALUES),(0x2FE27A,0x2091FE,VALUES),(0xF79AC,0x2095AE,COUNTS),(0xF79BA,0x2095C2,ADAPTER)]:
        patch(operand,struct.pack('>I',oldaddr),struct.pack('>I',newaddr))
    # Native shootout value special case compares the string-group pointer.
    patch(0xF7BBA,struct.pack('>I',0x209438),struct.pack('>I',VALUES+0x209438-0x2091FE+2))
    patch(0x2FE220,bytes.fromhex('32070c41000c'),bytes.fromhex('4ef9002de000'))
    patch(0xF777A,bytes.fromhex('0014'),bytes.fromhex('0016'))
    for addr in (0xF7758,0xF7762,0xF7C08):patch(addr,bytes.fromhex('0004'),bytes.fromhex('0005'))


def install(data,assemble,patch,with_crit_option=True,legacy_crit_value_draw=False,with_icing=True,with_height=False,with_speed=False,with_directional=False,with_penalty_rate=False,with_pass_charge=False,with_pickup_delay=False,with_alt_goalies=False,with_strict_so=False,with_ai_shot_plan=False,with_faceoff_dots=False,with_momentum=False,with_transfers=False,with_refinements=False):
    if not with_crit_option:
        return legacy_install(data,assemble,patch)
    labels_at=0x2E1200 if with_directional else LABELS
    adapter_at=0x2DEF40 if with_directional else ADAPTER
    code=assemble('replaymenu',ORIGIN);assert len(code)<(0x400 if with_height else 0x800)
    patch(ORIGIN,b'\xff'*len(code),code)
    labels=bytes(data[0x20915A:0x20915A+160])+b'\x00\x10Auto Replays \x00'+b'\x00\x10Crit Shots   \x00'
    if with_icing:labels+=b'\x00\x10Icing        \x00'
    if with_height:labels+=b'\x00\x10Pass Height  \x00'
    if with_speed:labels+=b'\x00\x10Pass Speed   \x00'
    if with_directional:labels+=b'\x00\x10Pass Mode    \x00'+b'\x00\x10Target Cone  \x00'
    if with_penalty_rate:
        labels=labels[:96]+b'\x00\x10Rules        \x00'+labels[112:]+b'\x00\x10Penaltys     \x00'
    if with_pass_charge:labels+=b'\x00\x10Pass Charge  \x00'
    if with_pickup_delay:labels+=b'\x00\x10Pickup Delay \x00'
    if with_alt_goalies:labels+=b'\x00\x10Alt Goalies  \x00'
    if with_strict_so:labels+=b'\x00\x10OT SO Rules  \x00'
    if with_ai_shot_plan:labels+=b'\x00\x10AI Shot Plan \x00'
    if with_faceoff_dots:labels+=b'\x00\x10Faceoff Dots \x00'
    if with_momentum:labels+=b'\x00\x10Momentum     \x00'
    assert len(labels)==(400 if with_momentum else 384 if with_faceoff_dots else 368 if with_ai_shot_plan else 352 if with_strict_so else 336 if with_alt_goalies else 320 if with_pickup_delay else 304 if with_pass_charge else 288 if with_penalty_rate else 272 if with_directional else 240 if with_speed else 224 if with_height else 208 if with_icing else 192)
    if with_transfers:
        assert with_momentum and with_directional and with_icing
        labels=labels[:208]+b'\x00\x10Transfers    \x00'+labels[208:]
    if with_refinements:
        assert with_transfers and len(labels)==416
        labels=labels[:22*16]+b''.join(b'\x00\x10'+v.ljust(13)+b'\x00' for v in [b'AI Shot Plan',b'Momentum',b'Shot Units',b'Fragile Glass'])
    patch(labels_at,b'\xff'*len(labels),labels)
    extra=30 if with_momentum else 28 if with_faceoff_dots else 26 if with_ai_shot_plan else 24 if with_strict_so else 22 if with_alt_goalies else 20 if with_pickup_delay else 18 if with_pass_charge else 16 if with_penalty_rate else 14 if with_directional else 10 if with_speed else 8 if with_height else 6 if with_icing else 4
    old=bytes(data[0x2091FE:0x209742]);values=bytearray(old[:20]+b'\x00'*extra+old[20:])
    for i in range(10):struct.pack_into('>H',values,2*i,struct.unpack_from('>H',old,2*i)[0]+extra)
    for i in ((20,22,24) if with_icing else (20,22)):struct.pack_into('>H',values,i,len(values))
    values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'On',b'Off'))
    if with_height:
        struct.pack_into('>H',values,26,len(values))
        values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Normal',b'Low'))
    if with_speed:
        struct.pack_into('>H',values,28,len(values))
        values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Normal',b'Slow'))
    if with_directional:
        struct.pack_into('>H',values,30,0x2E1400-VALUES)
        struct.pack_into('>H',values,32,0x2E1400+44-VALUES)
        extra_values=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Assisted',b'Directional',b'Default',b'15 degrees',b'30 degrees',b'45 degrees'))
        if with_penalty_rate:
            struct.pack_into('>H',values,34,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'100%',b'50%',b'Off'))
        if with_pass_charge:
            struct.pack_into('>H',values,36,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))
        if with_pickup_delay:
            struct.pack_into('>H',values,38,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))
        if with_alt_goalies:
            struct.pack_into('>H',values,40,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))
        if with_strict_so:
            struct.pack_into('>H',values,42,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))
        if with_ai_shot_plan:
            struct.pack_into('>H',values,44,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))
        if with_faceoff_dots:
            struct.pack_into('>H',values,46,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))
        if with_refinements:
            # Existing groups are contiguous22B records; rewrite only text bytes.
            for group,suffix in [(6*22+3*22+2*22,b'Brief Catch Lock'),(6*22+3*22+4*22,b'New Goalie Model'),(6*22+3*22+8*22,b'Smarter Hard AI')]:
                for n,val in enumerate([b'Off',b'On']):
                    text=val+b' '+suffix;assert len(text)<=20
                    at=group+22*n+2;extra_values=extra_values[:at]+text.ljust(20)+extra_values[at+20:]
        if with_momentum:
            struct.pack_into('>H',values,48,0x2E1400+len(extra_values)-VALUES)
            extra_values+=b''.join(b'\x00\x16'+(v+(b' ~10 % Stats'if with_refinements else b'')).ljust(20) for v in (b'On',b'Off'))
        if with_transfers:
            transfer_pointer=0x2E1400+len(extra_values)-VALUES
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))
        if with_refinements:
            unit_pointer=0x2E1400+len(extra_values)-VALUES
            extra_values+=b''.join(b'\x00\x16'+v.ljust(20)for v in [b'KMH',b'MPH'])
        assert len(extra_values)<=0x400
        patch(0x2E1400,b'\xff'*len(extra_values),extra_values)
    if with_transfers:
        # Insert a real row after Icing. Only in-table relative pointers move;
        # external value groups at2E1400 retain their existing absolute targets.
        old_values=bytes(values)
        values=bytearray(values[:26]+struct.pack('>H',transfer_pointer)+values[26:])
        for offset in range(0,50,2):
            target=struct.unpack_from('>H',old_values,offset)[0]
            if target<len(old_values):target+=2
            struct.pack_into('>H',values,offset+(2 if offset>=26 else 0),target)
        extra+=2
    if with_refinements:
        ai=struct.unpack_from('>H',values,46)[0]
        momentum=struct.unpack_from('>H',values,50)[0]
        glass=struct.unpack_from('>H',values,24)[0] # existing On/Off order
        struct.pack_into('>4H',values,44,ai,momentum,unit_pointer,glass)
    assert VALUES+len(values)<COUNTS
    patch(VALUES,b'\xff'*len(values),values)
    for oldaddr,newaddr in [(0x2095AE,COUNTS),(0x2095C2,adapter_at)]:
        counts=bytes(data[oldaddr:oldaddr+20])+b'\x00\x02'*(5 if with_speed else 4 if with_height else 3 if with_icing else 2)
        if with_directional:counts+=b'\x00\x02\x00\x04'
        if with_penalty_rate:counts+=b'\x00\x03'
        if with_pass_charge:counts+=b'\x00\x02'
        if with_pickup_delay:counts+=b'\x00\x02'
        if with_alt_goalies:counts+=b'\x00\x02'
        if with_strict_so:counts+=b'\x00\x02'
        if with_ai_shot_plan:counts+=b'\x00\x02'
        if with_faceoff_dots:counts+=b'\x00\x02'
        if with_momentum:counts+=b'\x00\x02'
        if with_transfers:counts=counts[:26]+b'\x00\x02'+counts[26:]
        patch(newaddr,b'\xff'*len(counts),counts)
    for operand,oldaddr,newaddr in [(0xF807C,0x20915A,labels_at),(0x209638,0x2091FE,VALUES),(0x2FE27A,0x2091FE,VALUES),(0xF79AC,0x2095AE,COUNTS),(0xF79BA,0x2095C2,adapter_at)]:
        patch(operand,struct.pack('>I',oldaddr),struct.pack('>I',newaddr))
    patch(0xF7BBA,struct.pack('>I',0x209438),struct.pack('>I',VALUES+0x209438-0x2091FE+extra))
    patch(0x2FE220,bytes.fromhex('32070c41000c'),bytes.fromhex('4ef9002de000'))
    patch(0xF777A,bytes.fromhex('0014'),bytes.fromhex('0032' if with_momentum else '0030' if with_faceoff_dots else '002e' if with_ai_shot_plan else '002c' if with_strict_so else '002a' if with_alt_goalies else '0028' if with_pickup_delay else '0026' if with_pass_charge else '0024' if with_penalty_rate else '0022' if with_directional else '001e' if with_speed else '001c' if with_height else '001a' if with_icing else '0018'))
    # Native full value redraw previously stopped after logical row20.
    if not legacy_crit_value_draw:patch(0xF7B44,bytes.fromhex('0014'),bytes.fromhex('0030' if with_momentum else '002e' if with_faceoff_dots else '002c' if with_ai_shot_plan else '002a' if with_strict_so else '0028' if with_alt_goalies else '0026' if with_pickup_delay else '0024' if with_pass_charge else '0022' if with_penalty_rate else '0020' if with_directional else '001c' if with_speed else '001a' if with_height else '0018' if with_icing else '0016'))
    for addr in (0xF7758,0xF7762,0xF7C08):patch(addr,bytes.fromhex('0004'),bytes.fromhex('0013' if with_momentum else '0012' if with_faceoff_dots else '0011' if with_ai_shot_plan else '0010' if with_strict_so else '000f' if with_alt_goalies else '000e' if with_pickup_delay else '000d' if with_pass_charge else '000c' if with_penalty_rate else '000b' if with_directional else '0009' if with_speed else '0008' if with_height else '0007' if with_icing else '0006'))
    if with_transfers:
        for at,old,new in [(0xF777A,50,52),(0xF7B44,48,50),(0xF7758,19,20),(0xF7762,19,20),(0xF7C08,19,20)]:
            patch(at,struct.pack('>H',old),struct.pack('>H',new))
