//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : hash_table.c
// Author      : Michael Jacobson, Jr. (MJ)
// Last change : MJ, September 24, 1996
//

#include "hash_table.h"

template < class T >
hash_table < T >::hash_table()
{
  size = 0;
  curr_size = 0;
  buckets = NULL;
  last_one = NULL;
  key = NULL;
}


template < class T >
hash_table < T >::~hash_table()
{
  if (buckets) {
    empty();
    delete [] buckets;
    buckets = NULL;
    last_one = NULL;
    key = NULL;
  }
}



template < class T >
void
hash_table < T >::assign(const hash_table < T > & old_table)
{
  lidia_size_t i;
  hentry < T > *ptr,*newone;
  T *tptr;

  initialize(old_table.size);
  key = old_table.key;

  newone = NULL;
  for (i=0; i<old_table.size; ++i) {
    ptr = (hentry < T > *) old_table.buckets[i];
    while (ptr) {
      tptr = (T *) ptr->item;
      hash(*tptr);
      ptr = ptr->next;
    }
  }
}



template < class T >
hash_table < T > & hash_table < T >::operator = (const hash_table < T > & old_table)
{
  this->assign(old_table);
  return *this;
}



template < class T >
void
hash_table < T >::initialize(const lidia_size_t table_size)
{
  bigint temp;
  long lsize;

  if (buckets) {
    empty();
    delete [] buckets;
  }

  temp = next_prime(bigint(table_size-1));
  temp.longify(lsize);
  size = (lidia_size_t) lsize;

  if (!(buckets = new int[lsize]))
    lidia_error_handler("hash_table","initialize - not enough memory");

  memset(buckets, 0, (int) (lsize) * sizeof(int));
}



template < class T >
void
hash_table < T >::set_key_function(bigint (*get_key) (const T &))
{
  key = get_key;
}



template < class T >
lidia_size_t
hash_table < T >::no_of_buckets() const
{
  return size;
}



template < class T >
lidia_size_t
hash_table < T >::no_of_elements() const
{
  return curr_size;
}



template < class T >
void
hash_table < T >::remove(const T & G)
{
  T *target,*tptr;
  hentry < T > *ptr,*pptr,*nptr;
  lidia_size_t i,j;

  /* check whether G is in the table */
  target = NULL;
  i = (lidia_size_t) remainder(key(G),size);
  if (i < 0)
    i += size;
  ptr = (hentry < T > *) buckets[i];
  while ((ptr) && (!target)) {
    tptr = (T *) ptr->item;
    if (G == *tptr)
      target = tptr;
    else
      ptr = ptr->next;
  }

  /* if so, remove it */
  if (target) {
    pptr = ptr->prev;
    nptr = ptr->next;

    if (pptr) {
      pptr->next = nptr;
      if (nptr)
        nptr->prev = pptr;
    }
    else {
      tptr = (T *) ptr->item;
      j = (lidia_size_t) remainder(key(*tptr),size);
      if (j < 0)
        j += size;
//      delete (hentry < T > *) buckets[j];
      buckets[j] = (int) nptr;
      if (nptr)
        nptr->prev = NULL;
    }

    delete ptr;
    --curr_size;
  }
}



template < class T >
void
hash_table < T >::empty()
{
  lidia_size_t i;
  hentry < T > *ptr,*nptr;

  for (i=0; i<size; ++i) {
    ptr = (hentry < T > *) buckets[i];
    while (ptr) {
      nptr = ptr->next;
      delete ptr;
      ptr = nptr;
    }
    buckets[i] = (int) NULL;
  }

  curr_size = 0;
  last_one = NULL;
}



template < class T >
const T & hash_table < T >::last_entry() const
{
  if (!last_one)
    lidia_error_handler("hash_table","last_entry - table is empty");

  return *last_one;
}


template < class T >
void
hash_table < T >::hash(const T & G)
{
  lidia_size_t i;
  hentry < T > *ptr,*nptr,*newone;
  T *newT;
  
  ++curr_size;

  if (!(newone = new hentry < T >))
    lidia_error_handler("hash_table","hash - not enough memory");
  if (!(newT = new T))
    lidia_error_handler("hash_table","hash - not enough memory");
  (*newT) = G;
  newone->item = (int) newT;
  last_one = newT;

  i = (lidia_size_t) remainder(key(G),size);
  if (i < 0)
    i += size;
  ptr = (hentry < T > *) buckets[i];
  if (ptr) {
    while (ptr) {
      nptr = ptr;
      ptr = nptr->next;
    }
    nptr->next = newone;
    newone->prev = nptr;
  }
  else
    buckets[i] = (int) newone;
}  



template < class T >
T * hash_table < T >::search(const T & G)
{
  lidia_size_t i;
  T *target,*tptr;
  hentry < T > *ptr;

  target = NULL;
  i = (lidia_size_t) remainder(key(G),size);
  if (i < 0)
    i += size;
  ptr = (hentry < T > *) buckets[i];
  while ((ptr) && (!target)) {
    tptr = (T *) ptr->item;
    if (G == (*tptr))
      target = tptr;
    else
      ptr = ptr->next;
  }

  return target;
}



template < class T >
void hash_table< T >::read (istream & in)
{
  lidia_size_t new_size,num,i;
  T new_item;

  in >> new_size;
  initialize((long) new_size);

  in >> num;
  for (i=0; i<num; ++i) {
    in >> new_item;
    hash(new_item);
  }
}



template < class T >
void hash_table< T >::print (ostream & out) const
{
  lidia_size_t i;
  hentry < T > *ptr;
  T *tptr;

  for (i=0; i<size; ++i) {
    if (buckets[i]) {
      out << "[" << i << "]";
      ptr = (hentry < T > *) buckets[i];
      while (ptr) {
        tptr = (T *) ptr->item;
        out << " : " << (*tptr);
        ptr = ptr->next;
      }
      out << "\n";
    }
  }
}
