/*
 * Timers (one-short and periodic)
 *
 * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
 */

#include <stdlib.h>
#include <time.h>
#include <libisns/isns.h>
#include <libisns/util.h>

typedef struct isns_timer isns_timer_t;
struct isns_timer {
	isns_list_t		it_list;
	time_t			it_when;
	unsigned int		it_period;
	isns_timer_callback_t *	it_func;
	void *			it_data;
};


static ISNS_LIST_DECLARE(timers);

static void
__isns_arm_timer(isns_timer_t *tm)
{
	isns_list_t	*pos, *next;
	time_t		when = tm->it_when;

	isns_list_foreach(&timers, pos, next) {
		isns_timer_t *cur = isns_list_item(isns_timer_t, it_list, pos);

		if (when < cur->it_when)
			break;
	}
	isns_item_insert_before(pos, &tm->it_list);
}

static isns_timer_t *
__isns_create_timer(time_t when,
		unsigned int period,
		isns_timer_callback_t *fn,
		void *data)
{
	isns_timer_t	*tm;

	tm = isns_calloc(1, sizeof(*tm));
	tm->it_when = when;
	tm->it_period = period;
	tm->it_func = fn;
	tm->it_data = data;
	return tm;
}

void
isns_add_timer(unsigned int period,
		isns_timer_callback_t *fn,
		void *data)
{
	isns_timer_t	*tm;

	isns_assert(period);
	tm = __isns_create_timer(time(NULL) + period, period, fn, data);
	__isns_arm_timer(tm);
}

void
isns_add_oneshot_timer(unsigned int expires,
		isns_timer_callback_t *fn,
		void *data)
{
	isns_timer_t	*tm;

	tm = __isns_create_timer(time(NULL) + expires, 0, fn, data);
	__isns_arm_timer(tm);
}

void
isns_cancel_timer(isns_timer_callback_t *fn, void *data)
{
	isns_list_t	*pos, *next;

	isns_list_foreach(&timers, pos, next) {
		isns_timer_t *tm = isns_list_item(isns_timer_t, it_list, pos);

		if (tm->it_func == fn
		 && (data == NULL || tm->it_data == data)) {
			isns_list_del(pos);
			isns_free(tm);
		}
	}
}

time_t
isns_run_timers(void)
{

	while (!isns_list_empty(&timers)) {
		isns_timer_t *tm = isns_list_item(isns_timer_t, it_list, timers.next);
		isns_timer_callback_t *func;
		time_t expire;
		void *data;

		expire = tm->it_when;
		if (time(NULL) < expire)
			return expire;

		isns_list_del(&tm->it_list);
		func = tm->it_func;
		data = tm->it_data;
		expire = 0;

		/* If it's a periodic timer, rearm it now. This allows
		 * the timer callback to cancel the timer. */
		if (tm->it_period) {
			tm->it_when = time(NULL) + tm->it_period;
			__isns_arm_timer(tm);
		} else {
			isns_free(tm);
		}

		func(data);
	}

	return 0;
}
