/* -----------------------------------------------------------------------------
 * hash.c
 *
 *     Implements a simple hash table object.
 *
 * Author(s) : David Beazley (beazley@cs.uchicago.edu)
 *
 * Copyright (C) 1999-2000.  The University of Chicago
 * See the file LICENSE for information on usage and redistribution.
 * ----------------------------------------------------------------------------- */

char cvsroot_hash_c[] = "$Header: /cvsroot/swig/SWIG/Source/DOH/hash.c,v 1.4 2004/10/29 21:34:01 wsfulton Exp $";

#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;

/* 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++) {
	if ((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.
 * ----------------------------------------------------------------------------- */

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

    for (i = 0; i < h->hashsize; i++) {
	if ((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 **) DohMalloc(newsize*sizeof(HashNode *));
    for (i = 0; i < newsize; i++ ) {
	table[i] = 0;
    }

    /* 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.
 * ----------------------------------------------------------------------------- */

static DOH *
Hash_getattr(DOH *ho, DOH *k) {
    int hv;
    HashNode *n;
    DohObjInfo *k_type;
    DohBase *nk;

    Hash *h = (Hash *) ObjData(ho);

    if (!DohCheck(k)) k = find_key(k);
    hv = Hashval(k) % h->hashsize;
    n = h->hashtable[hv];
    k_type = ((DohBase*)k)->type;
    while (n) {
      nk = (DohBase *)n->key;
      if ((k_type == nk->type) && ((k_type->doh_cmp)(k, nk) == 0)) return n->object;
      n = n->next;
    }
    return 0;
}

/* -----------------------------------------------------------------------------
 * 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(DOH *)
 *
 * 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;
}

/* -----------------------------------------------------------------------------
 * 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 indent = 4;
    Hash *h = (Hash *) ObjData(ho);

    s = NewString("");
    if (ObjGetMark(ho)) {
      Printf(s,"Hash(0x%x)",ho);
      return s;
    }
    ObjSetMark(ho,1);
    Printf(s,"Hash {\n");
    for (i = 0; i < h->hashsize; i++) {
	n = h->hashtable[i];
	while (n) {
	  for (j = 0; j < indent; j++) Putc(' ',s);
	  indent+=4;
	  Printf(s,"'%s' : %s, \n", n->key, n->object);
	  indent-=4;
	  n = n->next;
	}
    }
    for (j = 0; j < (indent-4); j++) Putc(' ',s);
    Printf(s,"}\n");
    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++) {
	if ((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 */
    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() {
    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);
}
