//
// Created by magino on 10.4.19.
//

#include <csignal>
#include <thread>

#include <configuration/Configuration.h>
#include <communication/ModbusTcpClient.h>
#include <composite/NodeTree.h>
#include <compositeBuilder/S7CsvDataBlock/S7CsvCompositeBuilder.h>
#include <compositeBuilder/ICompositeBuilder.h>

#include <open62541/plugin/log_stdout.h>
#include <Management.h>
#include <ProcessCell.h>
#include "Reservation.h"

static UA_Boolean running = true;

static void stopHandler(int sign) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Received ctrl-c");
    running = false;
}

int main(int argc, char** argv) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Hello, from process cell project!");
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    Configuration cfg;
    auto commCfg = cfg.GetCommunicationConfig();
    if(commCfg.commType == ECommunication::None || commCfg.builderType == EBuilder::None) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Unknown definition of communication type or builder. Terminating ..");
        return -1;
    }

    /////////////////////////////////
    /* OPC UA SERVER CONFIGURATION */
    /////////////////////////////////
    auto serverCfg = cfg.GetServerConfig();
    MyUaServer uaServer(serverCfg);
    uaServer.AddNamespace(serverCfg.namespaceName);

    // Creating OPC UA TYPES
    Management::CreateUaObjectType(uaServer);
    Reservation::CreateUaObjectType(uaServer);
    ProcessCell::CreateUaObjectType(uaServer);


    // Instantiating process-cell objects
    ProcessCell cell(uaServer);
    cell.Instantiate(UaNodeId::ObjectsFolder, UaNodeId::Organizes, serverCfg.appName);
    Management management(uaServer);
    management.Instantiate(cell.GetInstanceNodeId(), UaNodeId::HasComponent, "Management");

    // Space in logs after creating object types and object instances in opc ua address space
    std::cout << std::endl << std::endl;

    /////////////////////////////////
    /* COMMUNICATION CONFIGURATION */
    /////////////////////////////////
    ICommunication* comm;
    ICompositeBuilder* builder;

    // Creating correct instances of communication and composite builder
    switch (commCfg.commType) {
        case ECommunication::ModbusTcp:
            comm = new ModbusTcpClient(commCfg.GetModbusTcpConfig());
            break;
    }

    switch (commCfg.builderType) {
        case EBuilder::S7Csv:
            auto s7CsvCfg = commCfg.GetS7CsvBuilderConfig();
            builder = new S7CsvCompositeBuilder(s7CsvCfg.csvFileName, *comm, uaServer, management);
            break;
    }

    INode* node = builder->BuildRoot().BuildTree().GetRootNode();
    NodeTree plcObjectStructure(*node);
    plcObjectStructure.BuildOpcUaAddressSpace(cell.GetInstanceNodeId());

    // Space in logs after creating object instances from PLC structure in opc ua address space
    std::cout << std::endl << std::endl;

    // Starting communication and opc ua server thread
    std::thread commThread(&ICommunication::StartComm, comm, &running);
    //std::thread uaServerThread(&MyUaServer::Run, &uaServer, &running);
    UA_Server_run_startup(uaServer.GetServer());
    // OPC UA server callbacks needs to be set after start up
    uaServer.SetServerCallbacks();
    std::thread uaServerThread(&MyUaServer::Iterate, &uaServer, &running);

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Joining Threads.");
    commThread.join();
    uaServerThread.join();
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Threads finished.");
    return 0;
}