//
// Created by magino on 11.4.19.
//

#include <server/UaServer.h>
#include <open62541/server_config_default.h>
#include <open62541/client.h>
#include <sstream>

UaClient* UaServer::GetRegisterClient(std::string endpointUrl) {
    return _registerClients.at(endpointUrl);
}

UaServer::UaServer() {
    _server = UA_Server_new();
    UA_ServerConfig* _config = UA_Server_getConfig(_server);
    UA_ServerConfig_setDefault(_config);
}
UaServer::UaServer(unsigned short port,
                   UA_ByteString* certificate,
                   UA_ApplicationDescription appDescription,
                   UA_MdnsDiscoveryConfiguration mdnsCfg,
                   std::string customHostname) {
    _server = UA_Server_new();
    UA_ServerConfig* _config = UA_Server_getConfig(_server);
    UA_ServerConfig_setMinimal(_config, port, certificate);

    //UA_ApplicationDescription_clear(&_config->applicationDescription);
    //UA_ApplicationDescription_init(&_config->applicationDescription);
    //UA_ApplicationDescription_copy(&appDescription, &_config->applicationDescription);

    _config->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER;

    UA_LocalizedText_clear(&_config->applicationDescription.applicationName);
    _config->applicationDescription.applicationName = appDescription.applicationName;

    UA_String_clear(&_config->applicationDescription.applicationUri);
    _config->applicationDescription.applicationUri = appDescription.applicationUri;

    UA_String_clear(&_config->applicationDescription.productUri);
    _config->applicationDescription.productUri = appDescription.productUri;

    // Pridavam dalsiu discovery URL lebo vzdialene zariadenia by sa nevedeli pripojit pomocou hostname

    //_config->applicationDescription.discoveryUrlsSize = 1;
    //_config->applicationDescription.discoveryUrls = (UA_String *) UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
    //_config->applicationDescription.discoveryUrls[0] = UA_String_fromChars("opc.tcp://192.168.0.221:4850");//appDescription.discoveryUrls[0];
    //_config->applicationDescription.
    //UA_String_copy(&appDescription.discoveryUrls[0], &_config->applicationDescription.discoveryUrls[0]);

    // Default value of registered server cleanup is 1hour = 60*60
    // config->discoveryCleanupTimeout = 60*60;

    _config->discovery.mdnsEnable = true;
    UA_MdnsDiscoveryConfiguration_copy(&mdnsCfg, &_config->discovery.mdns);

    if(customHostname != "")
        UA_ServerConfig_setCustomHostname(_config, UA_String_fromChars(customHostname.c_str()));

    /*
    UA_ApplicationDescription_clear(&_config->applicationDescription);
    _config->applicationDescription = appDescription;
    UA_MdnsDiscoveryConfiguration_clear(&_config->discovery.mdns);
    _config->discovery.mdnsEnable = true;
    _config->discovery.mdns = mdnsCfg;
    */
}

void UaServer::CleanUp() {
    // TODO unregister all registered servers and remove repeated callbacks
    //UA_StatusCode retVal = UA_Server_unregister_discovery(_server, _registerClient);
    /*if(retVal != UA_STATUSCODE_GOOD)
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
                     "Could not unregister server from discovery server. StatusCode %s", UA_StatusCode_name(retVal));*/

    for (const auto& client : _registerClients) {
        delete client.second;
    }

    UA_Server_delete(_server);
    _server = nullptr;
}

bool UaServer::Run(volatile UA_Boolean *running) {
    if (UA_Server_run(_server, running) == UA_STATUSCODE_GOOD) {
        return true;
    } else return false;
}

bool UaServer::RunAsync(volatile UA_Boolean* running) {
    _thread = std::thread([this, running] () {
        UA_Server_run(_server, running);
    });
}

bool UaServer::AddPeriodicServerRegister(UaClient* client, std::string endpointUrl, unsigned period, unsigned delay, uint64_t* periodicCallbackId) {
    bool status = UA_Server_addPeriodicServerRegisterCallback(_server, client->GetClient(), endpointUrl.c_str(), UA_Double(period), UA_Double(delay), periodicCallbackId) == UA_STATUSCODE_GOOD;
    if(status) _registerClients.insert(std::pair<std::string,UaClient*>(endpointUrl,client));
    return status;
}

void UaServer::ServerOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean isServerAnnounce,
                                       UA_Boolean isTxtReceived, void *data) {
    if(!isServerAnnounce) {
        UA_LOG_DEBUG(UA_Log_Stdout,
                     UA_LOGCATEGORY_USERLAND,
                     "[mDNS] ServerOnNetworkCallback called, but discovery URL already initialized or is not announcing. Ignoring.");
        return; // We already have everything we need or we only want server announces
    }

    if(!isTxtReceived) return;
    // we wait until the corresponding TXT record is announced.
    // Problem: how to handle if a Server does not announce the optional TXT?

    /*
    UA_LOG_INFO(UA_Log_Stdout,
                UA_LOGCATEGORY_USERLAND,
                "[mDNS] Another server announced itself on %.*s",
                (int)serverOnNetwork->discoveryUrl.length, serverOnNetwork->discoveryUrl.data);
    */
    UaServer* server = static_cast<UaServer*>(data);
    server->ServerOnNetwork(serverOnNetwork);
}

void UaServer::RegisterServerCallback(const UA_RegisteredServer* registeredServer, void* data) {
    UaServer* server = static_cast<UaServer*>(data);
    server->ServerRegisterActivity();
}