/*
    libmaus2
    Copyright (C) 2009-2020 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_AUTOARRAY_AUTOARRAY_HPP)
#define LIBMAUS2_AUTOARRAY_AUTOARRAY_HPP

#include <libmaus2/autoarray/AutoArrayAllocType.hpp>

#include <libmaus2/arch/CacheLineSize.hpp>
#include <libmaus2/arch/PageSize.hpp>
#include <libmaus2/arch/I386Features.hpp>

#include <libmaus2/demangle/Demangle.hpp>

#include <libmaus2/exception/LibMausException.hpp>

#include <libmaus2/LibMausWindows.hpp>

#include <algorithm>
#include <atomic>
#include <cassert>
#include <cerrno>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <sstream>

#if defined(__INTEL_COMPILER)
#include <aligned_new>
#endif

#include <libmaus2/serialize/Serialize.hpp>
#include <libmaus2/util/MemoryStatistics.hpp>

// #define AUTOARRAY_TRACE 7
#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
#include <execinfo.h>
#endif

#include <libmaus2/LibMausConfig.hpp>
#if defined(LIBMAUS2_HAVE_UNISTD_H)
#include <unistd.h>
#endif

#if defined(__APPLE__)
#include <sys/sysctl.h>
#endif

// #define AUTOARRAY_DEBUG

#if defined(AUTOARRAY_DEBUG)
#include <iostream>
#endif

namespace libmaus2
{
	namespace autoarray
	{
		extern std::atomic<uint64_t> AutoArray_memusage;
		extern std::atomic<uint64_t> AutoArray_peakmemusage;
		extern std::atomic<uint64_t> AutoArray_maxmem;

		template<typename N, alloc_type atype = alloc_type_cxx>
		struct AutoArrayBase
		{
			typedef N value_type;

			/**
			 * return name of value type
			 * @return name of value type
			 **/
			static std::string getValueTypeName()
			{
				return ::libmaus2::demangle::Demangle::demangle<value_type>();
			}

			/**
			 * return name of allocation type
			 * @return name of allocation type
			 **/
			static std::string getAllocTypeName()
			{
				switch ( atype )
				{
					case alloc_type_cxx:
						return "alloc_type_cxx";
					case alloc_type_c:
						return "alloc_type_c";
					case alloc_type_memalign_cacheline:
						return "alloc_type_memalign_cacheline";
					case alloc_type_memalign_pagesize:
						return "alloc_type_memalign_pagesize";
					default:
						return "alloc_type_unknown";
				}
			}
			/**
			 * return full type name of array class
			 * @return full type name of array class
			 **/
			static std::string getTypeName()
			{
				return std::string("AutoArray<") + getValueTypeName() + "," + getAllocTypeName() + ">";
			}
		};


		#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
		template<unsigned int n>
		struct AutoArrayBackTrace
		{
			std::atomic<void *> P[n];
			unsigned int tracelength;
			std::atomic<uint64_t> alloccnt;
			std::atomic<uint64_t> freecnt;
			std::atomic<uint64_t> allocbytes;
			std::atomic<uint64_t> freebytes;
			std::string type;

			AutoArrayBackTrace() : tracelength(0), alloccnt(0), freecnt(0), allocbytes(0), freebytes(0)
			{
				for ( unsigned int i = 0; i < n; ++i )
					P[i].store(nullptr);
			}

			bool operator<(AutoArrayBackTrace<n> const & O) const
			{
				return allocbytes-freebytes < O.allocbytes-O.freebytes;
			}
		};
		#endif

		/**
		 * class for storing AutoArray memory snapshot
		 **/
		struct AutoArrayMemUsage
		{
			//! current AutoArray memory usage
			uint64_t memusage;
			//! observed peak of AutoArray memory usage
			uint64_t peakmemusage;
			//! maximum amount of memory that can be allocated through AutoArray objects
			uint64_t maxmem;

			/**
			 * constructor copying current values of AutoArray memory usage
			 **/
			AutoArrayMemUsage();

			/**
			 * copy constructor
			 * @param o object copied
			 **/
			AutoArrayMemUsage(AutoArrayMemUsage const & o);

			/**
			 * assignment operator
			 * @param o object copied
			 * @return *this
			 **/
			AutoArrayMemUsage & operator=(AutoArrayMemUsage const & o);

			/**
			 * return true iff *this == o
			 * @param o object to be compared
			 * @return true iff *this == o
			 **/
			bool operator==(AutoArrayMemUsage const & o) const;

			/**
			 * return true iff *this is different from o
			 * @param o object to be compared
			 * @return true iff *this != o
			 **/
			bool operator!=(AutoArrayMemUsage const & o) const;
		};

		/**
		 * class for erasing an array of type N
		 **/
		template<typename N>
		struct ArrayErase
		{
			/**
			 * erase an array of n elements of type N
			 * @param array memory to be erased
			 * @param n number of elements in array
			 **/
			static void erase(N * array, uint64_t const n);
		};

		/**
		 * class for erasing an array of std::unique_ptr objects
		 **/
		template<typename N>
		struct ArrayErase< std::unique_ptr<N> >
		{
			/**
			 * erase an array of n elements of type std::unique_ptr<N>
			 * @param array to be erased
			 * @param n number of elements in array
			 **/
			static void erase(std::unique_ptr<N> * array, uint64_t const n);
		};

		/**
		 * class for erasing an array of std::atomic objects
		 **/
		template<typename N>
		struct ArrayErase< std::atomic<N> >
		{
			/**
			 * erase an array of n elements of type std::atomic<N>
			 * @param array to be erased
			 * @param n number of elements in array
			 **/
			static void erase(std::atomic<N> * array, uint64_t const n);
		};

		/**
		 * class for erasing an array of std::atomic objects
		 **/
		template<typename N, typename M>
		struct ArrayErase< std::pair< std::atomic<N>, std::atomic<M> > >
		{
			/**
			 * erase an array of n elements of type std::atomic<N>
			 * @param array to be erased
			 * @param n number of elements in array
			 **/
			static void erase(std::pair< std::atomic<N>, std::atomic<M> > * array, uint64_t const n);
		};

		template<typename N, alloc_type atype>
		struct AutoArrayAllocate
		{
			static N * allocate(uint64_t const n);
		};

		template<typename N>
		struct AutoArrayAllocate<N,alloc_type_cxx>
		{
			static N * allocate(uint64_t const n)
			{
				try
				{
					N * p = new N[n];

					return p;
				}
				catch(std::bad_alloc const & ex)
				{
					::libmaus2::exception::LibMausException se;
					se.getStream()
						<< AutoArrayBase<N,alloc_type_cxx>::getTypeName()
						<< " failed to allocate " << n << " elements ("
						<< n*sizeof(N) << " bytes)" << "\n"
						<< "current total allocation " << AutoArray_memusage
						<< std::endl;
						;
					se.finish();
					throw se;
				}
			}

			static void deallocate(N * array)
			{
				delete [] array;
			}
		};

		template<typename N>
		struct AutoArrayAllocate<N,alloc_type_c>
		{
			static N * allocate(uint64_t const n)
			{
				try
				{
					N * p = (N*)malloc(n*sizeof(N));

					if ( ! p )
						throw std::bad_alloc();

					return p;
				}
				catch(std::bad_alloc const & ex)
				{
					::libmaus2::exception::LibMausException se;
					se.getStream()
						<< AutoArrayBase<N,alloc_type_c>::getTypeName()
						<< " failed to allocate " << n << " elements ("
						<< n*sizeof(N) << " bytes)" << "\n"
						<< "current total allocation " << AutoArray_memusage
						<< std::endl;
						;
					se.finish();
					throw se;
				}
			}

			static void deallocate(N * array)
			{
				::free(array);
			}
		};

		template<typename N>
		struct AutoArrayAllocate<N,alloc_type_memalign_cacheline>
		{
			static N * allocate(uint64_t const n)
			{
				try
				{
					std::size_t const cachelinesize = libmaus2::arch::CacheLineSize::getCacheLineSize();
					return new(std::align_val_t{cachelinesize}) N[n];

				}
				catch(std::bad_alloc const & ex)
				{
					::libmaus2::exception::LibMausException se;
					se.getStream()
						<< AutoArrayBase<N,alloc_type_memalign_cacheline>::getTypeName()
						<< " failed to allocate " << n << " elements ("
						<< n*sizeof(N) << " bytes)" << "\n"
						<< "current total allocation " << AutoArray_memusage
						<< std::endl;
						;
					se.finish();
					throw se;
				}
			}

			static void deallocate(N * array)
			{
				::std::size_t const cachelinesize = libmaus2::arch::CacheLineSize::getCacheLineSize();
				::operator delete[](array,std::align_val_t{cachelinesize});
			}
		};

		template<typename N>
		struct AutoArrayAllocate<N,alloc_type_memalign_pagesize>
		{
			static N * allocate(uint64_t const n)
			{
				try
				{
					std::size_t const pagesize = libmaus2::arch::PageSize::getPageSize();
					return new(std::align_val_t{pagesize}) N[n];
				}
				catch(std::bad_alloc const & ex)
				{
					::libmaus2::exception::LibMausException se;
					se.getStream()
						<< AutoArrayBase<N,alloc_type_memalign_pagesize>::getTypeName()
						<< " failed to allocate " << n << " elements ("
						<< n*sizeof(N) << " bytes)" << "\n"
						<< "current total allocation " << AutoArray_memusage
						<< std::endl;
						;
					se.finish();
					throw se;
				}
			}

			static void deallocate(N * array)
			{
				std::size_t const pagesize = libmaus2::arch::PageSize::getPageSize();
				::operator delete[](array,std::align_val_t{pagesize});
			}
		};

		#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
		extern std::vector< AutoArrayBackTrace<LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE> > tracevector;
		typedef std::mutex tracelocktype;
		typedef std::lock_guard<tracelocktype> tracescopelocktype;
		extern tracelocktype backtracelock;
		extern tracelocktype tracelock;

		extern void autoArrayPrintTraces(std::ostream & out);
		#endif

		template<typename N, alloc_type atype = alloc_type_cxx>
		struct AutoArrayReallocate
		{
			static N * reallocate(N * p, uint64_t const rn, uint64_t const n)
			{
				N * np = AutoArrayAllocate<N,atype>::allocate(n);
				uint64_t const tocopy = std::min(rn,n);
				std::copy(p,p+tocopy,np);
				AutoArrayAllocate<N,atype>::deallocate(p);
				return np;
			}
		};

		template<typename N>
		struct AutoArrayReallocate<N,alloc_type_c>
		{
			static N * reallocate(N * p, uint64_t const /* rn */, uint64_t const n)
			{
				N * np = reinterpret_cast<N *>(realloc(p,n*sizeof(N)));
				if ( ! np )
					throw std::bad_alloc();
				return np;
			}
		};

		#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
		template<typename _N, alloc_type atype = alloc_type_cxx>
		struct AutoArrayTraceBase
		{
			typedef _N N;
			typedef AutoArrayTraceBase<N,atype> this_type;

			void * trace[LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE];
			unsigned int tracelength;

			AutoArrayTraceBase()
			: tracelength(0)
			{
				std::fill(&trace[0],&trace[LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE],nullptr);
			}

			this_type & operator=(this_type const & O)
			{
				std::copy(
					&(O.trace[0]),
					&(O.trace[LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE]),
					&trace[0]
				);
				tracelength = O.tracelength;

				return *this;
			}

			void fillTrace()
			{
				tracescopelocktype slock(backtracelock);
				void * ltrace[LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE+2];

				tracelength = backtrace(&ltrace[0],LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE+2);

				if ( tracelength < 2 )
				{
					tracelength = 0;
					for ( unsigned int i = tracelength; i < LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE; ++i )
						trace[i] = nullptr;
				}
				else
				{
					for ( unsigned int i = 2; i < LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE+2; ++i )
						trace[i-2] = ltrace[i];
					tracelength -= 2;

					for ( unsigned int i = tracelength; i < LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE; ++i )
						trace[i] = nullptr;
				}
			}

			uint64_t findTrace()
			{
				uint64_t i = 0;

				for ( ; i < tracevector.size() ; ++i )
				{
					bool eq = true;
					AutoArrayBackTrace<LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE> & A = tracevector[i];

					for ( unsigned int j = 0; j < LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE; ++j )
						if ( A.P[j] != trace[j] )
						{
							eq = false;
							break;
						}

					if ( eq )
						break;
				}

				return i;
			}

			void traceIn(size_t const bytes)
			{
				fillTrace();

				tracescopelocktype slock(tracelock);
				uint64_t const i = findTrace();

				if ( i == tracevector.size() )
				{
					AutoArrayBackTrace<LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE> A;

					for ( unsigned int j = 0; j < LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE; ++j )
						A.P[j] = trace[j];
					A.tracelength = tracelength;

					if ( bytes )
						A.alloccnt = 1;
					else
						A.alloccnt = 0;

					A.allocbytes = bytes;
					A.type = AutoArrayBase<N,atype>::getValueTypeName();

					tracevector.push_back(A);
				}
				else
				{
					AutoArrayBackTrace<LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE> & A = tracevector[i];

					if ( bytes )
						A.alloccnt += 1;
					A.allocbytes += bytes;
				}
			}

			void traceOut(size_t const bytes)
			{
				tracescopelocktype slock(tracelock);
				uint64_t const i = findTrace();

				assert ( i < tracevector.size() );

				AutoArrayBackTrace<LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE> & A = tracevector[i];

				if ( bytes )
					A.freecnt += 1;

				A.freebytes += bytes;
			}
		};
		#endif

		/**
		 * array with automatic deallocation
		 */
		template<typename N, alloc_type atype = alloc_type_cxx, typename erase_type = ArrayErase<N> >
		struct AutoArray : public AutoArrayBase<N,atype>
			#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
			, public AutoArrayTraceBase<N,atype>
			#endif
		{
			public:
			//! element type
			typedef N value_type;
			//! symbol type equals element_type
			typedef value_type symbol_type;
			//! this type
			typedef AutoArray<value_type,atype,erase_type> this_type;
			//! unique pointer object for this type
			typedef std::unique_ptr<this_type> unique_ptr_type;
			//! shared pointer object for this type
			typedef std::shared_ptr<this_type> shared_ptr_type;

			//! iterator: pointer to N
			typedef N * iterator;
			//! const_iterator: constant pointer to N
			typedef N const * const_iterator;

			private:
			//! pointer to wrapped memory
			mutable N * array;
			//! number of elements in this array
			mutable uint64_t n;

			/**
			 * increase total AutoArray allocation counter by n elements of type N
			 * @param n number of elements allocated
			 **/
			void increaseTotalAllocation(uint64_t n)
			{
				#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
				AutoArrayTraceBase<N,atype>::traceIn(n * sizeof(N));
				#endif

				uint64_t const newmemusage = static_cast<uint64_t>(AutoArray_memusage += n*sizeof(N));

				if ( newmemusage > AutoArray_maxmem )
				{
					AutoArray_memusage -= n*sizeof(N);

					::libmaus2::exception::LibMausException se;
					se.getStream() << "libmaus2::autoarray::AutoArray<" << AutoArrayBase<value_type,atype>::getValueTypeName() << ">::increaseTotalAllocation: bad allocation: AutoArray mem limit of " << AutoArray_maxmem << " bytes exceeded by new allocation of " << n*sizeof(N) << " bytes." << std::endl;

					#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
					libmaus2::autoarray::autoArrayPrintTraces(se.getStream());
					#endif

					se.finish();
					throw se;
				}

				uint64_t setpeak = newmemusage;
				uint64_t prevpeak;

				while ( (prevpeak = AutoArray_peakmemusage.exchange(setpeak)) > setpeak )
					setpeak = prevpeak;
			}
			/**
			 * decrease total AutoArray allocation counter by n elements of type N
			 * @param n number of elements deallocated
			 **/
			void decreaseTotalAllocation(uint64_t n)
			{
				AutoArray_memusage -= n*sizeof(N);

				#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
				AutoArrayTraceBase<N,atype>::traceOut(n * sizeof(N));
				#endif
			}

			/**
			 * allocate an array of size n
			 * @param n size of array
			 **/
			void allocateArray(uint64_t const n)
			{
				array = AutoArrayAllocate<value_type,atype>::allocate(n);
			}

			public:
			/**
			 * return pointer to start of array
			 * @return pointer to start of array
			 **/
			N * begin()
			{
				return get();
			}
			/**
			 * return pointer to first element behind array
			 * @return pointer to first element behind array
			 **/
			N * end()
			{
				return get()+size();
			}
			/**
			 * return const pointer to start of array
			 * @return const pointer to start of array
			 **/
			N const * begin() const
			{
				return get();
			}
			/**
			 * return const pointer to first element behind array
			 * @return const pointer to first element behind array
			 **/
			N const * end() const
			{
				return get()+size();
			}
			/**
			 * return const pointer to start of array
			 * @return const pointer to start of array
			 **/
			N const * cbegin() const
			{
				return get();
			}
			/**
			 * return const pointer to first element behind array
			 * @return const pointer to first element behind array
			 **/
			N const * cend() const
			{
				return get()+size();
			}

			/**
			 * write AutoArray header
			 *
			 * @param out output stream
			 * @param n number of elements
			 * @return number of bytes written
			 **/
			static uint64_t putHeader(std::ostream & out, uint64_t const n)
			{
				return ::libmaus2::serialize::Serialize<uint64_t>::serializeChecked(out,n);
			}

			/**
			 * serialise object to an output stream
			 * @param out output stream
			 * @param writeHeader if true an AutoArray header is written (necessary for deserialisation)
			 * @return number of bytes written to out
			 **/
			uint64_t serialize(std::ostream & out, bool writeHeader=true) const
			{
				uint64_t s = 0;
				if ( writeHeader )
					s += putHeader(out,n);
				s += ::libmaus2::serialize::Serialize<N>::serializeArrayChecked(out,array,n);
				return s;
			}

			/**
			 * @return size of serialised object
			 **/
			uint64_t serialisedSize(bool const writeHeader = true) const
			{
				uint64_t s = 0;
				if ( writeHeader )
					s += sizeof(uint64_t);
				s += n * sizeof(N);
				return s;
			}

			/**
			 * resize array to rn elements. This calls realloc for c alloced arrays and uses a copy of the array otherwise
			 *
			 * @param rn new size of array
			 **/
			void resize(uint64_t const rn)
			{
				array = AutoArrayReallocate<N,atype>::reallocate(array,n,rn);
				decreaseTotalAllocation(n);
				n = rn;
				increaseTotalAllocation(n);
			}

			/**
			 * swap two objects
			 **/
			void swap(AutoArray<N,atype> & o)
			{
				std::swap(array,o.array);
				std::swap(n,o.n);
			}

			public:
			N * take()
			{
				decreaseTotalAllocation(n);
				N * p = array;
				array = 0;
				n = 0;
				return p;
			}

			/**
			 * release memory (set size of array to zero)
			 **/
			void release()
			{
				decreaseTotalAllocation(n);

				AutoArrayAllocate<value_type,atype>::deallocate(array);

				array = 0;
				n = 0;
			}

			/**
			 * deserialise array from stream in. Previous content of array is discarded by calling the release method
			 * @param in input stream
			 * @return number of bytes read from in
			 **/
			uint64_t deserialize(std::istream & in)
			{
				release();
				uint64_t s = 0;
				s += ::libmaus2::serialize::Serialize<uint64_t>::deserializeChecked(in,&n);
				increaseTotalAllocation(n);
				allocateArray(n);
				s += ::libmaus2::serialize::Serialize<N>::deserializeArrayChecked(in,array,n);
				return s;
			}

			/**
			 * ignore an AutoArray<N> object on stream in (read over it and discard the data). Note that this reads the data from the stream,
			 * it does not skip over the data by calling any seek type function (see skipArray for this).
			 * @param in input stream
			 * @return number of bytes ignored
			 **/
			static uint64_t ignore(std::istream & in)
			{
				uint64_t s = 0;
				uint64_t n;
				s += ::libmaus2::serialize::Serialize<uint64_t>::deserializeChecked(in,&n);
				s += ::libmaus2::serialize::Serialize<N>::ignoreArray(in,n);
				return s;
			}
			/**
			 * skip over an AutoArray<N> object in stream in
			 * @param in input stream
			 * @return size of serialised AutoArray object in bytes
			 **/
			static uint64_t skip(std::istream & in)
			{
				uint64_t s = 0;
				uint64_t n;
				s += ::libmaus2::serialize::Serialize<uint64_t>::deserializeChecked(in,&n);
				s += ::libmaus2::serialize::Serialize<N>::skipArray(in,n);
				return s;
			}

			/**
			 * @param n number of elements
			 * @return return estimate for size of an AutoArray<N> object of size n in bytes
			 **/
			static uint64_t byteSize(uint64_t const n)
			{
				return sizeof(N*) + sizeof(uint64_t) + n*sizeof(N);
			}


			public:
			/**
			 * @return a clone of this array
			 **/
			AutoArray<N,atype> clone() const
			{
				AutoArray<N,atype> O(n,false);
				for ( uint64_t i = 0; i < n; ++i )
					O[i] = array[i];
				return O;
			}

			/**
			 * @return estimated space in bytes
			 **/
			uint64_t byteSize() const
			{
				return  sizeof(uint64_t) + sizeof(N *) + n * sizeof(N);
			}

			/**
			 * @return number of elements in array
			 **/
			uint64_t size() const
			{
				return n;
			}

			// #define AUTOARRAY_DEBUG

			/**
			 * constructor for empty array
			 **/
			AutoArray() : array(0), n(0)
			{
				increaseTotalAllocation(0);

				#if defined(AUTOARRAY_DEBUG)
				std::cerr << getTypeName() << "(), " << this << std::endl;
				#endif
			}
			/**
			 * copy constructor. retrieves array from o and invalidates o.
			 * @param o
			 **/
			AutoArray(AutoArray<N,atype> const & o) : array(0), n(0)
			{
				#if defined(AUTOARRAY_DEBUG)
				std::cerr << getTypeName() << "(AutoArray &), " << this << std::endl;
				#endif

				array = o.array;
				n = o.n;
				o.array = 0;
				o.n = 0;

				#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
				AutoArrayTraceBase<N,atype>::operator=(o);
				#endif
			}

			/**
			 * copy constructor
			 **/
			AutoArray(uint64_t rn, N const * D) : array(0), n(rn)
			{
				#if defined(AUTOARRAY_DEBUG)
				std::cerr << getTypeName() << "(uint64_t, value_type const *), " << this << std::endl;
				#endif

				increaseTotalAllocation(n);
				allocateArray(n);

				for ( uint64_t i = 0; i < n; ++i )
					array[i] = D[i];

			}

			/**
			 * allocates an array with rn elements
			 * @param rn number of elements
			 * @param erase if true, elements will be assigned default value of type (i.e. 0 for numbers)
			 **/
			AutoArray(uint64_t rn, bool erase = true) : array(0), n(rn)
			{
				#if defined(AUTOARRAY_DEBUG)
				std::cerr << getTypeName() << "(uint64_t, bool), " << this << std::endl;
				#endif

				increaseTotalAllocation(n);
				allocateArray(n);

				if ( erase )
					erase_type::erase(array,n);
					#if 0
					for ( uint64_t i = 0; i < n; ++i )
						array[i] = N();
					#endif

			}

			/**
			 * read number from stream in and add size of number (sizeof(uint64_t)=8) to s
			 * @param in input stream
			 * @param s deserialisation byte number accu
			 * @return deserialised number
			 **/
			static uint64_t readNumber(std::istream & in, uint64_t & s)
			{
				uint64_t n = 0;
				s += ::libmaus2::serialize::Serialize<uint64_t>::deserializeChecked(in,&n);
				return n;
			}
			/**
			 * read number from stream in
			 * @param in input stream
			 * @return deserialised number
			 **/
			static uint64_t readNumber(std::istream & in)
			{
				uint64_t s = 0;
				return readNumber(in,s);
			}

			/**
			 * deserialise object from stream in. add number of bytes read from in to s
			 * @param in input stream
			 * @param s deserialisation byte number accu
			 **/
			AutoArray(std::istream & in, uint64_t & s)
			: n(readNumber(in,s))
			{
				#if defined(AUTOARRAY_DEBUG)
				std::cerr << getTypeName() << "(std::istream,uint64_t &), " << this << std::endl;
				#endif

				increaseTotalAllocation(n);
				allocateArray(n);
				s += ::libmaus2::serialize::Serialize<N>::deserializeArrayChecked(in,array,n);
			}
			/**
			 * deserialise object from stream in.
			 * @param in input stream
			 **/
			AutoArray(std::istream & in)
			: n(readNumber(in))
			{
				#if defined(AUTOARRAY_DEBUG)
				std::cerr << getTypeName() << "(std::istream), " << this << std::endl;
				#endif

				increaseTotalAllocation(n);
				allocateArray(n);
				::libmaus2::serialize::Serialize<N>::deserializeArrayChecked(in,array,n);
			}

			/**
			 * destructor freeing resources.
			 **/
			~AutoArray()
			{
				release();
				#if defined(AUTOARRAY_DEBUG)
				std::cerr << "~" << getTypeName() << "(), " << this << std::endl;
				#endif
			}

			/**
			 * retrieve pointer
			 * @return pointer to array
			 **/
			N * get() { return array; }
			/**
			 * retrieve const pointer
			 * @return const pointer to array
			 **/
			N const * get() const { return array; }

			/**
			 * retrieve reference to i'th element
			 * @param i
			 * @return reference to i'th element
			 **/
			N       & operator[](uint64_t i)       { return array[i]; }
			/**
			 * retrieve const reference to i'th element
			 * @param i
			 * @return const reference to i'th element
			 **/
			N const & operator[](uint64_t i) const { return array[i]; }
			/**
			 * retrieve reference to i'th element
			 * @param i
			 * @return reference to i'th element
			 **/
			N       & get(uint64_t i)       { return array[i]; }
			/**
			 * retrieve const reference to i'th element
			 * @param i
			 * @return const reference to i'th element
			 **/
			N const & get(uint64_t i) const { return array[i]; }
			/**
			 * retrieve reference to i'th element with range checking.
			 * the methods throws an exception if i is out of range
			 * @param i
			 * @return reference to i'th element
			 **/
			N       & at(uint64_t i)
			{
				if ( i < size() )
					return array[i];
				else
				{
					libmaus2::exception::LibMausException ex;
					ex.getStream() << "AutoArray<"<< AutoArrayBase<value_type,atype>::getTypeName()<<">::at(" << i << "): index is out of bounds for array of size " << size() << std::endl;
					ex.finish();
					throw ex;
				}
			}
			/**
			 * retrieve reference to i'th element with range checking.
			 * the methods throws an exception if i is out of range
			 * @param i
			 * @return reference to i'th element
			 **/
			N const & at(uint64_t i) const
			{
				if ( i < size() )
					return array[i];
				else
				{
					libmaus2::exception::LibMausException ex;
					ex.getStream() << "AutoArray<"<<AutoArrayBase<value_type,atype>::getTypeName()<<">::at(" << i << "): index is out of bounds for array of size " << size() << std::endl;
					ex.finish();
					throw ex;
				}
			}

			/**
			 * assignment. retrieves array from o and invalidates o
			 * @param o
			 * @return this array
			 **/
			AutoArray<N,atype> & operator=(AutoArray<N,atype> const & o)
			{
				#if defined(AUTOARRAY_DEBUG)
				std::cerr << this << "," << AutoArrayBase<value_type,atype>::getTypeName() << "::operator=(" << &o << ")" << std::endl;;
				#endif

				if ( this != &o )
				{
					release();

					this->array = o.array;
					this->n = o.n;
					o.array = 0;
					o.n = 0;

					#if defined(LIBMAUS2_AUTOARRAY_AUTOARRAYTRACE)
					AutoArrayTraceBase<N,atype>::operator=(o);
					#endif
				}
				return *this;
			}

			static uint64_t getDefaultBumpC()
			{
				return 2;
			}

			static uint64_t getDefaultBumpD()
			{
				return 1;
			}

			void bump(uint64_t const c = getDefaultBumpC(), uint64_t const d = getDefaultBumpD())
			{
				uint64_t const oldsize = size();
				uint64_t const one     = 1;
				uint64_t const nmult   = (oldsize*c)/d;
				uint64_t const newsize = std::max(nmult,oldsize+one);
				resize(newsize);
			}

			void ensureSize(uint64_t const s, uint64_t const c = getDefaultBumpC(), uint64_t const d = getDefaultBumpD())
			{
				while ( size() < s )
					bump(c,d);
			}

			void push(uint64_t & o, N const & v, uint64_t const c = getDefaultBumpC(), uint64_t const d = getDefaultBumpD())
			{
				ensureSize(o+1,c,d);
				(*this)[o++] = v;
			}

			template<typename iterator>
			void push(uint64_t & o, iterator v, iterator ve, uint64_t const c = getDefaultBumpC(), uint64_t const d = getDefaultBumpD())
			{
				uint64_t const n = ve-v;
				ensureSize(o+n,c,d);

				while ( v != ve )
					(*this)[o++] = *(v++);
			}

			void pushfront(uint64_t & o, N const & v)
			{
				if ( !o )
				{
					uint64_t const off = size();
					bump();
					o = size()-off;
				}
				assert ( o );
				(*this)[--o] = v;
			}
		};

		template<typename N>
		void ArrayErase<N>::erase(N * array, uint64_t const n)
		{
			for ( uint64_t i = 0; i < n; ++i )
				array[i] = N();
		}

		template<typename N>
		void ArrayErase< std::unique_ptr<N> >::erase(std::unique_ptr<N> * array, uint64_t const n)
		{
			for ( uint64_t i = 0; i < n; ++i )
				array[i] = std::move(std::unique_ptr<N>());
		}

		template<typename N>
		void ArrayErase< std::atomic<N> >::erase(std::atomic<N> * array, uint64_t const n)
		{
			for ( uint64_t i = 0; i < n; ++i )
				std::atomic_init(&array[i],N());
		}

		template<typename N, typename M>
		void ArrayErase< std::pair<std::atomic<N>,std::atomic<M> > >::erase(std::pair<std::atomic<N>,std::atomic<M> > * array, uint64_t const n)
		{
			for ( uint64_t i = 0; i < n; ++i )
			{
				std::atomic_init(&(array[i].first),N());
				std::atomic_init(&(array[i].second),M());
			}
		}

		template<typename N, alloc_type atype>
		bool operator==(AutoArray<N,atype> const & A, AutoArray<N,atype> const & B)
		{
			if ( A.getN() != B.getN() )
				return false;

			uint64_t const n = A.getN();

			for ( uint64_t i = 0; i < n; ++i )
				if ( A[i] != B[i] )
					return false;

			return true;
		}

		std::ostream & operator<<(std::ostream & out, libmaus2::autoarray::AutoArrayMemUsage const & aamu);
	}
}

#endif
