#ifndef hash_set_h_
#define hash_set_h_

//  File:     hash_set.h
//  Purpose:  Implement the basic function of the set class as a hash
//      table instead of a binary search tree.  In the usage of the
//      class, the primary difference between versions is that the
//      iterators do not step through the hash set in any intuitively
//      meaningful order --- it is just the order imposed by the hash
//      function.  
//
//  Details of the class are below and in the associated lecture notes.

#include <iostream>
#include <list>
#include <string>
#include <vector>

//  The hash_set is templated over both the type of key and the type
//  of the hash function, a function object.

template < typename KeyType, typename HashFunc >
class hash_set {
private:
  std::vector< std::list<KeyType> > m_table;  //  actual table
  HashFunc m_hash;                            //  hash function
  unsigned int m_size;                        //  number of keys

  typedef typename std::list<KeyType>::iterator hash_list_itr;

public:

  //  Start with the iterator class.  This is defined as a nested
  //  class so that it does not have to be templated separately over
  //  the hash function object type.

  class iterator {
  public:
    friend class hash_set;   // allows access to private variables
  private:
    hash_set* m_hs;          //  pointer to the hash_set object
    int m_index;             //  current index in the hash table
    hash_list_itr m_list_itr;     //  current iterator at the current index

  private:
    //  private constructors for use by the hash_set only
    iterator( hash_set * hs )
      : m_hs(hs), m_index(-1)
    {}

    iterator( hash_set* hs, int index, hash_list_itr loc )
      : m_hs(hs), m_index(index), m_list_itr(loc)
    {}

  public:

    //  Two ordinary constructors
    iterator() : m_hs(0), m_index(-1)  {}

    iterator( iterator const& itr )
      : m_hs(itr.m_hs), m_index(itr.m_index), m_list_itr(itr.m_list_itr)
    {}

    iterator&  operator=( const iterator& old )
    {
      m_hs = old.m_hs;
      m_index = old.m_index; 
      m_list_itr = old.m_list_itr;
      return *this;
    }

    //  The dereference operator need only worry about the current
    //  list iterator, and does not need to check the current index.
    const KeyType& operator*() const
    {
      return *m_list_itr;
    }

    //  The comparison operators must account for the list iterators
    //  being unassigned at the end.

    friend bool operator== ( const iterator& lft, const iterator& rgt )
    { return lft.m_hs == rgt.m_hs && lft.m_index == rgt.m_index && 
	( lft.m_index == -1 || lft.m_list_itr == rgt.m_list_itr); }
  
    friend bool operator!= ( const iterator& lft, const iterator& rgt )
    { return lft.m_hs != rgt.m_hs || lft.m_index != rgt.m_index || 
	( lft.m_index != -1 && lft.m_list_itr != rgt.m_list_itr); }

    //  pre-increment
    iterator& operator++( ) 
    { 
      this->next();
      return *this;
    }

    //  post-increment is indicated by the dummy int argument
    iterator operator++( int ) 
    {
      iterator temp( *this );
      this->next();
      return temp;
    }

    //  pre-decrement
    iterator & operator--( ) 
    { 
      this->prev();
      return *this;
    }

    //  post-decrement is indicated by the dummy int argument
    iterator operator--( int ) 
    {
      iterator temp( *this );
      this->prev();
      return temp;
    }

  private:
    //  Find the next entry in the table
    void next()
    {
      ++ m_list_itr;  // next item in the list

      //  If we are at the end of this list
      if ( m_list_itr == m_hs->m_table[m_index].end() )
	{
	  //  Find the next non-empty list in the table
	  for ( ++m_index; m_index < int(m_hs->m_table.size()) && m_hs->m_table[m_index].empty();
		++m_index )
	    ;

	  //  If one is found, assign the m_list_itr to the start
	  if ( m_index != int(m_hs->m_table.size()) )
	    m_list_itr = m_hs->m_table[m_index].begin();
	  
	  //  Otherwise, we are at the end
	  else
	    m_index = -1;
	}
    }

    //  Find the previous entry in the table
    void prev()
    {
      //  If we aren't at the start of the current list, just
      //  decrement the list iterator
      if ( m_list_itr != m_hs->m_table[m_index].begin() )
	m_list_itr -- ;

      else
	{
	  //  Otherwise, back down the table until the previous non-empty
	  //  list in the table is found
	  for ( --m_index; m_index >= 0 && m_hs->m_table[m_index].empty(); --m_index )
	    ;

	  //  Go to the last entry in the list.
	  m_list_itr = m_hs->m_table[m_index].begin();
	  hash_list_itr p = m_list_itr; ++p;
	  for ( ; p != m_hs->m_table[m_index].end(); ++p, ++m_list_itr )
	    ;
	}
    }
  };

  /////////////////////////////////
  //  On to the actual hash set  //
  /////////////////////////////////

  //  Constructor for the table set just accepts the size of the
  //  table.  The default constructor for the hash function object is
  //  implicitly used.
  hash_set( unsigned int init_size = 10 )
    : m_table(init_size), m_size(0) {}

  
  //  Copy constructor just uses the member function copy
  //  constructors.
  hash_set( const hash_set<KeyType, HashFunc>& old ) 
    : m_table(old.m_table), m_size(old.m_size)
  {}

  ~hash_set()
  {}

  hash_set& operator=( const hash_set<KeyType,HashFunc>& old )
  {
    if ( &old != this )
      *this = old;
  }

  unsigned int size() const { return m_size; }

  //  Insert the key if it is not already there.
  std::pair< iterator, bool > insert( KeyType const& key )
  {
    const float LOAD_FRACTION_FOR_RESIZE = 1.25;

    if ( m_size >= LOAD_FRACTION_FOR_RESIZE * m_table.size() )
      this->resize_table( 2*m_table.size()+1 );

    unsigned int index = m_hash(key) % m_table.size();
    hash_list_itr p = std::find( m_table[index].begin(), 
				 m_table[index].end(), key );
    if ( p != m_table[index].end() )
      { // key is already there
	iterator itr( this, index, p );  // hash_set, index, iterator
	return std::make_pair( itr, false );
      }
    else
      {
	m_table[index].push_front( key );
	iterator itr( this, index, m_table[index].begin() );
	return std::make_pair( itr, true );
      }
  }


  //  Find the key, using hash function computation, indexing and list
  //  find.
  iterator find( const KeyType& key )
  {
    unsigned int hash_value = m_hash(key);
    unsigned int index = hash_value % m_table.size();
    hash_list_itr p = std::find( m_table[index].begin(),
				 m_table[index].end(), key );
    if ( p == m_table[index].end() )
      return this->end();
    else
      return iterator( this, index, p );
  }


  // erase the key 
  int erase( const KeyType& key )
  {
    // Find the key and use the erase iterator function.
    iterator p = find( key );
    if ( p == end() )
      return 0;
    else
      {
	erase(p);
	return 1;
      }

  }

  //  erase at the iterator
  void erase( iterator p )
  {
    m_table[ p.m_index ].erase( p.m_list_itr );
  }

  //  Find the first entry in the table and create an associated
  //  iterator.
  iterator begin()
  {
    //  Implemented in class
    for( int index=0; index<m_table.size(); ++index )
      if ( !m_table[index].empty() )
	return iterator( this, index, m_table[index].begin() );
    return end();
  }
  

  //  Create an end iterator.
  iterator end()
  {
    iterator p( this );
    p.m_index = -1;
    return p;
  }

  
  //  A public print utility.
  void print( std::ostream & ostr )
  {
    for ( unsigned int i=0; i<m_table.size(); ++i )
      {
	ostr << i << ": ";
	for ( hash_list_itr p = m_table[i].begin(); p != m_table[i].end(); ++p )
	  ostr << ' ' << *p;
	ostr << std::endl;
      }
  }

private:
  //  resize the table with the same values but a 
  void resize_table( unsigned int new_size )
  {
    //  Implemented in class
    std::vector< std::list<KeyType> > scratch( m_table);
    m_table.clear();
    m_table.resize(new_size);

    for ( unsigned int i=0; i<scratch.size(); ++i )  // vector
      for ( hash_list_itr p = scratch[i].begin();   // each list
	    p!=scratch[i].end(); ++p )
	{
	  int index = m_hash(*p) % new_size;
	  m_table[index].push_front(*p);
	}
  }
};
	   
#endif

