#include <ncbi.h>
#include <gishlib.h>

static PoolBlkPtr	apool;
static AlarmBlkPtr	a0;

static void	(*alarm_handler_orig)();
static void	alarm_reset PROTO((AlarmBlkPtr, time_t));
static void	alarm_remove PROTO((AlarmBlkPtr));
static void	alarm_add PROTO((AlarmBlkPtr, time_t));
static AlarmBlkPtr alarm_set PROTO((time_t when, FnPtr func, VoidPtr arg));

static void
alarm_handler(int signo)
{
	AlarmBlk	ab;
	register time_t	now, t;
	double	since;

	if (a0 == NULL)
		return;

	critical_begin();

	while (a0 != NULL && a0->dingdong <= time(NULL)) {
		ab = *a0;
		if (a0->period == 0)
			alarm_remove(a0);
		else
			alarm_reset(a0, a0->period);
		(*ab.func)(ab.arg);
	}

	if (a0 != NULL) {
		sys_signal(SIGALRM, alarm_handler);
		now = time(NULL);
		since = difftime(a0->dingdong, now);
		t = MAX(1, since);
		alarm(t);
	}
	else {
		alarm(0);
		sys_sigpurge(SIGALRM);
		sys_signal(SIGALRM, alarm_handler_orig);
	}

	critical_end();
	return;
}

/* alarm_reset -- set a new alarm time */
static void
alarm_reset(abp, when)
	register AlarmBlkPtr	abp;
	time_t	when;
{
	register AlarmBlkPtr	ap, ap2;

	if (abp == NULL || when == 0)
		return;

	/* Find the previous pointer to abp */
	for (ap2 = NULL, ap = a0; ap != abp; ap2 = ap, ap = ap->next) {
		if (ap == NULL)
			return; /* not found */
	}
	/* Remove item from linked list */
	if (ap2 != NULL)
		ap2->next = ap->next;
	else
		a0 = ap->next;

	alarm_add(abp, when);
	return;
}

static void
alarm_remove(abp)
	AlarmBlkPtr	abp;
{
	register AlarmBlkPtr	ap, ap2;

	if (abp == NULL) { /* remove all alarms */
		PoolPutLink(apool, (VoidPtr)a0);
		a0 = NULL;
		return;
	}

	for (ap2 = NULL, ap = a0; ap != NULL; ap2 = ap, ap = ap->next) {
		if (ap == abp) {
			if (ap2 == NULL)
				a0 = ap->next;
			else
				ap2->next = ap->next;
			PoolPut(apool, (VoidPtr) ap);
			return;
		}
	}
	/* item not found */
}

/* alarm_add -- insert an event in temporal order in the linked list */
static void
alarm_add(abp, whence)
	AlarmBlkPtr	abp;
	time_t	whence;
{
	register AlarmBlkPtr	ap, ap2;
	register time_t	now;
	void	(*oldhandler)();

	now = time(NULL);
	whence = MAX(1, whence);
	abp->dingdong = now + whence;
	for (ap2 = NULL, ap = a0;
			ap != NULL && ap->dingdong <= abp->dingdong;
			ap2 = ap, ap = ap->next) {
	}
	if (ap2 == NULL) {
		a0 = abp;
		abp->next = ap;
		oldhandler = sys_signal(SIGALRM, alarm_handler);
		if (oldhandler != alarm_handler)
			alarm_handler_orig = oldhandler;
		alarm(whence);
	}
	else {
		ap2->next = abp;
		abp->next = ap;
	}
}

static AlarmBlkPtr
alarm_set(when, func, arg)
	time_t	when;
	FnPtr	func;
	VoidPtr	arg;
{
	register AlarmBlkPtr	abp;

	if (apool == NULL) {
		apool = PoolNew(20, sizeof(AlarmBlk), offsetof(AlarmBlk,next), NULL);
		if (apool == NULL)
			return NULL;
	}

	abp = (AlarmBlkPtr) PoolGet(apool);
	if (abp != NULL) {
		abp->next = NULL;
		abp->period = 0;
		abp->func = func;
		abp->arg = arg;
		alarm_add(abp, when);
	}

	return abp;
}

int LIBCALL
AlarmSet(abpp, when, func, arg)
	AlarmBlkPtr	PNTR abpp;
	time_t	when;
	FnPtr	func;
	VoidPtr	arg;
{
	AlarmBlkPtr	ap;

	if (when == 0 || abpp == NULL)
		return 1;
	critical_begin();
	ap = alarm_set(when, func, arg);
	*abpp = ap;
	critical_end();
	return ap == NULL;
}


int LIBCALL
AlarmEvery(abpp, period, func, arg)
	AlarmBlkPtr	PNTR abpp;
	time_t	period;
	FnPtr	func;
	VoidPtr	arg;
{
	AlarmBlkPtr	ap;

	if (period == 0 || abpp == NULL)
		return 1;
	critical_begin();
	ap = alarm_set(period, func, arg);
	if (ap != NULL)
		ap->period = period;
	*abpp = ap;
	critical_end();
	return ap == NULL;
}

void LIBCALL
AlarmReset(abp, when)
	AlarmBlkPtr	abp;
	time_t	when;
{
	critical_begin();
	alarm_reset(abp, when);
	critical_end();
}

void LIBCALL
AlarmClr(abp)
	AlarmBlkPtr	abp;
{
	if (abp == NULL || a0 == NULL)
		return;

	critical_begin();
	alarm_remove(abp);
	if (a0 == NULL) {
		alarm(0);
		sys_sigpurge(SIGALRM);
		sys_signal(SIGALRM, alarm_handler_orig);
	}
	critical_end();
}

void LIBCALL
AlarmClrAll()
{
	critical_begin();
	alarm_remove(NULL);
	if (a0 == NULL) {
		alarm(0);
		sys_sigpurge(SIGALRM);
		sys_signal(SIGALRM, alarm_handler_orig);
	}
	critical_end();
}

long LIBCALL
AlarmWhen()
{
	register time_t	t, now;
	double	til;

	critical_begin();
	t = (a0 != NULL ? a0->dingdong : 0);
	critical_end();
	if (t == 0)
		return t;

	now = time(NULL);
	til = difftime(t, now);
	if (til < 0.)
		return 0;
	til += 0.99999999999;
	return (long)til;
}
