//
// Created by magino on 1. 10. 2019.
//

#ifndef UANODECONTEXT_H
#define UANODECONTEXT_H

#include <open62541/server.h>
#include <server/UaServer.h>
#include "UaObjects.h"

class UaNodeContext {
private:
    static UA_DataSource _dataSource;
    static UA_ValueCallback _valueCallback;
    static UA_NodeTypeLifecycle _nodeTypeLifeCycle;
    static UA_MethodCallback _methodCallback;
public:
    UaNodeId instanceNodeId;
    UaNodeContext() = default;
    UaNodeContext(int nsIndex): instanceNodeId(UaNodeId(nsIndex, UaNodeContext::GetUniqueNumericNodeId())) {}
    UaNodeContext(UaNodeId uaNodeId): instanceNodeId(uaNodeId) {}
    virtual ~UaNodeContext() = default;

    UaNodeId& GetInstanceNodeId() { return instanceNodeId; }

    static int UNIQUE_NUMERIC_NODEID_IN_APP_NAMESPACE;
    static int GetUniqueNumericNodeId() {
        return UNIQUE_NUMERIC_NODEID_IN_APP_NAMESPACE++;
    }

    static bool SetTypeLifeCycle(UaServer &server, UaNodeId &typeNodeId) {
        return UA_Server_setNodeTypeLifecycle(server.GetServer(), typeNodeId.GetNodeId(), _nodeTypeLifeCycle) == UA_STATUSCODE_GOOD;
    }

    static UA_StatusCode TypeConstructor(UA_Server *server,
                                        const UA_NodeId *sessionId, void *sessionContext,
                                        const UA_NodeId *typeNodeId, void *typeNodeContext, // typeNodeContext = ctx of type
                                        const UA_NodeId *nodeId, void **nodeContext) { // nodeContext = ctx of instance
        auto ret = (UA_StatusCode)(-1);
        if(server && nodeId && typeNodeId) {
            UaNodeContext *typeCtx = (UaNodeContext*)typeNodeContext;
            UaNodeId instanceNodeId(*nodeId);
            UaNodeId _typeNodeId(*typeNodeId);

            // TODO ?? mali by sa vykonat oba konstruktori
            if(typeCtx) { typeCtx->TypeConstruct(instanceNodeId, _typeNodeId, *nodeContext); }
            else {
                UaNodeContext *instanceCtx = (UaNodeContext *) (*nodeContext);
                if (instanceCtx) {
                    if (instanceCtx->TypeConstruct(instanceNodeId, _typeNodeId)) ret = UA_STATUSCODE_GOOD;
                }
            }
        }
        return ret;
    }

    static void TypeDestructor(UA_Server *server,
                               const UA_NodeId *sessionId, void *sessionContext,
                               const UA_NodeId *typeNodeId, void *typeNodeContext,
                               const UA_NodeId *nodeId, void **nodeContext) {
        if(server && nodeId && typeNodeId) {
            UaNodeContext *ctx = (UaNodeContext *)(*nodeContext);
            if(ctx) {
                UaNodeId instanceNodeId(*nodeId);
                UaNodeId _typeNodeId(*typeNodeId);
                ctx->TypeDestruct(instanceNodeId, _typeNodeId);
            }
        }
    }

    virtual bool TypeConstruct(UaNodeId& /*instanceNodeId*/, UaNodeId& /*TYPE_NODEID*/, void* /*instanceCtx*/ = nullptr) { return true; }
    virtual void TypeDestruct(UaNodeId& /*instanceNodeId*/, UaNodeId& typeNodeId) {
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Hello, from Node type: %s destructor!", typeNodeId.ToString().c_str());
    }

    bool SetAsDataSource(UaServer& server, UaNodeId& nodeId) {
        return UA_Server_setVariableNode_dataSource(server.GetServer(), nodeId.GetNodeId(), _dataSource) == UA_STATUSCODE_GOOD;
    }

    static UA_StatusCode ReadDataSource(UA_Server *server, const UA_NodeId *sessionId,
                                        void *sessionContext, const UA_NodeId *nodeId,
                                        void *nodeContext, UA_Boolean includeSourceTimeStamp,
                                        const UA_NumericRange *range, UA_DataValue *value) {
        UA_StatusCode ret = UA_STATUSCODE_GOOD;
        if(nodeContext) {
            UaNodeContext *nodeCtx = (UaNodeContext*)nodeContext; // require node contexts to be NULL or NodeContext objects
            if(nodeCtx && nodeId && value) {
                UaNodeId node(*nodeId);
                if(!nodeCtx->ReadData(node, range, *value))
                    ret = UA_STATUSCODE_BADDATAUNAVAILABLE;

                if(includeSourceTimeStamp) {
                    value->hasServerTimestamp = true;
                    value->sourceTimestamp = UA_DateTime_now();
                }
            }
        }
        return ret;
    }

    static UA_StatusCode WriteDataSource(UA_Server *server, const UA_NodeId *sessionId,
                                         void *sessionContext, const UA_NodeId *nodeId,
                                         void *nodeContext, const UA_NumericRange *range,
                                         const UA_DataValue *value) {
        UA_StatusCode ret = UA_STATUSCODE_GOOD;
        if(nodeContext) {
            UaNodeContext *nodeCtx = (UaNodeContext*)nodeContext;
            if(nodeCtx && nodeId && value) {
                UaNodeId node(*nodeId);
                if(!nodeCtx->WriteData(node, range, *value))
                    ret = UA_STATUSCODE_BADDATAUNAVAILABLE;
            }
        }
        return ret;
    }

    virtual bool ReadData(/*UaServer &server,*/ UaNodeId& /*node*/, const UA_NumericRange* /*range*/, UA_DataValue& /*value*/) { return false; }
    virtual bool WriteData(/*UaServer &server,*/ UaNodeId& /*node*/, const UA_NumericRange* /*range*/, const UA_DataValue& /*value*/) { return false; }

    bool SetValueCallback(UaServer& server, UaNodeId& node) {
        return UA_Server_setVariableNode_valueCallback(server.GetServer(), node.GetNodeId(), _valueCallback) == UA_STATUSCODE_GOOD;
    }

    static void ReadValueCallback(UA_Server *server, const UA_NodeId *sessionId,
                                  void *sessionContext, const UA_NodeId *nodeId,
                                  void *nodeContext, const UA_NumericRange *range,
                                  const UA_DataValue *value) {
        if(nodeContext && nodeId && value) {
            UaNodeContext *nodeCtx = (UaNodeContext*)nodeContext;

            UaNodeId node(*nodeId);
            nodeCtx->ReadValue(node, range, value);
        }
    }

    static void WriteValueCallback(UA_Server *server, const UA_NodeId *sessionId,
                                   void *sessionContext, const UA_NodeId *nodeId,
                                   void *nodeContext, const UA_NumericRange *range,
                                   const UA_DataValue *value) {
        // If write is internal do not write????
        if(sessionId->namespaceIndex == 0)
            return;

        if(nodeContext && nodeId && value){
            UaNodeContext *nodeCtx = (UaNodeContext *)(nodeContext);
            UaNodeId node(*nodeId);
            nodeCtx->WriteValue(node, range, *value);
        }
    }

    virtual void ReadValue(/*UaServer &server,*/ UaNodeId& /*nodeId*/, const UA_NumericRange* /*range*/, const UA_DataValue* /*value*/) {}
    virtual void WriteValue(/*UaServer &server,*/ UaNodeId& /*nodeId*/, const UA_NumericRange* /*range*/, const UA_DataValue& /*value*/) {}

    static UA_StatusCode MethodCallback(UA_Server *server, const UA_NodeId *sessionId,
                               void *sessionContext, const UA_NodeId *methodId,
                               void *methodContext, const UA_NodeId *objectId,
                               void *objectContext, size_t inputSize,
                               const UA_Variant *input, size_t outputSize,
                               UA_Variant *output) {
        UA_StatusCode status = UA_STATUSCODE_BADMETHODINVALID;

        //UaNodeContext *objectCtx = (UaNodeContext*)objectContext; // ctx of object type where method belongs
        UaNodeContext *objectCtx = (UaNodeContext*)methodContext;
        if(objectCtx) {
            UaNodeId methodNodeId(*methodId);
            status = objectCtx->CallBack(methodNodeId, inputSize, input, outputSize, output);
        }
        return status;
    }

    virtual UA_StatusCode CallBack(UaNodeId& /*methodNodeId*/,
                                   size_t /*inputSize*/,
                                   const UA_Variant */*input*/,
                                   size_t /*outputSize*/,
                                   UA_Variant */*output*/) {
        return UA_STATUSCODE_GOOD;
    }
};
#endif //UANODECONTEXT_H