/*
 * Timer handling module. Keeps track of several timers.
 *
 * Current implementation only deals with timers for less than 1 second.
 * This is mainly to shorten the code slightly in places and hence speed
 * things up (marginally).
 * This is a flaw as with real times, the system may perhaps stop for > 1sec.
 *
 * 17/03/94 jkb  creation
 */

#ifdef NEED_TIMER

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>

#include "timer.h"

/*
 * Structures
 */

typedef struct {
    void (*handler)(int);
    long interval; /* usec */
    long value;    /* usec */
} timerx_t;

/*
 * Function prototypes
 */
static void sig_alarm(int signum);

/*
 * Static global variables
 */

static timerx_t timer[MAX_TIMERS];
static int time_index = 0;
static struct timeval last;
static struct itimerval itv = { {0, 0}, {0, 0} };

/*
 * And now, the code...
 */

/*
 * Initialises code. Adds the signal handler and sets the 'last' time to be
 * now. Returns 0 for success, -1 for failure.
 */
int init_timer() {
    if (gettimeofday(&last, NULL) == -1) {
	perror("gettimeofday");
	return -1;
    }

    if ((void (*)())-1 == (signal(SIGALRM, sig_alarm))) {
	perror("signal(SIGALRM)");
	return -1;
    }

    return 0;
}

/*
 * Adds a timer to the timer list.
 * Returns the timer index number, or -1 for failure.
 * The index number is for use with change_timer().
 */
int add_timer(long interval, long value, void (*func)(int)) {
    int i;

    /*
     * NB: No checks performed.
     */
    timer[time_index].handler  = func;
    timer[time_index].interval = interval;
    timer[time_index].value    = value;

    time_index++;
    /*
     * Create a dummy alarm to ensure update of itv
     */
    sig_alarm(0);

    return time_index-1;
}

/*
 * Removes a timer from the timer list.
 */
void remove_timer(int index) {
    if (index < time_index) {
	memcpy(&timer[index], &timer[index+1], 
	       sizeof(*timer) * (time_index - index));
    }
    time_index--;
}

/*
 * Changes existing timer attributes.
 */
void change_timer(int index, int change,
		  long interval, long value, void (*func)(int)) {
    if (change & TIMER_HANDLER) {
	timer[time_index].handler  = func;
    }
    
    if (change & TIMER_INTERVAL) {
	timer[time_index].interval = interval;
    }
    
    if (change & TIMER_VALUE) {
	timer[time_index].value    = value;
    }

    /*
     * Create a dummy alarm to ensure update of itv
     */
    sig_alarm(0);
}

/*
 * Gets information on how long a timer has left to expire.
 */
long query_timer_expire(int index) {
    /*
     * Create a dummy alarm to ensure update of itv
     */
    sig_alarm(0);

    return timer[time_index].value;
}

/*
 * Gets information on how a timer latch value
 */
long query_timer_interval(int index) {
    return timer[time_index].interval;
}

/*
 * The ALARM signal handler itself.
 * We subtract the time taken from our timer array entries and see which
 * timers have expired.
 */
/* ARGSUSED */
static void sig_alarm(int signum) {
    struct timeval current;
    struct timeval diff;
    int i;
    long next;

    (void)signal(SIGALRM, SIG_IGN);

    (void)gettimeofday(&current, NULL);
    if ((diff.tv_usec = current.tv_usec - last.tv_usec) < 0) {
	diff.tv_usec += 1000000;
	/* diff.tv_sec++; */
    }
    /* diff.tv_sec = current.tv_sec - last.tv_sec; */
    memcpy(&last, &current, sizeof(struct timeval));

    /*
     * Processing the timer array backwards means that remove_timer()
     * won't have any odd side effects within the loop.
     */
    next = 9999999;
    for (i = time_index-1; i >= 0; i--) {
	if (signum && (timer[i].value -= diff.tv_usec) <= 0) {
	    timer[i].handler(0);

	    if (timer[i].interval) {
		/*
		 * We may be chugging - so perhaps we've missed several
		 * events.
		 */
		while ((timer[i].value += timer[i].interval) <= 0)
		    timer[i].handler(0);
	    } else {
		remove_timer(i);
	    }
	}

	if (timer[i].value < next)
	    next = timer[i].value;
    }

    (void)signal(SIGALRM, sig_alarm);

    /*
     * 'next' now holds the usec time for the next timer.
     */
    itv.it_value.tv_usec = next;
    setitimer(ITIMER_REAL, &itv, NULL);

    /*
     * I'm unhappy with this - I've seen tv_usec set to 0 at times which
     * is wrong!
     * printf("Next at %d,%d\n", itv.it_value.tv_sec, itv.it_value.tv_usec);
     */
}

#if 0
/* test code */

void eek1(int x) {
    puts("boo");
}

void eek2(int x) {
    puts("   oob");
}

int main() {
    int i, j;

    init_timer();
    add_timer(1000000/50, 1000000/50, eek1);
    add_timer(1000000/40, 1000000/40, eek2);

    for (i = 0; i < 10000; i++) {
	for (j = 0; j < 1000; j++);
    }

    return 0;
}

#endif /* test code */

#else /* NEED_TIMER */

static int hack; /* for DEC Alpha, c89 */

#endif
