#include <string>
#include <iostream>

#include <vector>
#include <map>

using namespace std;

#define DEFAULT_BUCKETS 256

namespace HASH_NAMESPACE
{

template <class T>
class hasher
{
public:
	unsigned int GetHash(T & val) {cerr << "No hash function specified for this type" << endl;return 0;}
};

//DJB hash from http://www.partow.net/programming/hashfunctions/
//very simple, excellent performance
//the one at http://burtleburtle.net/bob/c/lookup3.c sounds good, but seems much slower
template <>
class hasher <string>
{
	private:
	
	public:
		unsigned int GetHash(string & str)
		{
			unsigned int hash = 5381;
			unsigned int strlen = str.length();
			for(std::size_t i = 0; i < strlen; i++)
			{
				hash = ((hash << 5) + hash) + str[i];
			}

			return (hash & 0x7FFFFFFF);
		}
};

template <>
class hasher <int>
{
	private:
	
	public:
		unsigned int GetHash(int & key)
		{
			unsigned int retval = *((unsigned int*) (&key));
			return retval;
		}
};

template <>
class hasher <float>
{
	private:
	
	public:
		unsigned int GetHash(float & key)
		{
			unsigned int retval = *((unsigned int *) (&key));
			return retval;
		}
};

template <>
class hasher <double>
{
	private:
	
	public:
		unsigned int GetHash(double & key)
		{
			//unsigned int retval = *((unsigned int *) (&key) + (sizeof(double)/2));
			unsigned int retval = *((unsigned int *) (&key));
			return retval;
		}
};

template <>
class hasher <unsigned int>
{
	private:
	
	public:
		unsigned int GetHash(unsigned int & key)
		{
			return key;
		}
};

}; //namespace HASH_NAMESPACE

template <class KEYCLASS, class DATACLASS>
class bucketed_hashmap
{
private:
	unsigned int nbuckets;
	vector < map <KEYCLASS, DATACLASS> > data;
	void set_buckets(unsigned int num_buckets) {data.resize(num_buckets);nbuckets = num_buckets;}
	HASH_NAMESPACE::hasher <KEYCLASS> hash;
	
	typename vector < map <KEYCLASS, DATACLASS> >::iterator vi;
	typename map <KEYCLASS, DATACLASS>::iterator mi;
	
public:
	bucketed_hashmap() {set_buckets(DEFAULT_BUCKETS);IteratorReset();}
	bucketed_hashmap(unsigned int num_buckets) {set_buckets(num_buckets);}
	
	bool Erase(DATACLASS * todel) //invalidates iterators
	{
		typename vector < map <KEYCLASS, DATACLASS> >::iterator todel_bucket;
		typename map <KEYCLASS, DATACLASS>::iterator todel_iterator;
		bool found = false;
		for (vi = data.begin(); vi != data.end(); vi++)
		{
			for (mi = vi->begin(); mi != vi->end(); mi++)
			{
				if (&(mi->second) == todel)
				{
					todel_iterator = mi;
				}
			}
		}
		
		if (found)
			todel_bucket->erase(mi);
			
		IteratorReset();
		
		return found;
	}
	
	void IteratorReset()
	{
		vi = data.begin();
		while (vi != data.end() && vi->empty()) //move vi to first non-empty bucket
			vi++;
		if (vi != data.end()) //found a non-empty bucket
			mi = vi->begin();
	}
	
	DATACLASS * IteratorGet() //returns NULL when at end
	{
		if (vi == data.end()) //vi is at end of buckets
			return NULL;
		if (mi == vi->end()) //mi is at the end of a bucket (shouldn't get here, mi should never be invalid when vi is valid)
			return NULL;
		return &(mi->second); //mi and vi are valid
	}
	
	bool IteratorIncrement() //returns true if not at end
	{
		if (vi == data.end()) //vi is at the end of buckets
			return false;
		
		if (mi != vi->end()) //mi is valid
		{
			mi++;
			
			if (mi == vi->end()) //we moved to the end of our bucket
			{
				vi++;
				while (vi != data.end() && vi->empty()) //move vi to next non-empty bucket
					vi++;
				if (vi != data.end()) //found a non-empty bucket
				{
					mi = vi->begin();
					return true;
				}
				else
					return false;
			}
			else
				return true;
		}
		else
		{
			//should never happen (mi should never be invalid when vi is valid)
			return false;
		}
	}
	
	void Clear() {data.clear();}
	
	DATACLASS * Get(KEYCLASS & key)
	{
		unsigned int idx = hash.GetHash(key) % nbuckets;
		//typedef map <KEYCLASS, DATACLASS> mymap;
		//mymap::iterator i;// = data[idx].find(key);
		/*if (i == data[idx].end())
			return NULL;
		else
			return i;*/
		
		if (data[idx].find(key) == data[idx].end())
			return NULL;
		else
			return &(((data[idx]).find(key))->second);
	}
	
	void Set(KEYCLASS & key, DATACLASS & ndata)
	{
		unsigned int idx = (hash.GetHash(key)) % nbuckets;
		(data[idx])[key] = ndata;
	}
	
	unsigned int GetNumCollisions()
	{
		unsigned int col = 0;
		unsigned int lim = data.size();
		
		for (unsigned int i = 0; i < lim; i++)
		{
			unsigned int subsize = data[i].size();
			if (subsize > 1)
				col += subsize-1;
		}
		
		return col;
	}
	
	unsigned int GetTotalObjects()
	{
		unsigned int obj = 0;
		unsigned int lim = data.size();
		
		for (unsigned int i = 0; i < lim; i++)
		{
			obj += data[i].size();
		}
		
		return obj;
	}
	
	float GetAvgBucketSize()
	{
		float avg = 0;
		unsigned int lim = data.size();
		
		for (unsigned int i = 0; i < lim; i++)
		{
			avg += data[i].size();
		}
		
		return avg / nbuckets;
	}
	
	void PrintBucketSizes()
	{
		unsigned int lim = data.size();
		
		for (unsigned int i = 0; i < lim; i++)
		{
			cout << i << ": " << data[i].size() << endl;
		}
	}
	
	float GetBucketEvenness()
	{
		return (GetTotalObjects() - (float) GetNumCollisions()) / nbuckets;
	}
	
	float GetEmptyBucketPercent()
	{
		unsigned int lim = data.size();
		unsigned int empty = 0;
		
		for (unsigned int i = 0; i < lim; i++)
		{
			if (data[i].empty())
				empty++;
		}
		
		return (float) empty / (float) nbuckets;
	}
	
	unsigned int GetLongestBucket()
	{
		unsigned int lim = data.size();
		unsigned int longest = 0;
		
		for (unsigned int i = 0; i < lim; i++)
		{
			if (data[i].size() > longest)
				longest = data[i].size();
		}
		
		return longest;
	}
};
