#ifndef EINTHASHOF_H
#define EINTHASHOF_H

#include "eutils.h"

#include "einthashof_dec.h"
#include "ehashmap_dec.h"
#include "evar_dec.h"




template <class T>
void einthashof<T>::addvar(evar& var)
{
  addref(0u,&var.get<T>());
}
template <class T>
void einthashof<T>::addvar(evar& evarkey,evar& var)
{
  addref(evarkey.get<unsigned int>(),&var.get<T>());
}
template <class T>
evar einthashof<T>::getvar(size_t i) const
{
//  return(evar((T*)&values(i)));
  return(evar());
}
template <class T>
evar einthashof<T>::getvarkey(size_t i) const
{
  return(evar());
//  return(evar((K*)&keys(i)));
}
template <class T>
void einthashof<T>::erase(size_t i)
{
  einthashof<T>::iter it=begin();
  for (; i>0 && it!=end(); ++it,--i);
  if (it!=end()) erase(it);
}

template <class T>
evar einthashof<T>::getvarByKey(const evar& var) const
{
  if (var.getTypeid()==typeid(int))
    return(evar(&values(var.get<int>())));
  return(evar());
}


#include "logger.h"

const size_t INTHASH_INIT_COUNT=256;

template <class T>
einthashof<T>::iter::iter(): hashmap(0x00), hitem(0x00), bucket(0) {}

template <class T>
typename einthashof<T>::iter& einthashof<T>::iter::operator++()
{
  hitem=hitem->next;
  
  if (hitem) return(*this);

  ++bucket;
  while (bucket<hashmap->_hashcount && hashmap->_hashitems[bucket]==0x00) ++bucket;

  if (bucket < hashmap->_hashcount)
    hitem=hashmap->_hashitems[bucket];
  return(*this); 
}

template <class T>
unsigned int einthashof<T>::iter::key() const
{
  lddieif(hitem==0x00,"trying to access end iterator");
  return(hitem->key);
}

template <class T>
T& einthashof<T>::iter::value() const
{
  lddieif(hitem==0x00,"trying to access end iterator");
  return(*hitem->value);
}

template <class T>
T& einthashof<T>::iter::operator*() const
{
  lddieif(hitem==0x00,"trying to access end iterator");
  return(*hitem->value);
}

template <class T>
T* einthashof<T>::iter::operator->() const
{
  lddieif(hitem==0x00,"trying to access end iterator");
  return(hitem->value);
}

template <class T>
bool einthashof<T>::iter::operator==(const einthashof<T>::iter& i) const
{
  return(hashmap==i.hashmap && hitem==i.hitem);
}

template <class T>
bool einthashof<T>::iter::operator!=(const einthashof<T>::iter& i) const
{
  return(hashmap!=i.hashmap || hitem!=i.hitem);
}

template <class T>
typename einthashof<T>::iter& einthashof<T>::iter::operator=(const einthashof<T>::iter& i)
{
  hashmap=i.hashmap;
  bucket=i.bucket;
  hitem=i.hitem;
  return(*this);
}




template <class T>
einthashof<T>::einthashof(): count(0)
{
  _hashcount = INTHASH_INIT_COUNT;
  _hashitems=new ehashitem<unsigned int,T>*[_hashcount];
  size_t i;
  for (i=0; i<_hashcount; ++i)
    _hashitems[i]=0x00;
}

template <class T>
einthashof<T>::einthashof(const einthashof<T>& oldhm): count(oldhm.count)
{
  _hashcount = oldhm._hashcount;
  _hashitems=new ehashitem<unsigned int,T>*[_hashcount];

  ehashitem<unsigned int,T> *oldhmitem;
  size_t i;
  for (i=0; i<_hashcount; ++i){
    _hashitems[i]=0x00;
    for (oldhmitem=oldhm._hashitems[i]; oldhmitem!=0x00; oldhmitem=oldhmitem->next)
      _hashitems[i]=new ehashitem<unsigned int,T>(oldhmitem->key,new T(*oldhmitem->value),_hashitems[i]);
  }
}




template <class T>
einthashof<T>::~einthashof()
{
  clear();
  delete[] _hashitems;
}

template <class T>
typename einthashof<T>::iter einthashof<T>::get(unsigned int key) const
{
  typename einthashof<T>::iter it;

  it.hashmap=this;
  it.bucket=key%_hashcount;
  it.hitem=_hashitems[it.bucket];

  while (it.hitem!=0x00){
    if (key == it.hitem->key)   // there is no collision
      return(it);
    it.hitem=it.hitem->next;
  }

  return(end());
}

template <class T>
typename einthashof<T>::iter einthashof<T>::begin() const
{
  typename einthashof<T>::iter i;
  i.hashmap=this;
  i.bucket=0;
  i.hitem=0x00;
  while (i.bucket<_hashcount && _hashitems[i.bucket]==0x00) ++i.bucket;

  if (i.bucket==_hashcount)
    return(end());

  i.hitem=_hashitems[i.bucket];
  return(i);
}

template <class T>
typename einthashof<T>::iter einthashof<T>::end() const
{
  typename einthashof<T>::iter i;
  i.hashmap=this;
  i.bucket=0;
  i.hitem=0x00;
  return(i);
}

template <class T>
void einthashof<T>::reserve(size_t i)
{
  size_t a;
  size_t c=1;
  a=0x01;
  while (i>0){
    i=i>>1;
    a=(a<<1)|0x01;
    ++c;
  }

  if (c>=sizeof(size_t)*8){
    lwarn("reached limit of hash table index size");
    a=0x8000000000000000u-0x01u;
  }
  
  resizehash(a);
}

template <class T>
void einthashof<T>::resizehash(size_t newcount) const
{
  size_t thashcount;
  ehashitem<unsigned int,T> **thashitems;

  if (newcount < _hashcount) return;

  ldinfo("resizing hash table");

  if (newcount==0) 
    thashcount = (_hashcount << 1);
  else
    thashcount = newcount;

  thashitems=new ehashitem<unsigned int,T>*[thashcount];
  ldieif(thashitems==0x00,"unable to allocate memory for hashmap");
  size_t i;
  for (i=0; i<thashcount; ++i)
    thashitems[i]=0x00;

  size_t khash;
  ehashitem<unsigned int,T>* hitem;

  typename einthashof<T>::iter it;
  for (it=begin(); it!=end(); ++it){
    hitem = _hashitems[it.key()%_hashcount]; //gethashitem(khash%_hashcount,it.key());

    if (hitem->prev)
      hitem->prev->next=hitem->next;
    else
      _hashitems[hitem->key%_hashcount]=hitem->next;
    if (hitem->next)
      hitem->next->prev=hitem->prev;

    hitem->prev=0x00;
    hitem->next=thashitems[hitem->key%thashcount];
    thashitems[hitem->key%thashcount]=hitem;
    if (hitem->next)
      hitem->next->prev=hitem;
  } 

  delete[] _hashitems;
  _hashcount=thashcount;
  _hashitems=thashitems;
  ldinfo("finished resize");
}

template <class T>
ehashitem<unsigned int,T>* einthashof<T>::gethashitem(unsigned int key) const
{
  ehashitem<unsigned int,T>* hitem;
  hitem = _hashitems[key%_hashcount];
  while (hitem != 0x00){
    if (hitem->key == key)
      return(hitem);
    hitem=hitem->next;
  }
  lderror("einthashof: did not find key");
  return(0x00); 
}


template <class T>
void einthashof<T>::clear()
{
  size_t i;
  ehashitem<unsigned int,T> *hitem;
  for (i=0; i<_hashcount; ++i){
    while (_hashitems[i]){
      hitem=_hashitems[i];
      _hashitems[i]=hitem->next;
      delete hitem->value;
      delete hitem;
    }
  }
  count=0;
}

template <class T>
bool einthashof<T>::exists(unsigned int key) const
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  i=key%_hashcount;
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (key == hitem->key)
      return(true);
    hitem=hitem->next;
  }
  // non existent value
  return(false);
}

template <class T>
void einthashof<T>::erase(const typename einthashof<T>::iter& it)
{
  ldieif(it.hitem==0x00,"trying to delete empty iterator");
  if (it.hitem->prev) it.hitem->prev->next=it.hitem->next;
  else _hashitems[it.bucket]=it.hitem->next;
  if (it.hitem->next) it.hitem->next->prev=it.hitem->prev;
  delete it.hitem->value;
  delete it.hitem;
//  it.hitem=0x00;
  --count;
}

template <class T>
void einthashof<T>::remove(unsigned int key)
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  i=key%_hashcount;
  hitem=_hashitems[i];

  size_t j;
  while (hitem!=0x00){
    if (key == hitem->key){
      if (hitem->prev) hitem->prev->next=hitem->next;
      else _hashitems[i]=hitem->next;
      if (hitem->next) hitem->next->prev=hitem->prev;
      delete hitem->value;
      delete hitem;
      --count;
      return;
    }
    hitem=hitem->next;
  }
  lddie("tried to delete key from hashmap that does not exist");
  // non existent value
}

template <class T>
einthashof<T>& einthashof<T>::operator+=(const einthashof<T>& hm)
{
  size_t i;
  for (i=0; i<hm.size(); ++i)
    add(hm.keys(i),hm.values(hm.keys(i)));
  return(*this);
}


template <class T>
T& einthashof<T>::addref(unsigned int key,T* value)
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  if (size() > (3u*(_hashcount))>>2u) resizehash();

  i=key%_hashcount;
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (key == hitem->key){   // there is no collision
      hitem->value = value;
      return(*hitem->value);
    }
    hitem=hitem->next;
  }

  // non existent value
  ++count;
  _hashitems[i]=new ehashitem<unsigned int,T>(key,value,_hashitems[i]);
  return(*_hashitems[i]->value);
}

template <class T>
T& einthashof<T>::add(unsigned int key,const T& value)
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  if (size() > (3u*(_hashcount))>>2u) resizehash();

  i=key%_hashcount;
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (key == hitem->key){   // there is no collision
      hitem->value=new T(value);
      return(*hitem->value);
    }
    hitem=hitem->next;
  }

  // non existent value
  ++count;
  _hashitems[i]=new ehashitem<unsigned int,T>(key,new T(value),_hashitems[i]);
  return(*_hashitems[i]->value);
/*
  operator[](key) = value;
  return(operator[](key));
*/
}

template <class T>
const T& einthashof<T>::operator[](unsigned int key) const
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  if (size() > (3u*(_hashcount))>>2u) resizehash();

  i=key%_hashcount;
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (key == hitem->key)   // there is no collision
      return(*hitem->value);
    hitem=hitem->next;
  }

  // non existent value
//  ++count;
  _hashitems[i]=new ehashitem<unsigned int,T>(key,new T,_hashitems[i]);
  return(*_hashitems[i]->value);
}

/*
template <class T>
const T& einthashof<T>::operator[](int ind) const
{
  int i;
  ehashitem<T>* hitem;

  lddieif(ind > _keys.size(),"einthashof: index out of bounds");
  i=hash(_keys.at(ind));
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (_keys.at(ind) == hitem->key)   // there is no collision
      return(*hitem->value);
    hitem=hitem->next;
  }

  ldie("einthashof: index out of bounds: "+estr(ind));
//  return(*(T*)0x00);
}
*/

template <class T>
T& einthashof<T>::operator[](unsigned int key)
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  if (size() > (3u*(_hashcount))>>2u) resizehash();

  i=key%_hashcount;
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (key == hitem->key)   // there is no collision
      return(*hitem->value);
    hitem=hitem->next;
  }

//  lerror("key not found");
//  throw "key not found";

  // non existent value
  ++count;
  _hashitems[i]=new ehashitem<unsigned int,T>(key,new T,_hashitems[i]);
  return(*_hashitems[i]->value);
}

//#pragma GCC diagnostic ignored "-Wreturn-type"
template <class T>
const T& einthashof<T>::values(unsigned int key) const
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  if (size() > (3u*(_hashcount))>>2u) resizehash();

  i=key%_hashcount;
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (key == hitem->key)   // there is no collision
      return(*hitem->value);
    hitem=hitem->next;
  }

  lerror("einthashof: key not found");
  throw "einthashof: key not found";
//  return(*(T*)0x00);
}

template <class T>
T& einthashof<T>::values(unsigned int key)
{
  size_t i;
  ehashitem<unsigned int,T>* hitem;

  if (size() > (3u*(_hashcount))>>2u) resizehash();

  i=key%_hashcount;
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (key == hitem->key)   // there is no collision
      return(*hitem->value);
    hitem=hitem->next;
  }
  
  lerror("einthashof: key not found");
  throw "einthashof: key not found";
//  return(*(T*)0x00);
}

/*
template <class T>
T& einthashof<T>::operator[](int ind)
{
  int i;
  ehashitem<T>* hitem;

  i=hash(_keys.at(ind));
  hitem=_hashitems[i];

  while (hitem!=0x00){
    if (_keys.at(ind) == hitem->key)   // there is no collision
      return(*hitem->value);
    hitem=hitem->next;
  }

  ldie("einthashof: index out of bounds: "+estr(ind));
  return(*(T*)0x00);
}
//#pragma GCC diagnostic warning "-Wreturn-type"
*/

/*
template <unsigned int (*hashfunc)(const evar&)>
void einthashof<evar,evar>::addvar(evar& key,evar& var)
{
  add(key,var);
}

template <unsigned int (*hashfunc)(const evar&)>
evar einthashof<evar,evar>::getvar(int i) const
{
  return(evar());
//  return(values(i));
}

template <unsigned int (*hashfunc)(const evar&)>
evar einthashof<evar,evar>::getvarkey(int i) const
{
  return(evar());
//  return(keys(i));
}
*/

#endif

