/* 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 2017, 2018, 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
 *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
 */

#include "ua_util_internal.h"
#include "ua_timer.h"

/* There may be several entries with the same nextTime in the tree. We give them
 * an absolute order by considering the memory address to break ties. Because of
 * this, the nextTime property cannot be used to lookup specific entries. */
static enum aa_cmp
cmpDateTime(const UA_DateTime *a, const UA_DateTime *b) {
    if(*a < *b)
        return AA_CMP_LESS;
    if(*a > *b)
        return AA_CMP_MORE;
    if(a == b)
        return AA_CMP_EQ;
    if(a < b)
        return AA_CMP_LESS;
    return AA_CMP_MORE;
}

/* The identifiers of entries are unique */
static enum aa_cmp
cmpId(const UA_UInt64 *a, const UA_UInt64 *b) {
    if(*a < *b)
        return AA_CMP_LESS;
    if(*a == *b)
        return AA_CMP_EQ;
    return AA_CMP_MORE;
}

static UA_DateTime
calculateNextTime(UA_DateTime currentTime, UA_DateTime baseTime,
                  UA_DateTime interval) {
    /* Take the difference between current and base time */
    UA_DateTime diffCurrentTimeBaseTime = currentTime - baseTime;

    /* Take modulo of the diff time with the interval. This is the duration we
     * are already "into" the current interval. Subtract it from (current +
     * interval) to get the next execution time. */
    UA_DateTime cycleDelay = diffCurrentTimeBaseTime % interval;

    /* Handle the special case where the baseTime is in the future */
    if(UA_UNLIKELY(cycleDelay < 0))
        cycleDelay += interval;

    return currentTime + interval - cycleDelay;
}

void
UA_Timer_init(UA_Timer *t) {
    memset(t, 0, sizeof(UA_Timer));
    aa_init(&t->root,
            (enum aa_cmp (*)(const void*, const void*))cmpDateTime,
            offsetof(UA_TimerEntry, treeEntry),
            offsetof(UA_TimerEntry, nextTime));
    aa_init(&t->idRoot,
            (enum aa_cmp (*)(const void*, const void*))cmpId,
            offsetof(UA_TimerEntry, idTreeEntry),
            offsetof(UA_TimerEntry, id));
    UA_LOCK_INIT(&t->timerMutex);
}

void
UA_Timer_addTimerEntry(UA_Timer *t, UA_TimerEntry *te, UA_UInt64 *callbackId) {
    UA_LOCK(&t->timerMutex);
    te->id = ++t->idCounter;
    if(callbackId)
        *callbackId = te->id;
    aa_insert(&t->root, te);
    aa_insert(&t->idRoot, te);
    UA_UNLOCK(&t->timerMutex);
}

static UA_StatusCode
addCallback(UA_Timer *t, UA_ApplicationCallback callback, void *application,
            void *data, UA_DateTime nextTime, UA_UInt64 interval,
            UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) {
    /* A callback method needs to be present */
    if(!callback)
        return UA_STATUSCODE_BADINTERNALERROR;

    /* Allocate the repeated callback structure */
    UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry));
    if(!te)
        return UA_STATUSCODE_BADOUTOFMEMORY;

    /* Set the repeated callback */
    te->interval = (UA_UInt64)interval;
    te->id = ++t->idCounter;
    te->callback = callback;
    te->application = application;
    te->data = data;
    te->nextTime = nextTime;
    te->timerPolicy = timerPolicy;

    /* Set the output identifier */
    if(callbackId)
        *callbackId = te->id;

    aa_insert(&t->root, te);
    aa_insert(&t->idRoot, te);
    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
UA_Timer_addTimedCallback(UA_Timer *t, UA_ApplicationCallback callback,
                          void *application, void *data, UA_DateTime date,
                          UA_UInt64 *callbackId) {
    UA_LOCK(&t->timerMutex);
    UA_StatusCode res = addCallback(t, callback, application, data, date,
                                    0, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME,
                                    callbackId);
    UA_UNLOCK(&t->timerMutex);
    return res;
}

/* Adding repeated callbacks: Add an entry with the "nextTime" timestamp in the
 * future. This will be picked up in the next iteration and inserted at the
 * correct place. So that the next execution takes place ät "nextTime". */
UA_StatusCode
UA_Timer_addRepeatedCallback(UA_Timer *t, UA_ApplicationCallback callback,
                             void *application, void *data, UA_Double interval_ms,
                             UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
                             UA_UInt64 *callbackId) {
    /* The interval needs to be positive */
    if(interval_ms <= 0.0)
        return UA_STATUSCODE_BADINTERNALERROR;
    UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
    if(interval == 0)
        return UA_STATUSCODE_BADINTERNALERROR;

    /* Compute the first time for execution */
    UA_DateTime currentTime = UA_DateTime_nowMonotonic();
    UA_DateTime nextTime;
    if(baseTime == NULL) {
        /* Use "now" as the basetime */
        nextTime = currentTime + (UA_DateTime)interval;
    } else {
        nextTime = calculateNextTime(currentTime, *baseTime, (UA_DateTime)interval);
    }

    UA_LOCK(&t->timerMutex);
    UA_StatusCode res = addCallback(t, callback, application, data, nextTime,
                                    interval, timerPolicy, callbackId);
    UA_UNLOCK(&t->timerMutex);
    return res;
}

UA_StatusCode
UA_Timer_changeRepeatedCallback(UA_Timer *t, UA_UInt64 callbackId,
                                UA_Double interval_ms, UA_DateTime *baseTime,
                                UA_TimerPolicy timerPolicy) {
    /* The interval needs to be positive */
    if(interval_ms <= 0.0)
        return UA_STATUSCODE_BADINTERNALERROR;
    UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
    if(interval == 0)
        return UA_STATUSCODE_BADINTERNALERROR;

    UA_LOCK(&t->timerMutex);

    /* Remove from the sorted tree */
    UA_TimerEntry *te = (UA_TimerEntry*)aa_find(&t->idRoot, &callbackId);
    if(!te) {
        UA_UNLOCK(&t->timerMutex);
        return UA_STATUSCODE_BADNOTFOUND;
    }
    aa_remove(&t->root, te);

    /* Compute the next time for execution. The logic is identical to the
     * creation of a new repeated callback. */
    UA_DateTime currentTime = UA_DateTime_nowMonotonic();
    if(baseTime == NULL) {
        /* Use "now" as the basetime */
        te->nextTime = currentTime + (UA_DateTime)interval;
    } else {
        te->nextTime = calculateNextTime(currentTime, *baseTime, (UA_DateTime)interval);
    }

    /* Update the remaining parameters and re-insert */
    te->interval = interval;
    te->timerPolicy = timerPolicy;
    aa_insert(&t->root, te);

    UA_UNLOCK(&t->timerMutex);
    return UA_STATUSCODE_GOOD;
}

void
UA_Timer_removeCallback(UA_Timer *t, UA_UInt64 callbackId) {
    UA_LOCK(&t->timerMutex);
    UA_TimerEntry *te = (UA_TimerEntry*)aa_find(&t->idRoot, &callbackId);
    if(UA_LIKELY(te != NULL)) {
        aa_remove(&t->root, te);
        aa_remove(&t->idRoot, te);
        UA_free(te);
    }
    UA_UNLOCK(&t->timerMutex);
}

UA_DateTime
UA_Timer_process(UA_Timer *t, UA_DateTime nowMonotonic,
                 UA_TimerExecutionCallback executionCallback,
                 void *executionApplication) {
    UA_LOCK(&t->timerMutex);
    UA_TimerEntry *first;
    while((first = (UA_TimerEntry*)aa_min(&t->root)) &&
          first->nextTime <= nowMonotonic) {
        aa_remove(&t->root, first);

        /* Reinsert / remove to their new position first. Because the callback
         * can interact with the zip tree and expects the same entries in the
         * root and idRoot trees. */

        if(first->interval == 0) {
            aa_remove(&t->idRoot, first);
            if(first->callback) {
                UA_UNLOCK(&t->timerMutex);
                executionCallback(executionApplication, first->callback,
                                  first->application, first->data);
                UA_LOCK(&t->timerMutex);
            }
            UA_free(first);
            continue;
        }

        /* Set the time for the next execution. Prevent an infinite loop by
         * forcing the execution time in the next iteration.
         *
         * If the timer policy is "CurrentTime", then there is at least the
         * interval between executions. This is used for Monitoreditems, for
         * which the spec says: The sampling interval indicates the fastest rate
         * at which the Server should sample its underlying source for data
         * changes. (Part 4, 5.12.1.2) */
        first->nextTime += (UA_DateTime)first->interval;
        if(first->nextTime < nowMonotonic) {
            if(first->timerPolicy == UA_TIMER_HANDLE_CYCLEMISS_WITH_BASETIME)
                first->nextTime = calculateNextTime(nowMonotonic, first->nextTime,
                                                    (UA_DateTime)first->interval);
            else
                first->nextTime = nowMonotonic + (UA_DateTime)first->interval;
        }

        aa_insert(&t->root, first);

        if(!first->callback)
            continue;

        /* Unlock the mutes before dropping into the callback. So that the timer
         * itself can be edited within the callback. When we return, only the
         * pointer to t must still exist. */
        UA_ApplicationCallback cb = first->callback;
        void *app = first->application;
        void *data = first->data;
        UA_UNLOCK(&t->timerMutex);
        executionCallback(executionApplication, cb, app, data);
        UA_LOCK(&t->timerMutex);
    }

    /* Return the timestamp of the earliest next callback */
    first = (UA_TimerEntry*)aa_min(&t->root);
    UA_DateTime next = (first) ? first->nextTime : UA_INT64_MAX;
    if(next < nowMonotonic)
        next = nowMonotonic;
    UA_UNLOCK(&t->timerMutex);
    return next;
}

void
UA_Timer_clear(UA_Timer *t) {
    UA_LOCK(&t->timerMutex);

    /* Free all entries */
    UA_TimerEntry *top;
    while((top = (UA_TimerEntry*)aa_min(&t->idRoot))) {
        aa_remove(&t->idRoot, top);
        UA_free(top);
    }

    /* Reset the trees to avoid future access */
    t->root.root = NULL;
    t->idRoot.root = NULL;

    UA_UNLOCK(&t->timerMutex);
#if UA_MULTITHREADING >= 100
    UA_LOCK_DESTROY(&t->timerMutex);
#endif
}
