/* -*- Mode: C -*-
 *  $Id: job_queue.c,v 1.2 1999/10/29 22:39:55 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 "job_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 */

/**
 * add_queue:
 * @interval: 
 * @function:
 * @data:
 *  Adds 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.
 *
 *  Return value: 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));
} 

/**
 * remove_queue:
 * @id: The link that was returned from the add_queue funtion of the event
 *      to remove.
 *  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 */
