/* Routines for time-delayed actions.
 *
 * IRC Services is copyright (c) 1996-2009 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#include "services.h"
#define IN_TIMEOUT_C
#include "timeout.h"

/*************************************************************************/

static Timeout *timeouts = NULL;
static int checking_timeouts = 0;

/*************************************************************************/

#ifdef DEBUG_COMMANDS

/* Send the timeout list to the given user. */

void send_timeout_list(User *u)
{
    Timeout *to;
    uint32 now = time_msec();

    notice(ServerName, u->nick, "Now: %u.%03u", now/1000, now%1000);
    LIST_FOREACH (to, timeouts) {
	notice(ServerName, u->nick, "%p: %u.%03u: %p (%p)",
	       to, to->timeout/1000, to->timeout%1000, to->code, to->data);
    }
}

#endif	/* DEBUG_COMMANDS */

/*************************************************************************/

/* Check the timeout list for any pending actions. */

void check_timeouts(void)
{
    Timeout *to, *to2;
    uint32 now = time_msec();

    if (checking_timeouts)
	fatal("check_timeouts() called recursively!");
    checking_timeouts = 1;
    if (debug >= 2) {
	log("debug: Checking timeouts at time_msec = %u.%03u",
	    now/1000, now%1000);
    }

    LIST_FOREACH_SAFE (to, timeouts, to2) {
	if (to->timeout) {
	    if ((int32)(to->timeout - now) > 0)
		continue;
	    if (debug >= 3) {
		log("debug: Running timeout %p (code=%p repeat=%d)",
		    to, to->code, to->repeat);
	    }
	    to->code(to);
	    if (to->repeat) {
		to->timeout = now + to->repeat;
		continue;
	    }
	}
	LIST_REMOVE(to, timeouts);
	free(to);
    }

    if (debug >= 2)
	log("debug: Finished timeout list");
    checking_timeouts = 0;
}

/*************************************************************************/

/* Add a timeout to the list to be triggered in `delay' seconds.  If
 * `repeat' is nonzero, do not delete the timeout after it is triggered.
 * This must maintain the property that timeouts added from within a
 * timeout routine do not get checked during that run of the timeout list.
 */

Timeout *add_timeout(int delay, void (*code)(Timeout *), int repeat)
{
    if (delay > 4294967)  /* 2^32 / 1000 */
	delay = 4294967;
    return add_timeout_ms((uint32)delay*1000, code, repeat);
}

/*************************************************************************/

Timeout *add_timeout_ms(uint32 delay, void (*code)(Timeout *), int repeat)
{
    Timeout *t = smalloc(sizeof(Timeout));
    t->settime = time(NULL);
    t->timeout = time_msec() + delay;
    t->code = code;
    t->data = NULL;
    t->repeat = repeat ? delay : 0;
    LIST_INSERT(t, timeouts);
    return t;
}

/*************************************************************************/

/* Remove a timeout from the list (if it's there). */

void del_timeout(Timeout *t)
{
    Timeout *ptr;

    LIST_FOREACH (ptr, timeouts) {
	if (ptr == t)
	    break;
    }
    if (!ptr) {
	log("timeout: BUG: attempted to remove timeout %p (not on list)", t);
	return;
    }
    if (checking_timeouts) {
	t->timeout = 0;  /* delete it when we hit it in the list */
	return;
    }
    LIST_REMOVE(t, timeouts);
    free(t);
}

/*************************************************************************/
