import argparse

hex2snes = lambda address: address << 1 & 0xFF0000 | address & 0xFFFF | 0x808000

argparser = argparse.ArgumentParser(description = 'Print IPS patch data.')
argparser.add_argument('patch', type = argparse.FileType('rb'), help = 'Filepath to IPS patch')
argparser.add_argument('-s', action = 'store_true', help = 'Write addresses with SNES addressing')
argparser.add_argument('-g', type = lambda n: int(n, 0x10), default = 0x20, help = 'Maximum gap of missing data to merge in bytes')
args = argparser.parse_args()


def formatAddress(address):
    if not args.s:
        return f'${address:X}'
    
    address = hex2snes(address)
    return f'${address >> 0x10:02X}:{address & 0xFFFF:04X}'

patch = args.patch

if patch.read(5) != b'PATCH':
    raise RuntimeError('File does not begin with PATCH')

changes = []

while True:
    data = patch.read(3)
    if len(data) < 3:
        raise RuntimeError("Could not read an address")
        
    address = int.from_bytes(data, 'big')
    if address == 0x454F46:
        break
    
    size = int.from_bytes(patch.read(2), 'big')
    if size == 0:
        size = int.from_bytes(patch.read(2), 'big')
        data = int.from_bytes(patch.read(1), 'big')
        print(f'RLE {formatAddress(address)}: {size:X}h bytes - {data:02X}')
        data = [data] * size
    else:
        data = [b for b in patch.read(size)]
        data_text = ' '.join(f'{b:02X}' for b in data)
        print(f'    {formatAddress(address)}: {size:X}h bytes - {data_text}')
        
    changes += [(address, size, data)]

print()
changes.sort()
i_change = 0
while i_change + 1 < len(changes):
    (address, size, data) = changes[i_change]
    (address_next, size_next, data_next) = changes[i_change + 1]
    address_delta = address_next - (address + size)
    if args.g >= address_delta:
        changes[i_change] = (address, size + address_delta + size_next, data + [None] * address_delta + data_next)
        del changes[i_change + 1]
    else:
        i_change += 1

for (address, size, data) in changes:
    address_text = formatAddress(address)
    n_row = 0x20 # bytes per row
    data_text = '\n          '.join(
        ' '.join(
            f'{b:02X}' if b is not None else '__' 
            for b in data[i * n_row : (i + 1) * n_row]
        ) 
        for i in range((len(data) + n_row - 1) // n_row)
    )
    print(f'{address_text}: {data_text}')
    print()
