#!/usr/bin/env python3
import os, struct, sys, ast, argparse

from elftools.elf.elffile import ELFFile

def align_up(base, align_to):
    return ((base + align_to - 1) // align_to) * align_to

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('files', help='file list')
    parser.add_argument('out', help='output file')
    parser.add_argument('-l', '--linkscript', help='output linker script for file VROM addresses', metavar='filename')
    parser.add_argument('-u', '--uncompressed', help='build dmadata from only uncompressed files', action='store_true', default=False)
    parser.add_argument('-e', '--elf', help='Override files with sections of an ELF, if it contains them')
    args = parser.parse_args()

    raw_elffile = None
    elffile = None
    if args.elf is not None:
       raw_elffile = open(args.elf, 'rb')
       elffile = ELFFile(raw_elffile)

    with open(args.out, 'wb') as dmadata, open(args.files, 'r') as files:
        curr_vrom = 0
        curr_phys = 0
        dmadata_table = ast.literal_eval(files.read())
        linker_info = list()
        for base_file, comp_file, alignment, size_if_missing in dmadata_table:
            try:
                file_name = base_file.split('/')[-1]
                uncompressed = comp_file == '' or args.uncompressed
                missing = base_file == '' and comp_file == ''
                blank = missing and size_if_missing == 0
                is_dmadata = base_file.endswith('dmadata')
                is_in_elf = file_name != '' and (elffile is not None) and (elffile.get_section_by_name(file_name) is not None)

                alignment = max(alignment, 0x10)

                if missing:
                    vrom_size = size_if_missing
                    phys_size = 0
                elif is_dmadata:
                    vrom_size = len(dmadata_table) * 0x10
                    phys_size = vrom_size
                elif is_in_elf:
                    vrom_size = elffile.get_section_by_name(file_name)['sh_size']
                    phys_size = vrom_size
                else:
                    vrom_size = os.path.getsize(base_file)
                    if uncompressed:
                        phys_size = vrom_size
                    else:
                        phys_size = os.path.getsize(comp_file)

                if blank:
                    vrom_start = 0
                    vrom_end = 0
                else:
                    vrom_start = align_up(curr_vrom, alignment)
                    vrom_end = vrom_start + vrom_size

                if blank:
                    phys_start = 0
                    phys_end = 0
                elif missing:
                    phys_start = 0xFFFFFFFF
                    phys_end = 0xFFFFFFFF
                else:
                    phys_start = align_up(curr_phys, 0x10)
                    phys_end = 0 if uncompressed or is_dmadata else phys_start + phys_size

                curr_vrom = align_up(curr_vrom, alignment) + vrom_size
                curr_phys = align_up(curr_phys, 0x10) + phys_size

                dmadata.write(vrom_start.to_bytes(4, 'big'))
                dmadata.write(vrom_end.to_bytes(4, 'big'))
                dmadata.write(phys_start.to_bytes(4, 'big'))
                dmadata.write(phys_end.to_bytes(4, 'big'))

                if base_file != '':
                    file_name = 'dmadata' if is_dmadata else base_file
                    linker_info.append((os.path.basename(file_name), vrom_start, vrom_end))
            except:
                print('Error when processing entry ' + base_file)
                sys.exit(1)

        if args.linkscript:
            with open(args.linkscript, 'w') as file:
                for name, vrom_start, vrom_end in linker_info:
                    formatted_name = '_' + name if name[0].isdigit() else name
                    file.write('{}_vrom_start = 0x{:08X};\n'.format(formatted_name, vrom_start))
                    file.write('{}_vrom_end = 0x{:08X};\n'.format(formatted_name, vrom_end))
                    file.write('\n')
