#!/usr/bin/python3

"""Make hex structure libraries for all nif versions.

Installation
------------

Make sure you have PyFFI installed (see http://pyffi.sourceforge.net).

Then, copy makehsl.py to your Hex Workshop structures folder

  C:\Program Files\BreakPoint Software\Hex Workshop 4.2\Structures

and run it. This will create a .hsl file per nif version.

Known issues
------------

Hex Workshop libraries cannot properly deal with conditionals, so
for serious hacking you probably want to edit the .hsl library as you
go, commenting out the parts which are not present in the particular
block you are investigating.
"""

# --------------------------------------------------------------------------
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (c) 2007-2012, NIF File Format Library and Tools.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#
#    * Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials provided
#      with the distribution.
#
#    * Neither the name of the NIF File Format Library and Tools
#      project nor the names of its contributors may be used to endorse
#      or promote products derived from this software without specific
#      prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# ***** END LICENSE BLOCK *****
# --------------------------------------------------------------------------

import sys
from types import *
from string import maketrans

from pyffi.formats.nif import NifFormat
from pyffi.object_models.xml.basic import BasicBase

def find_templates():
    # find all types that are used as a template (excluding the ones
    # occuring in Ref & its subclass Ptr)
    templates = set()
    for cls in NifFormat.xml_struct:
        for attr in cls._attribute_list:
            if attr.template != None and attr.template != type(None) and not issubclass(attr.type_, NifFormat.Ref):
                templates.add(attr.template)
    return templates

transtable = maketrans('?', '_')
def sanitize_attrname(s):
    return s.translate(transtable)

def write_hsl(f, ver, templates):
    # map basic NifFormat types to HWS types and enum byte size
    hsl_types = {
        NifFormat.int    : ('long', 4),
        NifFormat.uint   : ('ulong', 4),
        NifFormat.short  : ('short', 2),
        NifFormat.ushort : ('ushort', 2),
        NifFormat.Flags  : ('ushort', None),
        NifFormat.byte   : ('ubyte ', 1),
        NifFormat.char   : ('char', None),
        NifFormat.float  : ('float', None),
        NifFormat.Ref    : ('long', None),
        NifFormat.Ptr    : ('long', None),
        NifFormat.FileVersion : ('ulong', None),
        # some stuff we cannot do in hex workshop
        NifFormat.HeaderString : ('char', None),
        NifFormat.LineString : ('char', None) }
        # hack for string (TODO fix this in NifFormat)
        #NifFormat.string : ('struct string', None) }

    if ver <= 0x04000002:
        hsl_types[NifFormat.bool] = ('ulong', 4)
    else:
        hsl_types[NifFormat.bool] = ('ubyte ', 1)

    # write header
    f.write("""// hex structure library for NIF Format 0x%08X
#include "standard-types.hsl"
#pragma byteorder(little_endian)
#pragma maxarray(65535)

"""%ver)

    # write each enum class
    for cls in NifFormat.xml_enum:
        write_enum(cls, ver, hsl_types, f)

    # write each struct class
    for cls in NifFormat.xml_struct:
        if cls.__name__[:3] == 'ns ': continue # cheat: skip ns types
        if not cls._is_template:
            # write regular class
            write_struct(cls, ver, hsl_types, f, None)
        else:
            # write template classes
            for template in templates:
                write_struct(cls, ver, hsl_types, f, template)

def write_enum(cls, ver, hsl_types, f):
    # set enum size
    f.write('#pragma enumsize(%s)\n' % cls._numbytes)
    f.write('typedef enum tag' + cls.__name__ + ' {\n')
    ## list of all non-private attributes gives enum constants
    #enum_items = [x for x in cls.__dict__.items() if x[0][:2] != '__']
    ## sort them by value
    #enum_items = sorted(enum_items, key=lambda x: x[1])
    # and write out all name, value pairs
    enum_items = list(zip(cls._enumkeys, cls._enumvalues))
    for const_name, const_value in enum_items[:-1]:
        f.write('  ' + const_name + ' = ' + str(const_value) + ',\n')
    const_name, const_value = enum_items[-1]
    f.write('  ' + const_name + ' = ' + str(const_value) + '\n')
    f.write('} ' + cls.__name__ + ';\n\n')

def write_struct(cls, ver, hsl_types, f, template):
    # open the structure
    if not template:
        f.write('struct ' + cls.__name__ + ' {\n')
    else:
        f.write('struct ' + cls.__name__ + '_' + template.__name__ + ' {\n')
    #for attrname, typ, default, tmpl, arg, arr1, arr2, cond, ver1, ver2, userver, doc in cls._attribute_list:
    for attr in cls._attribute_list:
        # check version
        if not (ver is None):
            if (not (attr.ver1 is None)) and ver < attr.ver1:
                continue
            if (not (attr.ver2 is None)) and ver > attr.ver2:
                continue

        s = '  '

        # things that can only be determined at runtime (rt_xxx)
        rt_type = attr.type_ if attr.type_ != type(None) \
                  else template
        rt_template = attr.template if attr.template != type(None) \
                      else template

        # get the attribute type name
        try:
            s += hsl_types[rt_type][0]
        except KeyError:
            if rt_type in NifFormat.xml_enum:
                s += rt_type.__name__
            else: # it's in NifFormat.xml_struct
                s += 'struct ' + rt_type.__name__
        # get the attribute template type name
        if (not rt_template is None) and (not issubclass(rt_type, NifFormat.Ref)):
            s += '_'
            s += rt_template.__name__ # note: basic types are named by their xml name in the template
        # attribute name
        s = s.ljust(20) + ' ' + sanitize_attrname(attr.name)
        # array and conditional arguments
        arr_str = ''
        comments = ''
        if not attr.cond is None:
            # catch argument passing and double arrays
            if (str(attr.cond).find('arg') == -1) and (attr.arr2 is None):
                if attr.cond._op is None or (attr.cond._op == '!=' and attr.cond._right == 0):
                    arr_str += sanitize_attrname(str(attr.cond._left))
                else:
                    comments += ' (' + sanitize_attrname(str(attr.cond)) + ')'
            else:
                comments += ' (' + sanitize_attrname(str(attr.cond)) + ')'
        if attr.arr1 is None:
            pass
        elif attr.arr2 is None:
            if str(attr.arr1).find('arg') == -1: # catch argument passing
                if arr_str:
                    arr_str += ' * '
                arr_str += sanitize_attrname(str(attr.arr1._left))
                if attr.arr1._op:
                    comments += ' [' + sanitize_attrname(str(attr.arr1)) + ']'
            else:
                if arr_str:
                    arr_str += ' * '
                arr_str += '1'
                comments += ' [arg]'
        else:
            # TODO catch args here too (so far not used anywhere in nif.xml)
            if arr_str:
                arr_str += ' * '
            arr_str += sanitize_attrname(str(attr.arr1._left)) + ' * ' + sanitize_attrname(str(attr.arr2._left))
            if attr.arr1._op or attr.arr2._op:
                comments += ' [' + sanitize_attrname(str(attr.arr1)) + ' * ' + sanitize_attrname(str(attr.arr2)) + ']'
        arr_str = '[' + arr_str + ']' if arr_str else ''
        comments = ' //' + comments if comments else ''
        f.write(s + arr_str + ';' + comments + '\n')
    # close the structure
    f.write('};\n\n')

if __name__ == '__main__':
    # list all types used as a template
    templates = find_templates()
    # write out hex structure library for each nif version
    for ver_str, ver in list(NifFormat.versions.items()):
        f = open('nif_' + ver_str.replace('.', '_') + '.hsl', 'w')
        try:
            write_hsl(f, ver, templates)
        finally:
            f.close()
