/*
    libmaus2
    Copyright (C) 2009-2013 German Tischler
    Copyright (C) 2011-2013 Genome Research Limited

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#if ! defined(LIBMAUS2_UTIL_SIMPLEHASHMAPINSDEL_HPP)
#define LIBMAUS2_UTIL_SIMPLEHASHMAPINSDEL_HPP

#include <libmaus2/util/SimpleHashMapNumberCast.hpp>
#include <libmaus2/util/SimpleHashMapHashCompute.hpp>
#include <libmaus2/util/SimpleHashMapKeyPrint.hpp>
#include <libmaus2/util/SimpleHashMapConstants.hpp>
#include <libmaus2/exception/LibMausException.hpp>
#include <libmaus2/autoarray/AutoArray.hpp>
#include <libmaus2/hashing/hash.hpp>
#include <libmaus2/math/primes16.hpp>
#include <libmaus2/util/NumberSerialisation.hpp>
#include <libmaus2/parallel/SynchronousCounter.hpp>

namespace libmaus2
{
	namespace util
	{
		template<
			typename _key_type, typename _value_type,
			typename _constants_type = SimpleHashMapConstants<_key_type>,
			typename _key_print_type = SimpleHashMapKeyPrint<_key_type>,
			typename _hash_compute_type = SimpleHashMapHashCompute<_key_type>,
			typename _number_cast_type = SimpleHashMapNumberCast<_key_type>
		>
		struct SimpleHashMapInsDel :
			public _constants_type,
			public _key_print_type,
			public _hash_compute_type,
			public _number_cast_type
		{
			typedef _key_type key_type;
			typedef _value_type value_type;

			typedef _constants_type constants_type;
			typedef _key_print_type key_print_type;
			typedef _hash_compute_type hash_compute_type;
			typedef _number_cast_type number_cast_type;

			typedef std::pair<key_type,value_type> pair_type;
			typedef SimpleHashMapInsDel<key_type,value_type,constants_type,key_print_type,hash_compute_type,number_cast_type> this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;

			protected:
			unsigned int slog;
			uint64_t hashsize;
			uint64_t hashmask;
			uint64_t fill;
			uint64_t deleted;

			// hash array
			::libmaus2::autoarray::AutoArray<pair_type> H;
			// shrink array
			::libmaus2::autoarray::AutoArray<pair_type> R;

			inline uint64_t hash(key_type const & v) const
			{
				return hash_compute_type::hash(v) & hashmask;
			}

			inline uint64_t displace(uint64_t const p, key_type const & k) const
			{
				return (p + primes16[number_cast_type::cast(k)&0xFFFFu]) & hashmask;
			}

			void extendInternal()
			{
				if ( hashsize == H.size() )
				{
					uint64_t o = 0;
					for ( uint64_t i = 0; i < hashsize; ++i )
						if ( constants_type::isInUse(H[i].first) )
						{
							R[o++] = H[i];
						}

					slog++;
					hashsize <<= 1;
					hashmask = hashsize-1;
					fill = 0;
					deleted = 0;

					H = ::libmaus2::autoarray::AutoArray<pair_type>(hashsize,false);
					for ( uint64_t i = 0; i < hashsize; ++i )
						H[i].first = constants_type::unused();

					for ( uint64_t i = 0; i < o; ++i )
						insert(R[i].first,R[i].second);

					R = ::libmaus2::autoarray::AutoArray<pair_type>(hashsize,false);
				}
				else
				{
					assert ( hashsize < H.size() );
					assert ( 2*hashsize <= H.size() );

					uint64_t o = 0;
					for ( uint64_t i = 0; i < hashsize; ++i )
					{
						if ( constants_type::isInUse(H[i].first) )
							R[o++] = H[i];
						H[i].first = constants_type::unused();
					}
					for ( uint64_t i = hashsize; i < 2*hashsize; ++i )
						H[i].first = constants_type::unused();

					hashsize <<= 1;
					slog += 1;
					hashmask = hashsize-1;
					fill = 0;
					deleted = 0;

					for ( uint64_t i = 0; i < o; ++i )
						insert(R[i].first,R[i].second);
				}
			}

			void shrink()
			{
				assert ( fill <= hashsize/2 );

				if ( slog )
				{
					uint64_t o = 0;
					for ( uint64_t i = 0; i < hashsize; ++i )
					{
						if ( constants_type::isInUse(H[i].first) )
							R[o++] = H[i];
						H[i].first = constants_type::unused();
					}

					hashsize >>= 1;
					slog -= 1;
					hashmask = hashsize-1;
					fill = 0;
					deleted = 0;

					for ( uint64_t i = 0; i < o; ++i )
						insert(R[i].first,R[i].second);
				}
			}

			public:
			SimpleHashMapInsDel(unsigned int const rslog)
			: slog(rslog), hashsize(1ull << slog), hashmask(hashsize-1), fill(0), deleted(0), H(hashsize,false), R(hashsize,false)
			{
				std::fill(H.begin(),H.end(),pair_type(constants_type::unused(),value_type()));
			}
			virtual ~SimpleHashMapInsDel() {}

			pair_type const * begin() const { return H.begin(); }
			pair_type const * end() const { return begin()+hashsize; }
			pair_type * begin() { return H.begin(); }
			pair_type * end() { return begin()+hashsize; }

			uint64_t getTableSize() const
			{
				return H.size();
			}

			uint64_t size() const
			{
				return fill;
			}

			double loadFactor() const
			{
				return static_cast<double>(fill+deleted) / hashsize;
			}


			value_type const & getValue(uint64_t const i) const
			{
				return H[i].second;
			}

			value_type & getValue(uint64_t const i)
			{
				return H[i].second;
			}

			uint64_t insertExtend(key_type const & v, value_type const & w, double const loadthres)
			{
				if ( loadFactor() >= loadthres || (fill == hashsize) )
					extendInternal();

				return insert(v,w);
			}

			// returns true if value v is contained
			bool contains(key_type const & v) const
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// correct value stored
					if ( H[p].first == v )
					{
						return true;
					}
					// position in use?
					else if ( H[p].first == constants_type::unused() )
					{
						return false;
					}
					else
					{
						p = displace(p,v);
					}
				} while ( p != p0 );

				return false;
			}

			//
			uint64_t getIndex(key_type const & v) const
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// correct value stored
					if ( H[p].first == v )
					{
						return p;
					}
					// position in use?
					else if ( H[p].first == constants_type::unused() )
					{
						// break loop and fall through to exception below
						p = p0;
					}
					else
					{
						p = displace(p,v);
					}
				} while ( p != p0 );

				libmaus2::exception::LibMausException lme;
				lme.getStream() << "SimpleHashMapInsDel::getIndex called for non-existing key ";
				key_print_type::printKey(lme.getStream(),v);
				lme.getStream() << std::endl;
				lme.finish();
				throw lme;
			}

			// returns true if value v is contained
			bool contains(key_type const & v, value_type & r) const
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// correct value stored
					if ( H[p].first == v )
					{
						r = H[p].second;
						return true;
					}
					// position in use?
					else if ( H[p].first == constants_type::unused() )
					{
						return false;
					}
					else
					{
						p = displace(p,v);
					}
				} while ( p != p0 );

				return false;
			}

			// returns true if value v is contained
			// store index of element (if any) in index
			bool containsKey(key_type const & v, uint64_t & index) const
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// correct value stored
					if ( H[p].first == v )
					{
						index = p;
						return true;
					}
					// position in use?
					else if ( H[p].first == constants_type::unused() )
					{
						return false;
					}
					else
					{
						p = displace(p,v);
					}
				} while ( p != p0 );

				return false;
			}

			// get count for value v
			value_type get(key_type const & v) const
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// position in use?
					// correct value stored
					if ( H[p].first == v )
					{
						return H[p].second;
					}
					else if ( H[p].first == constants_type::unused() )
					{
						// trigger exception below
						p = p0;
					}
					else
					{
						p = displace(p,v);
					}
				} while ( p != p0 );

				::libmaus2::exception::LibMausException se;
				se.getStream() << "SimpleHashMapInsDel::get() called for key ";
				key_print_type::printKey(se.getStream(),v);
				se.getStream() << " which is not contained." << std::endl;
				se.finish();
				throw se;
			}

			// get count for value v without checks
			value_type getUnchecked(key_type const & v) const
			{
				uint64_t p = hash(v);

				while ( true )
					// correct value stored
					if ( H[p].first == v )
						return H[p].second;
					else
						p = displace(p,v);
			}

			//
			uint64_t getIndexUnchecked(key_type const & v) const
			{
				uint64_t p = hash(v);

				while ( true )
				{
					if ( H[p].first == v )
					{
						return p;
					}
					else
					{
						p = displace(p,v);
					}
				}
			}

			//
			int64_t getIndexChecked(key_type const & v) const
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// correct value stored
					if ( H[p].first == v )
					{
						return p;
					}
					// position in use?
					else if ( H[p].first == constants_type::unused() )
					{
						// break loop and fall through to exception below
						p = p0;
					}
					else
					{
						p = displace(p,v);
					}
				} while ( p != p0 );

				return -1;
			}

			// insert key value pair
			uint64_t insert(key_type const & v, value_type const & w)
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// position in use?
					if ( constants_type::isInUse(H[p].first) )
					{
						// key already present, replace value
						if ( H[p].first == v )
						{
							H[p].second = w;
							return p;
						}
						// in use but by other value (collision)
						else
						{
							p = displace(p,v);
						}
					}
					// position is not currently in use
					else
					{
						// replacing deleted index
						if ( H[p].first == constants_type::deleted() )
						{
							assert ( deleted );
							deleted--;
						}
						else
						{
							assert ( H[p].first == constants_type::unused() );
						}

						H[p].first = v;
						H[p].second = w;
						fill += 1;
						return p;
					}
				} while ( p != p0 );

				::libmaus2::exception::LibMausException se;
				se.getStream() << "SimpleHashMapInsDel::insert(): unable to insert, table is full." << std::endl;
				se.finish();
				throw se;
			}

			//
			void erase(key_type const & v)
			{
				uint64_t const p0 = hash(v);
				uint64_t p = p0;

				do
				{
					// correct value found
					if ( H[p].first == v )
					{
						H[p].first = constants_type::deleted();
						fill -= 1;
						deleted += 1;

						if ( deleted >= (hashsize/2) && slog )
							shrink();

						return;
					}
					// position in use?
					else if ( H[p].first == constants_type::unused() )
					{
						// break loop and fall through to exception below
						p = p0;
					}
					else
					{
						p = displace(p,v);
					}
				} while ( p != p0 );

				libmaus2::exception::LibMausException lme;
				lme.getStream() << "SimpleHashMapInsDel::erase called for non-existing key ";
				key_print_type::printKey(lme.getStream(),v);
				lme.getStream() << std::endl;
				lme.finish();
				throw lme;
			}

			void eraseIndex(uint64_t const i)
			{
				H[i].first = constants_type::deleted();
				deleted++;
				fill--;

				if ( deleted >= (hashsize/2) && slog )
					shrink();
			}

		};
	}
}
#endif
