/*
**  $Id: queue.c,v 1.29 1999/08/10 20:09:02 gregm Exp $
**  GXSNMP -- An snmp mangament application
**  Copyright (C) 1998 Gregory McLean
**
**  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.,  59 Temple Place - Suite 330, Cambridge, MA 02139, USA.
**
**  Queue handling functions.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "queue.h"
#include "debug.h"

/******************************************************************************
**
**  The queue_entry data structure is private to the queue module
**
******************************************************************************/

typedef struct _queue_entry
{
  guint       id;                       /* A unique id for this item */
  GTimeVal    time;                     /* Trigger time for this event */
  GXQueueFunc function;			/* Callback function */
  gpointer    data;                     /* Data to pass to above function */
}
queue_entry;

/******************************************************************************
**
**  Forward declarations and callback functions
**
******************************************************************************/

static gint insert_compare (gconstpointer p1, gconstpointer p2);

/******************************************************************************
**
**  Local static data
**
******************************************************************************/

static GList	* runqueue = NULL;  	/* The pending event queue */
static gint       nextid  = 1; 		/* Next available queue ID */    

G_LOCK_DEFINE_STATIC (runqueue_lock);	/* A lock for the run queue */
G_LOCK_DEFINE_STATIC (nextid_lock);	/* Another one for the queue ID */

/******************************************************************************
**
**  Function to add an entry to the event queue
**
**  Schedules an invocation of <function> approximately <interval>
**  milliseconds in the future, with <link_id> and <data> as the function 
**  parameters.
**
**  The return value is the <link_id>, which uniquely identifies the event.
**
******************************************************************************/

guint
add_queue (guint32	interval,
	   GXQueueFunc	function,
	   gpointer	data)
{
  queue_entry 	* link;	
  guint 	  seconds;
  guint 	  msecs;
  guint		  id;
  D_FUNC_START;
  link     = g_new0 (queue_entry, 1);
  if (link)
    {
      G_LOCK (nextid_lock);		/* Get a lock on the ID counter */
      id = link->id = nextid++;		/* Get the next available link ID */
      G_UNLOCK (nextid_lock);		/* Release the ID counter lock */

      g_get_current_time (&link->time);	/* Store the current timestamp */
      
      seconds = interval / 1000;	   /* Compute the trigger time */
      msecs   = interval - seconds * 1000;
      link->time.tv_sec += seconds;	   /* This stuff stolen from gmain.c */
      link->time.tv_usec += msecs * 1000;  /* - jms */
      if (link->time.tv_usec >= 1000000)
	{
	  link->time.tv_usec -= 1000000;
	  link->time.tv_sec++;
	}
      
      link->function = function;		/* Store the remaining data */
      link->data	 = data;		/* in the link block */
      
      G_LOCK (runqueue_lock);			     /* Lock the run queue */
      runqueue = g_list_insert_sorted (runqueue, link,   /* Insert the new */
				       insert_compare);  /* queue entry */
      G_UNLOCK (runqueue_lock);		 	     /* Unlock the run queue */
      
      run_queue (NULL);			/* Schedule the next event */
      d_print (DEBUG_DUMP, "Queue element added, id == %d", id);
      D_FUNC_END;
      return id;				/* Return the link ID */
    }
  D_FUNC_END;
  return 0;
}

static gint 
insert_compare (gconstpointer p1, gconstpointer p2)
{
  const queue_entry * q1 = p1;
  const queue_entry * q2 = p2;
  d_print (DEBUG_TRACE, "\n");
  return (q1->time.tv_sec < q2->time.tv_sec) ||
          ((q1->time.tv_sec  == q2->time.tv_sec) &&
           (q1->time.tv_usec <= q2->time.tv_usec));
} 

/******************************************************************************
**
**  Function to remove an entry from the event queue.
**
**  The return value is TRUE if the event was removed, FALSE if not.
**  A return value of FALSE generally means that the event already ran.
**
******************************************************************************/

gboolean
remove_queue (guint id)
{
  GList       * gl;
  queue_entry * entry;
  D_FUNC_START;
  G_LOCK (runqueue_lock);
  gl = runqueue;
  while (gl)
    {
      entry = (queue_entry *) gl->data;
      if (entry->id == id)
	{
	  d_print (DEBUG_DUMP, "Queue removeing item, id == %d\n", id);
	  runqueue = g_list_remove (runqueue, entry);
	  G_UNLOCK (runqueue_lock);
	  D_FUNC_END;
	  return TRUE;
	}
      gl = gl->next;
    } 
  G_UNLOCK (runqueue_lock);
  D_FUNC_END;
  return FALSE;
}

/******************************************************************************
**
**  Function to run the next scheduled entry in the event queue
**
**  run_queue() is invoked as a callback.  It checks the top entry on the 
**  queue to see if the timer has expired. If not, it starts a new
**  gtk_timeout so that run_queue() is re-invoked when it has some work
**  to do.  Otherwise, it removes the top entry from the queue and invokes
**  the callback.  On return, run_queue restarts.  When there are no more
**  events on the queue, run_queue returns without setting a timer.
**
******************************************************************************/

gboolean
run_queue (gpointer data)
{
  GList		* gl;			/* Top link on the event queue */
  queue_entry   * link;			/* Pointer to top link data field */
  GTimeVal	  current_time;		/* Used to store current time of day */
  guint		  interval;		/* Milliseconds until next event */
  D_FUNC_START;
restart:

  if (!runqueue)			/* If the queue is empty */
    {
      d_print (DEBUG_TRACE, "Queue is empty.. stop running.");
      return FALSE;			/* then there is nothing to do */
    }

  g_get_current_time (&current_time);	/* What time is it now? */

  G_LOCK (runqueue_lock);		/* Get a lock on the run queue */
  gl = runqueue;				
  link = (queue_entry *) gl->data;	/* link --> next scheduled event */

  if ((link->time.tv_sec < current_time.tv_sec) ||	/* If the time has */
      ((link->time.tv_sec == current_time.tv_sec) &&	/* arrived to run */
       (link->time.tv_usec <= current_time.tv_usec)))	/* the next event */
    {
      runqueue = g_list_remove_link (runqueue, gl);  /* Remove the event */
      G_UNLOCK (runqueue_lock);		     	/* Done manipulating list */
      link->function (link->id, data);	/* Run the queue function */
      g_free (link);			/* Discard the queue entry */
      g_list_free_1 (gl);		/* Discard the GList link */
      d_print (DEBUG_DUMP, "Queue fetch more elements to start.");
      goto restart;			/* Look for more events to start */
    }

  interval = (link->time.tv_sec  - current_time.tv_sec)  * 1000 +
	     (link->time.tv_usec - current_time.tv_usec) / 1000;  
  G_UNLOCK (runqueue_lock);
  d_print (DEBUG_DUMP, "Scheduling next queue event.\n");
  g_timeout_add (interval, 			/* Schedule the next */
		 run_queue, 	/* event */
		 NULL);
  D_FUNC_END;
  return FALSE;
}

/* EOF */
