//
// Created by magino on 11.4.19.
//

#ifndef UASERVER_H
#define UASERVER_H

#include <open62541/server.h>
#include <open62541/server_config.h>
#include <open62541/plugin/log_stdout.h>
#include <core/UaVariant.h>

#include "client/UaClient.h"
#include "core/UaNodeId.h"

class UaServer {
private:
    UA_Server* _server;
    std::thread _thread;
    int _appNsIndex; // Ns Index where are object types and instances of this application

    /**
     * This callback is called if another server registeres or unregisters with this instance.
     */
    static void RegisterServerCallback(const UA_RegisteredServer *registeredServer, void* data);

    /**
     * This callback runs when LDS ME server announce itself on network.
     */
    static void ServerOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean isServerAnnounce, UA_Boolean isTxtReceived, void *data);
public:
    std::map<std::string, UaClient*> _registerClients;
    UA_Server* GetServer() { return _server; }
    UaClient* GetRegisterClient(std::string endpointUrl);

    /**
    * Server Lifecycle
    * ---------------- */
    UaServer();
    UaServer(unsigned short port,
             UA_ByteString* certificate,
             UA_ApplicationDescription appDescription,
             UA_MdnsDiscoveryConfiguration mdnsCfg,
             std::string customHostname = "");
    virtual ~UaServer() { CleanUp(); }

    int GetAppNsIndex() { return _appNsIndex; }
    void CleanUp();

    // These callbacks needs to be set after UA_Server_run_startup or UA_Server_run
    void SetServerCallbacks() {
        // Callback which is called when a new server is detected through mDNS, needs to be set after UA_Server_run_startup or UA_Server_run
        UA_Server_setServerOnNetworkCallback(_server, ServerOnNetworkCallback, this);
        //UA_Server_setRegisterServerCallback(_server, RegisterServerCallback, this);
    }

    bool Run(volatile UA_Boolean* running);
    bool Iterate(volatile UA_Boolean* running) {
        while (*running) {
            UA_Server_run_iterate(_server, true);
        }
    }
    bool RunAsync(volatile UA_Boolean* running);
    // UA_Server_run_startup
    // UA_Server_run_iterate
    // UA_Server_run_shutdown

    /**
     * Timed Callbacks
     * --------------- */
    // -> callback UA_ServerCallback(UA_Server *server, void *data)
    //UA_Server_addTimedCallback(UA_Server *server, UA_ServerCallback callback, void *data, UA_DateTime date, UA_UInt64 *callbackId); // Runs just once
    //UA_Server_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, void *data, UA_Double interval_ms, UA_UInt64 *callbackId);
    //UA_Server_changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId, UA_Double interval_ms);

    //UA_Server_removeCallback(UA_Server *server, UA_UInt64 callbackId); // Remove a repeated callback. Does nothing if the callback is not found.

    /**
     * Reading and Writing Node Attributes
     * ----------------------------------- */
     // ReadBrowseName()
    std::string ReadBrowseName(UaNodeId& nodeId) {
        UA_QualifiedName qualifiedName;
        UA_QualifiedName_init(&qualifiedName);
        UA_Server_readBrowseName(_server, nodeId.GetNodeId(), &qualifiedName);
        return ToStdString(qualifiedName.name);
        //return qualifiedName;
    }

    bool WriteValue(UaNodeId &nodeId, UA_Variant &value) {
        return UA_Server_writeValue(_server, nodeId.GetNodeId(), value) == UA_STATUSCODE_GOOD;
    }


    /**
     * Browsing
     * -------- */
    bool Browse(UA_BrowseDescription* desc, ReferenceDescriptionArray& references, unsigned maxReferences = 0) {
        UA_BrowseResult result = UA_Server_browse(_server, maxReferences, desc);
        if(result.statusCode != UA_STATUSCODE_GOOD) return false;
        references.setList(result.referencesSize, result.references);
        // result.continuationPoint TODO what is continuationPoint?
        return true;
    }
    /**
     *
     * @param references
     * @param maxReferences The value 0 indicates that the Client is imposing no limitation.
     * @return
     */
    bool BrowseRoot(ReferenceDescriptionArray& references, unsigned maxReferences = 0) {
        UA_BrowseDescription* desc = UA_BrowseDescription_new();
        desc->nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
        desc->browseDirection = UA_BROWSEDIRECTION_FORWARD;
        desc->nodeClassMask = UA_NODECLASS_OBJECT | UA_NODECLASS_METHOD | UA_NODECLASS_VARIABLE;
        desc->includeSubtypes = true;
        desc->referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
        desc->resultMask = UA_BROWSERESULTMASK_ALL;

        return Browse(desc, references, maxReferences);
    }

    bool BrowseNode(UaNodeId startNodeId, ReferenceDescriptionArray& references, unsigned maxReferences = 0) {
        UA_BrowseDescription* desc = UA_BrowseDescription_new();
        desc->nodeId = startNodeId.GetNodeId();
        desc->browseDirection = UA_BROWSEDIRECTION_FORWARD;
        desc->nodeClassMask = UA_NODECLASS_OBJECT | UA_NODECLASS_METHOD | UA_NODECLASS_VARIABLE;
        desc->includeSubtypes = true;
        desc->referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
        desc->resultMask = UA_BROWSERESULTMASK_ALL;

        return Browse(desc, references, maxReferences);
    }

    // if there is more references to return then maxReferences, UA_Server_browse will return 'continuationPoint'
    // which is used to get rest of references with UA_Server_browseNext
    // UA_Server_browseNext(UA_Server *server, UA_Boolean releaseContinuationPoint, const UA_ByteString *continuationPoint);
    // UA_Server_browseRecursive(UA_Server *server, const UA_BrowseDescription *bd, size_t *resultsSize, UA_ExpandedNodeId **results);

    // UA_Server_translateBrowsePathToNodeIds(UA_Server *server, const UA_BrowsePath *browsePath);
    UA_BrowsePathResult TranslateBrowsePathToNodeIds(UA_BrowsePath& bp) {
        UA_BrowsePathResult browsePathResult = UA_Server_translateBrowsePathToNodeIds(_server, &bp);
        return browsePathResult;
    }

    UaNodeId FindSingleNodeId(UaNodeId& originNodeId, const std::string& name, int namespaceIndex) {
        UA_RelativePathElement rpe;
        UA_RelativePathElement_init(&rpe);
        rpe.referenceTypeId = UaNodeId::HasComponent.GetNodeId();
        rpe.isInverse = false;
        rpe.includeSubtypes = false;
        rpe.targetName = UA_QUALIFIEDNAME(namespaceIndex, (char*)&name[0]);

        UA_BrowsePath bp;
        UA_BrowsePath_init(&bp);
        bp.startingNode = originNodeId.GetNodeId();
        bp.relativePath.elementsSize = 1;
        bp.relativePath.elements = &rpe;

        UA_BrowsePathResult result = TranslateBrowsePathToNodeIds(bp);
        if (result.statusCode == UA_STATUSCODE_GOOD && result.targetsSize == 1) {
            return (result.targets[0].targetId.nodeId);
        }
        return UaNodeId::Null;
    }

    // UA_Server_browseSimplifiedBrowsePath(UA_Server *server, const UA_NodeId origin, size_t browsePathSize, const UA_QualifiedName *browsePath);
    UA_BrowsePathResult BrowseSimplifiedBrowsePath(UaNodeId& originNodeId, QualifiedNameArray& names) {
        return UA_Server_browseSimplifiedBrowsePath(_server, originNodeId.GetNodeId(), names.length(), names.data());
    }

    //Search for node id of node with name (node must be direct descendant)
    UaNodeId BrowseSimplifiedSingleNodeId(UaNodeId& originNodeId, const std::string& name, int namespaceIndex) {
        std::string nm = name;
        UA_QualifiedName qName = UA_QUALIFIEDNAME(namespaceIndex, &nm[0]);

        UA_BrowsePathResult result = UA_Server_browseSimplifiedBrowsePath(_server, originNodeId.GetNodeId(), 1, &qName);
        if(result.statusCode == UA_STATUSCODE_GOOD && result.targetsSize == 1) {
            return (result.targets[0].targetId.nodeId);
        }
        return UaNodeId::Null;
    }

    // UA_NodeIteratorCallback
    // UA_Server_forEachChildNodeCall()


    /**
     * Discovery
     * --------- */

     /**
      * Call this method after the register client is connected to correct endpoint on server
      * @return status
      */
    bool RegisterServer(UaClient& client) {
        return UA_Server_register_discovery(_server, client.GetClient(), nullptr) == UA_STATUSCODE_GOOD;
    }

    bool UnregisterServer(UaClient& client) {
        return UA_Server_unregister_discovery(_server, client.GetClient()) == UA_STATUSCODE_GOOD;
    }

    /**
     *
     * @param endpointUrl
     * @param period
     * @param delay
     * @param periodicCallbackId Id of created periodic callback. Use it for UA_Server_removeCallback when unregister server.
     * @return
     */
    bool AddPeriodicServerRegister(UaClient* client, std::string endpointUrl, unsigned period, unsigned delay, uint64_t* periodicCallbackId);

    virtual void ServerRegisterActivity() {}

    /**
     * Override this method for implementing own functionality when some LDS server announces itself on network
     * @param serverOnNetwork
     */
    virtual void ServerOnNetwork(const UA_ServerOnNetwork *serverOnNetwork) {}

    /**
     * Information Model Callbacks
     * ---------------------------
     * Node Lifecycle: Constructors, Destructors and Node Contexts
     * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
    // UA_GlobalNodeLifecycle constructor/destructor
    // UA_NodeTypeLifecycle constructor/destructor

    // UA_Server_setNodeTypeLifecycle
    bool SetTypeLifeCycle(UaNodeId &typeNodeId, UA_NodeTypeLifecycle nodeTypeLifecycle) {
        return UA_Server_setNodeTypeLifecycle(_server, typeNodeId.GetNodeId(), nodeTypeLifecycle) == UA_STATUSCODE_GOOD;
    }
    // UA_Server_getNodeContext


    // UA_Server_setNodeContext
    bool SetNodeContext(UaNodeId& nodeId, void* ctx) {
        return UA_Server_setNodeContext(_server, nodeId.GetNodeId(), ctx) == UA_STATUSCODE_GOOD;
    }


    /**
     * Data Source Callback
     * ^^^^^^^^^^^^^^^^^^^^
     * The server has a unique way of dealing with the content of variables. Instead
     * of storing a variant attached to the variable node, the node can point to a
     * function with a local data provider. Whenever the value attribute is read,
     * the function will be called and asked to provide a UA_DataValue return value
     * that contains the value content and additional timestamps.
     */
     // UA_DataSource read/write
     // UA_Server_setVariableNode_dataSource(UA_Server *server, const UA_NodeId nodeId, const UA_DataSource dataSource);

    /**
     * Value Callback
     * ^^^^^^^^^^^^^^
     * Value Callbacks can be attached to variable and variable type nodes. If
     * not ``NULL``, they are called before reading and after writing respectively.
     */
     // UA_ValueCallback onRead/onWrite
     //UA_Server_setVariableNode_valueCallback(UA_Server *server, const UA_NodeId nodeId, const UA_ValueCallback callback);

    /**
     * Local MonitoredItems
     * ^^^^^^^^^^^^^^^^^^^^ */
     // UA_Server_DataChangeNotificationCallback
     // UA_Server_EventNotificationCallback
     // UA_Server_createDataChangeMonitoredItem
     // UA_Server_deleteMonitoredItem

    /**
     * Method Callbacks
     * ^^^^^^^^^^^^^^^^ */
     // UA_MethodCallback

     // UA_Server_setMethodNode_callback
     bool SetMethodCallback(UaNodeId& methodNodeId, UA_MethodCallback callback) {
        return UA_Server_setMethodNode_callback(_server,
                                         methodNodeId.GetNodeId(),
                                         callback) == UA_STATUSCODE_GOOD;
     }

    /**
     * Interacting with Objects
     * ------------------------ */
     // UA_Server_writeObjectProperty
     // UA_Server_writeObjectProperty_scalar
     // UA_Server_readObjectProperty
     // UA_Server_call(UA_Server *server, const UA_CallMethodRequest *request);

    /**
     * Node Addition and Deletion
     * -------------------------- */
     // UA_Server_addVariableNode
     UaNodeId AddVariableNode(std::string name,
                              std::string browseName,
                              std::string description,
                              bool isMandatory,
                              UA_Byte accessLevel,
                              unsigned short type,
                              int valueRank,
                              int numberOfDimensions,
                              unsigned int sizeOfEachDimension[], // array of int
                              UaNodeId& parent,
                              UaNodeId& requestedNodeId = UaNodeId::Null,
                              UaNodeId& reference = UaNodeId::HasComponent,
                              UaNodeId& variableOfType = UaNodeId::BaseDataVariableType,
                              void* ctx = nullptr) {
         UA_VariableAttributes varAttr = UA_VariableAttributes_default;

         varAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US",&name[0]);
         varAttr.description = UA_LOCALIZEDTEXT((char*)"en-US",&description[0]);
         varAttr.accessLevel = accessLevel;
         varAttr.dataType = UA_TYPES[type].typeId;
         varAttr.valueRank = valueRank;

         if (numberOfDimensions != 0 && sizeOfEachDimension != nullptr) {
             // TODO check if numberOfDimensions == sizeOfEachDimension.length()
             // Value rank urcuje rozne kombinacie hodnoty scalar/dimenzie
             // Dimenzie urcuju uz presny pocet dimenzi a ich velkost
             /*
                 n >= 1: the value is an array with the specified number of dimensions
                 n =  0: the value is an array with one or more dimensions
                 n = -1: the value is a scalar
                 n = -2: the value can be a scalar or an array with any number of dimensions
                 n = -3: the value can be a scalar or a one dimensional array
              */
             //[n]  n = pocet dimenzii
             //{y1,y2,..,yn} = y1..yn = dlzka kazdej dimenzie

             varAttr.arrayDimensions = sizeOfEachDimension;
             varAttr.arrayDimensionsSize = numberOfDimensions;
         }

         //varAttr.value;
         /* vAttr.value is left empty, the server instantiates with the default value */

         UaNodeId variableNodeId;
         auto statusCode = UA_Server_addVariableNode(_server,
                                   requestedNodeId.GetNodeId(),
                                   parent.GetNodeId(),
                                   reference.GetNodeId(),
                                   UA_QUALIFIEDNAME(_appNsIndex, &browseName[0]),
                                   variableOfType.GetNodeId(),
                                   varAttr,
                                   ctx,
                                   requestedNodeId.IsNull() ? variableNodeId.Get() : NULL);

         bool status = statusCode == UA_STATUSCODE_GOOD;

         if(isMandatory && status) {
             bool statusRef = UA_Server_addReference(_server, requestedNodeId.IsNull() ? variableNodeId.GetNodeId() : requestedNodeId.GetNodeId(),
                                    UaNodeId::HasModellingRule.GetNodeId(), UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY)/*UaExpandedNodeId::ModellingRuleMandatory.GetExpNodeId()*/, true) == UA_STATUSCODE_GOOD;

             if(statusRef) {
                 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Variable Node - %s with Mandatory Reference.", &name[0]);
                 return requestedNodeId.IsNull() ? variableNodeId : requestedNodeId;
             } else {
                 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Variable Node - %s added but Mandatory Reference failed.", &name[0]);
                 return UaNodeId::Null;
             }
         }

         if(status) {
             UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Variable Node - %s.", &name[0]);
             return requestedNodeId.IsNull() ? variableNodeId : requestedNodeId;
         }
         UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error Adding Variable Node - %s. %s", &name[0], UA_StatusCode_name(statusCode));
         return UaNodeId::Null;
     }

     // UA_Server_addVariableTypeNode
     UaNodeId AddVariableTypeNode(std::string name,
                                  std::string browseName,
                                  std::string description,
                                  unsigned short type,
                                  UaVariant& defValue,
                                  int valueRank,
                                  int numberOfDimensions = 0,
                                  unsigned int sizeOfEachDimension[] = nullptr, // array of int
                                  UaNodeId& requestedNodeId = UaNodeId::Null,
                                  UaNodeId& parent = UaNodeId::BaseDataVariableType,
                                  UaNodeId& variableOfType = UaNodeId::BaseDataVariableType,
                                  void* typeCtx = nullptr) {
         UA_VariableTypeAttributes varTypeAttr = UA_VariableTypeAttributes_default;

         varTypeAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US",&name[0]);
         varTypeAttr.description = UA_LOCALIZEDTEXT((char*)"en-US",&description[0]);
         varTypeAttr.dataType = UA_TYPES[type].typeId;
         varTypeAttr.valueRank = valueRank;
         varTypeAttr.value = defValue.GetVariant();     // A matching default value is required

         if (numberOfDimensions != 0 && sizeOfEachDimension != nullptr) {
             // TODO check if numberOfDimensions == sizeOfEachDimension.length()
             // Value rank urcuje rozne kombinacie hodnoty scalar/dimenzie
             // Dimenzie urcuju uz presny pocet dimenzi a ich velkost
             /*
                 n >= 1: the value is an array with the specified number of dimensions
                 n =  0: the value is an array with one or more dimensions
                 n = -1: the value is a scalar
                 n = -2: the value can be a scalar or an array with any number of dimensions
                 n = -3: the value can be a scalar or a one dimensional array
              */
             //[n]  n = pocet dimenzii
             //{y1,y2,..,yn} = y1..yn = dlzka kazdej dimenzie

             varTypeAttr.arrayDimensions = sizeOfEachDimension;
             varTypeAttr.arrayDimensionsSize = numberOfDimensions;
         }

         // TODO mozno spavit ako parameter aj reference
         UaNodeId variableTypeNodeId;
         auto statusCode = UA_Server_addVariableTypeNode(_server,
                                       requestedNodeId.GetNodeId(),
                                       parent.GetNodeId(),
                                       UaNodeId::HasSubType.GetNodeId(),
                                       UA_QUALIFIEDNAME(_appNsIndex, &browseName[0]),
                                       variableOfType.GetNodeId(),// If this variable type is not derived from another variable type NULL
                                       varTypeAttr,
                                       typeCtx,
                                       requestedNodeId.IsNull() ? variableTypeNodeId.Get() : NULL);

         bool status = statusCode == UA_STATUSCODE_GOOD;

         if(status) {
             UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Variable Type Node - %s.", &name[0]);
             return requestedNodeId.IsNull() ? variableTypeNodeId : requestedNodeId;
         }
         UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error adding Variable Type Node - %s. %s", &name[0], UA_StatusCode_name(statusCode));
         return UaNodeId::Null;
     }

     // UA_Server_addObjectNode
     UaNodeId AddObjectNode(std::string name,
                            std::string browseName,
                            std::string description,
                            bool isMandatory,
                            UaNodeId& parent,
                            UaNodeId& requestedNodeId = UaNodeId::Null,
                            UaNodeId& reference = UaNodeId::HasComponent,
                            UaNodeId& objectOfType = UaNodeId::BaseObjectType,
                            void* nodeCtx = nullptr) {
         UA_ObjectAttributes objectAttr = UA_ObjectAttributes_default;

         objectAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", &name[0]);
         objectAttr.description = UA_LOCALIZEDTEXT((char*)"en-US", &description[0]);

         UaNodeId objectNodeId;


         auto statusCode = UA_Server_addObjectNode(_server,
                                     requestedNodeId.GetNodeId(),
                                     parent.GetNodeId(),
                                     reference.GetNodeId(),
                                     UA_QUALIFIEDNAME(_appNsIndex, &browseName[0]),
                                     objectOfType.GetNodeId(),
                                     objectAttr,
                                     nodeCtx,
                                     requestedNodeId.IsNull() ? objectNodeId.Get() : NULL);
        if(statusCode != UA_STATUSCODE_GOOD) {
             UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error adding Object Node - %s. %s", &name[0], UA_StatusCode_name(statusCode));
             return UaNodeId::Null;
         }

         if(isMandatory) {
             bool statusRef = UA_Server_addReference(_server, requestedNodeId.IsNull() ? objectNodeId.GetNodeId() : requestedNodeId.GetNodeId(), UaNodeId::HasModellingRule.GetNodeId(),
                                                     UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY)/*UaExpandedNodeId::ModellingRuleMandatory.GetExpNodeId()*/, true) == UA_STATUSCODE_GOOD;

             // TODO ak sa nepodarilo toto tak co sa ma stat?
             if(statusRef) {
                 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Object Node - %s with Mandatory Reference.", &name[0]);
                 return requestedNodeId.IsNull() ? objectNodeId : requestedNodeId;
             }
             else {
                 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Object Node - %s added but Reference Mandatory failed.", &name[0]);
                 return UaNodeId::Null;
             }
         }

         UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Object Node - %s.", &name[0]);
         return requestedNodeId.IsNull() ? objectNodeId : requestedNodeId;;
     }

     // UA_Server_addObjectTypeNode
     UaNodeId AddObjectTypeNode(std::string name,
                                std::string browseName,
                                std::string description,
                                UaNodeId& requestedNodeId = UaNodeId::Null,
                                UaNodeId& parent = UaNodeId::BaseObjectType,
                                void* typeCtx = nullptr) {
         UA_ObjectTypeAttributes objectTypeAttr = UA_ObjectTypeAttributes_default;

         objectTypeAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", &name[0]);
         objectTypeAttr.description = UA_LOCALIZEDTEXT((char*)"en-US", &description[0]);

         UaNodeId objectTypeNodeId;
         auto statusCode = UA_Server_addObjectTypeNode(_server,
                                        requestedNodeId.GetNodeId(),
                                        parent.GetNodeId(),
                                        UaNodeId::HasSubType.GetNodeId(),
                                        UA_QUALIFIEDNAME(_appNsIndex, &browseName[0]),
                                        objectTypeAttr,
                                        typeCtx, // This is node type context? so every node(instance) of this type will have acces to this ctx???
                                        requestedNodeId.IsNull() ? objectTypeNodeId.Get() : NULL);
         if(statusCode != UA_STATUSCODE_GOOD) {
             UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error Adding Object Type Node - %s. %s", &name[0], UA_StatusCode_name(statusCode));
             return UaNodeId::Null;
         }
         UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Object Type Node - %s.", &name[0]);
         return requestedNodeId.IsNull() ? objectTypeNodeId : requestedNodeId;;
     }
     // UA_Server_addViewNode
     // UA_Server_addReferenceTypeNode
     // UA_Server_addDataTypeNode
     // UA_Server_addDataSourceVariableNode
     // UA_Server_addMethodNodeEx

     // UA_Server_addMethodNode
     UaNodeId AddMethodNode(std::string name,
                            std::string browseName,
                            std::string description,
                            bool isMandatory,
                            ArgumentList& input,
                            ArgumentList& output,
                            UaNodeId& parent,
                            UaNodeId& requestedNodeId = UaNodeId::Null,
                            UaNodeId& reference = UaNodeId::HasComponent,
                            UA_MethodCallback callBack = NULL,
                            void* ctx = nullptr) {

         UA_MethodAttributes methodAttr = UA_MethodAttributes_default;
         methodAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", &name[0]);
         methodAttr.description = UA_LOCALIZEDTEXT((char*)"en-US", &description[0]);
         methodAttr.executable = true;
         methodAttr.userExecutable = true;
         //methodAttr.writeMask
         //methodAttr.userWriteMask

         UaNodeId methodNodeId;
         auto statusCode = UA_Server_addMethodNode(_server,
                                               requestedNodeId.GetNodeId(),
                                               parent.GetNodeId(),
                                               reference.GetNodeId(),
                                               UA_QUALIFIEDNAME(_appNsIndex, &browseName[0]),
                                               methodAttr,
                                               callBack,
                                               input.size(), input.GetArguments().data(),
                                               output.size(), output.GetArguments().data(),
                                               ctx,
                                               requestedNodeId.IsNull() ? methodNodeId.Get() : NULL);
         bool status = statusCode == UA_STATUSCODE_GOOD;

         if(isMandatory && status) {
             bool statusRef = UA_Server_addReference(_server, requestedNodeId.IsNull() ? methodNodeId.GetNodeId() : requestedNodeId.GetNodeId(),
                                                     UaNodeId::HasModellingRule.GetNodeId(),
                                                     UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY)/*UaExpandedNodeId::ModellingRuleMandatory.GetExpNodeId()*/, true) == UA_STATUSCODE_GOOD;

             if(statusRef) {
                 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Method Node - %s with Mandatory Reference.", &name[0]);
                 return requestedNodeId.IsNull() ? methodNodeId : requestedNodeId;
             }
             else {
                 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Method Node - %s added but Mandatory Reference failed.", &name[0]);
                 return UaNodeId::Null;
             }
         }
         if(status) {
             UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Added Method Node - %s.", &name[0]);
             return requestedNodeId.IsNull() ? methodNodeId : requestedNodeId;
         }
         UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error Adding Method Node - %s. %s", &name[0], UA_StatusCode_name(statusCode));
         return UaNodeId::Null;
     }
     // ?????????????????????
     // UA_Server_addNode_begin
     // UA_Server_addNode_finish
     // UA_Server_addMethodNode_finish

     // UA_Server_deleteNode
     bool DeleteNode(UaNodeId& nodeId, bool deteleReferences) {
         return UA_Server_deleteNode(_server, nodeId.GetNodeId(), deteleReferences) == UA_STATUSCODE_GOOD;
     }

     // UA_Server_addReference
    /* void AddReference(UaNodeId sourceNodeId,
                       UaNodeId referenceNodeId,
                       UaExpandedNodeId targetNodeId,
                       bool isForward = true) {
         UA_Server_addReference(_server,
                 sourceNodeId.GetNodeId(),
                                referenceNodeId.GetNodeId(),
                                targetNodeId.GetNodeId(),
                                isForward)
     }*/
     // UA_Server_deleteReference

    /**
     * Events
     * ------ */
     // UA_Server_createEvent
     // UA_Server_triggerEvent


     // UA_Server_updateCertificate

    /**
     * Utility Functions
     * ----------------- */
    int AddNamespace(std::string& ns) {
        _appNsIndex = UA_Server_addNamespace(_server, &ns[0]);
        return _appNsIndex;
    }

    int GetNamespaceByName(const std::string& namespaceUri) {
        size_t index;
        if(UA_Server_getNamespaceByName(_server, ToUA_String(namespaceUri), &index) == UA_STATUSCODE_GOOD)
            return index;

        index = -1;
        return index;
    }

     // UA_Server_AccessControl_allowHistoryUpdateUpdateData
     // UA_Server_AccessControl_allowHistoryUpdateDeleteRawModified
};

#endif //UASERVER_H