/*      $NetBSD: callout.c,v 1.7 2003/03/05 21:05:38 wiz Exp $  */

/*
* The mrouted program is covered by the license in the accompanying file
* named "LICENSE".  Use of the mrouted program represents acceptance of
* the terms and conditions listed in that file.
*
* The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
* Leland Stanford Junior University.
*/

#include "defs.h"

/* the code below implements a callout queue */
static int id = 0;
static struct timeout_q  *Q = 0; /* pointer to the beginning of timeout queue */

static int in_callout = 0;

struct timeout_q {
       struct timeout_q *next;         /* next event */
       int              id;
       cfunc_t          func;          /* function to call */
       char             *data;         /* func's data */
       int              time;          /* time offset to next event*/
};

#ifdef IGMP_DEBUG
static void print_Q(void);
#else
#define print_Q()
#endif

int secs_remaining(int);

void
callout_init(void)
{
   Q = (struct timeout_q *) 0;
}


/*
* signal handler for SIGALARM that is called once every second
*/
void
age_callout_queue(void)
{
   struct timeout_q *ptr;

   if (in_callout)
       return;

   in_callout = 1;
   ptr = Q;

   while (ptr) {
       if (!ptr->time) {
           /* timeout has happened */
           Q = Q->next;

           in_callout = 0;
           if (ptr->func)
               ptr->func(ptr->data);
           in_callout = 1;

           free(ptr);
           ptr = Q;
       }
       else {
           ptr->time --;
#ifdef IGMP_DEBUG
           logit(LOG_DEBUG,0,"[callout, age_callout_queue] -- time (%d)", ptr->time);
#endif /* IGMP_DEBUG */
           in_callout = 0; return;
       }
   }
   in_callout = 0;
   return;
}


/*
* sets the timer
*
* delay: number of units for timeout
* action: function to be called on timeout
* data: what to call the timeout function with
*/
int
timer_setTimer(int delay, cfunc_t action, char *data)
{
   struct     timeout_q  *ptr, *node, *prev;

   if (in_callout)
       return -1;

   in_callout = 1;

   /* create a node */
   node = (struct timeout_q *)malloc(sizeof(struct timeout_q));
   if (node == 0) {
       logit(LOG_WARNING, 0, "Malloc Failed in timer_settimer\n");
       in_callout = 0;
       return -1;
   }
   node->func = action;
   node->data = data;
   node->time = delay;
   node->next = 0;
   node->id   = ++id;

   prev = ptr = Q;

   /* insert node in the queue */

   /* if the queue is empty, insert the node and return */
   if (!Q)
       Q = node;
   else {
       /* chase the pointer looking for the right place */
       while (ptr) {

           if (delay < ptr->time) {
               /* right place */

               node->next = ptr;
               if (ptr == Q)
                   Q = node;
               else
                   prev->next = node;
               ptr->time -= node->time;
               print_Q();
               in_callout = 0;
               return node->id;
           } else  {
               /* keep moving */

               delay -= ptr->time; node->time = delay;
               prev = ptr;
               ptr = ptr->next;
           }
       }
       prev->next = node;
   }
   print_Q();
   in_callout = 0;
   return node->id;
}


/* clears the associated timer */
void
timer_clearTimer(int timer_id)
{
   struct timeout_q  *ptr, *prev;

   if (in_callout)
       return;
   if (!timer_id)
       return;

   in_callout = 1;

   prev = ptr = Q;

   /*
    * find the right node, delete it. the subsequent node's time
    * gets bumped up
    */

   print_Q();
   while (ptr) {
       if (ptr->id == timer_id) {
           /* got the right node */

           /* unlink it from the queue */
           if (ptr == Q)
               Q = Q->next;
           else
               prev->next = ptr->next;

           /* increment next node if any */
           if (ptr->next != 0)
               (ptr->next)->time += ptr->time;

           free(ptr->data);
           free(ptr);
           print_Q();
           in_callout = 0;
           return;
       }
       prev = ptr;
       ptr = ptr->next;
   }
   print_Q();
   in_callout = 0;
}

#ifdef IGMP_DEBUG
/*
* debugging utility
*/
static void
print_Q(void)
{
   struct timeout_q  *ptr;

   for(ptr = Q; ptr; ptr = ptr->next)
       logit(LOG_DEBUG,0,"(%d,%d) ", ptr->id, ptr->time);
}
#endif /* IGMP_DEBUG */

int
secs_remaining(int timer_id)
{
   struct timeout_q  *ptr;
   int left=0;

   for (ptr = Q; ptr && ptr->id != timer_id; ptr = ptr->next)
      left += ptr->time;

   if (!ptr) /* not found */
      return 0;

   return left + ptr->time;
}