#include "opcuaserver.h"
#include <QDebug>
#include "open62541.h"

static bool running = false;
static OpcUaWorker* globalWorker = nullptr;

static void
writeMicCallback(UA_Server *server,
                 const UA_NodeId *sessionId, void *sessionContext,
                 const UA_NodeId *nodeId, void *nodeContext,
                 const UA_NumericRange *range, const UA_DataValue *data) {
    if(data->hasValue && data->value.type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
        UA_Boolean val = *(UA_Boolean*)data->value.data;
        qDebug() << "OPC-UA: Mic changed to" << val;
        if (globalWorker) emit globalWorker->micToggled(val);
    }
}

static void
writeTorchCallback(UA_Server *server,
                   const UA_NodeId *sessionId, void *sessionContext,
                   const UA_NodeId *nodeId, void *nodeContext,
                   const UA_NumericRange *range, const UA_DataValue *data) {
    if(data->hasValue && data->value.type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
        UA_Boolean val = *(UA_Boolean*)data->value.data;
        qDebug() << "OPC-UA: Torch changed to" << val;
        if (globalWorker) emit globalWorker->torchToggled(val);
    }
}

OpcUaWorker::OpcUaWorker() : m_running(false) {
    globalWorker = this;
}

OpcUaWorker::~OpcUaWorker() {
    globalWorker = nullptr;
}

void OpcUaWorker::startServer() {
    m_running = true;
    running = true;
    
    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    // Port 4840 is default
    
    // Add Mic Node
    UA_VariableAttributes micAttr = UA_VariableAttributes_default;
    UA_Boolean micVal = true;
    UA_Variant_setScalar(&micAttr.value, &micVal, &UA_TYPES[UA_TYPES_BOOLEAN]);
    micAttr.description = UA_LOCALIZEDTEXT("en-US", "Remote Mic Enable");
    micAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Remote Mic Enable");
    micAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    
    UA_NodeId micNodeId = UA_NODEID_STRING(1, "mic_enable");
    UA_Server_addVariableNode(server, micNodeId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                              UA_QUALIFIEDNAME(1, "Mic Enable"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
                              micAttr, NULL, NULL);

    UA_ValueCallback callback;
    callback.onRead = NULL;
    callback.onWrite = writeMicCallback;
    UA_Server_setVariableNode_valueCallback(server, micNodeId, callback);

    // Add Torch Node
    UA_VariableAttributes torchAttr = UA_VariableAttributes_default;
    UA_Boolean torchVal = false;
    UA_Variant_setScalar(&torchAttr.value, &torchVal, &UA_TYPES[UA_TYPES_BOOLEAN]);
    torchAttr.description = UA_LOCALIZEDTEXT("en-US", "Remote Torch Enable");
    torchAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Remote Torch Enable");
    torchAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    
    UA_NodeId torchNodeId = UA_NODEID_STRING(1, "torch_enable");
    UA_Server_addVariableNode(server, torchNodeId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                              UA_QUALIFIEDNAME(1, "Torch Enable"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
                              torchAttr, NULL, NULL);

    UA_ValueCallback tCallback;
    tCallback.onRead = NULL;
    tCallback.onWrite = writeTorchCallback;
    UA_Server_setVariableNode_valueCallback(server, torchNodeId, tCallback);

    qDebug() << "Starting OPC-UA Server on port 4840";
    
    // Run loop
    while(m_running && running) {
        UA_Server_run_iterate(server, false);
        QThread::msleep(10);
    }
    
    UA_Server_delete(server);
    qDebug() << "OPC-UA Server stopped";
}

void OpcUaWorker::stopServer() {
    m_running = false;
    running = false;
}

OpcUaServer::OpcUaServer(QObject *parent) : QObject(parent) {
    m_worker = new OpcUaWorker();
    m_worker->moveToThread(&m_workerThread);
    
    connect(&m_workerThread, &QThread::started, m_worker, &OpcUaWorker::startServer);
    connect(&m_workerThread, &QThread::finished, m_worker, &QObject::deleteLater);
    
    connect(m_worker, &OpcUaWorker::micToggled, this, &OpcUaServer::micToggled);
    connect(m_worker, &OpcUaWorker::torchToggled, this, &OpcUaServer::torchToggled);
}

OpcUaServer::~OpcUaServer() {
    stop();
    m_workerThread.quit();
    m_workerThread.wait();
}

void OpcUaServer::start() {
    m_workerThread.start();
}

void OpcUaServer::stop() {
    if (m_worker) {
        m_worker->stopServer();
    }
}
