/*
 *  GXSNMP - An snmp managment 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.
 *
 *  The wire widget
 *
 *  Wire widgets connect hosts to networks.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <gnome.h>
#include "dbapi.h"
#include "gxsnmp/gxsnmp_dbapi.h"
#include "gxsnmp_wire.h"

#include "debug.h"

enum {
  WIRE_ARG_0,
  WIRE_ARG_FILL_COLOR
};

/****************************************************************************
 *  Forward references
 ***************************************************************************/
static void          wire_class_init          (GXsnmp_wireClass       *klass);
static void          wire_init                (GXsnmp_wire	    *wire);
static void          wire_get_arg             (GtkObject              *object,
					       GtkArg                 *arg,
					       guint                  arg_id);
static void          wire_set_arg             (GtkObject              *object,
					       GtkArg                 *arg,
					       guint                  arg_id);
static void          wire_item_destroy        (GtkObject              *object);
static gboolean      wire_item_select         (GXsnmp_map_item        *item);
static gboolean      wire_item_deselect       (GXsnmp_map_item        *item);
/* --  Database callback functions -- */
static void          graph_object_added_cb    (G_sqldb_table          *table,
					       gpointer               row,
					       G_sqldb_cb_type        type,
					       gpointer               data);
static void          graph_object_updated_cb  (G_sqldb_table          *table,
					       gpointer               row,
					       G_sqldb_cb_type        type,
					       gpointer               data);
static void          graph_object_deleted_cb  (G_sqldb_table          *table,
					       gpointer               row,
					       G_sqldb_cb_type        type,
					       gpointer               data);
/****************************************************************************
 *  gxsnmp_wire_get_type ()
 ***************************************************************************/
GtkType
gxsnmp_wire_get_type()
{
  static GtkType wire_type = 0;

  if (!wire_type)
    {
      GtkTypeInfo wire_info =
      {
        "GXsnmp_wire",
        sizeof (GXsnmp_wire),
        sizeof (GXsnmp_wireClass),
        (GtkClassInitFunc) wire_class_init,
        (GtkObjectInitFunc) wire_init,
        /* reserved 1 */ NULL,
        /* reserved 2 */ NULL,
	(GtkClassInitFunc) NULL,
      };
      wire_type = 
	    gtk_type_unique (gxsnmp_map_item_get_type (), &wire_info);
    }
  return wire_type;
}

/****************************************************************************
**
**  The class initialization subroutine
**
*****************************************************************************/

static void
wire_class_init (GXsnmp_wireClass * class)
{
  GtkObjectClass        *object_class;
  GXsnmp_map_itemClass  *map_item_class;

  D_FUNC_START;
  map_item_class = GXSNMP_MAP_ITEM_CLASS (class);
  object_class   = (GtkObjectClass *)class;

  /* Add arguments here */

  /* Object class methods */
  object_class->set_arg       = wire_set_arg;
  object_class->get_arg       = wire_get_arg;
  object_class->destroy       = wire_item_destroy;

  map_item_class->select      = wire_item_select;
  map_item_class->deselect    = wire_item_deselect;
  map_item_class->move        = NULL;     /* FIXME: Write a movement handler */

  map_item_class->add_wire    = NULL;	/* Can't attach wires to wires */
  map_item_class->move_wire   = NULL;
  map_item_class->remove_wire = NULL;

  map_item_class->popup_menu  = NULL;
  map_item_class->changed     = NULL;
  /* Hook up the db stuff */
  /* For the graph db */
  d_print (DEBUG_TRACE, "Registering DB hooks\n");
  g_sqldb_table_cb_add (graph_sqldb, NULL,
			G_SQLDB_CB_ADD | G_SQLDB_CB_AFTER,
			graph_object_added_cb, NULL);
  g_sqldb_table_cb_add (graph_sqldb, NULL, 
			G_SQLDB_CB_UPDATE | G_SQLDB_CB_AFTER,
			graph_object_updated_cb, NULL);
  g_sqldb_table_cb_add (graph_sqldb, NULL, 
			G_SQLDB_CB_DELETE | G_SQLDB_CB_AFTER,
			graph_object_deleted_cb, NULL);
  D_FUNC_END;
}
/****************************************************************************
 * GtkArg Functions 
 ***************************************************************************/
static void
wire_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
}

static void
wire_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
}
/****************************************************************************
 * The destructor 
 ***************************************************************************/
static void
wire_item_destroy (GtkObject *object)
{
  GXsnmp_wire    *wire;
  GXsnmp_map_itemClass *parent_class;

  D_FUNC_START;
  d_print (DEBUG_OBJECTS, "Destroy wire object.\n");
  parent_class = gtk_type_class (gxsnmp_map_item_get_type());

  g_return_if_fail (object != NULL);
  g_return_if_fail (GXSNMP_IS_WIRE (object));

  wire = GXSNMP_WIRE (object);
  gtk_signal_emit_by_name (GTK_OBJECT (wire->DB_graph_host->application),
			   "remove_wire", wire);

  gtk_signal_emit_by_name (GTK_OBJECT (wire->DB_graph_network->application),
			   "remove_wire", wire);
  if (wire->points)
    gnome_canvas_points_free (wire->points);
  if (wire->fill_color)
    g_free (wire->fill_color);
  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
  D_FUNC_END;
}

/*****************************************************************************
**
**  The widget initialization subroutine
**
*****************************************************************************/

static void
wire_init (GXsnmp_wire * wire)
{
  D_FUNC_START;
  wire->line 		 = NULL;
  wire->points  	 = NULL;
  wire->fill_color       = NULL;
  wire->DB_graph_host    = NULL;
  wire->DB_graph_network = NULL;
  D_FUNC_END;
}

/*****************************************************************************
**
**  Public function to create a new widget
**
**  This is the function to call to add a wire to the map.  
**
*****************************************************************************/

GXsnmp_map_item *
gxsnmp_wire_new (DB_graph * graph)
{
  GnomeCanvasGroup * root_group;
  GXsnmp_wire      * wire;
  GXsnmp_map_item  * wire_map_item;
  GList		   * gl;
  int		     i;
  DB_network        *net;

  D_FUNC_START;
  d_print (DEBUG_OBJECTS, "New wire object.\n");
  g_return_val_if_fail (graph             != NULL, NULL);
  g_return_val_if_fail (graph->DB_map     != NULL, NULL);
  g_return_val_if_fail (graph->DB_host    != NULL, NULL);


  root_group = gnome_canvas_root (GNOME_CANVAS (graph->DB_map->application));

  wire = GXSNMP_WIRE (gnome_canvas_item_new (root_group,
					     gxsnmp_wire_get_type (),
					     "x", 0.0, /* Wires are always */
					     "y", 0.0, /* left at the origin */
					     NULL));

  wire_map_item = GXSNMP_MAP_ITEM (wire);

/*
**  Point the DB_graph object and the gxsnmp_wire object at each other
*/
  gnome_canvas_item_lower_to_bottom (GNOME_CANVAS_ITEM (wire));
  wire_map_item->DB_graph = graph;	/* Save pointer to graph structure */
  graph->application = wire;		/* Save pointer to gxsnmp_wire object */

/*
**  Find the DB_graph entries that describe the DB_host and DB_network
**  objects in the current map.
**
**  Yes, this algorithm sucks.  -- JohnS
*/

  wire->DB_graph_host    = NULL;
  wire->DB_graph_network = NULL;

  gl = graph->DB_map->DB_graphs;	/* Linked list of objects in map */
  while (gl)
    {
      DB_graph * dbg;

      dbg = (DB_graph *) gl->data;

      if ((dbg->type == DB_GRAPH_HOST) &&
	  (dbg->DB_host == graph->DB_host))
	  wire->DB_graph_host = dbg;

      if ((dbg->type == DB_GRAPH_NETWORK) &&
	  (dbg->DB_network == graph->DB_network))
	  wire->DB_graph_network = dbg;

      gl = gl->next; 
    }

  g_return_val_if_fail (wire->DB_graph_host    != NULL, NULL);
  if (wire->DB_graph_network == NULL)
    {
      g_warning ("gxsnmp_wire_new: adding a wire to a non existant "
		 "network? wire rowid == %d, network rowid == %d\n", 
		 wire_map_item->DB_graph->rowid,
		 wire_map_item->DB_graph->network);
      /* Lets really see if this is correct, lets see if we can get 
	 the network by the rowid, if not axe this wire. In the db
	 and in memory.. */
      if ( (net = network_find_by_rowid (wire_map_item->DB_graph->network)))
	{
	  g_warning ("gxsnmp_wire_new: something is amiss.. the keys appear"
		     " to be corrupt. Suspect DB is corrupt.");
	  d_print (DEBUG_DUMP, "Network is %s, map is %s\n", 
		   net->address, graph->DB_map->tab);
	  return NULL;
	}
      g_warning ("gxsnmp_wire_new: yep. wire->network connection is invaild "
		 "the DB needs fixing..");
      
      return NULL;
    }

  wire->points = gnome_canvas_points_new (2);	/* Create a two-point wire */

  for (i=0; i<4; i++)				/* Zero the wire endpoints */
    wire->points->coords[i] = 0.0;
  if (!wire->fill_color)
    gxsnmp_wire_set_color (wire, "#225522");

  wire->line   = gnome_canvas_item_new (GNOME_CANVAS_GROUP (wire),
                                        gnome_canvas_line_get_type (),
                                        "points", wire->points,
                                        "fill_color", wire->fill_color,
                                        "width_units", 2.0,
                                        NULL);

  gtk_signal_emit_by_name (GTK_OBJECT (wire->DB_graph_host->application),
			   "add_wire", wire);

  gtk_signal_emit_by_name (GTK_OBJECT (wire->DB_graph_network->application),
			   "add_wire", wire);
  D_FUNC_END;
  return GXSNMP_MAP_ITEM (wire);
}
/****************************************************************************
 * Set the color of this wire.
 ***************************************************************************/
void
gxsnmp_wire_set_color (GXsnmp_wire *wire, gchar *color)
{
  D_FUNC_START;
  g_return_if_fail (wire != NULL);
  g_return_if_fail (GXSNMP_IS_WIRE (wire));
  g_return_if_fail (color != NULL);
  
  if (wire->fill_color)
    g_free (wire->fill_color);
  wire->fill_color = g_strdup (color);

  if (wire->line)
    gnome_canvas_item_set (GNOME_CANVAS_ITEM (wire->line),
			   "fill_color", wire->fill_color,
			   NULL);
  D_FUNC_END;
}
/****************************************************************************
**
**  Function invoked when the rubberband code tries to select a wire object.
**
**  Wires aren't selectable yet, so just return FALSE.
**
****************************************************************************/

static gboolean
wire_item_select (GXsnmp_map_item * item)
{
  return FALSE;
}

/******************************************************************************
**
**  Function invoked when the rubberband code tries to deselect a wire item
**
******************************************************************************/

static gboolean
wire_item_deselect (GXsnmp_map_item * item)
{
  g_print ("How the heck did this wire get selected anyway?\n");
  g_assert_not_reached ();
  return FALSE;
}

/******************************************************************************
 *
**  Function to move one or both endpoints of a wire when one or both
**  endpoint items of the wire are moved.
**
**  Call the wire position callbacks of each of the two items in turn, 
**  allowing each one to first center their wire, then allowing each
**  one to adjust its wire position.  When both ends indicate that they
**  have adjusted their wire, we are finished.
**
******************************************************************************/
void
gxsnmp_wire_move (GXsnmp_map_item * item,
		  GXsnmp_wire * wire)
{
  GXsnmp_map_item * otheritem = NULL;
  gdouble	  * item_0_endpoint;
  gdouble	  * item_0_nextpoint;
  gdouble	  * item_1_endpoint;
  gdouble	  * item_1_nextpoint;
  gint		    item_0_adjust;
  gint		    item_1_adjust;
  D_FUNC_START;
  g_return_if_fail (item != NULL);
  g_return_if_fail (GXSNMP_IS_MAP_ITEM (item));

  g_return_if_fail (wire != NULL);
  g_return_if_fail (GXSNMP_IS_WIRE (wire));

  if (item == wire->DB_graph_host->application)
    {
      item_0_endpoint  = &wire->points->coords[0];
      item_0_nextpoint = &wire->points->coords[2];
      if (wire->points->num_points == 2)
	{
	  otheritem = wire->DB_graph_network->application;
          item_1_endpoint  = 
		&wire->points->coords[2 * wire->points->num_points - 2];
          item_1_nextpoint = 
		&wire->points->coords[2 * wire->points->num_points - 4];
	}
    }
  else if (item == wire->DB_graph_network->application)
    {
      item_0_endpoint  = 
		&wire->points->coords[2 * wire->points->num_points - 2];
      item_0_nextpoint =
                &wire->points->coords[2 * wire->points->num_points - 4];
      if (wire->points->num_points == 2)
	{
	  otheritem = wire->DB_graph_host->application;
	  item_1_endpoint  = &wire->points->coords[0];
	  item_1_nextpoint = &wire->points->coords[2];
	} 
    }
  else g_assert_not_reached();

  item_0_adjust = WIRE_INCORRECT;	/* Both ends of the wire are */
  item_1_adjust = WIRE_INCORRECT;	/* assumed to be incorrectly placed */
  wire->adjuststate  = WIRE_INCORRECT;	/* Also set the state of the wire */
  do 
    {
      wire->endpoint  = item_0_endpoint;	   /* Set up endpoints for */
      wire->nextpoint = item_0_nextpoint;	   /* the first or only item */
      gtk_signal_emit_by_name (GTK_OBJECT (item),  /* Adjust the end of the */
				"move-wire", 	   /* wire that attaches to */
				wire);	   	   /* the first or only item */
      item_0_adjust = wire->adjuststate;	   /* Save the wire state */

      if (otheritem)	                           /* If the other end of  */
	{					   /* the wire is an item */
          wire->endpoint  = item_1_endpoint;	   /* then set up endpoints */
          wire->nextpoint = item_1_nextpoint;	   /* for the second item */
	  gtk_signal_emit_by_name ( 		   /* Adjust the other end */
	    		GTK_OBJECT (otheritem),    /* of the wire */
			"move-wire", wire);
          item_1_adjust = wire->adjuststate;	   /* Remember if it moved */
        }
      else 				  	   /* Otherwise, indicate */
	{				  	   /* that the other end of */
	  wire->adjuststate = WIRE_ADJUSTED;	   /* the wire is correctly */
	  item_1_adjust = WIRE_ADJUSTED;	   /* adjusted */
	} 
    }
  while ((item_0_adjust != WIRE_ADJUSTED) ||       /* Repeat until both */
	 (item_1_adjust != WIRE_ADJUSTED));	   /* wire ends are correct */

  gnome_canvas_item_set (GNOME_CANVAS_ITEM (wire->line),  /* Now actually */
			 "points", wire->points,	  /* move the line */
			 NULL);

  D_FUNC_END;
}
/****************************************************************************
 * This g_hook callback function is invoked every time a graph object is
 * added.
 ***************************************************************************/
static void
graph_object_added_cb (G_sqldb_table *table, gpointer row,
		       G_sqldb_cb_type type, gpointer data)
{
  DB_graph     *dbg;
  dbg = (DB_graph *) row;
  D_FUNC_START;

  if (dbg->type != DB_GRAPH_WIRE)
    {
      d_print (DEBUG_TRACE, "Invoked with a non wire object.\n");
      D_FUNC_END;
      return;
    }
  if (dbg->DB_network)
    gxsnmp_wire_new (dbg);
  D_FUNC_END;
}
/****************************************************************************
 * This g_hook callback function is invoked every time a graph object is
 * changed. It emits the changed signal to that particular graph
 ***************************************************************************/
static void
graph_object_updated_cb (G_sqldb_table *table, gpointer row,
			 G_sqldb_cb_type type, gpointer data)
{
  DB_graph          *dbg;
  GXsnmp_map_item   *map_item;
  dbg = (DB_graph *)row;
  D_FUNC_START;
  if (dbg->type != DB_GRAPH_WIRE)
    {
      d_print (DEBUG_TRACE, "Invoked with a non wire object.\n");
      D_FUNC_END;
      return;
    }
  g_assert (dbg->application != NULL);
  map_item = GXSNMP_MAP_ITEM (dbg->application);
  gtk_signal_emit_by_name (GTK_OBJECT (map_item), "changed", NULL);
  D_FUNC_END;
}
/****************************************************************************
 * This g_hook callback function is invoked every time a graph object is
 * deleted from the database. It destroys the corresponding map_item.
 ***************************************************************************/
static void
graph_object_deleted_cb (G_sqldb_table *table, gpointer row,
			 G_sqldb_cb_type type, gpointer data)
{
  DB_graph         *dbg;
  GXsnmp_map_item  *map_item;

  D_FUNC_START;
  g_return_if_fail (row != NULL);
  dbg = (DB_graph *)row;
  if (dbg->type != DB_GRAPH_WIRE)
    {
      d_print (DEBUG_TRACE, "Invoked with a non wire object.\n");
      D_FUNC_END;
      return;
    }
  if (dbg->application)
    {
      map_item = GXSNMP_MAP_ITEM (dbg->application);
      gtk_signal_emit_by_name (GTK_OBJECT (map_item), "destroy", NULL);
    }
  D_FUNC_END;
}
/* EOF */


