/* $Id$
 * $URL$
 *
 * Generic timer handling.
 *
 * Copyright (C) 2003, 2004 Michael Reinelt <michael@reinelt.co.at>
 * Copyright (C) 2004 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * Exported functions:
 *
 * int timer_add(void (*callback) (void *data), void *data, const int
 *     interval, const int one_shot)
 *
 *   Create a new timer and add it to the timer queue.
 *
 *
 * int timer_add_late(void (*callback) (void *data), void *data, const
 *     int interval, const int one_shot)
 *
 *   This function creates a new timer and adds it to the timer queue
 *   just as timer_add() does, but the timer will NOT be triggered
 *   immediately (useful for scheduling things).
 *
 *
 * int timer_process(struct timespec *delay)
 *
 *    Process timer queue.
 *
 *
 * int timer_remove(void (*callback) (void *data), void *data)
 *
 *   Remove a new timer with given callback and data.
 *
 *
 * void timer_exit(void)
 *
 *   Release all timers and free the associated memory block.
 *
 */


#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include "debug.h"
#include "cfg.h"
#include "timer.h"

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

/* threshold in milliseconds that differentiates between clock skew
   and clock jitter */
#define CLOCK_SKEW_DETECT_TIME_IN_MS 1000

/* structure for storing all relevant data of a single timer */
typedef struct TIMER {
    /* pointer to function of type void func(void *data) that will be
       called when the timer is processed; it will also be used to
       identify a specific timer */
    void (*callback) (void *data);

    /* pointer to data which will be passed to the callback function;
       it will also be used to identify a specific timer */
    void *data;

    /* struct to hold the time (in seconds and milliseconds since the
       Epoch) when the timer will be processed for the next time */
    struct timeval when;

    /* specifies the timer's triggering interval in milliseconds */
    int interval;

    /* specifies whether the timer should trigger indefinitely until
       it is deleted (value of 0) or only once (all other values) */
    int one_shot;

    /* marks timer as being active (so it will get processed) or
       inactive (which means the timer has been deleted and its
       allocated memory may be re-used) */
    int active;
} TIMER;

/* number of allocated timer slots */
int nTimers = 0;

/* pointer to memory allocated for storing the timer slots */
TIMER *Timers = NULL;


static void timer_inc(const int timer, struct timeval *now)
/*  Update the time a given timer updates next.

    timer (integer): internal ID of timer that is to be updated

	now (timeval pointer): struct holding the "current" time

	return value: void
 */
{
    /* calculate the time difference between the last time the given
       timer has been processed and the current time */
    struct timeval diff;
    timersub(now, &Timers[timer].when, &diff);

    /* convert this time difference to fractional seconds */
    float time_difference = diff.tv_sec + diff.tv_usec / 1000000.0f;

    /* convert time difference to fractional milliseconds */
    time_difference = time_difference * 1000.0f;

    /* calculate the number of timer intervals that have passed since
       the last timer the given timer has been processed -- value is
       truncated (rounded down) to an integer */
    int number_of_intervals = (int) (time_difference / Timers[timer].interval);

    /* notify the user in case one or more timer intervals have been
       missed */
    if (number_of_intervals > 0)
	info("Timer #%d skipped %d interval(s) or %d ms.", timer, number_of_intervals,
	     number_of_intervals * Timers[timer].interval);

    /* increment the number of passed intervals in order to skip all
       missed intervals -- thereby avoiding that unprocessed timers
       stack up, continuously update and are notoriously late (certain
       railway companies might learn a lesson from us <g>) */
    number_of_intervals++;

    /* calculate time difference between the last time the timer has
       been processed and the next time it will be processed */
    int interval = Timers[timer].interval * number_of_intervals;

    /* convert time difference (in milliseconds) to a "timeval"
       struct (in seconds and microseconds) */
    struct timeval tv_interval = {
	.tv_sec = interval / 1000,
	.tv_usec = (interval % 1000) * 1000
    };

    /* finally, add time difference to the timer's trigger */
    timeradd(&Timers[timer].when, &tv_interval, &Timers[timer].when);
}


int timer_remove(void (*callback) (void *data), void *data)
/*  Remove a timer with given callback and data.

    callback (void pointer): function of type void func(void *data);
    here, it will be used to identify the timer

	data (void pointer): data which will be passed to the callback
	function; here, it will be used to identify the timer

	return value (integer): returns a value of 0 on successful timer
	removal; otherwise returns a value of -1
*/
{
    int timer;			/* current timer's ID */

    /* loop through the timer slots and try to find the specified
       timer slot by looking for its settings */
    for (timer = 0; timer < nTimers; timer++) {
	/* skip inactive (i.e. deleted) timers */
	if (Timers[timer].active == 0)
	    continue;

	if (Timers[timer].callback == callback && Timers[timer].data == data) {
	    /* we have found the timer slot, so mark it as being inactive;
	       we will not actually delete the slot, so its allocated
	       memory may be re-used */
	    Timers[timer].active = 0;

	    /* signal successful timer removal */
	    return 0;
	}
    }

    /* we have NOT found the timer slot, so signal failure by
       returning a value of -1 */
    return -1;
}


int timer_add(void (*callback) (void *data), void *data, const int interval, const int one_shot)
/*  Create a new timer and add it to the timer queue.

    callback (void pointer): function of type void func(void *data)
	which will be called whenever the timer triggers; this pointer
	will also be used to identify a specific timer

	data (void pointer): data which will be passed to the callback
	function; this pointer will also be used to identify a specific
	timer

	interval (integer): specifies the timer's triggering interval in
	milliseconds

	one_shot (integer): specifies whether the timer should trigger
	indefinitely until it is deleted (value of 0) or only once (all
	other values)

	return value (integer): returns a value of 0 on successful timer
	creation; otherwise returns a value of -1
*/
{
    int timer;			/* current timer's ID */
    struct timeval now;		/* struct to hold current time */

    /* try to minimize memory usage by looping through the timer slots
       and looking for an inactive timer */
    for (timer = 0; timer < nTimers; timer++) {
	if (Timers[timer].active == 0) {
	    /* we've just found one, so let's reuse it ("timer" holds its
	       ID) by breaking the loop */
	    break;
	}
    }

    /* no inactive timers (or none at all) found, so we have to add a
       new timer slot */
    if (timer >= nTimers) {
	/* increment number of timers and (re-)allocate memory used for
	   storing the timer slots */
	nTimers++;
	Timers = realloc(Timers, nTimers * sizeof(*Timers));

	/* make sure "timer" points to valid memory */
	timer = nTimers - 1;

	/* realloc() has failed */
	if (Timers == NULL) {
	    /* restore old number of timers */
	    nTimers--;

	    /* signal unsuccessful timer creation */
	    return -1;
	}
    }

    /* get current time so the timer triggers immediately */
    gettimeofday(&now, NULL);

    /* initialize timer data */
    Timers[timer].callback = callback;
    Timers[timer].data = data;
    Timers[timer].when = now;
    Timers[timer].interval = interval;
    Timers[timer].one_shot = one_shot;

    /* set timer to active so that it is processed and not overwritten
       by the memory optimization routine above */
    Timers[timer].active = 1;

    /* one-shot timers should NOT fire immediately, so delay them by a
       single timer interval */
    if (one_shot) {
	timer_inc(timer, &now);
    }

    /* signal successful timer creation */
    return 0;
}


int timer_add_late(void (*callback) (void *data), void *data, const int interval, const int one_shot)
/*  This function creates a new timer and adds it to the timer queue
	just as timer_add() does, but the timer will NOT be triggered
	immediately (useful for scheduling things).

	callback (void pointer): function of type void func(void *data)
	which will be called whenever the timer triggers; this pointer
	will also be used to identify a specific timer

	data (void pointer): data which will be passed to the callback
	function; this pointer will also be used to identify a specific
	timer

	interval (integer): specifies the timer's triggering interval in
	milliseconds

	one_shot (integer): specifies whether the timer should trigger
	indefinitely until it is deleted (value of 0) or only once (all
	other values)

	return value (integer): returns a value of 0 on successful timer
	creation; otherwise returns a value of -1
*/
{
    /* create new timer slot and add it to the timer queue; mask it as
       one-shot timer for now, so the timer will be delayed by a
       single timer interval */
    if (!timer_add(callback, data, interval, 1)) {
	/* signal unsuccessful timer creation */
	return -1;
    }

    int timer;			/* current timer's ID */

    /* loop through the timer slots and try to find the new timer slot
       by looking for its settings */
    for (timer = 0; timer < nTimers; timer++) {
	/* skip inactive (i.e. deleted) timers */
	if (Timers[timer].active == 0)
	    continue;

	if (Timers[timer].callback == callback && Timers[timer].data == data && Timers[timer].interval == interval) {
	    /* we have found the new timer slot, so unmask it by setting
	       its "one_shot" variable to the REAL value; then signal
	       successful timer creation */
	    Timers[timer].one_shot = one_shot;

	    /* signal successful timer creation */
	    return 0;
	}
    }

    /* we have NOT found the new timer slot for some reason, so signal
       failure by returning a value of -1 */
    return -1;
}


int timer_process(struct timespec *delay)
/*  Process timer queue.

	delay (timespec pointer): struct holding delay till the next
	upcoming timer event

	return value (integer): returns a value of 0 when timers have been
	processed successfully; otherwise returns a value of -1
*/
{
    struct timeval now;		/* struct to hold current time */

    /* get current time to check which timers need processing */
    gettimeofday(&now, NULL);

    /* sanity check; by now, at least one timer should be
       instantiated */
    if (nTimers <= 0) {
	/* otherwise, print an error and return a value of -1 to
	   signal an error */
	error("Huh? Not even a single timer to process? Dazed and confused...");
	return -1;
    }

    int timer;			/* current timer's ID */

    /* process all expired timers */
    for (timer = 0; timer < nTimers; timer++) {
	/* skip inactive (i.e. deleted) timers */
	if (Timers[timer].active == 0)
	    continue;

	/* check whether current timer needs to be processed, i.e. the
	   timer's triggering time is less than or equal to the current
	   time; according to the man page of timercmp(), this avoids
	   using the operators ">=", "<=" and "==" which might be broken
	   on some systems */
	if (!timercmp(&Timers[timer].when, &now, >)) {
	    /* if the timer's callback function has been set, call it and
	       pass the corresponding data */
	    if (Timers[timer].callback != NULL) {
		Timers[timer].callback(Timers[timer].data);
	    }

	    /* check for one-shot timers */
	    if (Timers[timer].one_shot) {
		/* mark one-shot timer as inactive (which means the timer has
		   been deleted and its allocated memory may be re-used) */
		Timers[timer].active = 0;
	    } else {
		/* otherwise, re-spawn timer by adding one triggering interval
		   to its triggering time */
		timer_inc(timer, &now);
	    }
	}
    }

    int next_timer = -1;	/* ID of the next upcoming timer */

    /* loop through the timer slots and try to find the next upcoming
       timer */
    for (timer = 0; timer < nTimers; timer++) {
	/* skip inactive (i.e. deleted) timers */
	if (Timers[timer].active == 0)
	    continue;

	/* if this is the first timer that we check, mark it as the next
	   upcoming timer; otherwise, we'll have nothing to compare
	   against in this loop */
	if (next_timer < 0)
	    next_timer = timer;
	/* check whether current timer needs processing prior to the one
	   selected */
	else if (timercmp(&Timers[timer].when, &Timers[next_timer].when, <)) {
	    /* if so, mark it as the next upcoming timer */
	    next_timer = timer;
	}
    }

    /* sanity check; we should by now have found the next upcoming
       timer */
    if (next_timer < 0) {
	/* otherwise, print an error and return a value of -1 to signal an
	   error */
	error("Huh? Not even a single timer left? Dazed and confused...");
	return -1;
    }

    /* processing all the timers might have taken a while, so update
       the current time to compensate for processing delay */
    gettimeofday(&now, NULL);

    struct timeval diff;	/* struct holding the time difference
				   between current time and the triggering time of the
				   next upcoming timer event */

    /* calculate delay to the next upcoming timer event and store it
       in "diff" */
    timersub(&Timers[next_timer].when, &now, &diff);

    /* convert "diff" to milliseconds */
    int time_difference = (diff.tv_sec * 1000.0f) + (diff.tv_usec / 1000.0f);

    /* a notable negative delay has occurred (positive clock skew or
       some timers are faster than the time needed for processing
       their callbacks) */
    if (time_difference < (-CLOCK_SKEW_DETECT_TIME_IN_MS)) {
	/* zero "diff" so the next update is triggered immediately */
	timerclear(&diff);
    } else {
	/* if there is a notable difference between "time_difference" and
	   the next upcoming timer's interval, assume clock skew */
	if (time_difference > (Timers[next_timer].interval + CLOCK_SKEW_DETECT_TIME_IN_MS)) {
	    /* extract clock skew from "time_difference" by eliminating
	       the timer's triggering interval */
	    int skew = time_difference - Timers[next_timer].interval;

	    /* display an info message to inform the user */
	    info("Oops, clock skewed by %d ms, updating timestamps...", skew);

	    /* convert clock skew from milliseconds to "timeval"
	       structure */
	    struct timeval clock_skew = {
		.tv_sec = skew / 1000,
		.tv_usec = (skew % 1000) * 1000
	    };

	    /* process all timers */
	    for (timer = 0; timer < nTimers; timer++) {
		/* skip inactive (i.e. deleted) timers */
		if (Timers[timer].active == 0)
		    continue;

		/* correct timer's time stamp by clock skew */
		timersub(&Timers[timer].when, &clock_skew, &Timers[timer].when);
	    }

	    /* finally, zero "diff" so the next update is triggered
	       immediately */
	    timerclear(&diff);
	}
    }

    /* set timespec "delay" passed by calling function to "diff" */
    delay->tv_sec = diff.tv_sec;
    /* timespec uses nanoseconds instead of microseconds!!! */
    delay->tv_nsec = diff.tv_usec * 1000;

    /* signal successful timer processing */
    return 0;
}


void timer_exit(void)
/*  Release all timers and free the associated memory block.

	return value: void
*/
{
    /* reset number of allocated timer slots */
    nTimers = 0;

    /* free memory used for storing the timer slots */
    if (Timers != NULL) {
	free(Timers);
	Timers = NULL;
    }
}