/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>

#include <stdio.h>
#include <stdlib.h>

#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
static void
printUpdateType(UA_HistoryUpdateType type) {
    switch (type) {
    case UA_HISTORYUPDATETYPE_INSERT:
        printf("Insert\n");
        return;
    case UA_HISTORYUPDATETYPE_REPLACE:
        printf("Replace\n");
        return;
    case UA_HISTORYUPDATETYPE_UPDATE:
        printf("Update\n");
        return;
    case UA_HISTORYUPDATETYPE_DELETE:
        printf("Delete\n");
        return;
    default:
        printf("Unknown\n");
        return;
    }
}
#endif

static void
printTimestamp(char *name, UA_DateTime date) {
    UA_DateTimeStruct dts = UA_DateTime_toStruct(date);
    if (name)
        printf("%s: %02u-%02u-%04u %02u:%02u:%02u.%03u, ", name,
               dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
    else
        printf("%02u-%02u-%04u %02u:%02u:%02u.%03u, ",
               dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
}

static void
printDataValue(UA_DataValue *value) {
    /* Print status and timestamps */
    if (value->hasServerTimestamp)
        printTimestamp("ServerTime", value->serverTimestamp);

    if (value->hasSourceTimestamp)
        printTimestamp("SourceTime", value->sourceTimestamp);

    if (value->hasStatus)
        printf("Status 0x%08x, ", value->status);

    if (value->value.type == &UA_TYPES[UA_TYPES_UINT32]) {
        UA_UInt32 hrValue = *(UA_UInt32 *)value->value.data;
        printf("Uint32Value %u\n", hrValue);
    }

    if (value->value.type == &UA_TYPES[UA_TYPES_DOUBLE]) {
        UA_Double hrValue = *(UA_Double *)value->value.data;
        printf("DoubleValue %f\n", hrValue);
    }
}

static UA_Boolean
readRaw(const UA_HistoryData *data) {
    printf("readRaw Value count: %lu\n", (long unsigned)data->dataValuesSize);

    /* Iterate over all values */
    for (UA_UInt32 i = 0; i < data->dataValuesSize; ++i)
    {
        printDataValue(&data->dataValues[i]);
    }

    /* We want more data! */
    return true;
}

#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
static UA_Boolean
readRawModified(const UA_HistoryModifiedData *data) {
    printf("readRawModified Value count: %lu\n", (long unsigned)data->dataValuesSize);

    /* Iterate over all values */
    for(size_t i = 0; i < data->dataValuesSize; ++i) {
        printDataValue(&data->dataValues[i]);
    }
    printf("Modificaton Value count: %llu\n",
           (long long unsigned)data->modificationInfosSize);
    for(size_t j = 0; j < data->modificationInfosSize; ++j) {
        if(data->modificationInfos[j].userName.data)
            printf("Username: %s, ", data->modificationInfos[j].userName.data);

        printTimestamp("Modtime", data->modificationInfos[j].modificationTime);
        printUpdateType(data->modificationInfos[j].updateType);
    }

    /* We want more data! */
    return true;
}

static UA_Boolean
readEvents(const UA_HistoryEvent *data) {
    printf("readEvent Value count: %lu\n", (long unsigned)data->eventsSize);
    for (size_t i = 0; i < data->eventsSize; ++i) {
        printf("Processing event: %lu\n", (long unsigned)i);
        for (size_t j = 0; j < data->events[i].eventFieldsSize; ++j) {
             printf("Processing %lu: %s\n", (long unsigned)j, data->events[i].eventFields[j].type->typeName);
        }
    }
    return true;
}
#endif

static UA_Boolean
readHist(UA_Client *client, const UA_NodeId *nodeId,
         UA_Boolean moreDataAvailable,
         const UA_ExtensionObject *data, void *unused) {
    printf("\nRead historical callback:\n");
    printf("\tHas more data:\t%d\n\n", moreDataAvailable);
    if (data->content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYDATA]) {
        return readRaw((UA_HistoryData*)data->content.decoded.data);
    }
#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
    if (data->content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYMODIFIEDDATA]) {
        return readRawModified((UA_HistoryModifiedData*)data->content.decoded.data);
    }
    if (data->content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYEVENT]) {
        return readEvents((UA_HistoryEvent*)data->content.decoded.data);
    }
#endif
    return true;
}

int main(int argc, char *argv[]) {
    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));

    /* Connect to the Unified Automation demo server */
    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:53530/OPCUA/SimulationServer");
    if(retval != UA_STATUSCODE_GOOD) {
        printf("Could not connect\n");
        UA_Client_delete(client);
        return EXIT_SUCCESS;
    }

    /* Read historical values (uint32) */
    printf("\nStart historical read (1, \"myUintValue\"):\n");
    UA_NodeId node = UA_NODEID_STRING(2, "MyLevel");
    retval = UA_Client_HistoryRead_raw(client, &node, readHist,
                                       UA_DateTime_fromUnixTime(0), UA_DateTime_now(), UA_STRING_NULL, false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);

    if (retval != UA_STATUSCODE_GOOD) {
        printf("Failed. %s\n", UA_StatusCode_name(retval));
    }

#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
    printf("\nStart historical modified read (1, \"myUintValue\"):\n");
    retval = UA_Client_HistoryRead_modified(client, &node, readHist,
                                       UA_DateTime_fromUnixTime(0), UA_DateTime_now(), UA_STRING_NULL, false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);

    if (retval != UA_STATUSCODE_GOOD) {
        printf("Failed. %s\n", UA_StatusCode_name(retval));
    }

    printf("\nStart historical event read (1, \"myUintValue\"):\n");
    UA_EventFilter filter;
    UA_EventFilter_init(&filter);
    UA_NodeId eventNode = UA_NODEID_NUMERIC(0, 2253);
    retval = UA_Client_HistoryRead_events(client, &eventNode, readHist,
                                       UA_DateTime_fromUnixTime(0), UA_DateTime_now(), UA_STRING_NULL, filter, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);

    if (retval != UA_STATUSCODE_GOOD) {
        printf("Failed. %s\n", UA_StatusCode_name(retval));
    }
#endif
    UA_Client_disconnect(client);
    UA_Client_delete(client);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
