/*
 * (C) 2006-2008 by Pablo Neira Ayuso <pablo@netfilter.org>
 *
 * 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 of the License, 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.
 */

#include "alarm.h"
#include "date.h"
#include <stdlib.h>
#include <limits.h>

static struct rb_root alarm_root = RB_ROOT;

void init_alarm(struct alarm_block *t,
		void *data,
		void (*fcn)(struct alarm_block *a, void *data))
{
	/* initialize the head to check whether a node is inserted */
	RB_CLEAR_NODE(&t->node);
	timerclear(&t->tv);
	t->data = data;
	t->function = fcn;
}

static void __add_alarm(struct alarm_block *alarm)
{
	struct rb_node **new = &(alarm_root.rb_node);
	struct rb_node *parent = NULL;

	while (*new) {
		struct alarm_block *this;

		this = container_of(*new, struct alarm_block, node);

		parent = *new;
		if (timercmp(&alarm->tv, &this->tv, <))
			new = &((*new)->rb_left);
		else
			new = &((*new)->rb_right);
	}

	rb_link_node(&alarm->node, parent, new);
	rb_insert_color(&alarm->node, &alarm_root);
}

void add_alarm(struct alarm_block *alarm, unsigned long sc, unsigned long usc)
{
	struct timeval tv;

	del_alarm(alarm);
	alarm->tv.tv_sec = sc;
	alarm->tv.tv_usec = usc;
	gettimeofday_cached(&tv);
	timeradd(&alarm->tv, &tv, &alarm->tv);
	__add_alarm(alarm);
}

void del_alarm(struct alarm_block *alarm)
{
	/* don't remove a non-inserted node */
	if (!RB_EMPTY_NODE(&alarm->node)) {
		rb_erase(&alarm->node, &alarm_root);
		RB_CLEAR_NODE(&alarm->node);
	}
}

int alarm_pending(struct alarm_block *alarm)
{
	if (RB_EMPTY_NODE(&alarm->node))
		return 0;

	return 1;
}

static struct timeval *
calculate_next_run(struct timeval *cand,
		   struct timeval *tv,
		   struct timeval *next_run)
{
	if (cand->tv_sec != LONG_MAX) {
		if (timercmp(cand, tv, >))
			timersub(cand, tv, next_run);
		else {
			/* loop again inmediately */
			next_run->tv_sec = 0;
			next_run->tv_usec = 0;
		}
		return next_run;
	}
	return NULL;
}

struct timeval *
get_next_alarm_run(struct timeval *next_run)
{
	struct rb_node *node;
	struct timeval tv;

	gettimeofday_cached(&tv);

	node = rb_first(&alarm_root);
	if (node) {
		struct alarm_block *this;
		this = container_of(node, struct alarm_block, node);
		return calculate_next_run(&this->tv, &tv, next_run);
	}
	return NULL;
}

struct timeval *
do_alarm_run(struct timeval *next_run)
{
	struct list_head alarm_run_queue;
	struct rb_node *node;
	struct alarm_block *this, *tmp;
	struct timeval tv;

	gettimeofday_cached(&tv);

	INIT_LIST_HEAD(&alarm_run_queue);
	for (node = rb_first(&alarm_root); node; node = rb_next(node)) {
		this = container_of(node, struct alarm_block, node);

		if (timercmp(&this->tv, &tv, >))
			break;

		list_add(&this->list, &alarm_run_queue);
	}

	/* must be safe as entries can vanish from the callback */
	list_for_each_entry_safe(this, tmp, &alarm_run_queue, list) {
		rb_erase(&this->node, &alarm_root);
		RB_CLEAR_NODE(&this->node);
		this->function(this, this->data);
	}

	return get_next_alarm_run(next_run);
}