/*
  Copyright(C) 2007-2012 National Institute of Information and Communications Technology
*/

/*
  Hashtable library
*/


#include <stdlib.h>
#include <string.h>
#include "hashtable.h"


#define HT_INITSIZE 11		/* Initial hash table size */
#define HT_THRESHOLD 0.75	/* Threshold for hash table expansion */
#define HT_INCREMENT 2.0	/* Increment of hash table expansion */


typedef struct Record {
  void *key;
  void *val;
  size_t hashval;
  struct Record *next;
} RECORD;

/*
  Hash table type
*/
struct Ht {
  size_t size;  /* Hash table size */
  RECORD **tbl;	/* Hash table */
  int (*equal)(const void *, const void *);
  size_t (*hash)(const void *);
  RECORD *free;	/* Pointer to free list */
  RECORD *mem;
};


static int expand(HT *ht);


/*
  Create and return a new hash table object
*/
HT *ht_new(int (*equal)(const void *, const void *), size_t (*hash)(const void *)) {
  HT *ht;
  size_t i;
  size_t max;

  if ((ht = malloc(sizeof(HT))) == NULL) return NULL;
  ht->size = HT_INITSIZE;
  ht->equal = equal;
  ht->hash = hash;
  max = (size_t)(ht->size * HT_THRESHOLD);
  if ((ht->tbl = malloc(sizeof(RECORD *) * ht->size)) == NULL) {
    free(ht);
    return NULL;
  }
  if ((ht->mem = malloc(sizeof(RECORD) * max)) == NULL) {
    free(ht->tbl);
    free(ht);
    return NULL;
  }
  for (i = 0; i < ht->size; i++) {
    ht->tbl[i] = NULL;
  }
  ht->free = &ht->mem[0];
  for (i = 0; i < max - 1; i++) {
    ht->mem[i].next = &ht->mem[i + 1];
  }
  ht->mem[i].next = NULL;
  return ht;
}


/*
  Delete the hash table object <ht>
*/
void ht_delete(HT *ht) {
  free(ht->mem);
  free(ht->tbl);
  free(ht);
  return;
}


/*
  Clear the hash table <ht>
*/
void ht_clear(HT *ht) {
  size_t max;
  size_t i;

  max = (size_t)(ht->size * HT_THRESHOLD);
  for (i = 0; i < ht->size; i++) {
    ht->tbl[i] = NULL;
  }
  ht->free = &ht->mem[0];
  for (i = 0; i < max - 1; i++) {
    ht->mem[i].next = &ht->mem[i + 1];
  }
  ht->mem[i].next = NULL;
  return;
}


/*
  Get a value associated to <key>
*/
void *ht_get(HT *ht, const void *key) {
  size_t h;
  RECORD *r;

  h = (ht->hash)(key);
  for (r = ht->tbl[h % ht->size]; r != NULL; r = r->next) {
    if ((ht->equal)(key, r->key) == 0) break;
  }
  return (r == NULL) ? NULL : r->val;
}


/*
  Put <val> with <key> into the hash table <ht>
  <key>'s record is removed if <val> is NULL
*/
int ht_put(HT *ht, void *key, void *val) {
  size_t h, hh;
  RECORD *r;

  if (val == NULL) return (ht_remove(ht, key) == NULL);
  h = (ht->hash)(key);
  hh = h % ht->size;
  for (r = ht->tbl[hh]; r != NULL; r = r->next) {
    if ((ht->equal)(key, r->key) == 0) break;
  }
  if (r == NULL) {
    RECORD *r_new;

    if (ht->free == NULL) {
      if (expand(ht)) return 1;
      hh = h % ht->size;
    }
    r_new = ht->free;
    ht->free = ht->free->next;
    r_new->key = key;
    r_new->val = val;
    r_new->hashval = h;
    r_new->next = ht->tbl[hh];
    ht->tbl[hh] = r_new;
  } else {
    r->val = val;
  }

  return 0;
}


/*
  Expand the hash table <ht>
*/
static int expand(HT *ht) {
  size_t i;
  size_t hh;
  size_t max_old, max_new, size_new;
  RECORD **tbl_new, *mem_new;

  max_old = (size_t)(ht->size * HT_THRESHOLD);
  size_new = (size_t)(ht->size * HT_INCREMENT);
  max_new = (size_t)(size_new * HT_THRESHOLD);
  if ((tbl_new = realloc(ht->tbl, sizeof(RECORD *) * size_new)) == NULL) {
    return 1;
  }
  if ((mem_new = realloc(ht->mem, sizeof(RECORD) * max_new)) == NULL) {
    free(tbl_new);
    return 1;
  }
  ht->tbl = tbl_new;
  ht->mem = mem_new;
  ht->size = size_new;
  for (i = 0; i < size_new; i++) {
    ht->tbl[i] = NULL;
  }
  for (i = 0; i < max_old; i++) {
    hh = ht->mem[i].hashval % size_new;
    ht->mem[i].next = ht->tbl[hh];
    ht->tbl[hh] = &ht->mem[i];
  }
  ht->free = &ht->mem[i];
  for (; i < max_new - 1; i++) {
    ht->mem[i].next = &ht->mem[i + 1];
  }
  ht->mem[i].next = NULL;
  return 0;
}


/*
  Remove the record with <key> from the hash table <ht>
*/
void *ht_remove(HT *ht, const void *key) {
  size_t h;
  RECORD **rr;

  h = (ht->hash)(key);
  for (rr = &ht->tbl[h % ht->size]; *rr != NULL; rr = &(*rr)->next) {
    if ((ht->equal)(key, (*rr)->key) == 0) break;
  }
  if (*rr == NULL) {
    return NULL;
  } else {
    RECORD *r;

    r = *rr;
    *rr = r->next;
    r->next = ht->free;
    ht->free = r;
    return r->val;
  }
}


/*
  Return the hash value for <str>
*/
size_t strhash(const char *s) {
  size_t h = 0;

  while (*s != '\0') h = *(s++) + 31 * h;
  return h;
}


SDB *sdb_new(void) {
  SDB *sdb;

  sdb = malloc(sizeof(SDB));
  if (sdb == NULL) return NULL;
  sdb->size = 0;
  sdb->num = 0;
  sdb->tbl = NULL;
  sdb->ht = ht_new((int (*)(const void *, const void *))strcmp, (size_t (*)(const void *))strhash);
  if (sdb->ht == NULL) {
    free(sdb);
    return NULL;
  }
  return sdb;
}


void sdb_delete(SDB *sdb) {
  ht_delete(sdb->ht);
  if (sdb->size > 0) {
    free(sdb->tbl);
  }
  free(sdb);
  return;
}


void sdb_clear(SDB *sdb) {
  sdb->num = 0;
  ht_clear(sdb->ht);
  return;
}


#undef sdb_str
char *sdb_str(SDB *sdb, int id) {
  return sdb->tbl[id];
}


/*
  Return the id of the string <str>
  If <str> has no id and <add> is true, <str> is registered
*/
int sdb_id(SDB *sdb, char *str, int add) {
  int id;

  id = (int)((char *)ht_get(sdb->ht, str) - (char *)NULL) - 1;
  if (id == -1 && add) id = sdb_put(sdb, str);

  return id;
}


int sdb_put(SDB *sdb, char *str) {
  char *tmp;
  char *s;

  if (sdb->num + 1 > sdb->size) {
    sdb->size = 2 * sdb->size + 1;
    sdb->tbl = realloc(sdb->tbl, sizeof(char *) * sdb->size);
    if (sdb->tbl == NULL) return -1;
  }
  tmp = (char *)NULL + (sdb->num + 1);
  if ((s = strdup(str)) == NULL) return -1;
  sdb->tbl[sdb->num] = s;
  if (ht_put(sdb->ht, s, tmp)) return -1;
  sdb->num++;
  return (sdb->num - 1);
}


#undef sdb_size
int sdb_size(SDB *sdb) {
  return sdb->num;
}
