/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *    Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
 */

#include <open62541/plugin/historydata/history_data_gathering_default.h>
#include <open62541/plugin/historydata/history_database_default.h>

#include <limits.h>

typedef struct {
    UA_HistoryDataGathering gathering;
} UA_HistoryDatabaseContext_default;

static size_t
getResultSize_service_default(const UA_HistoryDataBackend* backend,
                              UA_Server *server,
                              const UA_NodeId *sessionId,
                              void* sessionContext,
                              const UA_NodeId *nodeId,
                              UA_DateTime start,
                              UA_DateTime end,
                              UA_UInt32 numValuesPerNode,
                              UA_Boolean returnBounds,
                              size_t *startIndex,
                              size_t *endIndex,
                              UA_Boolean *addFirst,
                              UA_Boolean *addLast,
                              UA_Boolean *reverse)
{
    size_t storeEnd = backend->getEnd(server, backend->context, sessionId, sessionContext, nodeId);
    size_t firstIndex = backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId);
    size_t lastIndex = backend->lastIndex(server, backend->context, sessionId, sessionContext, nodeId);
    *startIndex = storeEnd;
    *endIndex = storeEnd;
    *addFirst = false;
    *addLast = false;
    if (end == LLONG_MIN) {
        *reverse = false;
    } else if (start == LLONG_MIN) {
        *reverse = true;
    } else {
        *reverse = end < start;
    }
    UA_Boolean equal = start == end;
    size_t size = 0;
    if (lastIndex != storeEnd) {
        if (equal) {
            if (returnBounds) {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
                if (*startIndex == storeEnd) {
                    *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
                    *addFirst = true;
                }
                *endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
                size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *startIndex, *endIndex);
            } else {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL);
                *endIndex = *startIndex;
                if (*startIndex == storeEnd)
                    size = 0;
                else
                    size = 1;
            }
        } else if (start == LLONG_MIN) {
            *endIndex = firstIndex;
            if (returnBounds) {
                *addLast = true;
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_AFTER);
                if (*startIndex == storeEnd) {
                    *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_BEFORE);
                    *addFirst = true;
                }
            } else {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_BEFORE);
            }
            size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *endIndex, *startIndex);
        } else if (end == LLONG_MIN) {
            *endIndex = lastIndex;
            if (returnBounds) {
                *addLast = true;
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
                if (*startIndex == storeEnd) {
                    *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
                    *addFirst = true;
                }
            } else {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_AFTER);
            }
            size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *startIndex, *endIndex);
        } else if (*reverse) {
            if (returnBounds) {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_AFTER);
                if (*startIndex == storeEnd) {
                    *addFirst = true;
                    *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_BEFORE);
                }
                *endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_BEFORE);
                if (*endIndex == storeEnd) {
                    *addLast = true;
                    *endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_AFTER);
                }
            } else {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
                *endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_AFTER);
            }
            size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *endIndex, *startIndex);
        } else {
            if (returnBounds) {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
                if (*startIndex == storeEnd) {
                    *addFirst = true;
                    *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
                }
                *endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_AFTER);
                if (*endIndex == storeEnd) {
                    *addLast = true;
                    *endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_BEFORE);
                }
            } else {
                *startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_AFTER);
                *endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_BEFORE);
            }
            size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *startIndex, *endIndex);
        }
    } else if (returnBounds) {
        *addLast = true;
        *addFirst = true;
    }

    if (*addLast)
        ++size;
    if (*addFirst)
        ++size;

    if (numValuesPerNode > 0 && size > numValuesPerNode) {
        size = numValuesPerNode;
        *addLast = false;
    }
    return size;
}

static UA_StatusCode
getHistoryData_service_default(const UA_HistoryDataBackend* backend,
                               const UA_DateTime start,
                               const UA_DateTime end,
                               UA_Server *server,
                               const UA_NodeId *sessionId,
                               void *sessionContext,
                               const UA_NodeId* nodeId,
                               size_t maxSize,
                               UA_UInt32 numValuesPerNode,
                               UA_Boolean returnBounds,
                               UA_TimestampsToReturn timestampsToReturn,
                               UA_NumericRange range,
                               UA_Boolean releaseContinuationPoints,
                               const UA_ByteString *continuationPoint,
                               UA_ByteString *outContinuationPoint,
                               size_t *resultSize,
                               UA_DataValue ** result)
{
    size_t skip = 0;
    UA_ByteString backendContinuationPoint;
    UA_ByteString_init(&backendContinuationPoint);
    if (continuationPoint->length > 0) {
        if (continuationPoint->length >= sizeof(size_t)) {
            skip = *((size_t*)(continuationPoint->data));
            if (continuationPoint->length > 0) {
                backendContinuationPoint.length = continuationPoint->length - sizeof(size_t);
                backendContinuationPoint.data = continuationPoint->data + sizeof(size_t);
            }
        } else {
            return UA_STATUSCODE_BADCONTINUATIONPOINTINVALID;
        }
    }

    size_t storeEnd = backend->getEnd(server, backend->context, sessionId, sessionContext, nodeId);
    size_t startIndex;
    size_t endIndex;
    UA_Boolean addFirst;
    UA_Boolean addLast;
    UA_Boolean reverse;
    size_t _resultSize = getResultSize_service_default(backend,
                                                       server,
                                                       sessionId,
                                                       sessionContext,
                                                       nodeId,
                                                       start,
                                                       end,
                                                       numValuesPerNode == 0 ? 0 : numValuesPerNode + (UA_UInt32)skip,
                                                       returnBounds,
                                                       &startIndex,
                                                       &endIndex,
                                                       &addFirst,
                                                       &addLast,
                                                       &reverse);
    *resultSize = _resultSize - skip;
    if (*resultSize > maxSize) {
        *resultSize = maxSize;
    }
    UA_DataValue *outResult= (UA_DataValue*)UA_Array_new(*resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]);
    if (!outResult) {
        *resultSize = 0;
        return UA_STATUSCODE_BADOUTOFMEMORY;
    }
    *result = outResult;

    size_t counter = 0;
    if (addFirst) {
        if (skip == 0) {
            outResult[counter].hasStatus = true;
            outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND;
            outResult[counter].hasSourceTimestamp = true;
            if (start == LLONG_MIN) {
                outResult[counter].sourceTimestamp = end;
            } else {
                outResult[counter].sourceTimestamp = start;
            }
            ++counter;
        }
    }
    UA_ByteString backendOutContinuationPoint;
    UA_ByteString_init(&backendOutContinuationPoint);
    if (endIndex != storeEnd && startIndex != storeEnd) {
        size_t retval = 0;

        size_t valueSize = *resultSize - counter;
        if (valueSize + skip > _resultSize - addFirst - addLast) {
            if (skip == 0) {
                valueSize = _resultSize - addFirst - addLast;
            } else {
                valueSize = _resultSize - skip - addLast;
            }

        }

        UA_StatusCode ret = UA_STATUSCODE_GOOD;
        if (valueSize > 0)
            ret = backend->copyDataValues(server,
                                          backend->context,
                                          sessionId,
                                          sessionContext,
                                          nodeId,
                                          startIndex,
                                          endIndex,
                                          reverse,
                                          valueSize,
                                          range,
                                          releaseContinuationPoints,
                                          &backendContinuationPoint,
                                          &backendOutContinuationPoint,
                                          &retval,
                                          &outResult[counter]);
        if (ret != UA_STATUSCODE_GOOD) {
            UA_Array_delete(outResult, *resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]);
            *result = NULL;
            *resultSize = 0;
            return ret;
        }
        counter += retval;
    }
    if (addLast && counter < *resultSize) {
        outResult[counter].hasStatus = true;
        outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND;
        outResult[counter].hasSourceTimestamp = true;
        if (start == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) {
            outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp - UA_DATETIME_SEC;
        } else if (end == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) {
            outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp + UA_DATETIME_SEC;
        } else {
            outResult[counter].sourceTimestamp = end;
        }
    }
    // there are more values
    if (skip + *resultSize < _resultSize
            // there are not more values for this request, but there are more values in database
            || (backendOutContinuationPoint.length > 0
                && numValuesPerNode != 0)
            // we deliver just one value which is a FIRST/LAST value
            || (skip == 0
                && addFirst == true
                && *resultSize == 1)) {
        if(UA_ByteString_allocBuffer(outContinuationPoint, backendOutContinuationPoint.length + sizeof(size_t))
                != UA_STATUSCODE_GOOD) {
            return UA_STATUSCODE_BADOUTOFMEMORY;
        }
        *((size_t*)(outContinuationPoint->data)) = skip + *resultSize;
        if(backendOutContinuationPoint.length > 0)
            memcpy(outContinuationPoint->data + sizeof(size_t), backendOutContinuationPoint.data, backendOutContinuationPoint.length);
    }
    UA_ByteString_clear(&backendOutContinuationPoint);
    return UA_STATUSCODE_GOOD;
}

static void
updateData_service_default(UA_Server *server,
                           void *hdbContext,
                           const UA_NodeId *sessionId,
                           void *sessionContext,
                           const UA_RequestHeader *requestHeader,
                           const UA_UpdateDataDetails *details,
                           UA_HistoryUpdateResult *result)
{
    UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)hdbContext;
    UA_Byte accessLevel = 0;
    UA_Server_readAccessLevel(server,
                              details->nodeId,
                              &accessLevel);
    if (!(accessLevel & UA_ACCESSLEVELMASK_HISTORYWRITE)) {
        result->statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
        return;
    }

    UA_Boolean historizing = false;
    UA_Server_readHistorizing(server,
                              details->nodeId,
                              &historizing);
    if (!historizing) {
        result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
        return;
    }
    const UA_HistorizingNodeIdSettings *setting = ctx->gathering.getHistorizingSetting(
                server,
                ctx->gathering.context,
                &details->nodeId);

    if (!setting) {
        result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
        return;
    }

    UA_ServerConfig *config = UA_Server_getConfig(server);
    result->operationResultsSize = details->updateValuesSize;
    result->operationResults = (UA_StatusCode*)UA_Array_new(result->operationResultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
    for (size_t i = 0; i < details->updateValuesSize; ++i) {
        if (config->accessControl.allowHistoryUpdateUpdateData &&
            !config->accessControl.allowHistoryUpdateUpdateData(server, &config->accessControl, sessionId, sessionContext,
                                                                &details->nodeId, details->performInsertReplace,
                                                                &details->updateValues[i])) {
            result->operationResults[i] = UA_STATUSCODE_BADUSERACCESSDENIED;
            continue;
        }
        switch (details->performInsertReplace) {
        case UA_PERFORMUPDATETYPE_INSERT:
            if (!setting->historizingBackend.insertDataValue) {
                result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
                continue;
            }
            result->operationResults[i]
                    = setting->historizingBackend.insertDataValue(server,
                                                                  setting->historizingBackend.context,
                                                                  sessionId,
                                                                  sessionContext,
                                                                  &details->nodeId,
                                                                  &details->updateValues[i]);
            continue;
        case UA_PERFORMUPDATETYPE_REPLACE:
            if (!setting->historizingBackend.replaceDataValue) {
                result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
                continue;
            }
            result->operationResults[i]
                    = setting->historizingBackend.replaceDataValue(server,
                                                                   setting->historizingBackend.context,
                                                                   sessionId,
                                                                   sessionContext,
                                                                   &details->nodeId,
                                                                   &details->updateValues[i]);
            continue;
        case UA_PERFORMUPDATETYPE_UPDATE:
            if (!setting->historizingBackend.updateDataValue) {
                result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
                continue;
            }
            result->operationResults[i]
                    = setting->historizingBackend.updateDataValue(server,
                                                                  setting->historizingBackend.context,
                                                                  sessionId,
                                                                  sessionContext,
                                                                  &details->nodeId,
                                                                  &details->updateValues[i]);
            continue;
        default:
            result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
            continue;
        }
    }
}


static void
deleteRawModified_service_default(UA_Server *server,
                                  void *hdbContext,
                                  const UA_NodeId *sessionId,
                                  void *sessionContext,
                                  const UA_RequestHeader *requestHeader,
                                  const UA_DeleteRawModifiedDetails *details,
                                  UA_HistoryUpdateResult *result)
{
    if (details->isDeleteModified) {
        result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
        return;
    }
    UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)hdbContext;
    UA_Byte accessLevel = 0;
    UA_Server_readAccessLevel(server,
                              details->nodeId,
                              &accessLevel);
    if (!(accessLevel & UA_ACCESSLEVELMASK_HISTORYWRITE)) {
        result->statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
        return;
    }

    UA_Boolean historizing = false;
    UA_Server_readHistorizing(server,
                              details->nodeId,
                              &historizing);
    if (!historizing) {
        result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
        return;
    }
    const UA_HistorizingNodeIdSettings *setting = ctx->gathering.getHistorizingSetting(
                server,
                ctx->gathering.context,
                &details->nodeId);

    if (!setting) {
        result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
        return;
    }
    if (!setting->historizingBackend.removeDataValue) {
        result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
        return;
    }

    UA_ServerConfig *config = UA_Server_getConfig(server);
    if (config->accessControl.allowHistoryUpdateDeleteRawModified &&
        !config->accessControl.allowHistoryUpdateDeleteRawModified(server,
                 &config->accessControl, sessionId, sessionContext, &details->nodeId,
                 details->startTime, details->endTime, details->isDeleteModified)) {
        result->statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
        return;
    }

    result->statusCode
            = setting->historizingBackend.removeDataValue(server,
                                                          setting->historizingBackend.context,
                                                          sessionId,
                                                          sessionContext,
                                                          &details->nodeId,
                                                          details->startTime,
                                                          details->endTime);
}

static void
readRaw_service_default(UA_Server *server,
                        void *context,
                        const UA_NodeId *sessionId,
                        void *sessionContext,
                        const UA_RequestHeader *requestHeader,
                        const UA_ReadRawModifiedDetails *historyReadDetails,
                        UA_TimestampsToReturn timestampsToReturn,
                        UA_Boolean releaseContinuationPoints,
                        size_t nodesToReadSize,
                        const UA_HistoryReadValueId *nodesToRead,
                        UA_HistoryReadResponse *response,
                        UA_HistoryData * const * const historyData)
{
    UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)context;
    for (size_t i = 0; i < nodesToReadSize; ++i) {
        UA_Byte accessLevel = 0;
        UA_Server_readAccessLevel(server,
                                  nodesToRead[i].nodeId,
                                  &accessLevel);
        if (!(accessLevel & UA_ACCESSLEVELMASK_HISTORYREAD)) {
            response->results[i].statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
            continue;
        }

        UA_Boolean historizing = false;
        UA_Server_readHistorizing(server,
                                  nodesToRead[i].nodeId,
                                  &historizing);
        if (!historizing) {
            response->results[i].statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
            continue;
        }

        const UA_HistorizingNodeIdSettings *setting = ctx->gathering.getHistorizingSetting(
                    server,
                    ctx->gathering.context,
                    &nodesToRead[i].nodeId);

        if (!setting) {
            response->results[i].statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
            continue;
        }

        if (historyReadDetails->returnBounds && !setting->historizingBackend.boundSupported(
                    server,
                    setting->historizingBackend.context,
                    sessionId,
                    sessionContext,
                    &nodesToRead[i].nodeId)) {
            response->results[i].statusCode = UA_STATUSCODE_BADBOUNDNOTSUPPORTED;
            continue;
        }

        if (!setting->historizingBackend.timestampsToReturnSupported(
                    server,
                    setting->historizingBackend.context,
                    sessionId,
                    sessionContext,
                    &nodesToRead[i].nodeId,
                    timestampsToReturn)) {
            response->results[i].statusCode = UA_STATUSCODE_BADTIMESTAMPNOTSUPPORTED;
            continue;
        }

        UA_NumericRange range;
        range.dimensionsSize = 0;
        range.dimensions = NULL;
        if (nodesToRead[i].indexRange.length > 0) {
            UA_StatusCode rangeParseResult = UA_NumericRange_parse(&range, nodesToRead[i].indexRange);
            if (rangeParseResult != UA_STATUSCODE_GOOD) {
                response->results[i].statusCode = rangeParseResult;
                continue;
            }
        }

        UA_StatusCode getHistoryDataStatusCode;
        if (setting->historizingBackend.getHistoryData) {
            getHistoryDataStatusCode = setting->historizingBackend.getHistoryData(
                        server,
                        sessionId,
                        sessionContext,
                        &setting->historizingBackend,
                        historyReadDetails->startTime,
                        historyReadDetails->endTime,
                        &nodesToRead[i].nodeId,
                        setting->maxHistoryDataResponseSize,
                        historyReadDetails->numValuesPerNode,
                        historyReadDetails->returnBounds,
                        timestampsToReturn,
                        range,
                        releaseContinuationPoints,
                        &nodesToRead[i].continuationPoint,
                        &response->results[i].continuationPoint,
                        historyData[i]);
        } else {
            getHistoryDataStatusCode = getHistoryData_service_default(
                        &setting->historizingBackend,
                        historyReadDetails->startTime,
                        historyReadDetails->endTime,
                        server,
                        sessionId,
                        sessionContext,
                        &nodesToRead[i].nodeId,
                        setting->maxHistoryDataResponseSize,
                        historyReadDetails->numValuesPerNode,
                        historyReadDetails->returnBounds,
                        timestampsToReturn,
                        range,
                        releaseContinuationPoints,
                        &nodesToRead[i].continuationPoint,
                        &response->results[i].continuationPoint,
                        &historyData[i]->dataValuesSize,
                        &historyData[i]->dataValues);
        }
        if (getHistoryDataStatusCode != UA_STATUSCODE_GOOD) {
            response->results[i].statusCode = getHistoryDataStatusCode;
            continue;
        }
    }
    response->responseHeader.serviceResult = UA_STATUSCODE_GOOD;
    return;
}

static void
setValue_service_default(UA_Server *server,
                         void *context,
                         const UA_NodeId *sessionId,
                         void *sessionContext,
                         const UA_NodeId *nodeId,
                         UA_Boolean historizing,
                         const UA_DataValue *value)
{
    UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)context;
    if (ctx->gathering.setValue)
        ctx->gathering.setValue(server,
                                ctx->gathering.context,
                                sessionId,
                                sessionContext,
                                nodeId,
                                historizing,
                                value);
}

static void
clear_service_default(UA_HistoryDatabase *hdb)
{
    if (hdb == NULL || hdb->context == NULL)
        return;
    UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)hdb->context;
    ctx->gathering.deleteMembers(&ctx->gathering);
    UA_free(ctx);
}

UA_HistoryDatabase
UA_HistoryDatabase_default(UA_HistoryDataGathering gathering)
{
    UA_HistoryDatabase hdb;
    memset(&hdb, 0, sizeof(UA_HistoryDatabase));
    UA_HistoryDatabaseContext_default *context =
            (UA_HistoryDatabaseContext_default*)
            UA_calloc(1, sizeof(UA_HistoryDatabaseContext_default));
    context->gathering = gathering;
    hdb.context = context;
    hdb.readRaw = &readRaw_service_default;
    hdb.setValue = &setValue_service_default;
    hdb.updateData = &updateData_service_default;
    hdb.deleteRawModified = &deleteRawModified_service_default;
    hdb.clear = clear_service_default;
    return hdb;
}
