#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 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/.

###
### Author:  Chris Iatrou (ichrispa@core-vector.net)
### Version: rev 13
###
### This program was created for educational purposes and has been
### contributed to the open62541 project by the author. All licensing
### terms for this source is inherited by the terms and conditions
### specified for by the open62541 project (see the projects readme
### file for more information on the MPLv2 terms and restrictions).
###
### This program is not meant to be used in a production environment. The
### author is not liable for any complications arising due to the use of
### this program.
###

import logging
from ua_constants import *
import string


logger = logging.getLogger(__name__)

__unique_item_id = 0

defined_typealiases = []

class open62541_MacroHelper():
  def __init__(self, supressGenerationOfAttribute=[]):
    self.supressGenerationOfAttribute = supressGenerationOfAttribute

  def getCreateExpandedNodeIDMacro(self, node):
    if node.id().i != None:
      return "UA_EXPANDEDNODEID_NUMERIC(" + str(node.id().ns) + ", " + str(node.id().i) + ")"
    elif node.id().s != None:
      return "UA_EXPANDEDNODEID_STRING("  + str(node.id().ns) + ", " + node.id().s + ")"
    elif node.id().b != None:
      logger.debug("NodeID Generation macro for bytestrings has not been implemented.")
      return ""
    elif node.id().g != None:
      logger.debug("NodeID Generation macro for guids has not been implemented.")
      return ""
    else:
      return ""

  def substitutePunctuationCharacters(self, input):
    ''' substitutePunctuationCharacters

        Replace punctuation characters in input. Part of this class because it is used by
        ua_namespace on occasion.

        returns: C-printable string representation of input
    '''
    # No punctuation characters <>!$
    for illegal_char in list(string.punctuation):
        if illegal_char == '_': # underscore is allowed
            continue
        input = input.replace(illegal_char, "_") # map to underscore
    return input

  def getNodeIdDefineString(self, node):
    code = []
    extrNs = node.browseName().split(":")
    symbolic_name = ""
    # strip all characters that would be illegal in C-Code
    if len(extrNs) > 1:
        nodename = extrNs[1]
    else:
        nodename = extrNs[0]

    symbolic_name = self.substitutePunctuationCharacters(nodename)
    if symbolic_name != nodename :
        logger.warn("Subsituted characters in browsename for nodeid " + str(node.id().i) + " while generating C-Code ")

    if symbolic_name in defined_typealiases:
      logger.warn(self, "Typealias definition of " + str(node.id().i) + " is non unique!")
      extendedN = 1
      while (symbolic_name+"_"+str(extendedN) in defined_typealiases):
        logger.warn("Typealias definition of " + str(node.id().i) + " is non unique!")
        extendedN+=1
      symbolic_name = symbolic_name+"_"+str(extendedN)

    defined_typealiases.append(symbolic_name)

    code.append("#define UA_NS"  + str(node.id().ns) + "ID_" + symbolic_name.upper() + " " + str(node.id().i))
    return code

  def getCreateNodeIDMacro(self, node):
    if node.id().i != None:
      return "UA_NODEID_NUMERIC(" + str(node.id().ns) + ", " + str(node.id().i) + ")"
    elif node.id().s != None:
      return "UA_NODEID_STRING("  + str(node.id().ns) + ", " + node.id().s + ")"
    elif node.id().b != None:
      logger.debug("NodeID Generation macro for bytestrings has not been implemented.")
      return ""
    elif node.id().g != None:
      logger.debug("NodeID Generation macro for guids has not been implemented.")
      return ""
    else:
      return ""

  def getCreateStandaloneReference(self, sourcenode, reference):
    code = []

    if reference.isForward():
      code.append("UA_Server_addReference(server, " + self.getCreateNodeIDMacro(sourcenode) + ", " + self.getCreateNodeIDMacro(reference.referenceType()) + ", " + self.getCreateExpandedNodeIDMacro(reference.target()) + ", true);")
    else:
      code.append("UA_Server_addReference(server, " + self.getCreateNodeIDMacro(sourcenode) + ", " + self.getCreateNodeIDMacro(reference.referenceType()) + ", " + self.getCreateExpandedNodeIDMacro(reference.target()) + ", false);")
    return code

  def getCreateNodeNoBootstrap(self, node, parentNode, parentReference, unprintedNodes):
    code = []
    code.append("// Node: " + str(node) + ", " + str(node.browseName()))

    if node.nodeClass() == NODE_CLASS_OBJECT:
      nodetype = "Object"
    elif node.nodeClass() == NODE_CLASS_VARIABLE:
      nodetype = "Variable"
    elif node.nodeClass() == NODE_CLASS_METHOD:
      nodetype = "Method"
    elif node.nodeClass() == NODE_CLASS_OBJECTTYPE:
      nodetype = "ObjectType"
    elif node.nodeClass() == NODE_CLASS_REFERENCETYPE:
      nodetype = "ReferenceType"
    elif node.nodeClass() == NODE_CLASS_VARIABLETYPE:
      nodetype = "VariableType"
    elif node.nodeClass() == NODE_CLASS_DATATYPE:
      nodetype = "DataType"
    elif node.nodeClass() == NODE_CLASS_VIEW:
      nodetype = "View"
    else:
      code.append("/* undefined nodeclass */")
      return code;

    # If this is a method, construct in/outargs for addMethod
    #inputArguments.arrayDimensionsSize = 0;
    #inputArguments.arrayDimensions = NULL;
    #inputArguments.dataType = UA_TYPES[UA_TYPES_STRING].typeId;

    # Node ordering should have made sure that arguments, if they exist, have not been printed yet
    if node.nodeClass() == NODE_CLASS_METHOD:
        inArgVal = []
        outArgVal = []
        code.append("UA_Argument *inputArguments = NULL;")
        code.append("UA_Argument *outputArguments = NULL;")
        for r in node.getReferences():
            if r.isForward():
                if r.target() != None and r.target().nodeClass() == NODE_CLASS_VARIABLE and r.target().browseName() == 'InputArguments':
                    while r.target() in unprintedNodes:
                        unprintedNodes.remove(r.target())
                    if r.target().value() != None:
                        inArgVal = r.target().value().value
                elif r.target() != None and r.target().nodeClass() == NODE_CLASS_VARIABLE and r.target().browseName() == 'OutputArguments':
                    while r.target() in unprintedNodes:
                        unprintedNodes.remove(r.target())
                    if r.target().value() != None:
                        outArgVal = r.target().value().value
        if len(inArgVal)>0:
            code.append("")
            code.append("inputArguments = (UA_Argument *) UA_malloc(sizeof(UA_Argument) * " + str(len(inArgVal)) + ");")
            code.append("int inputArgumentCnt;")
            code.append("for (inputArgumentCnt=0; inputArgumentCnt<" + str(len(inArgVal)) + "; ++inputArgumentCnt) UA_Argument_init(&inputArguments[inputArgumentCnt]); ")
            argumentCnt = 0
            for inArg in inArgVal:
                if inArg.getValueFieldByAlias("Description") != None:
                    code.append("inputArguments[" + str(argumentCnt) + "].description = UA_LOCALIZEDTEXT(\"" + str(inArg.getValueFieldByAlias("Description")[0]) + "\",\"" + str(inArg.getValueFieldByAlias("Description")[1]) + "\");")
                if inArg.getValueFieldByAlias("Name") != None:
                    code.append("inputArguments[" + str(argumentCnt) + "].name = UA_STRING(\"" + str(inArg.getValueFieldByAlias("Name")) + "\");")
                if inArg.getValueFieldByAlias("ValueRank") != None:
                    code.append("inputArguments[" + str(argumentCnt) + "].valueRank = " + str(inArg.getValueFieldByAlias("ValueRank")) + ";")
                if inArg.getValueFieldByAlias("DataType") != None:
                    code.append("inputArguments[" + str(argumentCnt) + "].dataType = " + str(self.getCreateNodeIDMacro(inArg.getValueFieldByAlias("DataType"))) + ";")
                #if inArg.getValueFieldByAlias("ArrayDimensions") != None:
                #  code.append("inputArguments[" + str(argumentCnt) + "].arrayDimensions = " + str(inArg.getValueFieldByAlias("ArrayDimensions")) + ";")
                argumentCnt += 1
        if len(outArgVal)>0:
            code.append("")
            code.append("outputArguments = (UA_Argument *) UA_malloc(sizeof(UA_Argument) * " + str(len(outArgVal)) + ");")
            code.append("int outputArgumentCnt;")
            code.append("for (outputArgumentCnt=0; outputArgumentCnt<" + str(len(outArgVal)) + "; ++outputArgumentCnt) UA_Argument_init(&outputArguments[outputArgumentCnt]); ")
            argumentCnt = 0
            for outArg in outArgVal:
                if outArg.getValueFieldByAlias("Description") != None:
                    code.append("outputArguments[" + str(argumentCnt) + "].description = UA_LOCALIZEDTEXT(\"" + str(outArg.getValueFieldByAlias("Description")[0]) + "\",\"" + str(outArg.getValueFieldByAlias("Description")[1]) + "\");")
                if outArg.getValueFieldByAlias("Name") != None:
                    code.append("outputArguments[" + str(argumentCnt) + "].name = UA_STRING(\"" + str(outArg.getValueFieldByAlias("Name")) + "\");")
                if outArg.getValueFieldByAlias("ValueRank") != None:
                    code.append("outputArguments[" + str(argumentCnt) + "].valueRank = " + str(outArg.getValueFieldByAlias("ValueRank")) + ";")
                if outArg.getValueFieldByAlias("DataType") != None:
                    code.append("outputArguments[" + str(argumentCnt) + "].dataType = " + str(self.getCreateNodeIDMacro(outArg.getValueFieldByAlias("DataType"))) + ";")
                #if outArg.getValueFieldByAlias("ArrayDimensions") != None:
                #  code.append("outputArguments[" + str(argumentCnt) + "].arrayDimensions = " + str(outArg.getValueFieldByAlias("ArrayDimensions")) + ";")
                argumentCnt += 1

    # print the attributes struct
    code.append("UA_%sAttributes attr;" % nodetype)
    code.append("UA_%sAttributes_init(&attr);" %  nodetype);
    code.append("attr.displayName = UA_LOCALIZEDTEXT(\"\", \"" + str(node.displayName()) + "\");")
    code.append("attr.description = UA_LOCALIZEDTEXT(\"\", \"" + str(node.description()) + "\");")
    
    if nodetype == "Variable":
      code.append("attr.accessLevel = %s;"     % str(node.accessLevel()))
      code.append("attr.userAccessLevel = %s;" % str(node.userAccessLevel()))
    if nodetype in ["Variable", "VariableType"]:
      code.append("attr.valueRank = %s;"       % str(node.valueRank()))
      
    if nodetype in ["Variable", "VariableType"]:
      code = code + node.printOpen62541CCode_SubtypeEarly(bootstrapping = False)
    elif nodetype == "Method":
      if node.executable():
        code.append("attr.executable = true;")
      if node.userExecutable():
        code.append("attr.userExecutable = true;")

    code.append("UA_NodeId nodeId = " + str(self.getCreateNodeIDMacro(node)) + ";")
    if nodetype in ["Object", "Variable", "VariableType"]:
      typeDefinition = None
      for r in node.getReferences():
        if r.isForward() and r.referenceType().id().ns == 0 and r.referenceType().id().i == 40:
          typeDefinition = r.target()
      if typeDefinition == None:
        # FIXME: We might want to resort to BaseXYTTypes here?
        code.append("UA_NodeId typeDefinition = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);")
      else:
        code.append("UA_NodeId typeDefinition = " + str(self.getCreateNodeIDMacro(typeDefinition)) + ";")
    code.append("UA_NodeId parentNodeId = " + str(self.getCreateNodeIDMacro(parentNode)) + ";")
    code.append("UA_NodeId parentReferenceNodeId = " + str(self.getCreateNodeIDMacro(parentReference.referenceType())) + ";")
    extrNs = node.browseName().split(":")
    if len(extrNs) > 1:
      code.append("UA_QualifiedName nodeName = UA_QUALIFIEDNAME(" +  str(extrNs[0]) + ", \"" + extrNs[1] + "\");")
    else:
      code.append("UA_QualifiedName nodeName = UA_QUALIFIEDNAME(0, \"" + str(node.browseName()) + "\");")

    # In case of a MethodNode: Add in|outArg struct generation here. Mandates that namespace reordering was done using
    # Djikstra (check that arguments have not been printed). (@ichrispa)
    code.append("UA_Server_add%sNode(server, nodeId, parentNodeId, parentReferenceNodeId, nodeName" % nodetype)

    if nodetype in ["Object", "Variable", "VariableType"]:
      code.append("       , typeDefinition")
    if nodetype != "Method":
      code.append("       , attr, NULL, NULL);")
    else:
      code.append("       , attr, (UA_MethodCallback) NULL, " + str(len(inArgVal)) + ", inputArguments,  " + str(len(outArgVal)) + ", outputArguments, NULL, NULL);")
      
    #Adding a Node with typeDefinition = UA_NODEID_NULL will create a HasTypeDefinition reference to BaseDataType - remove it since 
    #a real Reference will be add in a later step (a single HasTypeDefinition reference is assumed here)
    #The current API does not let us specify IDs of Object's subelements.
    if nodetype is "Object":
      code.append("UA_Server_deleteReference(server, nodeId, UA_NODEID_NUMERIC(0, 40), true, UA_EXPANDEDNODEID_NUMERIC(0, 58), true); //remove HasTypeDefinition refs generated by addObjectNode");
    if nodetype is "Variable":
      code.append("UA_Server_deleteReference(server, nodeId, UA_NODEID_NUMERIC(0, 40), true, UA_EXPANDEDNODEID_NUMERIC(0, 62), true); //remove HasTypeDefinition refs generated by addVariableNode");
    return code

  def getCreateNodeBootstrap(self, node):
    nodetype = ""
    code = []

    code.append("// Node: " + str(node) + ", " + str(node.browseName()))

    if node.nodeClass() == NODE_CLASS_OBJECT:
      nodetype = "Object"
    elif node.nodeClass() == NODE_CLASS_VARIABLE:
      nodetype = "Variable"
    elif node.nodeClass() == NODE_CLASS_METHOD:
      nodetype = "Method"
    elif node.nodeClass() == NODE_CLASS_OBJECTTYPE:
      nodetype = "ObjectType"
    elif node.nodeClass() == NODE_CLASS_REFERENCETYPE:
      nodetype = "ReferenceType"
    elif node.nodeClass() == NODE_CLASS_VARIABLETYPE:
      nodetype = "VariableType"
    elif node.nodeClass() == NODE_CLASS_DATATYPE:
      nodetype = "DataType"
    elif node.nodeClass() == NODE_CLASS_VIEW:
      nodetype = "View"
    else:
      code.append("/* undefined nodeclass */")
      return;

    code.append("UA_" + nodetype + "Node *" + node.getCodePrintableID() + " = UA_NodeStore_new" + nodetype + "Node();")
    if not "browsename" in self.supressGenerationOfAttribute:
      extrNs = node.browseName().split(":")
      if len(extrNs) > 1:
        code.append(node.getCodePrintableID() + "->browseName = UA_QUALIFIEDNAME_ALLOC(" +  str(extrNs[0]) + ", \"" + extrNs[1] + "\");")
      else:
        code.append(node.getCodePrintableID() + "->browseName = UA_QUALIFIEDNAME_ALLOC(0, \"" + node.browseName() + "\");")
    if not "displayname" in self.supressGenerationOfAttribute:
      code.append(node.getCodePrintableID() + "->displayName = UA_LOCALIZEDTEXT_ALLOC(\"en_US\", \"" +  node.displayName() + "\");")
    if not "description" in self.supressGenerationOfAttribute:
      code.append(node.getCodePrintableID() + "->description = UA_LOCALIZEDTEXT_ALLOC(\"en_US\", \"" +  node.description() + "\");")

    if not "writemask" in self.supressGenerationOfAttribute:
        if node.__node_writeMask__ != 0:
          code.append(node.getCodePrintableID() + "->writeMask = (UA_Int32) " +  str(node.__node_writeMask__) + ";")
    if not "userwritemask" in self.supressGenerationOfAttribute:
        if node.__node_userWriteMask__ != 0:
          code.append(node.getCodePrintableID() + "->userWriteMask = (UA_Int32) " + str(node.__node_userWriteMask__) + ";")
    if not "nodeid" in self.supressGenerationOfAttribute:
      if node.id().ns != 0:
        code.append(node.getCodePrintableID() + "->nodeId.namespaceIndex = " + str(node.id().ns) + ";")
      if node.id().i != None:
        code.append(node.getCodePrintableID() + "->nodeId.identifier.numeric = " + str(node.id().i) + ";")
      elif node.id().b != None:
        code.append(node.getCodePrintableID() + "->nodeId.identifierType = UA_NODEIDTYPE_BYTESTRING;")
        logger.error("ByteString IDs for nodes has not been implemented yet.")
        return []
      elif node.id().g != None:
        #<jpfr> the string is sth like { .length = 111, .data = <ptr> }
        #<jpfr> there you _may_ alloc the <ptr> on the heap
        #<jpfr> for the guid, just set it to {.data1 = 111, .data2 = 2222, ....
        code.append(node.getCodePrintableID() + "->nodeId.identifierType = UA_NODEIDTYPE_GUID;")
        logger.error(self, "GUIDs for nodes has not been implemented yet.")
        return []
      elif node.id().s != None:
        code.append(node.getCodePrintableID() + "->nodeId.identifierType = UA_NODEIDTYPE_STRING;")
        code.append(node.getCodePrintableID() + "->nodeId.identifier.numeric = UA_STRING_ALLOC(\"" + str(node.id().i) + "\");")
      else:
        logger.error("Node ID is not numeric, bytestring, guid or string. I do not know how to create c code for that...")
        return []

    return code