/*
    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_GAMMA_SPARSEGAMMAGAPFILEINDEXDECODER_HPP)
#define LIBMAUS2_GAMMA_SPARSEGAMMAGAPFILEINDEXDECODER_HPP

#include <libmaus2/aio/InputStreamInstance.hpp>
#include <libmaus2/gamma/SparseGammaGapFileIndexDecoderEntry.hpp>
#include <libmaus2/util/GenericIntervalTree.hpp>
#include <libmaus2/util/iterator.hpp>
#include <libmaus2/util/NumberSerialisation.hpp>

namespace libmaus2
{
	namespace gamma
	{
		struct SparseGammaGapFileIndexDecoder;
		std::ostream & operator<<(std::ostream & out, SparseGammaGapFileIndexDecoder const & o);

		struct SparseGammaGapFileIndexDecoder
		{
			typedef SparseGammaGapFileIndexDecoder this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;
			typedef libmaus2::gamma::SparseGammaGapFileIndexDecoderEntry value_type;

			typedef libmaus2::util::ConstIterator<this_type,value_type> const_iterator;

			private:
			libmaus2::aio::InputStreamInstance::unique_ptr_type CIS;
			std::istream & in;
			uint64_t maxkey;
			uint64_t numentries;

			mutable libmaus2::parallel::StdMutex lock;
			mutable std::map<uint64_t,value_type> cache;

			void init()
			{
				in.clear();
				in.seekg(-2*static_cast<int64_t>(sizeof(uint64_t)),std::ios::end);
				maxkey = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				numentries = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
			}

			const_iterator begin() const
			{
				return const_iterator(this,0);
			}

			const_iterator end() const
			{
				return const_iterator(this,numentries);
			}

			public:
			SparseGammaGapFileIndexDecoder(std::istream & rin)
			: in(rin)
			{
				init();
			}

			SparseGammaGapFileIndexDecoder(std::string const & filename)
			: CIS(new libmaus2::aio::InputStreamInstance(filename)), in(*CIS)
			{
				init();
			}

			value_type get(uint64_t const i) const
			{
				if ( i >= numentries )
				{
					libmaus2::exception::LibMausException se;
					se.getStream() << "SparseGammaGapFileIndexDecoder::get() out of range: " << i << " >= " << numentries << std::endl;
					se.finish();
					throw se;
				}

				libmaus2::parallel::StdMutex::scope_lock_type slock(lock);

				// check cache
				if ( cache.find(i) != cache.end() )
					return cache.find(i)->second;

				in.clear();
				in.seekg(-2*sizeof(uint64_t)-2*numentries*sizeof(uint64_t) + i*(2*sizeof(uint64_t)), std::ios::end);
				uint64_t const ikey = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				uint64_t const ibitoff = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				value_type const v(ikey,ibitoff);

				// extend cache
				cache[i] = v;

				return v;
			}

			value_type operator[](uint64_t const i) const
			{
				return get(i);
			}

			bool isEmpty() const
			{
				return numentries == 0;
			}

			uint64_t getMinKey() const
			{
				if ( isEmpty() )
				{
					libmaus2::exception::LibMausException ex;
					ex.getStream() << "SparseGammaGapFileIndexDecoder::getMinKey(): index is empty" << std::endl;
					ex.finish();
					throw ex;
				}

				return get(0).ikey;
			}

			uint64_t getMaxKey() const
			{
				return maxkey;
			}

			uint64_t getBlockIndex(uint64_t const ikey) const
			{
				// std::cerr << "getBlockIndex(" << ikey << ")" << std::endl;

				if ( ! numentries )
					return numentries;
				else if ( ikey < getMinKey() )
					return 0;
				else if ( ikey > getMaxKey() )
					return numentries-1;

				const_iterator ita = std::lower_bound(begin(),end(),value_type(ikey));

				uint64_t const index = ita - begin();

				if ( (ita != end()) && (*ita).ikey == ikey )
				{
					return index;
				}
				else
				{
					assert ( index > 0 );
					return index-1;
				}
			}

			uint64_t size() const
			{
				return numentries;
			}

			friend std::ostream & operator<<(std::ostream & out, SparseGammaGapFileIndexDecoder const & o);
		};

		inline std::ostream & operator<<(std::ostream & out, SparseGammaGapFileIndexDecoder const & O)
		{
			for ( SparseGammaGapFileIndexDecoder::const_iterator ita = O.begin(); ita != O.end(); ++ita )
				out << *ita << std::endl;

			return out;
		}
	}
}
#endif
