#include <stdio.h>
#include "global.h"
#include "timer.h"
#include "proc.h"
#include "mbuf.h"
#include "commands.h"
#include "daemon.h"
#include "hardware.h"
#include "socket.h"

/* Head of running timer chain.
 * The list of running timers is sorted in increasing order of interval;
 * i.e., the first timer to expire is always at the head of the list.
 * For performance reasons, the intervals are incremental, so that only
 * the first entry needs to be decremented on each tick. Hence there are
 * special routines for reading the number of ticks remaining on a
 * running timer; you can't just look at the field directly.
 */
static struct timer *Timers;

/* This variable keeps a running count of ticks since program startup */
int32 Clock;

static void t_alarm(void *x);

/* Process that handles clock ticks */
void
timerproc(i,v1,v2)
int i;
void *v1,*v2;
{
	register struct timer *t;
	register struct timer *expired;
	char i_state;

	for(;;){
		i_state = dirps();	/* Tick is modified by an interrupt */
		while(Tick == 0)
			pwait(&Tick);
		Tick--;
		restore(i_state);

		if(!istate()){
			printf("timer: ints were off!\n");
			restore(1);
		}
		Clock++;	/* Keep system time */
		refiq();	/* Replenish interrupt pool */
		systick();	/* Machine-dependent per-tick stuff */
		tflush();	/* Flush current session output */
		pwait(NULL);	/* Let them all do their writes */
		fflush(stdout);	/* Flush out buffered console stuff */

		if(Timers == NULLTIMER)
			continue;	/* Nothing to do on this tick */

		/* Decrement the first timer. If it expires,
		 * take it off the running list and put it
		 * on a singly linked list of expired timers
		 */
		Timers->count--;

		/* Initialize null expired timer list */
		expired = NULLTIMER;

		while(Timers != NULLTIMER && Timers->count <= 0){
			if(Timers->next == Timers){
				printf("PANIC: Timer loop at %lx\n",
				 (long)Timers);
				iostop();
				exit(1);
			}
			/* Save Timers since stop_timer will change it */
			t = Timers;
			stop_timer(t);
			t->state = TIMER_EXPIRE;
			/* Add to expired timer list */
			t->next = expired;
			expired = t;
		}
		/* Now go through the list of expired timers, removing each
		 * one and kicking the notify function, if there is one
		 */
		while((t = expired) != NULLTIMER){
			expired = t->next;
			if(t->func){
				(*t->func)(t->arg);
			}
		}
		pwait(NULL);	/* Let them run before handling more ticks */
	}
}
/* Start a timer */
void
start_timer(t)
struct timer *t;
{
	register struct timer *tnext,*tprev;
	int32 tot;

	if(t == NULLTIMER)
		return;
	if(t->state == TIMER_RUN)
		stop_timer(t);
	if(t->start == 0)
		return;		/* A start value of 0 disables the timer */

	t->state = TIMER_RUN;
	/* Find right place on list for this guy */
	tot = 0;
	tprev = NULLTIMER;
	for(tnext = Timers;tnext != NULLTIMER;tnext = tprev->next){
		if(tnext->count + tot > t->start)
			break;
		tprev = tnext;
		tot += tnext->count;
	}
	/* At this point, tprev points to the entry that should go right
	 * before us, and tnext points to the entry just after us. Either or
	 * both may be null.
	 */
	t->count = t->start - tot;	/* Adjust for entries before us */
	if((t->prev = tprev) == NULLTIMER)
		Timers = t;		/* Put at beginning */
	else
		tprev->next = t;

	if((t->next = tnext) != NULLTIMER){
		tnext->prev = t;
		/* Adjust the following entry's count */
		tnext->count -= t->count;
	}
}
/* Stop a timer */
void
stop_timer(t)
register struct timer *t;
{
	register struct timer *tp;

	if(t == NULLTIMER)
		return;
	if(t->state == TIMER_RUN){
		/* Delete from active timer list */
		if(t->next != NULLTIMER)
			t->next->prev = t->prev;
		if(t->prev != NULLTIMER)
			t->prev->next = t->next;
		else
			Timers = t->next;
		/* Adjust count for the next timer */
		if((tp = t->next) != NULLTIMER)
			tp->count += t->count;
	}
	t->state = TIMER_STOP;
}
/* Return count of ticks remaining on this timer */
int32
read_timer(t)
struct timer *t;
{
	register struct timer *tp;
	int32 tot;

	if(t->state != TIMER_RUN)
		return 0;

	tot = 0;
	for(tp = Timers;tp != NULLTIMER; tp = tp->next){
		tot += tp->count;
		if(tp == t)
			break;
	}
	return tot;
}
/* Delay process for specified number of ticks */
void
pause(ticks)
int32 ticks;
{
	if(Curproc == NULLPROC || ticks == 0)
		return;
	alarm(ticks);
	/* The actual event doesn't matter, since we'll be alerted */
	while(Curproc->alarm.state == TIMER_RUN)
		pwait(Curproc);

	alarm(0L); /* Make sure it's stopped, in case we were killed */	
}
static void
t_alarm(x)
void *x;
{
	alert((struct proc *)x,EALARM);
}
/* Send signal to current process after specified number of ticks */
void
alarm(ticks)
int32 ticks;
{
	if(Curproc != NULLPROC){
		Curproc->alarm.start = ticks;
		Curproc->alarm.func = t_alarm;
		Curproc->alarm.arg = (char *)Curproc;
		start_timer(&Curproc->alarm);
	}
}
/* Convert time count in ticks to printable days:hr:min:sec format */
char *
tformat(t)
int32 t;
{
	static char buf[16];
	unsigned int days,hrs,mins,secs;

	t = (t * MSPTICK) / 1000;	/* Convert to seconds */
	secs = t % 60;
	t /= 60;
	mins = t % 60;
	t /= 60;
	hrs = t % 24;
	t /= 24;
	days = t;
	sprintf(buf,"%u:%02u:%02u:%02u",days,hrs,mins,secs);
	return buf;
}
	
