import argparse
import os
import string
import struct
import sys

import obj_pb2

_HERE = os.path.dirname(__file__)
CRT0_FILE = os.path.join(_HERE, 'ctr0.o')


def parse_objs(objs):
    sections = []
    relocs = []

    for obj in objs:
        of = obj_pb2.ObjFile()
        of.ParseFromString(obj.read())
        sections += of.sections
        relocs += of.relocs

    return sections, relocs


def map_sections(sections, offset=0):
    secmap = []

    addr = offset

    # _start goes first
    for sec in sections:
        if sec.name == '_start':
            secmap.append((addr, sec))
            addr += len(sec.text)
            sections.remove(sec)
            break
    assert secmap, "could not find symbol _start :/"

    for sec in sections:
        secmap.append((addr, sec))
        addr += len(sec.text)

    return secmap


def do_relocs(secmap, relocs):
    namemap = {s[1].name: s for s in secmap}
    for reloc in relocs:
        assert reloc.section in namemap
        assert reloc.target in namemap
        _, sec = namemap[reloc.section]
        # the reloc hex should look like /e.ff0000/
        buff = bytearray(sec.text)
        target_addr = namemap[reloc.target][0]
        reg = buff[reloc.offset] & 0xf
        buff[reloc.offset+0:reloc.offset+4] = [
                0xe0 | reg, (target_addr >> 0) & 0xff,
                0x90 | reg, (target_addr >> 8) & 0xff,
                ]
        sec.text = bytes(buff)


def dump(secmap):
    out = bytearray()
    for _, sec in secmap:
        out += sec.text
    return out


def parse_args():
    parser = argparse.ArgumentParser(description='Link.')
    parser.add_argument('--debug', action='store_true',
                        help='print debug info')
    parser.add_argument('objfiles', metavar='O', nargs='+',
                        type=argparse.FileType('rb'),
                        help='input file (default: stdin)')
    parser.add_argument('--output', '-o', type=argparse.FileType('wb'),
                        default=sys.stdout.buffer, help='output file')
    parser.add_argument('--vhdl', help='vhdl output with given template')
    parser.add_argument('--offset', default=0, type=int,
                        help='memory offset to link from')
    return parser.parse_args()


def main():
    args = parse_args()

    with open(CRT0_FILE, 'rb') as crt0:
        sections, relocs = parse_objs(args.objfiles + [crt0])

    sectionmap = map_sections(sections, offset=args.offset)
    do_relocs(sectionmap, relocs)
    text = dump(sectionmap)

    if args.vhdl:
        words = struct.unpack(f'>{len(text) // 2}H', text)
        subd = dict(words=',\n'.join(f'x"{w:04x}"' for w in words), nwords=len(words))

        with open(args.vhdl) as fin:
            tpl = string.Template(fin.read())
        args.output.write(tpl.substitute(subd).encode())

    else:
        args.output.write(text)


if __name__ == "__main__":
    main()
