/*
 * Copyright (c) 2000-2003
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <string.h>
#include "termhash.h"
#include "hash.h"
#include "termhash.h"
#include "utils.h"

#define UB(n) ((1<<n)-1)   
#define CAP(n) (1<<n)      
#define INITIAL_TABLE_SIZE 8 /* the initial table size is 2^8 */
  
/*  An individual entry in the table consists of an array of stamps  */ 
/*  (same arity as the expr's constructor) in addition to the expr */ 
/*  itself. */ 
typedef struct hash_entry *hash_entry;

/*  A term_bucket is a list of entries (an list of exprs that have  */ 
/*  collided after hashing) */ 
typedef struct term_bucket *term_bucket;

struct hash_entry
{
  int length;
  stamp *stamps;
  term e;
};

struct term_bucket
{
  hash_entry entry;
  struct term_bucket *next;
};

#define scan_term_bucket(b,var) for(var = b; var; var = var->next)

/*  size: initial_table_size + number of rehashes */ 
/*  capacity: 2^size (for array size) */ 
/*  ub: 2^size-1 (for array indexing) */ 
/*  inserts: num of elements inserted into the array */ 
struct term_hash
{
  term_bucket * term_buckets;
  region rgn;
  int ub;
  int size;
  int capacity;
  int inserts;
};

static int hash(int ub, stamp stamps[], int len);
static void post_insert(term_hash tab) deletes;
static void rehash(term_hash tab) deletes;
static void reinsert(term_hash tab, term_bucket b);
static void insert(term_hash tab, term e, stamp * stamps, int len);
static void insert_entry(term_hash tab, struct hash_entry *entry);
static term walk(term_bucket b, stamp * stamps, int len);

static const int primes[] =
  { 83, 1789, 5189, 5449, 5659, 6703, 7517, 7699, 8287, 8807, 9067, 9587,
    10627, 10939, 11239};
/*
static const int prime_1 = 83;
static const int prime_2 = 1789;
*/
static const int initial_table_size = INITIAL_TABLE_SIZE;

term_hash make_term_hash(region rgn)
{
  int ub, n;
  int i;

  region r;
  
  term_hash tab = ralloc(rgn, struct term_hash);

  r = newregion();
  ub = UB(initial_table_size);
  n = CAP(initial_table_size);
  
  
  tab->term_buckets = rarrayalloc(r, n, term_bucket);
  
  for (i = 0; i < n; i++)
    {
      tab->term_buckets[i] = NULL;
    }
  
  tab->rgn = r;
  tab->ub = ub;
  tab->size = initial_table_size;
  tab->capacity = n;
  tab->inserts = 0;
  return tab;
}

void term_hash_delete(term_hash tab) deletes
{
  deleteregion(tab->rgn);
}

term term_hash_find(term_hash tab, stamp stamps[], int len)
{
  int hash_val;

  term_bucket b;
  hash_val = hash(tab->ub, stamps, len);
  b = tab->term_buckets[hash_val];
  return walk(b, stamps, len);
}

static term walk(term_bucket b, stamp stamps[], int len)
{
  term_bucket cur;
  scan_term_bucket(b,cur)
    {
      if (len == cur->entry->length 
	  && (memcmp(stamps, cur->entry->stamps, sizeof(stamp)*len) == 0) )
	return cur->entry->e;
    }
  return NULL;
}

/*  Should call t_hash_find to see if a term with the given stamp  */ 
/*  signature is already in the table. If so, insert should return */ 
/*  true and do nothing. */ 
bool term_hash_insert(term_hash tab, term e, stamp * stamps, int len) deletes
{
  if (term_hash_find(tab, stamps, len) != NULL)
    {
      return TRUE;
    }
  insert(tab, e, stamps, len);
  post_insert(tab);
  return FALSE;
}


/*  Insert an expression e represented by the given stamp array into */ 
/*  the hash table. */ 
static void insert(term_hash tab, term e, stamp stamps[], int len)
{
  hash_entry entry;
  stamp * stamp_cpy;
  int i;

  
  entry = ralloc(tab->rgn, struct hash_entry);

  stamp_cpy = rarrayalloc(tab->rgn, len, stamp);
  for (i = 0; i < len; i++)
    {
      stamp_cpy[i] = stamps[i];
    }

  entry->length = len;
  entry->stamps = stamp_cpy;
  entry->e = e;
  insert_entry(tab, entry);
}

static void insert_entry(term_hash tab, hash_entry entry)
{
  int hash_val;

  term_bucket b, new_term_bucket;
  hash_val = hash(tab->ub, entry->stamps, entry->length);
  b = tab->term_buckets[hash_val];
  new_term_bucket = ralloc(tab->rgn, struct term_bucket);

  new_term_bucket->entry = entry;
  new_term_bucket->next = b;
  tab->term_buckets[hash_val] = new_term_bucket;
}

static void post_insert(term_hash tab) deletes
{
  if (tab->capacity == ++tab->inserts)
    {
      rehash(tab);
    }
}

/*  Double the size of the hash table and reinsert all of the elements. */ 
static void rehash(term_hash tab) deletes
{
  region old_rgn;
  term_bucket * old_term_buckets;
  int i;
  int old_table_size = tab->capacity;

  old_term_buckets = tab->term_buckets;
  tab->capacity *= 2;
  tab->ub = UB(++tab->size);
  old_rgn = tab->rgn;
  tab->rgn = newregion();
  
  
  tab->term_buckets = rarrayalloc(tab->rgn, tab->capacity, term_bucket);
  for (i = 0; i < old_table_size; i++)
    {
      if (old_term_buckets[i] != NULL && old_term_buckets[i]->entry != NULL)
	reinsert(tab, old_term_buckets[i]);
    }

  deleteregion(old_rgn);
  
  
}

static void reinsert(term_hash tab, term_bucket b)
{
  term_bucket cur;
  scan_term_bucket(b,cur)
    insert(tab, cur->entry->e, cur->entry->stamps, cur->entry->length);
}

static int hash(int ub, stamp stamps[], int len)
{
  int i, n;

  n = 0;
  for (i = 0; i < len; i++)
    {
      n = (n + (primes[i % 15] * abs(stamps[i]))) & ub;
    }
  return n;
}





    
