/*
    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(SIMPLEBLOOMFILTER_HPP)
#define SIMPLEBLOOMFILTER_HPP

#include <libmaus2/hashing/hash.hpp>
#include <libmaus2/bitio/BitVector.hpp>
#include <libmaus2/math/ilog.hpp>
#include <cmath>

namespace libmaus2
{
	namespace util
	{
		template<uint64_t _lockblocksize>
		struct SimpleBloomFilterLockBase
		{
			static uint64_t const lockblocksize = _lockblocksize;

			uint64_t const vectorsize;
			::libmaus2::autoarray::AutoArray< std::atomic<uint64_t> > lockvector;

			SimpleBloomFilterLockBase(uint64_t const rvectorsize)
			: vectorsize(rvectorsize), lockvector((vectorsize+lockblocksize-1)/lockblocksize,false)
			{
				for ( uint64_t i = 0; i < lockvector.size(); ++i )
					lockvector[i].store(0);
			}

			void lockBlock(uint64_t const i)
			{
				uint64_t const wordoff = i >> 6;
				uint64_t const bitoff = i - (wordoff<<6);
				uint64_t const mask = (1ull << bitoff);
				std::atomic<uint64_t> & word = lockvector[wordoff];

				bool locked = false;
				while ( !locked )
				{
					uint64_t v = word.load();

					if ( ! (v&mask) )
						locked = word.compare_exchange_strong(v,v|mask);
				}
			}

			void unlockBlock(uint64_t const i)
			{
				uint64_t const wordoff = i >> 6;
				uint64_t const bitoff = i - (wordoff<<6);
				uint64_t const mask = (1ull << bitoff);
				uint64_t const invmask = ~mask;
				std::atomic<uint64_t> & word = lockvector[wordoff];
				word &= invmask;
			}

			void lockForIndex(uint64_t const i)
			{
				lockBlock(i/lockblocksize);
			}
			void unlockForIndex(uint64_t const i)
			{
				unlockBlock(i/lockblocksize);
			}
		};

		struct SimpleBloomFilter
		{
			typedef SimpleBloomFilter this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;

			static uint64_t const baseseed = 0x9e3779b97f4a7c13ULL;
			unsigned int const numvectors;
			unsigned int const vectorsizelog;
			uint64_t const vectorsize;
			uint64_t const hashmask;

			::libmaus2::autoarray::AutoArray< ::libmaus2::bitio::SynchronousBitVector::unique_ptr_type > vectors;

			SimpleBloomFilterLockBase<64> lockbase;

			SimpleBloomFilter(unsigned int const rnumvectors, unsigned int const rvectorsizelog)
			: numvectors(rnumvectors), vectorsizelog(rvectorsizelog), vectorsize(1ull << vectorsizelog), hashmask(vectorsize-1ull), vectors(numvectors),
			  lockbase(vectorsize)
			{
				for ( unsigned int i = 0; i < numvectors; ++i )
				{
					::libmaus2::bitio::SynchronousBitVector::unique_ptr_type tvectorsi(new ::libmaus2::bitio::SynchronousBitVector(vectorsize));
					vectors[i] = std::move(tvectorsi);
				}
			}


			uint64_t hash(uint64_t const v, unsigned int const i) const
			{
				return libmaus2::hashing::EvaHash::hash642(&v,1,baseseed+i) & hashmask;
			}

			bool contains(uint64_t const v) const
			{
				bool present = true;

				for ( unsigned int i = 0; i < numvectors; ++i )
					present = present && vectors[i]->get(hash(v,i));

				return present;
			}

			bool containsNot(uint64_t const v) const
			{
				bool containsNot = true;

				for ( unsigned int i = 0; i < numvectors; ++i )
					containsNot = containsNot && (!(vectors[i]->get(hash(v,i))));

				return containsNot;
			}

			bool insert(uint64_t const v)
			{
				bool present = true;

				for ( unsigned int i = 0; i < numvectors; ++i )
				{
					uint64_t const h = hash(v,i);
					present = present && vectors[i]->get(h);
					vectors[i]->set(h,1);
				}

				return present;
			}

			/*
			 * insert value v using synchronous bit set operations
			 * and return true if all the respective bits where set previously
			 * WARNING:
			 * this function DOES NOT GUARANTEE correct concurrent operations
			 * in the sense that all but one concurrently running inserts
			 * for the same value v will return true. If you need this guarantee,
			 * you need to use insertSyncPrecise (which is about an order of
			 * magnitude slower than insertSync)
			 */
			bool insertSync(uint64_t const v)
			{
				bool present = true;

				for ( unsigned int i = 0; i < numvectors; ++i )
					present = present && vectors[i]->set(hash(v,i));

				return present;
			}

			bool insertSyncPrecise(uint64_t const v)
			{
				uint64_t const h0 = hash(v,0);

				lockbase.lockForIndex(h0);

				bool present = true;

				present = present && vectors[0]->set(h0);
				for ( unsigned int i = 1; i < numvectors; ++i )
					present = present && vectors[i]->set(hash(v,i));

				lockbase.unlockForIndex(h0);

				return present;
			}

			static unsigned int optimalK(uint64_t const m, uint64_t const n)
			{
				double const dm = m;
				double const dn = n;
				double const l2 = ::std::log(2.0);
				double const optk = (dm*l2)/dn;
				unsigned int const roundk = ::std::floor(optk+0.5);
				return std::max(roundk,1u);
			}
			static uint64_t optimalM(uint64_t const n, double const p)
			{
				double const l2 = ::std::log(2.0);
				double const optm = -(static_cast<double>(n) * ::std::log(p)) / (l2*l2);
				uint64_t const roundm = std::floor(optm+0.5);
				uint64_t const nexttwopow = ::libmaus2::math::nextTwoPow(roundm);
				return std::max(nexttwopow,static_cast<uint64_t>(1u));
			}

			static unique_ptr_type construct(uint64_t const n, double const p)
			{
				uint64_t const optm = optimalM(n,p);
				unsigned int const optk = optimalK(optm,n);
				unsigned int const logoptm = ::libmaus2::math::ilog(optm);
				// std::cerr << "optm=" << optm << " logoptm=" << logoptm << " optk=" << optk << std::endl;
				unique_ptr_type ptr(new this_type(optk,logoptm));
				return ptr;
			}
		};
	}
}
#endif
