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

#include <libmaus2/autoarray/AutoArray.hpp>
#include <libmaus2/bitio/CompactArrayBase.hpp>
#include <libmaus2/util/iterator.hpp>
#include <libmaus2/util/MemoryStatistics.hpp>
#include <libmaus2/math/gcd.hpp>
#include <libmaus2/aio/InputStreamInstance.hpp>
#include <libmaus2/arch/PageSize.hpp>

namespace libmaus2
{
	namespace bitio
	{
		template<bool _synchronous>
		struct CompactArrayTypeBase
		{
		};
		template<>
		struct CompactArrayTypeBase<false>
		{
			typedef uint64_t word_type;
			typedef uint64_t wrapped_word_type;

			static word_type load(wrapped_word_type const & w)
			{
				return w;
			}

			static void store(wrapped_word_type & w, word_type const & v)
			{
				w = v;
			}

			static uint64_t serialiseAutoArray(std::ostream & out, libmaus2::autoarray::AutoArray<wrapped_word_type> const & A)
			{
				return A.serialize(out);
			}

			static ::libmaus2::autoarray::AutoArray<wrapped_word_type> deserializeAutoArray(std::istream & in, uint64_t & t)
			{
				::libmaus2::autoarray::AutoArray<wrapped_word_type> AD;
				t += AD.deserialize(in);
				return AD;
			}
		};
		template<>
		struct CompactArrayTypeBase<true>
		{
			typedef uint64_t word_type;
			typedef std::atomic<uint64_t> wrapped_word_type;

			static word_type load(wrapped_word_type const & w)
			{
				return w.load();
			}

			static void store(wrapped_word_type & w, word_type const & v)
			{
				w.store(v);
			}

			static uint64_t serialiseAutoArray(std::ostream & out, libmaus2::autoarray::AutoArray<wrapped_word_type> const & A)
			{
				uint64_t s = 0;
				s += libmaus2::autoarray::AutoArray<wrapped_word_type>::putHeader(out,A.size());
				for ( uint64_t i = 0; i < A.size(); ++i )
					s += ::libmaus2::serialize::Serialize<word_type>::serialize(out,load(A[i]));
				return s;
			}

			static ::libmaus2::autoarray::AutoArray<wrapped_word_type> deserializeAutoArray(std::istream & in, uint64_t & t)
			{
				uint64_t n;
				t += ::libmaus2::serialize::Serialize<uint64_t>::deserialize(in,&n);
				::libmaus2::autoarray::AutoArray<wrapped_word_type> AD(n);
				word_type v;
				for ( uint64_t i = 0; i < n; ++i )
				{
					t += ::libmaus2::serialize::Serialize<uint64_t>::deserialize(in,&v);
					AD[i].store(v);
				}
				return AD;
			}
		};

		template<bool _synchronous>
		struct CompactArrayTemplate : public CompactArrayBase, public CompactArrayTypeBase<_synchronous>
		{
			static bool const synchronous = _synchronous;

			typedef CompactArrayTemplate<synchronous> this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;

			typedef uint64_t value_type;
			typedef typename CompactArrayTypeBase<_synchronous>::word_type word_type;
			typedef typename CompactArrayTypeBase<_synchronous>::wrapped_word_type wrapped_word_type;

			typedef typename ::libmaus2::util::AssignmentProxy<CompactArrayTemplate<synchronous>,value_type> proxy_type;
			typedef typename ::libmaus2::util::AssignmentProxyIterator<CompactArrayTemplate<synchronous>,value_type> iterator;
			typedef typename ::libmaus2::util::ConstIterator<CompactArrayTemplate<synchronous>,value_type> const_iterator;

			private:
			uint64_t n;
			uint64_t s; // number of words
			::libmaus2::autoarray::AutoArray<wrapped_word_type> AD;

			static uint64_t computeS(uint64_t const n, uint64_t const b)
			{
				return (n*b+(getWordBits()-1))/getWordBits();
			}

			static uint64_t getWordBits()
			{
				return 8 * sizeof(uint64_t);
			}

			static uint64_t deserializeNumber(std::istream & in, uint64_t & t)
			{
				uint64_t n;
				t += ::libmaus2::serialize::Serialize<uint64_t>::deserialize(in, &n);
				return n;
			}

			static ::libmaus2::autoarray::AutoArray<wrapped_word_type> deserializeArray(std::istream & in, uint64_t & t)
			{
				::libmaus2::autoarray::AutoArray<wrapped_word_type> AD(CompactArrayTypeBase<synchronous>::deserializeAutoArray(in,t));
				return AD;
			}

			static ::libmaus2::autoarray::AutoArray<wrapped_word_type> deserializeArray(std::istream & in)
			{
				uint64_t t;
				return deserializeArray(in,t);
			}

			static ::libmaus2::autoarray::AutoArray<wrapped_word_type> allocate(uint64_t const n, uint64_t const b, uint64_t const pad = 0, bool erase = true)
			{
				return ::libmaus2::autoarray::AutoArray<wrapped_word_type>( computeS(n,b) + pad, erase );
			}

			public:
			template<typename iterator>
			CompactArrayTemplate(iterator a, iterator e, uint64_t const rb, uint64_t const pad = 0, bool const erase = true)
			: CompactArrayBase(rb), n(e-a), s ( computeS(n,b) ), AD( allocate(n,b,pad,erase) )
			{
				uint64_t i = 0;
				for ( ; a != e; ++a, ++i )
					set(i,*a);
			}
			CompactArrayTemplate(uint64_t const rn, uint64_t const rb, uint64_t const pad = 0, bool const erase = true)
			: CompactArrayBase(rb), n(rn), s ( computeS(n,b) ), AD( allocate(n,b,pad,erase) )
			{
			}
			CompactArrayTemplate(std::istream & in)
			: CompactArrayBase(deserializeNumber(in)), n(deserializeNumber(in)), s(deserializeNumber(in)),
			  AD(deserializeArray(in))
			{

			}
			CompactArrayTemplate(std::istream & in, uint64_t & t)
			: CompactArrayBase(deserializeNumber(in,t)), n(deserializeNumber(in,t)), s(deserializeNumber(in,t)),
			  AD(deserializeArray(in,t))
			{

			}

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

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

			iterator begin()
			{
				return iterator(this,0);
			}

			iterator end()
			{
				return iterator(this,size());
			}

			wrapped_word_type * getD()
			{
				return AD.begin();
			}
			wrapped_word_type const * getD() const
			{
				return AD.begin();
			}

			uint64_t size() const
			{
				return n;
			}

			uint64_t byteSize() const
			{
				return 3*sizeof(uint64_t) + 1*sizeof(uint64_t *) + AD.byteSize();
			}

			uint64_t minparoffset() const
			{
				return ( b + getWordBits() ) / b;
			}

			void erase()
			{
				for ( uint64_t i = 0; i < s; ++i )
					CompactArrayTypeBase<_synchronous>::store(AD[i],0);
			}

			uint64_t serialize(std::ostream & out) const
			{
				uint64_t t = 0;
				t += ::libmaus2::serialize::Serialize<uint64_t>::serialize(out, CompactArrayBase::b);
				t += ::libmaus2::serialize::Serialize<uint64_t>::serialize(out, n);
				t += ::libmaus2::serialize::Serialize<uint64_t>::serialize(out, s);
				t += CompactArrayTypeBase<synchronous>::serialiseAutoArray(out,AD);
				return t;
			}

			static uint64_t deserializeNumber(std::istream & in)
			{
				uint64_t t;
				return deserializeNumber(in,t);
			}

			static unique_ptr_type load(std::string const & fn)
			{
				libmaus2::aio::InputStreamInstance ISI(fn);
				unique_ptr_type ptr(new this_type(ISI));
				return ptr;
			}

			unique_ptr_type clone() const
			{
				unique_ptr_type O( new CompactArrayTemplate<synchronous>(n,b) );
				for ( uint64_t i = 0; i < n; ++i )
					O->set( i, this->get(i) );
				return O;
			}

			uint64_t get(uint64_t const i) const
			{
				return getBits(i*b);
			}

			void set(uint64_t const i, uint64_t const v)
			{
				putBits(i*b, v);
			}

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

			proxy_type operator[](uint64_t const i)
			{
				return proxy_type(this,i);
			}

			// get bits from an array
			uint64_t getBits(uint64_t const offset) const
			{
				uint64_t const byteSkip = (offset >> bshf);
				unsigned int const bitSkip = (offset & bmsk);
				unsigned int const restBits = bcnt-bitSkip;

				wrapped_word_type const * DD = getD() + byteSkip;
				uint64_t v = CompactArrayTypeBase<_synchronous>::load(*DD);

				// skip bits by masking them
				v &= getFirstMask[bitSkip];

				if ( b <= restBits )
				{
					return v >> (restBits - b);
				}
				else
				{
					unsigned int const numbits = b - restBits;

					v = (v<<numbits) | (( CompactArrayTypeBase<_synchronous>::load(*(++DD)) ) >> (bcnt-numbits));

					return v;
				}
			}

			void putBits(uint64_t const offset, uint64_t v)
			{
				#if 0
				if ( ! (( v & vmask ) == v) )
				{
					::libmaus2::exception::LibMausException se;
					se.getStream() << "CompactArray::putBits(): value " << v << " out of range for bit width " << b << std::endl;
					se.finish();
					throw se;
				}
				#else
				assert ( ( v & vmask ) == v );
				#endif

				wrapped_word_type * DD = getD() + (offset >> bshf);
				unsigned int const bitSkip = (offset & bmsk);
				unsigned int const bitsinfirstword = bitsInFirstWord[bitSkip];

				if ( synchronous )
				{
					(*DD) &= firstKeepMask[bitSkip];
					(*DD) |= (v >> (b - bitsinfirstword)) << firstShift[bitSkip];
				}
				else
				{
					uint64_t t = *DD;
					t &= firstKeepMask[bitSkip];
					t |= (v >> (b - bitsinfirstword)) << firstShift[bitSkip];
					*DD = t;
				}

				if ( b - bitsinfirstword )
				{
					v &= firstValueKeepMask[bitSkip];
					DD++;

					if ( synchronous )
					{
						*DD &= lastMask[bitSkip];
						*DD |= (v << lastShift[bitSkip]);
					}
					else
					{
						uint64_t t = *DD;
						t &= lastMask[bitSkip];
						t |= (v << lastShift[bitSkip]);
						*DD = t;
					}
				}
			}
		};

		typedef CompactArrayTemplate<false> CompactArray;
		typedef CompactArrayTemplate<true> SynchronousCompactArray;
	}
}
#endif
