/*
**  $Id: g_sqldb.c,v 1.19 2000/02/19 19:11:39 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

#define TBL_DB_HOST 1           /* DB_host */

#include <glib.h>
#include <string.h>
#include <stdio.h>
#include <g_sql.h>
#include <g_sql-table.h>
#include <g_sqldb.h>
#include <gxsnmpdb_types.h>

#include "tables.h"
#include "debug.h"

gchar packet[4000];

/******************************************************************************
**
**  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)
{
  G_sqldb_column    * column;
  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)
{
  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 */
  G_sql_table         * tph;

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

/*
  row_private = g_malloc0 (sizeof (G_sqldb_row_private) +
                           sizeof (gpointer) * (table->private->keys - 1));

  row_private->table = table;
  G_STRUCT_MEMBER (G_sqldb_row_private *, row, table->row_private) = row_private;
*/

/*we're adding a row to a hashtable, and
  must check we don't duplicate
  a primary key :
1  ask table-plugin of number to primary key field or primary key field name
2  send row and glist to table-plugin and pray. I prefer this.
*/

/*
  tph = g_sql_table_lookup(table->table);
  g_sql_add_hash(tph, column->private->hash, row);  
*/

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

/******************************************************************************
**
**  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)
{
  G_sqldb_row_private * row_private;  /* Pointer to private area for row */
  G_sqldb_column      * column;       /* Used to scale the column list */
  G_sql_table         * tph;
  gpointer              keyp;         /* Pointer to key copy of column data */
  D_FUNC_START;
  g_return_if_fail (row != NULL);
  g_return_if_fail (table != NULL);
/*
  tph = g_sql_table_lookup(table->table);

  g_sql_remove_hash(tph, column->private->hash, row);
*/
  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)
{
  G_sqldb_row_private * row_private;  /* Pointer to private area for row */
  G_sqldb_column      * column;       /* Used to scale the column list */
  G_sql_table         * tph;
  gpointer              datap;        /* Pointer to original column data */
  gpointer              keyp;         /* Pointer to key copy of column data */

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

/*
I don't know if it is neccessery to determine wheter a primary key has changed?

  tph = g_sql_table_lookup(table->table);
  g_sql_remove_hash(tph, column->private->hash, row);
  g_sql_insert_hash(tph, column->private->hash, row);

*/
  table->private->glist = g_list_remove (table->private->glist, row);

  D_FUNC_END;
}

/* g_sqldb_dbctl()
 *
 * control parameters of your database connection
 *
 *
 *
 */
int g_sqldb_dbctl(gint sock, gint value)
{
  gchar *query;
  gint n,plen,i,h;
  gint rlen;
  G_sql_table *tph;
  gpointer tableres;

  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(sock, 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 (int sock, G_sqldb_table * table)
{
  gchar		    * query;		/* Text of SQL query */
  gint n,plen,i,h;
  gint rlen;
  G_sql_table *tph;			/*table plugin selector */

  gpointer tableres;
  D_FUNC_START;
  table_setup (table);
  g_return_val_if_fail (table != NULL, FALSE);
  if(!(tph = db_table_lookup(table->type))){
    return FALSE;
  }
  h = set_pdu_header(packet, TABLE_LOAD, table->type);			/*copy in a header*/
  memcpy(packet+sizeof(gint), &h, sizeof(gint));			/*write total packet length*/
  dd_write(sock, h, &packet);						/*send packet to database daemon*/
  if((n = dd_read_header(sock, packet)) == -1){
	fprintf(stderr,"  dd_read_header() error\n");
	 return -1;
  }

  memcpy(&plen,packet+sizeof(gint), sizeof(guint)); 			/*get packet length*/
  fprintf(stderr,"  TPL: %d\n", plen);
  if(plen <= 8) return FALSE; 						/*table is empty*/
  if((query = dd_read_rest(sock, plen, packet)) == -1) {
    fprintf(stderr,"  dd_read_rest() error\n");
    return -1;
  }
fprintf(stderr,"  done_read\n");
  /*ADD: cmp rlen to plen  ie what length packet says it has and what length readed*/
 
  n = sizeof(gint) * 2; 						/* header size */

  while((tableres = g_sql_decode_members(tph, query, &n, plen))){	/*we have the first row in an unknown table in tableres*/
    register_row(table, tableres); 					/* hmm, is this enough ? */ 
  }

  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 (int sock, G_sqldb_table * table, gpointer row)
{
  guint n, i, id, h;
  G_sql_table *tph;

  D_FUNC_START;
  g_return_val_if_fail (table != NULL, FALSE);
  g_return_val_if_fail (row != NULL, FALSE);
  fprintf(stderr,"tablename: %s\n", table->table);
  issue_callbacks (table, row, G_SQLDB_CB_ADD | G_SQLDB_CB_BEFORE);

  register_row (table, row);					/* Add row to memory structure first */
  tph = db_table_lookup(table->type);				/* lookup which table plugin to use */
  h = set_pdu_header(packet, ROW_ADD, table->type);		/*copy in a header*/
  if(!(n = g_sql_encode_members(tph, 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+sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(sock, n, &packet);					/*send packet to database daemon*/

  issue_callbacks (table, 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 (gint sock, G_sqldb_table * table, gpointer row)
{
  gint n,i,h;
  G_sql_table *tph;

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

  update_row (table, row);
  tph = db_table_lookup(table->type);
  h = set_pdu_header(packet, ROW_UPDATE, table->type);		/*copy in a header*/
  if(!(n = g_sql_encode_members(tph, 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+sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(sock, n, &packet);					/*send packet to database daemon*/

  issue_callbacks (table, 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(int sock, G_sqldb_table * table, gpointer row)
{
  gint n,i,h;
  G_sql_table *tph;

  D_FUNC_START;
  g_return_val_if_fail (table != NULL, FALSE);
  g_return_val_if_fail (row != NULL, FALSE);
  issue_callbacks (table, row, G_SQLDB_CB_DELETE | G_SQLDB_CB_BEFORE);

  unregister_row (table, row);
  tph = db_table_lookup(table->type);
  h = set_pdu_header(packet, ROW_DELETE, table->type);		/*copy in a header*/
    /*its unnessecery to build all
      members, rowid would suffice*/
  if(!(n = g_sql_encode_members(tph, 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+sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(sock, n, &packet);					/*send packet to database daemon*/

  issue_callbacks (table, 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){
    if(((DB_host *) 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->table);
      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 (guint sock, G_sqldb_table *table, gpointer row)
{
  guint i,h,n,rlen,plen;
  gchar             *query;
  G_sql_table *tph;
  gpointer tableres;

  D_FUNC_START;
  /* Call all the 'update' callbacks as we will be possibly updating this */
  issue_callbacks (table, row, G_SQLDB_CB_UPDATE | G_SQLDB_CB_BEFORE);

  tph = g_sql_table_lookup(table->type);
  h = set_pdu_header(packet, ROW_READ, table->type);		/*copy in a header*/
  if(!(n = g_sql_encode_members(tph, 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+sizeof(gint), &n, sizeof(gint));		/*write total packet length*/
  dd_write(sock, n, &packet);					/*send packet to database daemon*/

  if((rlen = dd_read_header(sock, packet)) == -1){
    fprintf(stderr,"  dd_read_header() error\n");
    return -1;
  }
  memcpy(&plen,packet+sizeof(gint), sizeof(guint));		/*get packet length*/
  if(plen <= 8) return FALSE;
  if((query = dd_read_rest(sock, plen, packet)) == -1){
    fprintf(stderr,"  dd_read_rest() error\n");
    return -1;
  }
  n = sizeof(gint) *2;
  tableres = g_sql_decode_members(tph, packet, &n, plen);	/*we have the first row in an unknown table in tableres*/
  update_row(table, row);
  g_free(tableres);
  issue_callbacks (table, row, 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 * table, gchar * field)
{
  G_sqldb_column * column;
  GList *gl;
  gint r=0;

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

  gl = g_sqldb_table_list(table);

    /* 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_host *) gl->data)->rowid > r) r =((DB_host *) 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 */
