/* Timer functions

   Cygnus IRC Services - Copyright (c) 2001-2002 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/services.h"

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

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

    if (checking_timeouts)
	fatal (1, "check_timeouts() called recursively!");

    checking_timeouts = 1;

    to = timeouts;

    while (to)
    {
	if (!to->timeout)
	{
	    to2 = to->next;

	    if (to->next)
		to->next->prev = to->prev;
	    if (to->prev)
		to->prev->next = to->next;
	    else
		timeouts = to->next;

	    free (to);

	    to = to2;
	}

	if ((int32)(to->timeout - now) > 0)
	{
	    to = to->next;
	    continue;
	}

	to->code (to);

	if (to->repeat)
	{
	    to = to->next;
	    continue; 
	}

	to2 = to->next;   

	if (to->next)
	    to->next->prev = to->prev;
	if (to->prev)
	    to->prev->next = to->next;
	else
	    timeouts = to->next;

	free (to);
	to = to2; 
    }

    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)
        delay = 4294967;

    return add_timeout_ms (delay * 1000, code, repeat);
}

Timeout *add_timeout_ms (int delay, void (*code) (Timeout *), int repeat)
{
    Timeout *t = smalloc (sizeof (Timeout));
    t->settime = time (NULL);
    t->timeout = time_msec () + delay;
    t->code = code;
    t->repeat = repeat;   
    t->next = timeouts;
    t->prev = NULL;

    if (timeouts)
	timeouts->prev = t;
    timeouts = t;

    return t;
}

/* Remove a timeout from the list (if it's there). */  
void del_timeout (Timeout *t)
{
    Timeout *ptr;

    for (ptr = timeouts; ptr; ptr = ptr->next)
	if (ptr == t)
	    break;

    if (!ptr)
	return;

    if (checking_timeouts)
    {
	t->timeout = 0;
	return;
    }

    if (t->prev) 
	t->prev->next = t->next;
    else
	timeouts = t->next;

    if (t->next)
	t->next->prev = t->prev;

    free (t);
}

/* Remove a timeout and release a nick */
static void timeout_release (Timeout *t)
{
    NickInfo *ni = t->data;

    release (ni, 1);
}

/* Remove a timeout and collide a nick */
static void timeout_collide (Timeout *t)
{
    NickInfo *ni = t->data;
    User *u;

    /* If they don't exist anymore, don't kill them. */
    if (!(u = finduser (ni->nick)))
	return;

    /* If they've identified, don't kill them. */
    if (u->lastnick)
    {
	if (ni->host)
	{
	    if (!stricmp (u->lastnick, ni->host))
		return;
	}       
	else
	    if (!stricmp (u->lastnick, ni->nick))
		return;
    }

    collide (ni, 1, 0);
}

/* 40 seconds left notice (20 seconds in) */
static void timeout_mess40 (Timeout *t)
{
    NickInfo *ni = t->data;
    User *u;

    /* If they don't exist anymore, don't send them the notice. */
    if (!(u = finduser (ni->nick)))
	return;

    /* If they've identified, don't send them the notice. */
    if (u->lastnick)
    {
	if (ni->host)
	{
	    if (!stricmp (u->lastnick, ni->host))
		return;
	}
	else
	    if (!stricmp (u->lastnick, ni->nick))
		return;
    }

    notice (s_NickServ, ni->nick, NS_40_SECONDS);
}

/* 20 seconds left notice (40 seconds in) */
static void timeout_mess20 (Timeout *t)
{
    NickInfo *ni = t->data;
    User *u;

    /* If they don't exist anymore, don't send them the notice. */
    if (!(u = finduser (ni->nick)))
	return;

    /* If they've identified, don't send them the notice. */
    if (u->lastnick)
    {
	if (ni->host)
	{
	    if (!stricmp (u->lastnick, ni->host))
		return;
	}       
	else
	    if (!stricmp (u->lastnick, ni->nick))
		return;
    }

    notice (s_NickServ, ni->nick, NS_20_SECONDS);
    send_cmd (me.name, "433 %s %s :Nickname is registered to someone else.", ni->nick, ni->nick);
}

/* Remove ChanServ from the channel */
static void timeout_uninhabit (Timeout *t)
{
    Channel *ci = t->data;

    send_cmd (s_ChanServ, "%s %s", me.token ? "D" : "PART", ci->name);
}

static struct ns_timeout {
    struct ns_timeout *next, *prev;
    NickInfo *ni;
    Timeout *to;
    int type;
} *ns_timeouts;

static struct cs_timeout {
    struct cs_timeout *next, *prev;
    ChanInfo *ci;
    Timeout *to;
    int type;
} *cs_timeouts;

/* Add a NickServ timeout */
void add_ns_timeout (NickInfo *ni, int type, time_t delay)
{
    Timeout *to;
    struct ns_timeout *t;
    void (*timeout_routine)(Timeout *);

    if (type == TO_COLLIDE)
	timeout_routine = timeout_collide;
    else if (type == TO_RELEASE)
	timeout_routine = timeout_release;
    else if (type == TO_MESS40)
	timeout_routine = timeout_mess40;
    else if (type == TO_MESS20)
	timeout_routine = timeout_mess20;
    else
	return;

    to = add_timeout (delay, timeout_routine, 0);
    to->data = ni;
    t = smalloc (sizeof (*t));
    t->next = ns_timeouts;
    ns_timeouts = t;
    t->prev = NULL;
    t->ni = ni;
    t->to = to; 
    t->type = type;
}

/* Add a ChanServ timeout */
void add_cs_timeout (ChanInfo *ci, int type, time_t delay)
{       
    Timeout *to;
    struct cs_timeout *t;
    void (*timeout_routine)(Timeout *);

    if (type == TO_UNINHABIT)
        timeout_routine = timeout_uninhabit;
    else
	return;

    to = add_timeout (delay, timeout_routine, 0);
    to->data = ci;
    t = smalloc (sizeof (*t));
    t->next = cs_timeouts;
    cs_timeouts = t;
    t->prev = NULL;
    t->ci = ci;
    t->to = to;
    t->type = type;
}

/* Delete a NickServ timeout */
void del_ns_timeout (NickInfo *ni, int type, int del_to)
{
    struct ns_timeout *t, *t2;

    t = ns_timeouts;

    while (t)
    {
	if (t->ni == ni && (type < 0 || t->type == type))
	{
	    t2 = t->next;

	    if (t->next)
		t->next->prev = t->prev;
	    if (t->prev)
		t->prev->next = t->next;
	    else
		ns_timeouts = t->next;

	    if (del_to)
		del_timeout (t->to);

	    free (t);
	    t = t2;
        }
	else
	    t = t->next;
    }
}

/* Delete a ChanServ timeout */
void del_cs_timeout (ChanInfo *ci, int type, int del_to)
{
    struct cs_timeout *t, *t2;

    t = cs_timeouts;

    while (t)
    {
	if (t->ci == ci && (type < 0 || t->type == type))
	{
	    t2 = t->next;

	    if (t->next)
		t->next->prev = t->prev;
	    if (t->prev)
		t->prev->next = t->next;
	    else  
		cs_timeouts = t->next;

	    if (del_to)
		del_timeout (t->to);

	    free (t);
	    t = t2;
	}
	else
	    t = t->next;
    }
}
