/* -----------------------------------------------------------------------------
 * This file is part of SWIG, which is licensed as a whole under version 3 
 * (or any later version) of the GNU General Public License. Some additional
 * terms also apply to certain portions of SWIG. The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at https://www.swig.org/legal.html.
 *
 * hash.c
 *
 *     Implements a simple hash table object.
 * ----------------------------------------------------------------------------- */

#include "dohint.h"

extern DohObjInfo DohHashType;

/* Hash node */
typedef struct HashNode {
  DOH *key;
  DOH *object;
  struct HashNode *next;
} HashNode;

/* Hash object */
typedef struct Hash {
  DOH *file;
  int line;
  HashNode **hashtable;
  int hashsize;
  int nitems;
} Hash;

/* Key interning structure */
typedef struct KeyValue {
  char *cstr;
  DOH *sstr;
  struct KeyValue *left;
  struct KeyValue *right;
} KeyValue;

static KeyValue *root = 0;
static int max_expand = 1;

/* Find or create a key in the interned key table */
static DOH *find_key(DOH *doh_c) {
  char *c = (char *) doh_c;
  KeyValue *r, *s;
  int d = 0;
  /* OK, sure, we use a binary tree for maintaining interned
     symbols.  Then we use their hash values for accessing secondary
     hash tables. */
  r = root;
  s = 0;
  while (r) {
    s = r;
    d = strcmp(r->cstr, c);
    if (d == 0)
      return r->sstr;
    if (d < 0)
      r = r->left;
    else
      r = r->right;
  }
  /*  fprintf(stderr,"Interning '%s'\n", c); */
  r = (KeyValue *) DohMalloc(sizeof(KeyValue));
  r->cstr = (char *) DohMalloc(strlen(c) + 1);
  strcpy(r->cstr, c);
  r->sstr = NewString(c);
  DohIntern(r->sstr);
  r->left = 0;
  r->right = 0;
  if (!s) {
    root = r;
  } else {
    if (d < 0)
      s->left = r;
    else
      s->right = r;
  }
  return r->sstr;
}

#define HASH_INIT_SIZE   7

/* Create a new hash node */
static HashNode *NewNode(DOH *k, void *obj) {
  HashNode *hn = (HashNode *) DohMalloc(sizeof(HashNode));
  hn->key = k;
  Incref(hn->key);
  hn->object = obj;
  Incref(obj);
  hn->next = 0;
  return hn;
}

/* Delete a hash node */
static void DelNode(HashNode *hn) {
  Delete(hn->key);
  Delete(hn->object);
  DohFree(hn);
}

/* -----------------------------------------------------------------------------
 * DelHash()
 *
 * Delete a hash table.
 * ----------------------------------------------------------------------------- */

static void DelHash(DOH *ho) {
  Hash *h = (Hash *) ObjData(ho);
  HashNode *n, *next;
  int i;

  for (i = 0; i < h->hashsize; i++) {
    n = h->hashtable[i];
    while (n) {
      next = n->next;
      DelNode(n);
      n = next;
    }
  }
  DohFree(h->hashtable);
  h->hashtable = 0;
  h->hashsize = 0;
  DohFree(h);
}

/* -----------------------------------------------------------------------------
 * Hash_clear()
 *
 * Clear all of the entries in the hash table.
 * File and line numbering info left unmodified.
 * ----------------------------------------------------------------------------- */

static void Hash_clear(DOH *ho) {
  Hash *h = (Hash *) ObjData(ho);
  HashNode *n, *next;
  int i;

  for (i = 0; i < h->hashsize; i++) {
    n = h->hashtable[i];
    while (n) {
      next = n->next;
      DelNode(n);
      n = next;
    }
    h->hashtable[i] = 0;
  }
  h->nitems = 0;
}

/* resize the hash table */
static void resize(Hash *h) {
  HashNode *n, *next, **table;
  int oldsize, newsize;
  int i, p, hv;

  if (h->nitems < 2 * h->hashsize)
    return;

  /* Too big. We have to rescale everything now */
  oldsize = h->hashsize;

  /* Calculate a new size */
  newsize = 2 * oldsize + 1;
  p = 3;
  while (p < (newsize >> 1)) {
    if (((newsize / p) * p) == newsize) {
      newsize += 2;
      p = 3;
      continue;
    }
    p = p + 2;
  }

  table = (HashNode **) DohCalloc(newsize, sizeof(HashNode *));

  /* Walk down the old set of nodes and re-place */
  h->hashsize = newsize;
  for (i = 0; i < oldsize; i++) {
    n = h->hashtable[i];
    while (n) {
      hv = Hashval(n->key) % newsize;
      next = n->next;
      n->next = table[hv];
      table[hv] = n;
      n = next;
    }
  }
  DohFree(h->hashtable);
  h->hashtable = table;
}

/* -----------------------------------------------------------------------------
 * Hash_setattr()
 *
 * Set an attribute in the hash table.  Deletes the existing entry if it already
 * exists.
 * ----------------------------------------------------------------------------- */

static int Hash_setattr(DOH *ho, DOH *k, DOH *obj) {
  int hv;
  HashNode *n, *prev;
  Hash *h = (Hash *) ObjData(ho);

  if (!obj) {
    return DohDelattr(ho, k);
  }
  if (!DohCheck(k))
    k = find_key(k);
  if (!DohCheck(obj)) {
    obj = NewString((char *) obj);
    Decref(obj);
  }
  hv = (Hashval(k)) % h->hashsize;
  n = h->hashtable[hv];
  prev = 0;
  while (n) {
    if (Cmp(n->key, k) == 0) {
      /* Node already exists.  Just replace its contents */
      if (n->object == obj) {
	/* Whoa. Same object.  Do nothing */
	return 1;
      }
      Delete(n->object);
      n->object = obj;
      Incref(obj);
      return 1;			/* Return 1 to indicate a replacement */
    } else {
      prev = n;
      n = n->next;
    }
  }
  /* Add this to the table */
  n = NewNode(k, obj);
  if (prev)
    prev->next = n;
  else
    h->hashtable[hv] = n;
  h->nitems++;
  resize(h);
  return 0;
}

/* -----------------------------------------------------------------------------
 * Hash_getattr()
 *
 * Get an attribute from the hash table. Returns 0 if it doesn't exist.
 * ----------------------------------------------------------------------------- */
typedef int (*binop) (DOH *obj1, DOH *obj2);


static DOH *Hash_getattr(DOH *h, DOH *k) {
  DOH *obj = 0;
  Hash *ho = (Hash *) ObjData(h);
  DOH *ko = DohCheck(k) ? k : find_key(k);
  int hv = Hashval(ko) % ho->hashsize;
  DohObjInfo *k_type = ((DohBase*)ko)->type;
  HashNode *n = ho->hashtable[hv];
  if (k_type->doh_equal) {
    binop equal = k_type->doh_equal;
    while (n) {
      DohBase *nk = (DohBase *)n->key;
      if ((k_type == nk->type) && equal(ko, nk)) obj = n->object;
      n = n->next;
    }
  } else {
    binop cmp = k_type->doh_cmp;
    while (n) {
      DohBase *nk = (DohBase *)n->key;
      if ((k_type == nk->type) && (cmp(ko, nk) == 0)) obj = n->object;
      n = n->next;
    }
  }
  return obj;
}

/* -----------------------------------------------------------------------------
 * Hash_delattr()
 *
 * Delete an object from the hash table.
 * ----------------------------------------------------------------------------- */

static int Hash_delattr(DOH *ho, DOH *k) {
  HashNode *n, *prev;
  int hv;
  Hash *h = (Hash *) ObjData(ho);

  if (!DohCheck(k))
    k = find_key(k);
  hv = Hashval(k) % h->hashsize;
  n = h->hashtable[hv];
  prev = 0;
  while (n) {
    if (Cmp(n->key, k) == 0) {
      /* Found it, kill it */

      if (prev) {
	prev->next = n->next;
      } else {
	h->hashtable[hv] = n->next;
      }
      DelNode(n);
      h->nitems--;
      return 1;
    }
    prev = n;
    n = n->next;
  }
  return 0;
}

static DohIterator Hash_firstiter(DOH *ho) {
  DohIterator iter;
  Hash *h = (Hash *) ObjData(ho);
  iter.object = ho;
  iter._current = 0;
  iter.item = 0;
  iter.key = 0;
  iter._index = 0;		/* Index in hash table */
  while ((iter._index < h->hashsize) && !h->hashtable[iter._index])
    iter._index++;

  if (iter._index >= h->hashsize) {
    return iter;
  }
  iter._current = h->hashtable[iter._index];
  iter.item = ((HashNode *) iter._current)->object;
  iter.key = ((HashNode *) iter._current)->key;

  /* Actually save the next slot in the hash.  This makes it possible to
     delete the item being iterated over without trashing the universe */
  iter._current = ((HashNode *) iter._current)->next;
  return iter;
}

static DohIterator Hash_nextiter(DohIterator iter) {
  Hash *h = (Hash *) ObjData(iter.object);
  if (!iter._current) {
    iter._index++;
    while ((iter._index < h->hashsize) && !h->hashtable[iter._index]) {
      iter._index++;
    }
    if (iter._index >= h->hashsize) {
      iter.item = 0;
      iter.key = 0;
      iter._current = 0;
      return iter;
    }
    iter._current = h->hashtable[iter._index];
  }
  iter.key = ((HashNode *) iter._current)->key;
  iter.item = ((HashNode *) iter._current)->object;

  /* Store the next node to iterator on */
  iter._current = ((HashNode *) iter._current)->next;
  return iter;
}

/* -----------------------------------------------------------------------------
 * Hash_keys()
 *
 * Return a list of keys
 * ----------------------------------------------------------------------------- */

static DOH *Hash_keys(DOH *so) {
  DOH *keys;
  Iterator i;

  keys = NewList();
  for (i = First(so); i.key; i = Next(i)) {
    Append(keys, i.key);
  }
  return keys;
}

/* -----------------------------------------------------------------------------
 * DohSetMaxHashExpand()
 *
 * Controls how many Hash objects are displayed in full in Hash_str
 * ----------------------------------------------------------------------------- */

void DohSetMaxHashExpand(int count) {
  max_expand = count;
}

/* -----------------------------------------------------------------------------
 * DohGetMaxHashExpand()
 *
 * Returns how many Hash objects are displayed in full in Hash_str
 * ----------------------------------------------------------------------------- */

int DohGetMaxHashExpand(void) {
  return max_expand;
}

/* -----------------------------------------------------------------------------
 * Hash_str()
 *
 * Create a string representation of a hash table (mainly for debugging).
 * ----------------------------------------------------------------------------- */

static DOH *Hash_str(DOH *ho) {
  int i, j;
  HashNode *n;
  DOH *s;
  static int expanded = 0;
  static const char *tab = "  ";
  Hash *h = (Hash *) ObjData(ho);

  s = NewStringEmpty();
  if (ObjGetMark(ho)) {
    Printf(s, "Hash(%p)", ho);
    return s;
  }
  if (expanded >= max_expand) {
    /* replace each hash attribute with a '.' */
    Printf(s, "Hash(%p) {", ho);
    for (i = 0; i < h->hashsize; i++) {
      n = h->hashtable[i];
      while (n) {
	Putc('.', s);
	n = n->next;
      }
    }
    Putc('}', s);
    return s;
  }
  ObjSetMark(ho, 1);
  Printf(s, "Hash(%p) {\n", ho);
  for (i = 0; i < h->hashsize; i++) {
    n = h->hashtable[i];
    while (n) {
      for (j = 0; j < expanded + 1; j++)
	Printf(s, tab);
      expanded += 1;
      Printf(s, "'%s' : %s, \n", n->key, n->object);
      expanded -= 1;
      n = n->next;
    }
  }
  for (j = 0; j < expanded; j++)
    Printf(s, tab);
  Printf(s, "}");
  ObjSetMark(ho, 0);
  return s;
}

/* -----------------------------------------------------------------------------
 * Hash_len()
 *
 * Return number of entries in the hash table.
 * ----------------------------------------------------------------------------- */

static int Hash_len(DOH *ho) {
  Hash *h = (Hash *) ObjData(ho);
  return h->nitems;
}

/* -----------------------------------------------------------------------------
 * CopyHash()
 *
 * Make a copy of a hash table.  Note: this is a shallow copy.
 * ----------------------------------------------------------------------------- */

static DOH *CopyHash(DOH *ho) {
  Hash *h, *nh;
  HashNode *n;
  DOH *nho;

  int i;
  h = (Hash *) ObjData(ho);
  nh = (Hash *) DohMalloc(sizeof(Hash));
  nh->hashsize = h->hashsize;
  nh->hashtable = (HashNode **) DohMalloc(nh->hashsize * sizeof(HashNode *));
  for (i = 0; i < nh->hashsize; i++) {
    nh->hashtable[i] = 0;
  }
  nh->nitems = 0;
  nh->line = h->line;
  nh->file = h->file;
  if (nh->file)
    Incref(nh->file);

  nho = DohObjMalloc(&DohHashType, nh);
  for (i = 0; i < h->hashsize; i++) {
    n = h->hashtable[i];
    while (n) {
      Hash_setattr(nho, n->key, n->object);
      n = n->next;
    }
  }
  return nho;
}



static void Hash_setfile(DOH *ho, DOH *file) {
  DOH *fo;
  Hash *h = (Hash *) ObjData(ho);

  if (!DohCheck(file)) {
    fo = NewString(file);
    Decref(fo);
  } else
    fo = file;
  Incref(fo);
  Delete(h->file);
  h->file = fo;
}

static DOH *Hash_getfile(DOH *ho) {
  Hash *h = (Hash *) ObjData(ho);
  return h->file;
}

static void Hash_setline(DOH *ho, int line) {
  Hash *h = (Hash *) ObjData(ho);
  h->line = line;
}

static int Hash_getline(DOH *ho) {
  Hash *h = (Hash *) ObjData(ho);
  return h->line;
}

/* -----------------------------------------------------------------------------
 * type information
 * ----------------------------------------------------------------------------- */

static DohHashMethods HashHashMethods = {
  Hash_getattr,
  Hash_setattr,
  Hash_delattr,
  Hash_keys,
};

DohObjInfo DohHashType = {
  "Hash",			/* objname */
  DelHash,			/* doh_del */
  CopyHash,			/* doh_copy */
  Hash_clear,			/* doh_clear */
  Hash_str,			/* doh_str */
  0,				/* doh_data */
  0,				/* doh_dump */
  Hash_len,			/* doh_len */
  0,				/* doh_hash    */
  0,				/* doh_cmp */
  0,				/* doh_equal    */
  Hash_firstiter,		/* doh_first    */
  Hash_nextiter,		/* doh_next     */
  Hash_setfile,			/* doh_setfile */
  Hash_getfile,			/* doh_getfile */
  Hash_setline,			/* doh_setline */
  Hash_getline,			/* doh_getline */
  &HashHashMethods,		/* doh_mapping */
  0,				/* doh_sequence */
  0,				/* doh_file */
  0,				/* doh_string */
  0,				/* doh_positional */
  0,
};

/* -----------------------------------------------------------------------------
 * NewHash()
 *
 * Create a new hash table.
 * ----------------------------------------------------------------------------- */

DOH *DohNewHash(void) {
  Hash *h;
  int i;
  h = (Hash *) DohMalloc(sizeof(Hash));
  h->hashsize = HASH_INIT_SIZE;
  h->hashtable = (HashNode **) DohMalloc(h->hashsize * sizeof(HashNode *));
  for (i = 0; i < h->hashsize; i++) {
    h->hashtable[i] = 0;
  }
  h->nitems = 0;
  h->file = 0;
  h->line = 0;
  return DohObjMalloc(&DohHashType, h);
}
