#include "sdb.h"

static int sdb_put
(
   struct Sdb *db,
   const struct Sdb_datum *key,
   struct Sdb_datum *data,
   const u_long flags
);

static int sdb_get
(
   const struct Sdb *db,
   const struct Sdb_datum *key,
   struct Sdb_datum *data,
   const u_long flags
);

static int sdb_del
(
   const struct Sdb *db,
   struct Sdb_datum *key,
   const u_long flags
);

static int sdb_seq
(
   const struct Sdb *db,
   struct Sdb_datum *key,
   struct Sdb_datum *data,
   const u_long flags
);

static int sdb_close
(
   struct Sdb *db
);

static void sdb_stat
(
   const struct Sdb *db
);

static u_short sdb_phong_hash
(
   const struct Sdb *db,
   const struct Sdb_datum *key
);

static u_short sdb_torek_hash
(
   const struct Sdb *db,
   const struct Sdb_datum *key
);

static u_short sdb_torek_100_hash
(
   const struct Sdb *db,
   const struct Sdb_datum *key
);


/******************************************************************
 *
 * Creates a new DB struct, i.e. a database using hashing.
 * Possible methods are: PUT (enter a key/datum pair), GET
 * (retrive a kay/datum pair), DEL (remove a key/datum pair),
 * SEQ (retrieve all key/datum pairs sequentialy),
 * STAT (get a hash statistics).
 * To completely remove a DB, method CLOSE may be used.
 * All methods may be changed by assigning an other function to the
 * corresponding function pointer, especially the hash function may
 * be replaced by a more specific one.
 * The length of the hashlist may be selected at open, if 0 is given
 * the default value is used.
 * Note: hashlength should be a prime number (e.g. 251, 499, 997).
 *
 *    returns:  Pointer to a DB struct
 *
 *****************************************************************/
struct Sdb *sdb_open
(
   const u_short hashlength,           /* in: length of hashlist */
   const Sdb_hashtype hashtype
)
{
   u_short k;
   struct Sdb *db;

   db = malloc(sizeof(struct Sdb));
   db->put = sdb_put;
   db->get = sdb_get;
   db->del = sdb_del;
   db->seq = sdb_seq;
   db->close = sdb_close;
   db->stat = sdb_stat;
   switch (hashtype) {
   case SDB_PHONG:
     db->hash = sdb_phong_hash;
     break;
   case SDB_TOREK:
     db->hash = sdb_torek_hash;
     break;
   case SDB_TOREK_100:
     db->hash = sdb_torek_100_hash;
     break;
   default:
     db->hash = sdb_phong_hash;
   }
   db->hashlength = hashlength > 0 ? hashlength : SDB_HASHLENGTH;;
   db->hashlist = malloc(db->hashlength*sizeof(struct Sdb_chainlink *));
   for (k = 0; k < db->hashlength; k++)
   {
      db->hashlist[k] = (struct Sdb_chainlink *)NULL;
   }
   return db;
}


/******************************************************************
 *
 * PUTs a key/data pair into DB.
 * Flags (may be ORed):
 *   SDB_KEY_REFERENCE: store only a pointer to the key
 *   SDB_DATA_REFERENCE: store only a pointer to the data
 *   SDB_NOOVERWRITE: dont store if key already exist
 *
 *    returns:  0: pair entered into DB
 *              1: pair not entered (already included)
 *             -1: an error occured
 *
 *****************************************************************/
static int sdb_put
(
   struct Sdb *db,
   const struct Sdb_datum *key,
   struct Sdb_datum *data,
   const u_long flags
)
{
   int rv = -1;
   u_short n;
   struct Sdb_chainlink **ptr;

   if (db == NULL)
   {
      return -1;
   }
   /* search entry */
   n = db->hash(db, key) % db->hashlength;
/*
fprintf(stderr,"PUT %d\n", n);
*/
   ptr = &db->hashlist[n];
   while (*ptr != NULL &&
          memcmp((*ptr)->key_data, key->data, (*ptr)->key_size) != 0)
   {
      ptr = &(*ptr)->next;
   }

   if (*ptr == NULL)          /* not found -> append to chain */
   {
      *ptr = malloc(sizeof(struct Sdb_chainlink));
      (*ptr)->next = NULL;
      if (SDB_KEY_REFERENCE & flags)
      {
         (*ptr)->key_data = key->data;
         (*ptr)->key_reference = TRUE;
      }
      else
      {
         (*ptr)->key_data = malloc(key->size);
         memcpy((*ptr)->key_data, key->data, key->size);
         (*ptr)->key_reference = FALSE;
      }
      (*ptr)->key_size = key->size;
      if (SDB_DATA_REFERENCE & flags)
      {
         (*ptr)->data_data = data->data;
         (*ptr)->data_reference = TRUE;
      }
      else
      {
         (*ptr)->data_data = malloc(data->size);
         memcpy((*ptr)->data_data, data->data, data->size);
         (*ptr)->data_reference = FALSE;
      }
      (*ptr)->data_size = data->size;
      rv = 0;
   }
   else                         /* already included */
   {
      if (SDB_NOOVERWRITE & flags)  /* let old entry untouched */
      {
         rv = 1;
      }
      else    /* replace current entry */
      {
         if (! (*ptr)->key_reference)
         {
            free((*ptr)->key_data);
         }
         if (! (*ptr)->data_reference)
         {
            free((*ptr)->data_data);
         }
         if (SDB_KEY_REFERENCE & flags)
         {
            (*ptr)->key_data = key->data;
            (*ptr)->key_reference = TRUE;
         }
         else
         {
            (*ptr)->key_data = malloc(key->size);
            memcpy((*ptr)->key_data, key->data, key->size);
            (*ptr)->key_reference = FALSE;
         }
         (*ptr)->key_size = key->size;
         if (SDB_DATA_REFERENCE & flags)
         {
            (*ptr)->data_data = data->data;
            (*ptr)->data_reference = TRUE;
         }
         else
         {
            (*ptr)->data_data = malloc(data->size);
            memcpy((*ptr)->data_data, data->data, data->size);
            (*ptr)->data_reference = FALSE;
         }
         (*ptr)->data_size = data->size;
         rv = 0;
      }
   }
   return rv;
}


/******************************************************************
 *
 * GETs a key/data pair from DB.
 * Flags (may be ORed):
 *   SDB_DATA_REFERENCE: return only a pointer to the data
 *
 *    returns:  0: key found in DB
 *              1: key not found
 *             -1: an error occured
 *
 *****************************************************************/
static int sdb_get
(
   const struct Sdb *db,
   const struct Sdb_datum *key,
   struct Sdb_datum *data,
   const u_long flags
)
{
   int rv = -1;
   u_short n;
   struct Sdb_chainlink *ptr;

   if (db == NULL)
   {
      return -1;
   }
   /* search entry */
   n = db->hash(db, key) % db->hashlength;
/*
fprintf(stderr,"GET %d\n", n);
*/
   ptr = db->hashlist[n];
   while (ptr != NULL &&
          memcmp(ptr->key_data, key->data, ptr->key_size) != 0)
   {
      ptr = ptr->next;
   }

   if (ptr != NULL)          /* found */
   {
      if (SDB_DATA_REFERENCE & flags)
      {
         data->data = ptr->data_data;
      }
      else
      {
         memcpy(data->data, ptr->data_data, ptr->data_size);
      }
      data->size = ptr->data_size;
      rv = 0;
   }
   else                         /* not found */
   {
      data->data = NULL;
      data->size = 0;      
      rv = 1;
   }
   return rv;
}


/******************************************************************
 *
 * DELs a key/data pair from DB.
 * Flags (may be ORed):
 *    none meaningfull
 *
 *    returns:  0: key found in DB
 *              1: key not found
 *             -1: an error occured
 *
 *****************************************************************/
static int sdb_del
(
   const struct Sdb *db,
   struct Sdb_datum *key,
   const u_long flags
)
{
   u_short n;
   struct Sdb_chainlink *ptr, *p_ptr;

   if (db == NULL)
   {
      return -1;
   }
   /* search entry */
   n = db->hash(db, key) % db->hashlength;
/*
fprintf(stderr,"DEL %d\n", n);
*/
   ptr = db->hashlist[n];
   p_ptr = ptr;
   while (ptr != NULL &&
          memcmp(ptr->key_data, key->data, ptr->key_size) != 0)
   {
      p_ptr = ptr;
      ptr = ptr->next;
   }

   if (ptr == NULL)          /* not found */
   {
      return 1;
   }

   /* free DB data */
   if (! ptr->key_reference)
   {
      free(ptr->key_data);
   }
   if (! ptr->data_reference)
   {
      free(ptr->data_data);
   }

   /* remove chainlink and free it */
   if (p_ptr == db->hashlist[n])
   {
      db->hashlist[n] = ptr->next;
   }
   else
   {
      p_ptr->next = ptr->next;
   }
   free(ptr);

   return 0;
}


/******************************************************************
 *
 * SEQs a key/data pair from DB, each call the next.
 * Flags (may be ORed):
 *   SDB_FIRST: start a new sequence. At each call without
 *                    this flag, the next pair is returned until
 *                    the last is reached.
 *   SDB_KEY_REFERENCE: return only a pointer to the key
 *   SDB_DATA_REFERENCE: return only a pointer to the data
 *
 *    returns:  0: key found in DB
 *              1: key not found
 *             -1: an error occured
 *
 *****************************************************************/
static int sdb_seq
(
   const struct Sdb *db,
   struct Sdb_datum *key,
   struct Sdb_datum *data,
   const u_long flags
)
{
   static u_short k = 0;
   static const struct Sdb_chainlink *ptr = NULL;

   if (db == NULL)
   {
      return -1;
   }
   if (SDB_FIRST & flags)
   {
      k = 0;
      ptr = db->hashlist[0];
   }

   if (ptr == NULL)
   {
      do
      {
         k++;
      }
      while (db->hashlist[k] == NULL && k < db->hashlength);
      if (k == db->hashlength)
      {
         return 1;
      }
/*
fprintf(stderr,"SEQ %d\n", k);
*/
      ptr = db->hashlist[k];
   }

/*
fprintf(stderr,"SEQSEQ %d\n", k);
*/
   if (SDB_KEY_REFERENCE & flags)
   {
      key->data = ptr->key_data;
   }
   else
   {
      memcpy(key->data, ptr->key_data, ptr->key_size);
   }
   key->size = ptr->key_size;
   if (SDB_DATA_REFERENCE & flags)
   {
      data->data = ptr->data_data;
   }
   else
   {
      memcpy(data->data, ptr->data_data, ptr->data_size);
   }
   data->size = ptr->data_size;
   
   ptr = ptr->next;

   return 0;
}


/******************************************************************
 *
 * CLOSE DB.
 *
 *****************************************************************/
static int sdb_close
(
   struct Sdb *db
)
{
   u_short k;
   struct Sdb_chainlink *ptr, *p_ptr;   

   if (db == NULL)
   {
      return -1;
   }
   for (k = 0; k < db->hashlength; k++)
   {
      ptr = db->hashlist[k];
      while (ptr != NULL)
      {
         if (! ptr->key_reference)
         {
            free(ptr->key_data);
         }
         if (! ptr->data_reference)
         {
            free(ptr->data_data);
         }
         p_ptr = ptr;
         ptr = ptr->next;
         free(p_ptr);
      }
   }
   free(db->hashlist);
   free(db);
   db = NULL;
   return 0;
}


/******************************************************************
 *
 * STATs DB, i.e. a statistic about current hashig is returned.
 *
 *****************************************************************/
static void sdb_stat
(
   const struct Sdb *db
)
{
   u_short k, p, max, sum, chains;
   struct Sdb_chainlink *ptr;

   if (db == NULL)
   {
      return;
   }
   max = 0;
   sum = 0;
   chains = 0;
   for (k = 0; k < db->hashlength; k++)
   {
      ptr = db->hashlist[k];
      for (p = 0; ptr != NULL; p++, sum++, ptr = ptr->next)
      {; }
      if (p > 0)
      {
         chains++;
      }
      if (p > max)
      {
         max = p;
      }
   }
   if (chains > 0)
   {
      fprintf(stderr,"HASH statistics: %u entries, %u chains (%3.1f entries/chain, longest chain %u)",
                   sum, chains, (double)sum / (double)chains, max);
   }
   else
   {
      fprintf(stderr,"HASH statistics: DB is empty");
   }
   return;
}


/******************************************************************
 *
 * Hash function with very good scattering but a bit expensive
 * due to multiplications.
 *
 *    returns:  hash value h, 0 <= h < hashlength
 *
 *****************************************************************/
#define SPHONG_HASH(h, c) (0x63c63cd9*(h) + 0x9c39c33d + *(c)++)
static u_short sdb_phong_hash
(
   const struct Sdb *db,
   const struct Sdb_datum *key
)
{
   const u_char *e, *key_data;
   u_int h;
   
   key_data = key->data;
   e = key_data + key->size;
   h = 0;
   while (key_data < e)
   {
      h = SPHONG_HASH(h, key_data);
   }
   return (u_short)(h % db->hashlength);
}


/******************************************************************
 *
 * Hash function with medium scattering but quite inexpensive.
 *
 *    returns:  hash value h, 0 <= h < hashlength
 *
 *****************************************************************/
#define STOREK_HASH(h, c) (((h) << 5) + (h) + *(c)++)
static u_short sdb_torek_hash
(
   const struct Sdb *db,
   const struct Sdb_datum *key
)
{
   const u_char *key_data;
   u_int h;
   u_long len, n;
   
   h = 0;
   len = key->size;
   if (len > 0)
   {
      key_data = key->data;
      n = (len + 7) >> 3;
      switch (len & 7)
      {
      case 0:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 7:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 6:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 5:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 4:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 3:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 2:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 1:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      default:
      }
      while (--n > 0)
      {
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
      }
   }
   return (u_short)(h % db->hashlength);
}


static u_short sdb_torek_100_hash
(
   const struct Sdb *db,
   const struct Sdb_datum *key
)
{
   const u_char *key_data;
   u_int h;
   u_long len, n;
   
   h = 0;
   len = key->size;
   if (len > 0)
   {
     if (len > 100) {
       len = 96;
     }
      key_data = key->data;
      n = (len + 7) >> 3;
      switch (len & 7)
      {
      case 0:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 7:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 6:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 5:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 4:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 3:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 2:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      case 1:
         h = STOREK_HASH(h, key_data);
         /* FALLTHROUGH */
      default:
      }
      while (--n > 0)
      {
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
         h = STOREK_HASH(h, key_data);
      }
   }
   return (u_short)(h % db->hashlength);
}
