/*
**  $Id: g_sqldb.c,v 1.5 2001/02/21 11:46:46 remlali Exp $
**
**  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.,  59 Temple Place - Suite 330, Cambridge, MA 02139, USA.
**
**  Subroutines for maintaining an in-storage, SQL-backed keyed database.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>
#include <string.h>
#include <stdio.h>
#include "debug.h"

#include "tables.h"
#include "g_sqldb.h"
#include "ddserver.h"
#include "ddclient.h"
#include "table-common.h"
#include "pdu.h"

const gchar version_of_g_sqldb[] = "$Id: g_sqldb.c,v 1.5 2001/02/21 11:46:46 remlali Exp $"; /*usefull to strings' the binary*/

static gchar packet[80000];

/******************************************************************************
**
**  Forward declarations
**
******************************************************************************/

static void          table_setup	    (G_sqldb_table * table);

static void          register_row 	    (G_sqldb_table * table,
               				     gchar         * row);

static void          issue_callbacks        (G_sqldb_table * table, 
					     gpointer        row, 
					     gint 	     type);

/******************************************************************************
**
**  Definition of the private data block for each row in the table.
**  This structure is chained off of each row, via a pointer located at
**  offset (row_private) bytes into the row structure.
**
******************************************************************************/

typedef struct _G_sqldb_row_private
{
  G_sqldb_table	    * table;	   /* Table this row belongs to */
  gpointer	      key[1];	   /* Array of pointers to copies of keys */
}
G_sqldb_row_private;

/******************************************************************************
**
**  Definition of the private data block for each column in the table.
**  This structure is chained off of the private field in G_sqldb_column
**
******************************************************************************/

typedef struct _G_sqldb_column_private
{
  G_sql_accelerator   accelerator;  /* Accelerator for faster G_sql access */
  GHashTable	    * hash;	    /* Pointer to hash table for column */
  gint		      key_index;    /* Index of column key in key table */
  gint		      highest; 	    /* Highest value of field */
}
G_sqldb_column_private;

/******************************************************************************
**
**  Definition of the private data block for the table as a whole.
**  This structure is chained off of the private field in G_sqldb_table
**
******************************************************************************/

typedef struct _G_sqldb_table_private
{
  G_sql_connection * connection;    /* SQL engine connection block */
  GList		   * glist;	    /* GList of all rows in table */
  gint		     keys;	    /* Total number of hash-keyed columns */
  GHookList	   * hooks;	    /* Holds list of callback hooks */
}
G_sqldb_table_private;

/******************************************************************************
**
**  Definition of an SQL callback hook.
**
******************************************************************************/

typedef struct _G_sqldb_hook
{
  GHook	    hook;		/* The underlying hook structure */
  gint	    type;		/* Event mask for issuing this callback */
  gpointer  row;		/* Row this hook applies to, or NULL for any */
  gpointer  data;		/* User data to supply to callback */
}
G_sqldb_hook;

/******************************************************************************
**
**  Internal function to initialize a G_sqldb_table structure.
**
**  Allocates and initializes:
**
**  o  the table private area
**  o  each column's private area
**  o  a hash table for each keyed column
**
**  Returns TRUE if everything succeeded, FALSE otherwise.
**
******************************************************************************/

static void
table_setup (G_sqldb_table * table)
{

  D_FUNC_START;
  g_return_if_fail (table != NULL);
  table->private             = g_new (G_sqldb_table_private, 1);
  if (table->private)
    {
      table->private->connection     = NULL;
      table->private->glist          = NULL;
      table->private->keys 	     = 0;
      table->private->hooks	     = NULL;
    }
  else
    {
      g_warning ("table_setup: table allocation failed expect trouble.\n");
    }
  D_FUNC_END;
}

/******************************************************************************
**
**  Internal subroutine to register a new row in the table indices.
**
**  o  Allocate a private area for the row and chain it off of the user-
**     provided pointer in the row.
**  o  For each keyed column, make a copy of the key, and store it in the 
**     private area, then add the key to the appropriate hash table.
**  o  Add the row to the master GList.
**
******************************************************************************/

static void
register_row  (G_sqldb_table * table,
               gchar         * row)
{
#if 0
  G_sqldb_row_private * row_private;  /* Pointer to private area for row */
  G_sqldb_column      * column;       /* Used to scale the column list */
  gpointer		datap;	      /* Pointer to column data */
  gpointer	       	keyp;	      /* Pointer to key copy of column data */
#endif

  D_FUNC_START;
  g_return_if_fail (table != NULL);
  g_return_if_fail (row != NULL);

  table->private->glist = g_list_append (table->private->glist, row);

  D_FUNC_END;
}

/******************************************************************************
**
**  Internal subroutine to unregister a row from the table indices.
**
**  o  Allocate a private area for the row and chain it off of the user-
**     provided pointer in the row.
**  o  For each keyed column, make a copy of the key, and store it in the
**     private area, then add the key to the appropriate hash table.
**  o  Add the row to the master GList.
**
******************************************************************************/

static void
unregister_row  (G_sqldb_table * table,
                 gchar         * row)
{
#if 0
  G_sqldb_row_private * row_private;  /* Pointer to private area for row */
  G_sqldb_column      * column;       /* Used to scale the column list */
  gpointer              keyp;         /* Pointer to key copy of column data */
#endif
  D_FUNC_START;
  g_return_if_fail (row != NULL);
  g_return_if_fail (table != NULL);

  table->private->glist = g_list_remove (table->private->glist, row);
  D_FUNC_END;
}

/******************************************************************************
**
**  Internal subroutine to update a row in the table indices.
**
**  Compare each keyed column to the saved copy of the key.  
**  If it is different, then:
**
**  o  Remove the old key from the hash table for the column
**  o  free the old key
**  o  Store a copy of the new key in the key table
**  o  Add the copy of the new key to the hash table for the column
**
******************************************************************************/

static void
update_row  (G_sqldb_table *table, gchar *row)
{
#if 0
  G_sqldb_row_private * row_private;  /* Pointer to private area for row */
  G_sqldb_column      * column;       /* Used to scale the column list */
  gpointer              datap;        /* Pointer to original column data */
  gpointer              keyp;         /* Pointer to key copy of column data */
#endif

  D_FUNC_START;
  g_return_if_fail (row != NULL);
  g_return_if_fail (table != NULL);

  D_FUNC_END;
}

/* g_sqldb_dbctl()
 *
 * control parameters of your database connection
 *
 *
 *
 */
int g_sqldb_dbctl(G_sqldb_table *sqldb, gint value)
{
  gint i, h;
  GIOChannel *channel;

  channel = sqldb->channel;

  i = NOTIFY;
  memcpy(packet               , &i, sizeof(guint));	/*set action type*/
  h = sizeof(gint)*4;
  memcpy(packet+sizeof(gint)  , &h, sizeof(guint));	/*write total packet length*/
  h = sizeof(gint);
  memcpy(packet+sizeof(gint)*2, &h, sizeof(guint));	/*set table name length*/
  memcpy(packet+sizeof(gint)*3, &value, sizeof(guint));	/*copy table name*/
  dd_write(channel, h, &packet);				/*send packet to database daemon*/
  return TRUE;
}

/*******************************************************************************
**
**  g_sqldb_table_load
**
**  Load a database table into storage
**
**  Return Values:
**
**  TRUE  --  The table was successfully loaded
**  FALSE --  A problem occurred, and the table was not loaded.

FIX:
     update a highest_rowid for table beeing loaded
     calculate a free_rowid for table beeing loaded
**
*******************************************************************************/

gboolean
g_sqldb_table_load (G_sqldb_table * gtable)
{
  gint n, h;
  db_table_descriptor *table;
  gpointer tableres;
  GIOChannel *channel;
  db_ddr *result;

  D_FUNC_START;
  table_setup (gtable);
  g_return_val_if_fail (gtable != NULL, FALSE);
  channel = gtable->channel;
  table = db_lookup_table(gtable->name);
  h = set_pdu_header(packet, TABLE_LOAD, gtable->name);	/*copy in a header*/
  memcpy(packet+h-sizeof(gint), &h, sizeof(gint));	/*write total packet length*/

  result = db_write_read(channel, h, &packet);

  if(!result) return FALSE;
 
  n = sizeof(gint) * 2; 						/* header size */

  while((tableres = decode_members(table, result->data, &n, result->len))){	
    /*we have the first row in an unknown table in tableres*/
    register_row(gtable, tableres);    /* hmm, is this enough ? */ 
  }

  g_free(result->data);
  g_free(result);

  D_FUNC_END;
  return TRUE;				/* Table was read in successfully */
}

/******************************************************************************
**
**  g_sqldb_table_list
**
**  Returns a glist of all rows in a table
**
******************************************************************************/

GList *
g_sqldb_table_list (G_sqldb_table * table)
{
  d_print (DEBUG_TRACE, " \n");
  g_return_val_if_fail (table != NULL, NULL);
  return table->private->glist;
}  

/******************************************************************************
**
**  g_sqldb_row_add
**
**  Adds a new row to an existing table, both in memory and in the SQL database
**
******************************************************************************/

gboolean      
g_sqldb_row_add (G_sqldb_table * gtable, gpointer row)
{
  guint n, h;
  db_table_descriptor *table;
  GIOChannel *channel;

  D_FUNC_START;
  g_return_val_if_fail (gtable != NULL, FALSE);
  g_return_val_if_fail (row != NULL, FALSE);
  channel = gtable->channel;
  issue_callbacks (gtable, row, G_SQLDB_CB_ADD | G_SQLDB_CB_BEFORE);

  register_row (gtable, row);			/* Add row to memory structure first */
  table = db_lookup_table(gtable->name);	/* lookup which table plugin to use */
  h = set_pdu_header(packet, ROW_ADD, gtable->name);		/*copy in a header*/
  if(!(n = encode_members(table, packet+h, row))){		/*return length of all membersRecords encoded*/
    fprintf(stderr,"Fail encoding members\n");
    return FALSE;
  }
  n += h;							/*add header to set total length*/

  memcpy(packet+h-sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(channel, n, &packet);					/*send packet to database daemon*/

  issue_callbacks (gtable, row, G_SQLDB_CB_ADD | G_SQLDB_CB_AFTER);
  D_FUNC_END;
  return TRUE;
}

/******************************************************************************
**
**  g_sqldb_row_update
**
**  Update an existing row, both in memory and in the backing database
**
******************************************************************************/

gboolean
g_sqldb_row_update (G_sqldb_table * gtable, gpointer row)
{
  gint n,h;
  db_table_descriptor *table;
  GIOChannel *channel;

  D_FUNC_START;
  g_return_val_if_fail (gtable != NULL, FALSE);
  issue_callbacks (gtable, row, G_SQLDB_CB_UPDATE | G_SQLDB_CB_BEFORE);
  channel = gtable->channel;
  update_row (gtable, row);
  table = db_lookup_table(gtable->name);
  h = set_pdu_header(packet, ROW_UPDATE, gtable->name);		/*copy in a header*/
  if(!(n = encode_members(table, packet+h, row))){		/*return length of all membersRecords encoded*/
    fprintf(stderr,"Fail encoding members\n");
    return FALSE;
  }
  n += h;							/*add header to set total length*/
  memcpy(packet+h-sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(channel, n, &packet);					/*send packet to database daemon*/

  issue_callbacks (gtable, row, G_SQLDB_CB_UPDATE | G_SQLDB_CB_AFTER);
  D_FUNC_END;
  return TRUE;
}

/******************************************************************************
**
**  g_sqldb_row_update2
**
**  Update an existing row, both in memory and in the backing database
**
******************************************************************************/

gboolean
g_sqldb_row_update2 (GIOChannel *channel, gchar * tablename, gpointer row)
{
  gint n,h;

  db_table_descriptor *table;

  D_FUNC_START;
//  g_return_val_if_fail (gtable != NULL, FALSE);
//  issue_callbacks (gtable, row, G_SQLDB_CB_UPDATE | G_SQLDB_CB_BEFORE);

//  update_row (gtable, row);
  table = db_lookup_table(tablename);
  h = set_pdu_header(packet, ROW_UPDATE, tablename);		/*copy in a header*/
  if(!(n = encode_members(table, packet+h, row))){		/*return length of all membersRecords encoded*/
    fprintf(stderr,"Fail encoding members\n");
    return FALSE;
  }
  n += h;							/*add header to set total length*/
  memcpy(packet+h-sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(channel, n, &packet);					/*send packet to database daemon*/

//  issue_callbacks (gtable, row, G_SQLDB_CB_UPDATE | G_SQLDB_CB_AFTER);
  D_FUNC_END;
  return TRUE;
}

/******************************************************************************
**
**  g_sqldb_row_delete
**
**  Delete an existing row both from memory and from the backing database
**
**  NOTE assumes rowid is a primary key.
**
******************************************************************************/

gboolean
g_sqldb_row_delete(G_sqldb_table * gtable, gpointer row)
{
  gint n,h;
  db_table_descriptor *table;
  GIOChannel *channel;

  D_FUNC_START;
  g_return_val_if_fail (gtable != NULL, FALSE);
  g_return_val_if_fail (row != NULL, FALSE);
  channel = gtable->channel;
  issue_callbacks (gtable, row, G_SQLDB_CB_DELETE | G_SQLDB_CB_BEFORE);

  unregister_row (gtable, row);
  table = db_lookup_table(gtable->name);
  h = set_pdu_header(packet, ROW_DELETE, gtable->name);		/*copy in a header*/
    /*its unnessecery to build all
      members, rowid would suffice*/
  if(!(n = encode_members(table, packet+h, row))){		/*return length of all membersRecords encoded*/
    fprintf(stderr,"Fail encoding members\n");
    return FALSE;
  }
  n += h;							/*add header to set total length*/
  memcpy(packet+h-sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(channel, n, &packet);					/*send packet to database daemon*/

  issue_callbacks (gtable, row, G_SQLDB_CB_DELETE | G_SQLDB_CB_AFTER);
  D_FUNC_END;
  return TRUE;
}

/******************************************************************************
**
**  g_sqldb_row_find
**
**  Search a database for a record
**
**  Returns the record, or NULL if the record was not found.
**
******************************************************************************/

gpointer
g_sqldb_row_find (G_sqldb_table * table, gchar * field, gpointer keyp)
{
  G_sqldb_column      * column;       /* Used to scale the column list */
  GList *gl;

  D_FUNC_START;
  g_return_val_if_fail (field != NULL, NULL);
  g_return_val_if_fail (table != NULL, NULL);
  g_return_val_if_fail (keyp != NULL, NULL);

  gl = g_sqldb_table_list(table);
  while(gl){
     /*this has nothing to do with DB_dbfieldconfig it has only the
       right offset to the rowid :-) */
    if(((DB_dbfieldconfig *) gl->data)->rowid == *((int *)keyp)) return gl->data;
    gl = gl->next;
  }
  return 0;

  column = table->columns;
  while (column->name)
    {
      if (!strcmp(column->name, field))
        break;
      column++;
    }
  if (!column->name) 
    {
      g_warning ("g_sqldb: Column %s does not exist in table %s\n",
		 field, table->name);

      D_FUNC_END;
      return NULL;
    }
  D_FUNC_END;
  return g_hash_table_lookup (column->private->hash, keyp);
}
/****************************************************************************
 * g_sqldb_reload_row:
 *
 * Will re-read the specified row from the database. (In case another client
 * has changed the datum.
 ***************************************************************************/
gboolean
g_sqldb_reload_row (G_sqldb_table *gtable, gpointer row)
{
  guint h,n;
  db_table_descriptor *table;
  gpointer tableres;
  GIOChannel *channel;
  db_ddr *result;
  GList *gl;
  gpointer row2;

  D_FUNC_START;
  /* Call all the 'update' callbacks as we will be possibly updating this */
  issue_callbacks (gtable, row, G_SQLDB_CB_UPDATE | G_SQLDB_CB_BEFORE);
  channel = gtable->channel;
  table = db_lookup_table(gtable->name);
  h = set_pdu_header(packet, ROW_READ, gtable->name);		/*copy in a header*/
  if(!(n = encode_members(table, packet+h, row))){		/*return length of all membersRecords encoded*/
    fprintf(stderr,"Fail encoding members\n");
    return FALSE;
  }

  n += h;					/*add header to set total length*/
  memcpy(packet+h-sizeof(gint), &n, sizeof(gint));	/*write total packet length*/
  result = db_write_read(channel, n, &packet);
 
  n = sizeof(gint) * 2; 			/* header size */

  tableres = decode_members(table, result->data, &n, result->len);	/*we have the first row in an unknown table in tableres*/

  g_free(result->data);
  g_free(result);

  update_row(gtable, tableres);
/*this is so ugly, but this is a issue in udpate_row() not resolved */
  gl = gtable->private->glist;
  while(gl){
    row2 = gl->data;
    if(row2 == row){ /* OK we have found the old row in sqldb table */
      gtable->private->glist = g_list_remove(gtable->private->glist, row);
      break;
    }
    gl = gl->next;
  }
  gtable->private->glist = g_list_append(gtable->private->glist, tableres);
  issue_callbacks (gtable, tableres, G_SQLDB_CB_UPDATE | G_SQLDB_CB_AFTER);
  D_FUNC_END;
  return TRUE;
}

/******************************************************************************
**
**  g_sqldb_highest_rowid
**
**  Find the highest existing rowid in a column
FIX:
     use free_rowid found in table struct.
     calculate a new free_rowid.
**
******************************************************************************/

gint
g_sqldb_highest_rowid (G_sqldb_table * gtable, gchar * field)
{
  GList *gl;
  gint r=0;

  D_FUNC_START;
  g_return_val_if_fail (gtable != NULL, -1);

  gl = g_sqldb_table_list(gtable);

    /* we use DB_host here, and if all table structures keep their
       rowid first, we are safe. But its still dirty */
  while(gl){
    if(((DB_dbfieldconfig *) gl->data)->rowid > r) r =((DB_dbfieldconfig *) gl->data)->rowid;
    gl = gl->next;
  }
  return r;

}

/******************************************************************************
**
**  g_sqldb_table_cb_add
**
**  Add a callback to a table
**
**  Returns a hook_id, which is required by g_sqldb_table_cb_remove().
**
**  If the row is NULL, then this callback applies to all rows in the
**  table.
**
******************************************************************************/

gint
g_sqldb_table_cb_add (G_sqldb_table   * table,
		      gpointer		row,
		      G_sqldb_cb_type   type,
		      G_sqldb_cb        func,
		      gpointer		data)
{
  GHookList    * hook_list;
  G_sqldb_hook * hook;
  D_FUNC_START;
  g_return_val_if_fail (table != NULL, -1);
  hook_list = table->private->hooks;

  if (!hook_list)
    {
      hook_list = table->private->hooks = g_new (GHookList, 1);
      g_hook_list_init (hook_list, sizeof (G_sqldb_hook));
    }

  hook            = (G_sqldb_hook *) g_hook_alloc (hook_list);
  hook->row       = row;
  hook->type      = type;
  hook->hook.func = func;
  hook->data	  = data;

  g_hook_prepend (hook_list, (GHook *)hook);
  D_FUNC_END;
  return hook->hook.hook_id;
}

/******************************************************************************
**
** issue_callbacks
**
**  Internal function to send the callback stream
**
******************************************************************************/

static void
issue_callbacks (G_sqldb_table * table, gpointer row, gint type)
{

  GHookList * hook_list;
  GHook     * hook;
  D_FUNC_START;
  g_return_if_fail (table != NULL);
  g_return_if_fail (row != NULL);
  hook_list = table->private->hooks;
  if (!hook_list)
    return;

  hook = g_hook_first_valid (hook_list, FALSE);
  while (hook)
    {
      G_sqldb_hook * sqldb_hook = (G_sqldb_hook *) hook;
      G_sqldb_cb     func;
      gboolean       was_in_call;

      func = (G_sqldb_cb) hook->func;
    
      was_in_call = G_HOOK_IN_CALL (hook);
      hook->flags |= G_HOOK_FLAG_IN_CALL;

      if (!sqldb_hook->row || (row == sqldb_hook->row))
        if ((type & sqldb_hook->type) == type)
          func (table, row, type, sqldb_hook->data); 

      if (!was_in_call)
        hook->flags &= ~G_HOOK_FLAG_IN_CALL;

      hook = g_hook_next_valid (hook_list, hook, FALSE);
    }
  D_FUNC_END;
}
 
/* EOF */
