/*
**  GXSNMP -- An snmp management application
**
**  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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <stdio.h>
#include <glib.h>
#include <smi.h>
#include <g_snmp.h>

#include "main.h"
#include "config_file.h"
#include "snmp_setup.h"
#include "oidcmp.h"
#include "event.h"
#include "poll.h"

/******************************************************************************
**
**  Forward references
**
******************************************************************************/

static gboolean reply_callback (host_snmp 	* host, 
				void 		* magic, 
				SNMP_PDU 	* spdu, 
				GSList 		* objs);

static void     reply_timeout  (host_snmp	* host,
				void		* magic);

static gint     oidnodecompare (gconstpointer     a, 
				gconstpointer     b);

/******************************************************************************
**
**  Local, private data structures
**
******************************************************************************/

typedef struct pollwork
{
  EVENT    * event;		/* Pointer to event block that started this */
  GList    * oidlist;		/* Sorted list of requested OIDs */
  gboolean   got_top;		/* TRUE means top element was retrieved */
  GSList   * request_list;      /* Request list presented to g_snmp */
  gpointer   request;      	/* Handle of current g_snmp async request */
}
POLLWORK;

typedef struct oidnode
{
  gchar  * name;		/* The variable name */
  gulong * oid;			/* Pointer to oid integer array */
  guint    oidlen;		/* Length of oid integer array */
}
OIDNODE;

/******************************************************************************
**
**  This routine retrieves a list of variables from a host.
**
******************************************************************************/

gboolean
poll_event (EVENT * event)
{
  CONFIG_NODE * node;
  gchar       * variables;
  GScanner    * scanner;
  GTokenType    token;
  SmiNode     * sminode;
  OIDNODE     * oidnode;
  POLLWORK    * pollwork;
  GList	      * oidlist = NULL;	
  host_snmp   * hostsnmp;

/*
**  Find the variable list.  Look first for 
**  host:<hostid>:poll:<eventid>:variables, and then in
**  poll:<eventid>:variables.
*/

  node = config_node_lookup (config, "host");
  if (!node)
    {
      g_print ("Cannot poll host %s because the host table is gone!\n", 
		event->hostid);
      return FALSE;
    }

  node = config_node_lookup (node, event->hostid);
  if (!node)
    {
      g_print ("Cannot poll host %s because the host entry has "
               "disappeared from the host table!\n", event->hostid);
      return FALSE;
    }

  node = config_node_lookup (node, "poll");
  if (!node)
    {
      g_print ("Cannot run poll event %s for host %s because the "
               "poll table is gone from the host entry!\n",
                event->eventid, event->hostid);
      return FALSE;
    }

  node = config_node_lookup (node, event->eventid);
  if (!node)
    {
      g_print ("Cannot run poll event %s for host %s because the "
               "poll entry is gone from the poll table!\n",
                event->eventid, event->hostid);
      return FALSE;
    }

  node = config_node_lookup (node, "variables");
  if (!node)
    {
      node = config_node_lookup (config, "poll");
      if (!node)
        {
          g_print ("Cannot run poll event %s for host %s because the "
                   "poll table is gone from the host entry!\n",
                    event->eventid, event->hostid);
          return FALSE;
        }

      node = config_node_lookup (node, event->eventid);
      if (!node)
        {
          g_print ("Cannot run poll event %s for host %s because the "
               "poll entry is gone from the poll table!\n",
                event->eventid, event->hostid);
          return FALSE;
        }

      node = config_node_lookup (node, "variables");
      if (!node)
        {
          g_print ("Cannot run poll event %s for host %s because "
		   "no variables were configured\n",
		   event->eventid, event->hostid);
	}
    }

  variables = config_node_get_string (node);

/*
**  Now that we have the list of variables to be retrieved in this polling
**  request, we need to sort the variables in OID order.  The following
**  code creates a linked list of the variables in sequential order:
*/

  scanner = g_scanner_new(NULL);
  g_scanner_input_text (scanner, variables, strlen (variables));
  do {
    token = g_scanner_get_next_token (scanner);
    switch (token)
      {
	case G_TOKEN_EOF:
          break;
	
        case G_TOKEN_IDENTIFIER:

          sminode = smiGetNode (NULL, scanner->value.v_string);
          if (!sminode)
	    {
	      g_print ("Error:  Variable %s not found by SMI\n",
			scanner->value.v_string);
	      break;
	    }
	  oidnode = g_malloc (sizeof (OIDNODE));
	  oidnode->name = g_strdup (scanner->value.v_string);
	  oidnode->oid  = g_memdup (sminode->oid, 
				    sminode->oidlen * sizeof (gulong));
	  oidnode->oidlen = sminode->oidlen;
	  oidlist = g_list_insert_sorted (oidlist, oidnode, oidnodecompare);
	  break;	
       
        default:
          g_print ("Syntax error in list: invalid variable ignored\n");
	  break;	
      }         
  } while (token != G_TOKEN_EOF);
  g_scanner_destroy (scanner);

  if (!oidlist)
    {
      g_print ("No variables to poll!  Returning\n");
      return FALSE;
    }

/*
**  Prepare the necessary data structures for the g_async_getnext request
**  and schedule the first request.  Control continues at reply_callback
**  when the host replies with the first variable.
*/

  pollwork 	         = g_malloc (sizeof (POLLWORK));
  pollwork->event	 = event;	/* Event block that started this */
  pollwork->oidlist      = oidlist;	/* Sorted list of OIDs to retrieve */
  pollwork->got_top      = FALSE;       /* Nothing retrieved yet */
  pollwork->request_list = NULL;	/* Initialize the request list */

  hostsnmp 		    = snmp_setup (event->hostid);
  hostsnmp->magic           = pollwork;		/* Handle to our workarea */
  hostsnmp->done_callback   = reply_callback;	/* Initialize the completion */
  hostsnmp->time_callback   = reply_timeout;    /* ... callbacks */

  oidnode = pollwork->oidlist->data;	/* First OID to retrieve */

  g_pdu_add_oid (&pollwork->request_list, 
		 oidnode->oid, oidnode->oidlen, 
		 SNMP_NULL, NULL);

  pollwork->request = g_async_getnext(hostsnmp, pollwork->request_list);
  if (!pollwork->request)
    g_error("poll.c:  g_async_getnext() failed (1)\n");

  return TRUE;
}

/******************************************************************************
**
**  Control is reached here each time the target host returns a MIB 
**  variable.
**
******************************************************************************/

gboolean
reply_callback (host_snmp * host, 
		gpointer    magic, 
		SNMP_PDU  * spdu, 
		GSList    * objs)
{
  POLLWORK       * pollwork;
  OIDNODE        * oidnode;
  SNMP_OBJECT    * obj;
  gulong	 * oid;
  gulong	   oidlen;
  GTimeVal	   time;
  gint		   oidcompare;
  gint		   i;
  gchar            result[1024] = "";

  pollwork = magic;			/* Pointer to local data area */
  oidnode  = pollwork->oidlist->data;	/* Top element of OID GList */

  obj    = objs->data;		/* We only request one variable at a time */
  oid    = obj->id;		/* Object ID returned by target host */
  oidlen = obj->id_len;		/* Corresponding OID length */

  pollwork->request_list = NULL;                /* FIXME: Delete old request */

  g_get_current_time (&time);	/* Get a timestamp */

  oidcompare = oidcmp (oid, oidnode->oid, oidlen, oidnode->oidlen);

  while (oidcompare == 1) 	/* Variable from host follows top list entry */
    {
      if (pollwork->got_top == FALSE)	/* Nothing for this variable? */
        g_print ("%ld %s %s variable not supported by host\n", 
	         time.tv_sec, pollwork->event->hostid, oidnode->name);
      g_free (oidnode->name);
      g_free (oidnode->oid);
      g_free (oidnode);
      pollwork->oidlist = g_list_remove (pollwork->oidlist, oidnode);
      pollwork->got_top = FALSE;
      if (!pollwork->oidlist)
	{
	  g_print ("Event %s ended for host %s\n", 
		   pollwork->event->eventid, pollwork->event->hostid);
	  return TRUE;				/* All finished! */
	}
      oidnode = pollwork->oidlist->data;
      oidcompare = oidcmp (oid, oidnode->oid, oidlen, oidnode->oidlen);
    }

  if (oidcompare == (-1))      /* Variable from host preceeds top list entry */
    {
      oid    = oidnode->oid;	        /* Jump ahead to the next desired */
      oidlen = oidnode->oidlen;		/* variable */
      pollwork->got_top = FALSE;
    }

  if (oidcompare == 0)		/* Variable from host matches top list entry */
    {
      pollwork->got_top = TRUE;                  /* Set got-something flag */
      g_print ("%ld %s %s", 
	       time.tv_sec, pollwork->event->hostid, oidnode->name);
      for (i = oidnode->oidlen; i < oidlen; i++)
        g_print (".%ld", oid[i]);
      g_snmp_printf(result, 1023, obj);
      g_print (" = %s\n", result);
    }

  g_pdu_add_oid (&pollwork->request_list, 	/* Schedule the next request */
	         oid, oidlen, SNMP_NULL, NULL);
  pollwork->request = g_async_getnext(host, pollwork->request_list);
  if (!pollwork->request)
    {
      g_error("poll.c:  g_async_getnext() [2] failed\n");
    }
  return FALSE;
}

/******************************************************************************
**
**  Control is reached here if the target host times out.
**
******************************************************************************/

void
reply_timeout (host_snmp * host,
		gpointer    magic)
{
/* FIXME:  This needs to be written */
  g_print ("Timeout!\n");
}

/*
**  Compare function used to correctly sort the OIDs in the OID list.
*/

static gint oidnodecompare (gconstpointer a, gconstpointer b)
{
  OIDNODE * first  = (OIDNODE *)a;
  OIDNODE * second = (OIDNODE *)b;
  gint      result;  
  result = oidcmp (first->oid, second->oid, first->oidlen, second->oidlen);
  if (!result)
    return (first->oidlen < second->oidlen ? -1 : 1);	
  else
    return result;
}

/* EOF */
