//
// Created by magino on 18.7.2019.
//

#include "client/UaClient.h"

UaClient::UaClient() {
    _client = UA_Client_new();
    _config = UA_Client_getConfig(_client);
    UA_ClientConfig_setDefault(_config);

    _config->clientContext = this;
    _config->stateCallback = StateCallback;
    _config->subscriptionInactivityCallback = SubscriptionInactivityCallback;

    //UA_ApplicationDescription* desc = UA_ApplicationDescription_new();
    //desc->applicationName
    //_config->clientDescription
}

// TODO check what happens on delete
UaClient::~UaClient() {
    if(_client) {
        UA_Client_delete(_client);
        _client = nullptr;
    }
}

void UaClient::StateCallback(UA_Client* client, UA_ClientState clientState) {
    UaClient* uaClient = (UaClient*)(UA_Client_getContext(client));
    if(uaClient) uaClient->StateChange(clientState);
}

void UaClient::SubscriptionInactivityCallback(UA_Client* client, UA_UInt32 subscriptionId, void* subContext) {}
void UaClient::ClientAsyncServiceCallback(UA_Client* client, void* userdata, UA_UInt32 requestId, void* response) {}

void UaClient::StateChange(UA_ClientState clientState) {
    switch (clientState) {
        case UA_CLIENTSTATE_DISCONNECTED:
            // LOG
            break;
        case UA_CLIENTSTATE_CONNECTED:
            // LOG
            break;
        case UA_CLIENTSTATE_SECURECHANNEL:
            // LOG
            break;
        case UA_CLIENTSTATE_SESSION:
            // LOG
            break;
        case UA_CLIENTSTATE_SESSION_RENEWED:
            // LOG
            break;
    }
}

/**
 * Connect to a Server
 * ------------------- */
bool UaClient::ConnectAnonymous(const std::string& url) const {
    UA_StatusCode status = UA_Client_connect(_client, &url[0]);
    return status == UA_STATUSCODE_GOOD;
}

bool UaClient::ConnectAnonymousAsync(const std::string& url) const {
    // TODO last parameter are user data, _client has UaClient as context so we can pass something else to last parameter
    UA_StatusCode status = UA_Client_connect_async(_client, url.c_str(), ClientAsyncServiceCallback, nullptr);
    return status == UA_STATUSCODE_GOOD;
}

bool UaClient::ConnectNoSession(const std::string &url) const {
    UA_StatusCode status = UA_Client_connect_noSession(_client, url.c_str());
    return status == UA_STATUSCODE_GOOD;
}

bool UaClient::ConnectWithCredentials(const std::string& url, const std::string& userName, const std::string& password) const {
    UA_StatusCode status = UA_Client_connect_username(_client, url.c_str(), userName.c_str(), password.c_str());
    return status == UA_STATUSCODE_GOOD;
}

bool UaClient::Disconnect() {
    // TODO check what happens with subscriptions and monitored items on client disconnect
    return UA_Client_disconnect(_client) == UA_STATUSCODE_GOOD;
}

bool UaClient::DisconnectAsync() {
    // TODO what is second parameter *requestid ??
    UA_StatusCode status = UA_Client_disconnect_async(_client, NULL);
    return status == UA_STATUSCODE_GOOD;
}

/**
 * Discovery Service set
 * ^^^^^^^^^^^^^^^^^^^^^ */
bool UaClient::FindServers(const std::string& url, StringArray& serverUris, StringArray& localeIds, ApplicationDescriptionArray& registeredServers) {
    UA_StatusCode status = UA_Client_findServers(_client, url.c_str(), serverUris.length(),
                                                 serverUris.data(), localeIds.length(), localeIds,
                                                 registeredServers.lengthRef(), registeredServers.dataRef());
    return status == UA_STATUSCODE_GOOD;
}

//TODO test
bool UaClient::FindServersByUri(const std::string &serverUri,
                                ApplicationDescriptionArray &registeredServers) {
    UA_String* uri = UA_String_new();
    FromStdString(serverUri, *uri);
    UA_StatusCode status = UA_Client_findServers(_client, "opc.tcp://localhost:4840", 1,
                                                 uri, 0, nullptr,
                                                 registeredServers.lengthRef(), registeredServers.dataRef());
    return status == UA_STATUSCODE_GOOD;
}

bool UaClient::FindServersOnNetwork(const std::string& url, unsigned startingRecordId, unsigned maxRecordsToReturn,
                                    StringArray& serverCapabilityFilter, ServerOnNetworkArray& serversOnNetwork) {
    UA_StatusCode status = UA_Client_findServersOnNetwork(_client, url.c_str(), startingRecordId, maxRecordsToReturn,
                                        serverCapabilityFilter.length(), serverCapabilityFilter.data(),
                                        serversOnNetwork.lengthRef(), serversOnNetwork.dataRef());
    //UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "FindServersOnNetwork %s", UA_StatusCode_name(status));
    return status == UA_STATUSCODE_GOOD;
}

bool UaClient::GetEndpoints(const std::string& url, EndpointDescriptionArray& endpoints) {
    UA_StatusCode status = UA_Client_getEndpoints(_client, url.c_str(), endpoints.lengthRef(), endpoints.dataRef());
    return status == UA_STATUSCODE_GOOD;
}

/*
 * Attribute Service Set
 * ^^^^^^^^^^^^^^^^^^^^^ */
bool UaClient::ReadAttribute(UaNodeId& nodeId, UA_AttributeId attributeId, const UA_DataType* outDataType, void* outData) {
    // TODO call from this point services from ua_client_highlevel.h and make utility static class which will cast correct type from attribute and void* data
    UA_StatusCode status = __UA_Client_readAttribute(_client, &nodeId.GetNodeId(), attributeId, outData, outDataType);
    return status == UA_STATUSCODE_GOOD;
}

bool UaClient::WriteAttribute(UaNodeId& nodeId, UA_AttributeId attributeId, const UA_DataType* inDataType, const void* inData) {
    UA_StatusCode status = __UA_Client_writeAttribute(_client, &nodeId.GetNodeId(), attributeId, inData, inDataType);
    return status == UA_STATUSCODE_GOOD;
}

/* TODO Historical Access Service Set */

/* TODO Method Service Set */

/* TODO NodeManagement Service Set */

/* TODO View Service Set */

/* TODO Query Service Set */

/*
 * Subscription Service Set
 * ^^^^^^^^^^^^^^^^^^^^^^^^ */
UaSubscriptionRef UaClient::CreateSubscription() {
    // TODO how to check if this failed?
    UaSubscriptionRef sub(new UaSubscription(*this));
    _subscriptions[ sub->GetId()] = sub;
    return sub;
}

bool UaClient::RemoveSubscription(unsigned id) {
    //subscriptions.
    return false;
}