//
// Created by magino on 21. 4. 2020.
//

#ifndef PROCESSCELL_H
#define PROCESSCELL_H

class ProcessCellClient {
public:
    static const int MY_NAMESPACE_INDEX;
    static const std::string DISCOVERY_SERVER_ENDPOINT;

    static const std::string CELL_BROWSENAME;
    static const std::string INFO_SERVICE_BROWSENAME;
    static const std::string CELLID_VAR_BROWSENAME;

    static const std::string MANAGEMENT_SERVICE_BROWSENAME;
    static const std::string RESERVE_METHOD_BROWSENAME;
    static const std::string DELETE_METHOD_BROWSENAME;
    static const std::string ACTUALRESID_VAR_BROWSENAME;

    static const std::string MANUFACTURING_SERVICE_BROWSENAME;
    static const std::string RUNACTION_METHOD_BROWSENAME;
    static const std::string STATE_VAR_BROWSENAME;
    static const std::string STATUS_VAR_BROWSENAME;
    static const std::string DONECMD_VAR_BROWSENAME;

    UaClient cellUaClient;
    UaSubscription cellSubscription;
    uint32_t connectedCellId;
    uint64_t cellReservationId;
    unsigned reservationMonitoredItemId;

    bool isConnected;
    bool iterateForSub;

    bool CanIterateSub() const { return isConnected && iterateForSub; }

    UaNodeId CellId;

    UaNodeId CellInfoId;
    UaNodeId CellIdId;

    UaNodeId CellManagementId;
    UaNodeId CellMakeReservationId;
    UaNodeId CellDeleteReservationId;
    UaNodeId CellActualReservationId;

    UaNodeId CellManufacturingId;
    UaNodeId CellRunActionId;
    UaNodeId CellManufacturingStateId;
    UaNodeId CellManufacturingStatusId;
    UaNodeId CellManufacturingDoneCmdId;

    std::mutex clientMutex;

    ProcessCellClient()
        : connectedCellId(0),
          cellReservationId(0),
          cellSubscription(cellUaClient)
    {}

    uint32_t GetManufacturingState() {
        uint32_t state;
        cellUaClient.ReadValue<uint32_t>(CellManufacturingStateId, &state);
        return state;
    }

    uint32_t GetManufacturingStatus() {
        uint32_t status;
        cellUaClient.ReadValue<uint32_t>(CellManufacturingStatusId, &status);
        return status;
    }

    bool WriteManufacturingDoneCmd() {
        return cellUaClient.WriteValue<uint32_t>(CellManufacturingDoneCmdId, true, UA_TYPES[UA_TYPES_BOOLEAN]);
    }

    bool ConnectToLocal() {
        clientMutex.lock();
        int status = cellUaClient.ConnectAnonymous(DISCOVERY_SERVER_ENDPOINT);
        clientMutex.unlock();
        if(status) {
            clientMutex.lock();
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Successfully connected to [%s].", DISCOVERY_SERVER_ENDPOINT.c_str());
            isConnected = true;

            cellSubscription.CreateSubscription();

            FindNodeIds();

            if(cellUaClient.ReadValue<uint32_t>(CellIdId, &connectedCellId))
                UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Current Process cell id [%d].", connectedCellId);

            clientMutex.unlock();
            return true;
        }
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Connection failed to [%s].", DISCOVERY_SERVER_ENDPOINT.c_str());
        return false;
    }



    bool FindCell(std::string operation) {
        StringArray capabilities;
        ServerOnNetworkArray servers;
        //UA_String capability = UA_String_fromChars(operation.c_str());
        //capabilities.setList(1, &capability);

        clientMutex.lock();
        bool status = cellUaClient.FindServersOnNetwork(DISCOVERY_SERVER_ENDPOINT, 0, 0, capabilities, servers);
        clientMutex.unlock();

        if(!status) return false;


        int foundedIndex = -1;
        for(int i=0; i<servers.length() && foundedIndex ==-1; ++i) {
            auto server = servers.at(i);
            for(int j=0; j<server.serverCapabilitiesSize; ++j) {
                auto capability = ToStdString(server.serverCapabilities[j]);
                if(capability == operation) {
                    foundedIndex = i;
                    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Process cell with desired capabilities found.");
                    break;
                }
            }
        }
        auto server = servers.at(foundedIndex);

        //if(servers.length() == 0) return false;
        //auto server = servers.at(0);
        std::string discoveryUrl = ToStdString(server.discoveryUrl);

        clientMutex.lock();
        status = cellUaClient.ConnectAnonymous(discoveryUrl);
        clientMutex.unlock();
        if(status) {
            clientMutex.lock();
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Successfully connected to [%s].", discoveryUrl.c_str());

            isConnected = true;

            cellSubscription.CreateSubscription();

            FindNodeIds();
            if(cellUaClient.ReadValue<uint32_t>(CellIdId, &connectedCellId))
                UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Current Process cell id [%d].", connectedCellId);
            clientMutex.unlock();
            return true;
        }
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Connection failed to [%s].", DISCOVERY_SERVER_ENDPOINT.c_str());
        return false;
    }

    void FindNodeIds() {
        CellId = cellUaClient.FindSingleNodeId(UaNodeId::ObjectsFolder, UaNodeId::Organizes, ProcessCellClient::CELL_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);

        CellInfoId = cellUaClient.FindSingleNodeId(CellId, UaNodeId::HasComponent, ProcessCellClient::INFO_SERVICE_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellIdId = cellUaClient.FindSingleNodeId(CellInfoId, UaNodeId::HasComponent, ProcessCellClient::CELLID_VAR_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);

        CellManagementId = cellUaClient.FindSingleNodeId(CellId, UaNodeId::HasComponent, ProcessCellClient::MANAGEMENT_SERVICE_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellMakeReservationId = cellUaClient.FindSingleNodeId(CellManagementId, UaNodeId::HasComponent, ProcessCellClient::RESERVE_METHOD_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellDeleteReservationId = cellUaClient.FindSingleNodeId(CellManagementId, UaNodeId::HasComponent, ProcessCellClient::DELETE_METHOD_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellActualReservationId = cellUaClient.FindSingleNodeId(CellManagementId, UaNodeId::HasComponent, ProcessCellClient::ACTUALRESID_VAR_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);

        CellManufacturingId = cellUaClient.FindSingleNodeId(CellId, UaNodeId::HasComponent, ProcessCellClient::MANUFACTURING_SERVICE_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellRunActionId = cellUaClient.FindSingleNodeId(CellManufacturingId, UaNodeId::HasComponent, ProcessCellClient::RUNACTION_METHOD_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellManufacturingStateId = cellUaClient.FindSingleNodeId(CellManufacturingId, UaNodeId::HasComponent, ProcessCellClient::STATE_VAR_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellManufacturingStatusId = cellUaClient.FindSingleNodeId(CellManufacturingId, UaNodeId::HasComponent, ProcessCellClient::STATUS_VAR_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
        CellManufacturingDoneCmdId = cellUaClient.FindSingleNodeId(CellManufacturingId, UaNodeId::HasComponent, ProcessCellClient::DONECMD_VAR_BROWSENAME, ProcessCellClient::MY_NAMESPACE_INDEX);
    }

    bool MakeResevation(uint64_t productId, UA_Byte operationId, UA_Byte operationOrder, float parA, float parB, uint64_t& reservationId) {
        VariantArray inputs;
        inputs.allocate(5);

        UA_Variant_setScalarCopy(&inputs.data()[0], &productId, &UA_TYPES[UA_TYPES_UINT64]);
        UA_Variant_setScalarCopy(&inputs.data()[1], &operationId, &UA_TYPES[UA_TYPES_BYTE]);
        UA_Variant_setScalarCopy(&inputs.data()[2], &operationOrder, &UA_TYPES[UA_TYPES_BYTE]);
        UA_Variant_setScalarCopy(&inputs.data()[3], &parA, &UA_TYPES[UA_TYPES_FLOAT]);
        UA_Variant_setScalarCopy(&inputs.data()[4], &parB, &UA_TYPES[UA_TYPES_FLOAT]);

        VariantArray outputs;
        clientMutex.lock();
        int status = cellUaClient.CallMethod(CellManagementId, CellMakeReservationId, inputs, outputs);
        clientMutex.unlock();
        if(status) {
            bool boolVal = GetVariantValue<bool>(outputs[0]);
            reservationId = GetVariantValue<uint64_t>(outputs[1]);
            inputs.release();
            outputs.release();
            return boolVal;
        }
        reservationId = 0;
        inputs.release();
        outputs.release();
        return false;
    }
    bool DeleteResevation(uint64_t reservationId) {
        VariantArray inputs;
        inputs.allocate(1);

        UA_Variant_setScalarCopy(&inputs.data()[0], &reservationId, &UA_TYPES[UA_TYPES_UINT64]);

        VariantArray outputs;
        clientMutex.lock();
        int status = cellUaClient.CallMethod(CellManagementId, CellDeleteReservationId, inputs, outputs);
        clientMutex.unlock();
        if(status) {
            bool output = GetVariantValue<bool>(outputs[0]);
            inputs.release();
            outputs.release();
            return output;
        }

        inputs.release();
        outputs.release();
        return false;
    }

    bool RunAction(UA_Byte actionId, float parA, float parB) {
        VariantArray inputs;
        inputs.allocate(3);
        UA_Variant_setScalarCopy(&inputs.data()[0], &actionId, &UA_TYPES[UA_TYPES_BYTE]);
        UA_Variant_setScalarCopy(&inputs.data()[1], &parA, &UA_TYPES[UA_TYPES_FLOAT]);
        UA_Variant_setScalarCopy(&inputs.data()[2], &parB, &UA_TYPES[UA_TYPES_FLOAT]);

        VariantArray outputs;
        clientMutex.lock();
        int status = cellUaClient.CallMethod(CellManufacturingId, CellRunActionId, inputs, outputs);
        clientMutex.unlock();
        if(status) {
            usleep(210);
            bool output = GetVariantValue<bool>(outputs[0]);
            inputs.release();
            outputs.release();
            return output;
        }

        inputs.release();
        outputs.release();

        return false;
    }

    void Release() {
        isConnected = false;
        cellSubscription.DeleteSubscription();
        cellUaClient.Disconnect();
    }
};

const int ProcessCellClient::MY_NAMESPACE_INDEX = 2;
const std::string ProcessCellClient::DISCOVERY_SERVER_ENDPOINT = "opc.tcp://localhost:4850"; // Discovery server is on every cell on localhost with port 4850

const std::string ProcessCellClient::CELL_BROWSENAME = "ProcessCell";

const std::string ProcessCellClient::INFO_SERVICE_BROWSENAME = "Info";
const std::string ProcessCellClient::CELLID_VAR_BROWSENAME = "Id";

const std::string ProcessCellClient::MANAGEMENT_SERVICE_BROWSENAME = "Management";
const std::string ProcessCellClient::RESERVE_METHOD_BROWSENAME = "MakeReservation";
const std::string ProcessCellClient::DELETE_METHOD_BROWSENAME = "DeleteReservation";
const std::string ProcessCellClient::ACTUALRESID_VAR_BROWSENAME = "ActualReservationId";

const std::string ProcessCellClient::MANUFACTURING_SERVICE_BROWSENAME = "Manufacturing";
const std::string ProcessCellClient::RUNACTION_METHOD_BROWSENAME = "RunAction";
const std::string ProcessCellClient::STATE_VAR_BROWSENAME = "State";
const std::string ProcessCellClient::STATUS_VAR_BROWSENAME = "Status";
const std::string ProcessCellClient::DONECMD_VAR_BROWSENAME = "DoneCmd";

#endif //PROCESSCELL_H