#!/usr/bin/env python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from __future__ import print_function
import sys
import time
import platform
import getpass
from collections import OrderedDict
import re
import xml.etree.ElementTree as etree
import itertools
import argparse
import csv
import json
from nodeset_compiler.opaque_type_mapping import get_base_type_for_opaque as get_base_type_for_opaque_ns0

types = OrderedDict() # contains types that were already parsed
types_imported = OrderedDict() # contains types that were already parsed and marked as imported types (do not write to source code)
typedescriptions = {} # contains type nodeids
user_opaque_type_mapping = {} # contains user defined opaque type mapping

excluded_types = ["NodeIdType", "InstanceNode", "TypeNode", "Node", "ObjectNode",
                  "ObjectTypeNode", "VariableNode", "VariableTypeNode", "ReferenceTypeNode",
                  "MethodNode", "ViewNode", "DataTypeNode",
                  "NumericRange", "NumericRangeDimensions",
                  "UA_ServerDiagnosticsSummaryDataType", "UA_SamplingIntervalDiagnosticsDataType",
                  "UA_SessionSecurityDiagnosticsDataType", "UA_SubscriptionDiagnosticsDataType",
                  "UA_SessionDiagnosticsDataType"]

builtin_types = ["Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32",
                 "Int64", "UInt64", "Float", "Double", "String", "DateTime", "Guid",
                 "ByteString", "XmlElement", "NodeId", "ExpandedNodeId", "StatusCode",
                 "QualifiedName", "LocalizedText", "ExtensionObject", "DataValue",
                 "Variant", "DiagnosticInfo"]

# If set to False, every defined datatype must have a corresponding ID entry in the csv file
isInternalTypes = False

# Some types can be memcpy'd off the binary stream. That's especially important
# for arrays. But we need to check if they contain padding and whether the
# endianness is correct. This dict gives the C-statement that must be true for the
# type to be overlayable. Parsed types are added if they apply.
builtin_overlayable = {"Boolean": "true",
                       "SByte": "true", "Byte": "true",
                       "Int16": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "UInt16": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "Int32": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "UInt32": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "Int64": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "UInt64": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "Float": "UA_BINARY_OVERLAYABLE_FLOAT",
                       "Double": "UA_BINARY_OVERLAYABLE_FLOAT",
                       "DateTime": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "StatusCode": "UA_BINARY_OVERLAYABLE_INTEGER",
                       "Guid": "(UA_BINARY_OVERLAYABLE_INTEGER && " +
                       "offsetof(UA_Guid, data2) == sizeof(UA_UInt32) && " +
                       "offsetof(UA_Guid, data3) == (sizeof(UA_UInt16) + sizeof(UA_UInt32)) && " +
                       "offsetof(UA_Guid, data4) == (2*sizeof(UA_UInt32)))"}

whitelistFuncAttrWarnUnusedResult = []  # for instances [ "String", "ByteString", "LocalizedText" ]

# Type aliases
type_aliases = { "CharArray" : "String" }
def getTypeName(xmlTypeName):
    typeName = xmlTypeName[xmlTypeName.find(":")+1:]
    return type_aliases.get(typeName, typeName)

# Escape C strings:
def makeCLiteral(value):
    return re.sub(r'(?<!\\)"', r'\\"', value.replace('\\', r'\\\\').replace('\n', r'\\n').replace('\r', r''))

# Strip invalid characters to create valid C identifiers (variable names etc):
def makeCIdentifier(value):
    return re.sub(r'[^\w]', '', value)

def get_base_type_for_opaque(name):
    if name in user_opaque_type_mapping:
        return user_opaque_type_mapping[name]
    else:
        return get_base_type_for_opaque_ns0(name)

################
# Type Classes #
################

class StructMember(object):
    def __init__(self, name, memberType, isArray):
        self.name = name
        self.memberType = memberType
        self.isArray = isArray

def getNodeidTypeAndId(nodeId):
    if '=' not in nodeId:
        return "UA_NODEIDTYPE_NUMERIC, {{{0}}}".format(nodeId)
    if nodeId.startswith("i="):
        return "UA_NODEIDTYPE_NUMERIC, {{{0}}}".format(nodeId[2:])
    if nodeId.startswith("s="):
        strId = nodeId[2:]
        return "UA_NODEIDTYPE_STRING, {{ .string = UA_STRING_STATIC(\"{id}\") }}".format(id=strId.replace("\"", "\\\""))


class Type(object):
    def __init__(self, outname, xml, namespace):
        self.name = None
        if xml is not None:
            self.name = xml.get("Name")
            self.typeIndex = "UA_" + makeCIdentifier(outname.upper() + "_" + self.name.upper())
        else:
            self.typeIndex = makeCIdentifier(outname.upper())
        self.ns0 = ("true" if namespace == 0 else "false")
        self.outname = outname
        self.kind = None
        self.description = ""
        self.pointerfree = "false"
        self.overlayable = "false"
        self.members = []
        if xml is not None:
            for child in xml:
                if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
                    self.description = child.text
                    break

    def datatype_c(self):
        # xmlEncodingId = "0"
        binaryEncodingId = "0"
        if self.name in typedescriptions:
            description = typedescriptions[self.name]
            typeid = "{%s, %s}" % (description.namespaceid, getNodeidTypeAndId(description.nodeid))
            # xmlEncodingId = description.xmlEncodingId
            binaryEncodingId = description.binaryEncodingId
        else:
            if not isInternalTypes:
                raise RuntimeError("NodeId for " + self.name + " not found in .csv file")
            else:
                typeid = "{0, UA_NODEIDTYPE_NUMERIC, {0}}"
        idName = makeCIdentifier(self.name)
        return "{\n    UA_TYPENAME(\"%s\") /* .typeName */\n" % idName + \
            "    " + typeid + ", /* .typeId */\n" + \
            "    sizeof(UA_" + idName + "), /* .memSize */\n" + \
            "    " + self.typeIndex + ", /* .typeIndex */\n" + \
            "    " + self.kind + ", /* .typeKind */\n" + \
            "    " + self.pointerfree + ", /* .pointerFree */\n" + \
            "    " + self.overlayable + ", /* .overlayable */\n" + \
            "    " + str(len(self.members)) + ", /* .membersSize */\n" + \
            "    " + binaryEncodingId + ", /* .binaryEncodingId */\n" + \
            "    %s_members" % idName + " /* .members */\n}"

    def members_c(self):
        idName = makeCIdentifier(self.name)
        if len(self.members) == 0:
            return "#define %s_members NULL" % (idName)
        members = "static UA_DataTypeMember %s_members[%s] = {" % (idName, len(self.members))
        before = None
        size = len(self.members)
        for i, member in enumerate(self.members):
            memberName = makeCIdentifier(member.name)
            memberNameCapital = memberName
            if len(memberName) > 0:
                memberNameCapital = memberName[0].upper() + memberName[1:]
            m = "\n{\n    UA_TYPENAME(\"%s\") /* .memberName */\n" % memberNameCapital
            m += "    UA_%s_%s, /* .memberTypeIndex */\n" % (member.memberType.outname.upper(), makeCIdentifier(member.memberType.name.upper()))
            m += "    "
            if not before:
                m += "0,"
            else:
                if member.isArray:
                    m += "offsetof(UA_%s, %sSize)" % (idName, memberName)
                else:
                    m += "offsetof(UA_%s, %s)" % (idName, memberName)
                m += " - offsetof(UA_%s, %s)" % (idName, makeCIdentifier(before.name))
                if before.isArray:
                    m += " - sizeof(void*),"
                else:
                    m += " - sizeof(UA_%s)," % makeCIdentifier(before.memberType.name)
            m += " /* .padding */\n"
            m += "    %s, /* .namespaceZero */\n" % member.memberType.ns0
            m += ("    true" if member.isArray else "    false") + " /* .isArray */\n}"
            if i != size:
                m += ","
            members += m
            before = member
        return members + "};"

    def datatype_ptr(self):
        return "&UA_" + self.outname.upper() + "[UA_" + makeCIdentifier(self.outname.upper() + "_" + self.name.upper()) + "]"

    def functions_c(self):
        idName = makeCIdentifier(self.name)
        funcs = "static UA_INLINE void\nUA_%s_init(UA_%s *p) {\n    memset(p, 0, sizeof(UA_%s));\n}\n\n" % (idName, idName, idName)
        funcs += "static UA_INLINE UA_%s *\nUA_%s_new(void) {\n    return (UA_%s*)UA_new(%s);\n}\n\n" % (idName, idName, idName, self.datatype_ptr())
        if self.pointerfree == "true":
            funcs += "static UA_INLINE UA_StatusCode\nUA_%s_copy(const UA_%s *src, UA_%s *dst) {\n    *dst = *src;\n    return UA_STATUSCODE_GOOD;\n}\n\n" % (idName, idName, idName)
            funcs += "static UA_INLINE void\nUA_%s_deleteMembers(UA_%s *p) {\n    memset(p, 0, sizeof(UA_%s));\n}\n\n" % (idName, idName, idName)
            funcs += "static UA_INLINE void\nUA_%s_clear(UA_%s *p) {\n    memset(p, 0, sizeof(UA_%s));\n}\n\n" % (idName, idName, idName)
        else:
            for entry in whitelistFuncAttrWarnUnusedResult:
                if idName == entry:
                    funcs += "UA_INTERNAL_FUNC_ATTR_WARN_UNUSED_RESULT "
                    break

            funcs += "static UA_INLINE UA_StatusCode\nUA_%s_copy(const UA_%s *src, UA_%s *dst) {\n    return UA_copy(src, dst, %s);\n}\n\n" % (idName, idName, idName, self.datatype_ptr())
            funcs += "static UA_INLINE void\nUA_%s_deleteMembers(UA_%s *p) {\n    UA_clear(p, %s);\n}\n\n" % (idName, idName, self.datatype_ptr())
            funcs += "static UA_INLINE void\nUA_%s_clear(UA_%s *p) {\n    UA_clear(p, %s);\n}\n\n" % (idName, idName, self.datatype_ptr())
        funcs += "static UA_INLINE void\nUA_%s_delete(UA_%s *p) {\n    UA_delete(p, %s);\n}" % (idName, idName, self.datatype_ptr())
        return funcs

    def encoding_h(self):
        idName = makeCIdentifier(self.name)
        enc = "static UA_INLINE size_t\nUA_%s_calcSizeBinary(const UA_%s *src) {\n    return UA_calcSizeBinary(src, %s);\n}\n"
        enc += "static UA_INLINE UA_StatusCode\nUA_%s_encodeBinary(const UA_%s *src, UA_Byte **bufPos, const UA_Byte *bufEnd) {\n    return UA_encodeBinary(src, %s, bufPos, &bufEnd, NULL, NULL);\n}\n"
        enc += "static UA_INLINE UA_StatusCode\nUA_%s_decodeBinary(const UA_ByteString *src, size_t *offset, UA_%s *dst) {\n    return UA_decodeBinary(src, offset, dst, %s, NULL);\n}"
        return enc % tuple(list(itertools.chain(*itertools.repeat([idName, idName, self.datatype_ptr()], 3))))

class BuiltinType(Type):
    def __init__(self, name):
        Type.__init__(self, name, None, 0)
        self.name = name
        self.ns0 = "true"
        self.typeIndex = makeCIdentifier("UA_TYPES_" + self.name.upper())
        self.outname = "types"
        self.kind = "UA_DATATYPEKIND_" + self.name.upper()
        self.description = ""
        self.pointerfree = "false"
        if self.name in builtin_overlayable.keys():
            self.pointerfree = "true"
        self.overlayable = "false"
        if name in builtin_overlayable:
            self.overlayable = builtin_overlayable[name]
        self.members = []

class EnumerationType(Type):
    def __init__(self, outname, xml, namespace):
        Type.__init__(self, outname, xml, namespace)
        self.pointerfree = "true"
        self.overlayable = "UA_BINARY_OVERLAYABLE_INTEGER"
        self.members = []
        self.kind = "UA_DATATYPEKIND_ENUM"
        self.typeIndex = "UA_TYPES_INT32"
        self.elements = OrderedDict()
        for child in xml:
            if child.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedValue":
                self.elements[child.get("Name")] = child.get("Value")

    def typedef_h(self):
        if sys.version_info[0] < 3:
            values = self.elements.iteritems()
        else:
            values = self.elements.items()
        return "typedef enum {\n    " + ",\n    ".join(map(lambda kv : makeCIdentifier("UA_" + self.name.upper() + "_" + kv[0].upper()) + \
                                                           " = " + kv[1], values)) + \
               ",\n    __UA_{0}_FORCE32BIT = 0x7fffffff\n".format(makeCIdentifier(self.name.upper())) + "} " + \
               "UA_{0};\nUA_STATIC_ASSERT(sizeof(UA_{0}) == sizeof(UA_Int32), enum_must_be_32bit);".format(makeCIdentifier(self.name))

class OpaqueType(Type):
    def __init__(self, outname, xml, namespace, baseType):
        Type.__init__(self, outname, xml, namespace)
        self.kind = "UA_DATATYPEKIND_" + baseType.upper()
        self.baseType = baseType
        self.members = []

    def typedef_h(self):
        return "typedef UA_" + self.baseType + " UA_%s;" % self.name

class StructType(Type):
    def __init__(self, outname, xml, namespace):
        Type.__init__(self, outname, xml, namespace)
        self.members = []
        lengthfields = [] # lengthfields of arrays are not included as members
        for child in xml:
            if child.get("LengthField"):
                lengthfields.append(child.get("LengthField"))
        for child in xml:
            if not child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
                continue
            if child.get("Name") in lengthfields:
                continue
            memberName = child.get("Name")
            memberName = memberName[:1].lower() + memberName[1:]
            memberTypeName = getTypeName(child.get("TypeName"))
            memberType = types[memberTypeName]
            isArray = True if child.get("LengthField") else False
            self.members.append(StructMember(memberName, memberType, isArray))

        self.pointerfree = "true"
        self.overlayable = "true"
        self.kind = "UA_DATATYPEKIND_STRUCTURE"
        before = None
        for m in self.members:
            if m.isArray or m.memberType.pointerfree != "true":
                self.pointerfree = "false"
                self.overlayable = "false"
            else:
                self.overlayable += "\n\t\t && " + m.memberType.overlayable
                if before:
                    self.overlayable += "\n\t\t && offsetof(UA_%s, %s) == (offsetof(UA_%s, %s) + sizeof(UA_%s))" % \
                                        (makeCIdentifier(self.name), makeCIdentifier(m.name), makeCIdentifier(self.name), makeCIdentifier(before.name), makeCIdentifier(before.memberType.name))
            if "false" in self.overlayable:
                self.overlayable = "false"
            before = m

    def typedef_h(self):
        if len(self.members) == 0:
            return "typedef void * UA_%s;" % makeCIdentifier(self.name)
        returnstr =  "typedef struct {\n"
        for member in self.members:
            if member.isArray:
                returnstr += "    size_t %sSize;\n" % makeCIdentifier(member.name)
                returnstr += "    UA_%s *%s;\n" % (makeCIdentifier(member.memberType.name), makeCIdentifier(member.name))
            else:
                returnstr += "    UA_%s %s;\n" % (makeCIdentifier(member.memberType.name), makeCIdentifier(member.name))
        return returnstr + "} UA_%s;" % makeCIdentifier(self.name)

#########################
# Parse Typedefinitions #
#########################

def parseTypeDefinitions(outname, xmlDescription, namespace, addToTypes=None):
    def typeReady(element):
        "Are all member types defined?"
        for child in element:
            if child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
                childname = getTypeName(child.get("TypeName"))
                if childname not in types:
                    return False
        return True

    def unknownTypes(element):
        "Return all unknown types"
        unknowns = []
        for child in element:
            if child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
                childname = getTypeName(child.get("TypeName"))
                if childname not in types:
                    unknowns.append(childname)
        return unknowns

    def skipType(name):
        if name in excluded_types:
            return True
        if re.search("NodeId$", name) != None:
            return True
        return False

    snippets = {}
    for typeXml in etree.parse(xmlDescription).getroot():
        if not typeXml.get("Name"):
            continue
        name = typeXml.get("Name")
        snippets[name] = typeXml

    detectLoop = len(snippets)+1
    while(len(snippets) > 0):
        if detectLoop == len(snippets):
            name, typeXml = snippets.popitem()
            raise RuntimeError("Infinite loop detected or type not found while processing types " + name + ": unknonwn subtype " +
                               str(unknownTypes(typeXml)) + " Maybe you need to import additional types with the --import flag. " +
                               "E.g. '--import==UA_TYPES#/path/to/deps/ua-nodeset/Schema/Opc.Ua.Types.bsd'")
        detectLoop = len(snippets)
        for name, typeXml in list(snippets.items()):
            if name in types or skipType(name):
                del snippets[name]
                continue
            if not typeReady(typeXml):
                continue
            if name in builtin_types:
                newType = BuiltinType(name)
            elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedType":
                newType = EnumerationType(outname, typeXml, namespace)
            elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}OpaqueType":
                newType = OpaqueType(outname, typeXml, namespace, get_base_type_for_opaque(name)['name'])
            elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}StructuredType":
                newType = StructType(outname, typeXml, namespace)
            else:
                raise Exception("Type not known")

            types[name] = newType
            if addToTypes is not None:
                addToTypes[name] = newType

            del snippets[name]

##########################
# Parse TypeDescriptions #
##########################

class TypeDescription(object):
    def __init__(self, name, nodeid, namespaceid):
        self.name = name
        self.nodeid = nodeid
        self.namespaceid = namespaceid
        self.xmlEncodingId = "0"
        self.binaryEncodingId = "0"

def parseTypeDescriptions(f, namespaceid):
    definitions = {}

    csvreader = csv.reader(f, delimiter=',')
    delay_init = []

    for row in csvreader:
        if len(row) < 3:
            continue
        if row[2] == "Object":
            # Check if node name ends with _Encoding_(DefaultXml|DefaultBinary) and store the node id in the corresponding DataType
            m = re.match('(.*?)_Encoding_Default(Xml|Binary)$',row[0])
            if (m):
                baseType = m.group(1)
                if baseType not in types:
                    continue

                delay_init.append({
                    "baseType": baseType,
                    "encoding": m.group(2),
                    "id": row[1]
                })
            continue
        if row[2] != "DataType":
            continue
        if row[0] == "BaseDataType":
            definitions["Variant"] = TypeDescription(row[0], row[1], namespaceid)
        elif row[0] == "Structure":
            definitions["ExtensionObject"] = TypeDescription(row[0], row[1], namespaceid)
        elif row[0] not in types:
            continue
        else:
            definitions[row[0]] = TypeDescription(row[0], row[1], namespaceid)
    for i in delay_init:
        if i["baseType"] not in definitions:
            raise Exception("Type {} not found in definitions file.".format(i["baseType"]))
        if i["encoding"] == "Xml":
            definitions[i["baseType"]].xmlEncodingId = i["id"]
        else:
            definitions[i["baseType"]].binaryEncodingId = i["id"]
    return definitions

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

###############################
# Parse the Command Line Input#
###############################

parser = argparse.ArgumentParser()
parser.add_argument('-c', '--type-csv',
                    metavar="<typeDescriptions>",
                    type=argparse.FileType('r'),
                    dest="type_csv",
                    action='append',
                    default=[],
                    help='csv file with type descriptions')

parser.add_argument('--namespace',
                    type=int,
                    dest="namespace",
                    default=0,
                    help='namespace id of the generated type nodeids (defaults to 0)')

parser.add_argument('-s', '--selected-types',
                    metavar="<selectedTypes>",
                    type=argparse.FileType('r'),
                    dest="selected_types",
                    action='append',
                    default=[],
                    help='file with list of types (among those parsed) to be generated. If not given, all types are generated')

parser.add_argument('--no-builtin',
                    action='store_true',
                    dest="no_builtin",
                    help='Do not generate builtin types')

parser.add_argument('--opaque-map',
                    metavar="<opaqueTypeMap>",
                    type=argparse.FileType('r'),
                    dest="opaque_map",
                    action='append',
                    default=[],
                    help='JSON file with opaque type mapping: { \'typename\': { \'ns\': 0,  \'id\': 7, \'name\': \'UInt32\' }, ... }')

parser.add_argument('--internal',
                    action='store_true',
                    dest="internal",
                    help='Given bsd are internal types which do not have any .csv file')

parser.add_argument('-t', '--type-bsd',
                    metavar="<typeBsds>",
                    type=argparse.FileType('r'),
                    dest="type_bsd",
                    action='append',
                    default=[],
                    help='bsd file with type definitions')

parser.add_argument('-i', '--import',
                    metavar="<importBsds>",
                    type=str,
                    dest="import_bsd",
                    action='append',
                    default=[],
                    help='combination of TYPE_ARRAY#filepath.bsd with type definitions which should be loaded but not exported/generated')

parser.add_argument('outfile',
                    metavar='<outputFile>',
                    help='output file w/o extension')
args = parser.parse_args()

outname = args.outfile.split("/")[-1]
inname = ', '.join(list(map(lambda x:x.name.split("/")[-1], args.type_bsd)))

isInternalTypes = args.internal

################
# Create Types #
################

for builtin in builtin_types:
    types[builtin] = BuiltinType(builtin)

for f in args.opaque_map:
    user_opaque_type_mapping.update(json.load(f))

for i in args.import_bsd:
    (outname_import, file_import) = i.split("#")
    outname_import = outname_import.lower()
    if outname_import.startswith("ua_"):
        outname_import = outname_import[3:]
    parseTypeDefinitions(outname_import, file_import, args.namespace, addToTypes=types_imported)

for f in args.type_bsd:
    parseTypeDefinitions(outname, f, args.namespace)

typedescriptions = {}
for f in args.type_csv:
    typedescriptions = merge_dicts(typedescriptions, parseTypeDescriptions(f, args.namespace))

# Read the selected data types
selected_types = []
for f in args.selected_types:
    selected_types += list(filter(len, [line.strip() for line in f]))
# Use all types if none are selected
if len(selected_types) == 0:
    selected_types = types.keys()

#############################
# Write out the Definitions #
#############################

fh = open(args.outfile + "_generated.h", 'w')
ff = open(args.outfile + "_generated_handling.h", 'w')
fe = open(args.outfile + "_generated_encoding_binary.h", 'w')
fc = open(args.outfile + "_generated.c",'w')
def printh(string):
    print(string, end='\n', file=fh)
def printf(string):
    print(string, end='\n', file=ff)
def printe(string):
    print(string, end='\n', file=fe)
def printc(string):
    print(string, end='\n', file=fc)

def iter_types(v):
    l = None
    if sys.version_info[0] < 3:
        l = list(v.itervalues())
    else:
        l = list(v.values())
    if len(selected_types) > 0:
        l = list(filter(lambda t: t.name in selected_types, l))
    if args.no_builtin:
        l = list(filter(lambda t: type(t) != BuiltinType, l))
    l = list(filter(lambda t: t.name not in types_imported, l))
    return l

################
# Print Header #
################

printh('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
 * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + \
       ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + ''' */

#ifndef ''' + outname.upper() + '''_GENERATED_H_
#define ''' + outname.upper() + '''_GENERATED_H_

#ifdef UA_ENABLE_AMALGAMATION
#include "open62541.h"
#else
#include <open62541/types.h>
''' + ('#include <open62541/types_generated.h>\n' if outname != "types" else '') + '''
#endif

_UA_BEGIN_DECLS

''')

filtered_types = iter_types(types)

printh('''/**
 * Every type is assigned an index in an array containing the type descriptions.
 * These descriptions are used during type handling (copying, deletion,
 * binary encoding, ...). */''')
printh("#define UA_" + outname.upper() + "_COUNT %s" % (str(len(filtered_types))))
printh("extern UA_EXPORT const UA_DataType UA_" + outname.upper() + "[UA_" + outname.upper() + "_COUNT];")

for i, t in enumerate(filtered_types):
    printh("\n/**\n * " +  t.name)
    printh(" * " + "^" * len(t.name))
    if t.description == "":
        printh(" */")
    else:
        printh(" * " + t.description + " */")
    if type(t) != BuiltinType:
        printh(t.typedef_h() + "\n")
    printh("#define UA_" + makeCIdentifier(outname.upper() + "_" + t.name.upper()) + " " + str(i))

printh('''

_UA_END_DECLS

#endif /* %s_GENERATED_H_ */''' % outname.upper())

##################
# Print Handling #
##################

printf('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
 * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + \
       ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + ''' */

#ifndef ''' + outname.upper() + '''_GENERATED_HANDLING_H_
#define ''' + outname.upper() + '''_GENERATED_HANDLING_H_

#include "''' + outname + '''_generated.h"

_UA_BEGIN_DECLS

#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
# pragma GCC diagnostic ignored "-Wmissing-braces"
#endif
''')

for t in filtered_types:
    printf("\n/* " + t.name + " */")
    printf(t.functions_c())

printf('''
#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
# pragma GCC diagnostic pop
#endif

_UA_END_DECLS

#endif /* %s_GENERATED_HANDLING_H_ */''' % outname.upper())

###########################
# Print Description Array #
###########################

printc('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
 * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + \
       ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + ''' */

#include "''' + outname + '''_generated.h"''')

for t in filtered_types:
    printc("")
    printc("/* " + t.name + " */")
    printc(t.members_c())

printc("const UA_DataType UA_%s[UA_%s_COUNT] = {" % (outname.upper(), outname.upper()))
for t in filtered_types:
    printc("/* " + t.name + " */")
    printc(t.datatype_c() + ",")
printc("};\n")

##################
# Print Encoding #
##################

printe('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
 * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + \
       ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + ''' */

#ifdef UA_ENABLE_AMALGAMATION
# include "open62541.h"
#else
# include "ua_types_encoding_binary.h"
# include "''' + outname + '''_generated.h"
#endif

''')

for t in filtered_types:
    printe("\n/* " + t.name + " */")
    printe(t.encoding_h())

fh.close()
ff.close()
fc.close()
fe.close()
