#!/usr/bin/env python3

import sys
import struct

def bytes_to_int(some_bytes):
    return struct.unpack('>i', some_bytes)[0]

def bytes_to_string(some_bytes):
    return some_bytes.decode("macintosh")

def bytes_to_short(some_bytes):
    return struct.unpack('>h', some_bytes)[0]

def bytes_to_shorter(byte):
    shorter = b"\x00%b" % byte
    return struct.unpack('>h', shorter)[0]

def bytes_to_unsigned_short(some_bytes):
    return struct.unpack('>H', some_bytes)[0]

def bytes_to_fixed(some_bytes):
    # "Fixed" numbers in avara are stored
    # in a 8 bit long type, two unsigned ints
    assert(len(some_bytes) == 4)
    whole = bytes_to_short(some_bytes[0:2])
    # maximum unsigned 8-bit int is 65535
    frac = bytes_to_unsigned_short(some_bytes[2:4]) / 65535.0
    # print("Whole: %d Frac: %f" % (whole, frac))
    return -1 if whole == -1 and frac == 1 else whole + frac


def parse_tmpl(tmpl, data):
    result = {}

    structure = []
    # we consume all the template values into a structure
    # to use later
    while len(tmpl) > 1:
        # name is a pascal string with no padding
        namelength = tmpl[0]
        tmpl = tmpl[1:]
        dtname = (tmpl[0:namelength]).decode('macroman')
        tmpl = tmpl[namelength:]
        # always a TNAM, or four character string
        restype = (tmpl[:4]).decode('macroman')
        tmpl = tmpl[4:]

        structure.append({
            'name': dtname,
            'type': restype
            })

    _, stuff = read_data_with_template(data, structure)

    return stuff

def read_data_with_template(data, structure):
    # this is an awful recursive thing to parse the list types available in ResEdit
    result = {}
    end = -1
    panic = 0
    for i, dtype in enumerate(structure):

        if i < end:
            # skip items if we're inside a list
            continue

        t = dtype["type"]

        if t == "OCNT":
            # a OCNT means we have a sub-list of records coming up, with the fields
            # of the records being between the LSTC and LSTE records following
            data, count = get_value(data, t)

            # the next index is a LSTC and we don't need that for the sub records
            start = i + 2

            # get the last field on the sub records
            end = next((index for (index, d) in
                enumerate(structure) if d["name"] == structure[start - 1]["name"] and d["type"] == "LSTE"))

            # print ("list of %d things, start: %d end: %d" % (count, start, end))

            # create a "substructure" for those records in this list
            substructure = structure[start:end]

            # add  one to the end value so that we don't attempt to
            # process the LSTE element later on as a field
            end += 1

            # print ("with substructure %s" % substructure)
            the_list = []

            if count == 0:
                # zero count list means there will be nothing
                # left to parse, move on to the next thing
                # print("zero count list")
                continue

            for i in range(0, count):
                # recursively read the list and append results to our result
                # by passing the inside of the list type to this function again
                data, the_result = read_data_with_template(data, substructure)
                the_list.append(the_result)
                # this is usually a weird separator, i think by default its "*****"
                list_key = structure[start-1]["name"]
                result[list_key] = the_list
                pass

        # this skips values inside a list type
        elif i > end:
            # print(dtype)
            data, the_value = get_value(data, t)
            # print(the_value)
            result[dtype["name"]] = the_value

    return data, result

def get_value(the_data, dtype):
    # this data was compiled from ResEdit Reference For ResEdit 2.1
    # published by Apple Computer Developer Press 1995
    # page 79-80 (93-94 of PDF)
    # I only added the datatypes we needed. There are quite a few
    # more available for TMPL fields. This is where you want to add
    # more field types if you need them for something else.

    # TNAM: Type Name (Four characters)
    if dtype == "TNAM":
        return (the_data[4:], bytes_to_string(the_data[:4]))

    # OCNT: "One Count" works with LSTC and LSTE to define a list type.
    if dtype == "OCNT":
        value = bytes_to_short(the_data[:2])
        return (the_data[2:], value)

    # ESTR: Pascal string (length byte followed by characters)
    # padded to even length
    if dtype == "ESTR":
        eat_one_more_byte = False
        length = bytes_to_shorter(the_data[:1])
        # if we have an EVEN number of charcters, there is
        # a padding byte on the end, because the length byte
        # counts as one--so we need to eat an extra byte afterwards
        if length % 2 == 0:
            eat_one_more_byte = True
        # Even padded pascal string
        the_str = bytes_to_string(the_data[1:length + 1])
        if eat_one_more_byte:
            return (the_data[length + 2:], the_str)
        else:
            return (the_data[length + 1:], the_str)

    # DWRD: Decimal word (two bytes)
    if dtype == "DWRD":
        value = bytes_to_short(the_data[:2])
        return (the_data[2:], value)

    # DLNG: Decimal long word (four bytes)
    if dtype == "DLNG":
        value = bytes_to_int(the_data[:4])
        return (the_data[4:], value)

    # FIXD: 16:16 fixed point number
    # this one wasn't in the book. I got it from
    # http://www.mathemaesthetics.com/ResTemplates.html
    # it is four bytes long
    if dtype == "FIXD":
        value = bytes_to_fixed(the_data[:4])
        return (the_data[4:], value)

    # BOOL: Boolean (two bytes)
    if dtype == "BOOL":
        value = bytes_to_short(the_data[:2])
        the_bool = value > 0
        return (the_data[2:], the_bool)

    # HLNG: Hex long word (four bytes)
    if dtype == "HLNG":
        value = bytes_to_int(the_data[:4])
        return (the_data[4:], value)

    # HEXD: Hex dump of remaining bytes in resource
    # can only be at the END of a resource so we can
    # just return everything as bytes
    if dtype == "HEXD":
        return (None, the_data)



if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage: tmpl.py <TMPL.r> <RSRC.r>")
        sys.exit(1)
    else:
        with open(sys.argv[1], "rb") as tmpl_file:
            with open(sys.argv[2], "rb") as rsrc_file:
                print(parse_tmpl(tmpl_file.read(), rsrc_file.read()))
