/*
    libmaus2
    Copyright (C) 2009-2014 German Tischler
    Copyright (C) 2011-2014 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/>.
*/
#include <libmaus2/bambam/BamAlignment.hpp>
#include <libmaus2/bambam/BamAlignmentPosComparator.hpp>
#include <libmaus2/bambam/BamDecoder.hpp>
#include <libmaus2/bambam/BamHeader.hpp>
#include <libmaus2/bambam/BamWriter.hpp>
#include <libmaus2/lz/BgzfInflateBase.hpp>
#include <libmaus2/lz/SnappyCompressorObjectFactory.hpp>
#include <libmaus2/lz/SnappyDecompressorObjectFactory.hpp>
#include <libmaus2/lz/ZlibCompressorObjectFactory.hpp>
#include <libmaus2/lz/ZlibDecompressorObjectFactory.hpp>
#include <libmaus2/lz/SimpleCompressedOutputStream.hpp>
#include <libmaus2/lz/SimpleCompressedStreamInterval.hpp>
#include <libmaus2/lz/SimpleCompressedConcatInputStreamFragment.hpp>
#include <libmaus2/lz/SimpleCompressedConcatInputStream.hpp>
#include <libmaus2/lz/SimpleCompressedStreamNamedInterval.hpp>
#include <libmaus2/parallel/LockedBool.hpp>
#include <libmaus2/parallel/LockedQueue.hpp>
#include <libmaus2/parallel/SimpleThreadPool.hpp>
#include <libmaus2/parallel/SimpleThreadPoolWorkPackageFreeList.hpp>
#include <libmaus2/sorting/ParallelStableSort.hpp>
#include <libmaus2/util/GetCObject.hpp>
#include <libmaus2/util/TempFileRemovalContainer.hpp>
#include <libmaus2/bambam/BamAlignmentHeapComparator.hpp>

struct BamThreadPoolDecodeBamParseQueueInfo
{
	uint64_t packageid;
	libmaus2::lz::BgzfInflateBase::BaseBlockInfo blockmeta;
	uint64_t baseid;
	uint64_t blockid;

	BamThreadPoolDecodeBamParseQueueInfo()
	: packageid(0), blockmeta(0,0,0,0), baseid(0), blockid(0)
	{

	}
	BamThreadPoolDecodeBamParseQueueInfo(
		uint64_t rpackageid,
		libmaus2::lz::BgzfInflateBase::BaseBlockInfo rblockmeta,
		uint64_t rbaseid,
		uint64_t rblockid
	)
	: packageid(rpackageid), blockmeta(rblockmeta), baseid(rbaseid), blockid(rblockid)
	{

	}

	bool operator<(BamThreadPoolDecodeBamParseQueueInfo const & o) const
	{
		return blockid > o.blockid;
	}
};

struct BamThreadPoolDecodeBamProcessQueueInfo
{
	uint64_t packageid;
	uint64_t baseid;
	uint64_t blockid;

	BamThreadPoolDecodeBamProcessQueueInfo()
	: packageid(0), baseid(0), blockid(0)
	{

	}
	BamThreadPoolDecodeBamProcessQueueInfo(
		uint64_t rpackageid,
		uint64_t rbaseid,
		uint64_t rblockid
	)
	: packageid(rpackageid), baseid(rbaseid), blockid(rblockid)
	{

	}

	bool operator<(BamThreadPoolDecodeBamProcessQueueInfo const & o) const
	{
		return blockid > o.blockid;
	}
};

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

	typedef uint64_t pointer_type;

	libmaus2::autoarray::AutoArray<pointer_type> B8;
	uint64_t const bspace;

	uint8_t * const ca;
	uint8_t * cc;

	pointer_type * const pa;
	pointer_type * pc;

	int64_t bufferid;

	void reset()
	{
		cc = ca;
		pc = pa;
	}

	BamProcessBuffer(uint64_t const bytesize)
	: B8(
		(bytesize + sizeof(pointer_type) - 1)/sizeof(pointer_type),
		false
	  ),
	  bspace(sizeof(pointer_type) * B8.size()),
	  ca(reinterpret_cast<uint8_t *>(B8.begin())), cc(ca),
	  pa(B8.end()), pc(pa),
	  bufferid(-1)
	{
	}

	uint64_t getFill() const
	{
		return pa-pc;
	}

	bool put(uint8_t const * p, uint64_t const s)
	{
		uint64_t const ptrmult = 2;
		uint64_t const spaceused_data = cc-ca;
		uint64_t const spaceused_ptr = (pa-pc)*sizeof(pointer_type)*ptrmult;
		uint64_t const spaceused_len = sizeof(uint32_t);
		uint64_t const spaceused = (spaceused_data+spaceused_ptr+spaceused_len);
		uint64_t const spaceav = bspace - spaceused;

		if ( spaceav >= s + sizeof(uint32_t) + ptrmult*sizeof(pointer_type) )
		{
			*(--pc) = cc-ca;
			for ( unsigned int i = 0; i < sizeof(uint32_t); ++i )
				*(cc++) = (s >> (i*8)) & 0xFF;
			std::copy(p,p+s,cc);
			cc += s;
			return true;
		}
		else
		{
			return false;
		}
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeContextBase;

struct BamThreadPoolDecodeContextBaseConstantsBase
{
	enum bamthreadpooldecodecontextbase_dispatcher_ids
	{
		bamthreadpooldecodecontextbase_dispatcher_id_read = 0,
		bamthreadpooldecodecontextbase_dispatcher_id_decompress = 1,
		bamthreadpooldecodecontextbase_dispatcher_id_bamparse = 2,
		bamthreadpooldecodecontextbase_dispatcher_id_bamprocess = 3,
		bamthreadpooldecodecontextbase_dispatcher_id_bamsort = 4,
		bamthreadpooldecodecontextbase_dispatcher_id_bamwrite = 5
	};

	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_read = 10;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_decompress = 10;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_bamparse = 10;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_bamprocess = 10;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_bamsort = 0;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_bamwrite = 0;
};

template<typename _order_type>
struct BamThreadPoolDecodeReadPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolDecodeReadPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolDecodeContextBase<order_type> * contextbase;

	BamThreadPoolDecodeReadPackage() : contextbase(0) {}
	BamThreadPoolDecodeReadPackage(
		uint64_t const rpackageid,
		BamThreadPoolDecodeContextBase<order_type> * rcontextbase
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_read,
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_read,
		rpackageid
	), contextbase(rcontextbase)
	{

	}

	virtual char const * getPackageName() const
	{
		return "BamThreadPoolDecodeReadPackage";
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeDecompressPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolDecodeDecompressPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolDecodeContextBase<order_type> * contextbase;

	libmaus2::lz::BgzfInflateBase::BaseBlockInfo blockmeta; // block size compressed and uncompressed
	uint64_t baseid;
	uint64_t blockid;

	BamThreadPoolDecodeDecompressPackage() : contextbase(0) {}
	BamThreadPoolDecodeDecompressPackage(
		uint64_t const rpackageid,
		BamThreadPoolDecodeContextBase<order_type> * rcontextbase,
		libmaus2::lz::BgzfInflateBase::BaseBlockInfo rblockmeta,
		uint64_t rbaseid,
		uint64_t rblockid

	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_decompress,
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_decompress,
		rpackageid
	), contextbase(rcontextbase), blockmeta(rblockmeta), baseid(rbaseid), blockid(rblockid)
	{

	}

	virtual char const * getPackageName() const
	{
		return "BamThreadPoolDecodeDecompressPackage";
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamParsePackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolDecodeBamParsePackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolDecodeContextBase<order_type> * contextbase;

	libmaus2::lz::BgzfInflateBase::BaseBlockInfo blockmeta; // block size compressed and uncompressed
	uint64_t baseid;
	uint64_t blockid;

	BamThreadPoolDecodeBamParsePackage() : contextbase(0) {}
	BamThreadPoolDecodeBamParsePackage(
		uint64_t const rpackageid,
		BamThreadPoolDecodeContextBase<order_type> * rcontextbase,
		libmaus2::lz::BgzfInflateBase::BaseBlockInfo rblockmeta,
		uint64_t rbaseid,
		uint64_t rblockid
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_bamparse,
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_bamparse,
		rpackageid
	), contextbase(rcontextbase), blockmeta(rblockmeta), baseid(rbaseid), blockid(rblockid)
	{

	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolDecodeBamParsePackage";
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamProcessPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolDecodeBamProcessPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolDecodeContextBase<order_type> * contextbase;
	uint64_t baseid;
	uint64_t blockid;

	BamThreadPoolDecodeBamProcessPackage() : contextbase(0) {}
	BamThreadPoolDecodeBamProcessPackage(
		uint64_t const rpackageid,
		BamThreadPoolDecodeContextBase<order_type> * rcontextbase,
		uint64_t rbaseid,
		uint64_t rblockid
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_bamprocess,
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_bamprocess,
		rpackageid
	), contextbase(rcontextbase), baseid(rbaseid), blockid(rblockid)
	{

	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolDecodeBamProcessPackage";
	}
};


template<typename _order_type>
struct BamSortInfo
{
	typedef _order_type order_type;
	typedef BamSortInfo this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	typedef libmaus2::sorting::ParallelStableSort::ParallelSortControl<
		BamProcessBuffer::pointer_type *,order_type
	> sort_type;

	BamProcessBuffer * processBuffer;
	order_type BAPC;
	typename sort_type::unique_ptr_type sortControl;

	BamSortInfo(
		BamProcessBuffer * rprocessBuffer, uint64_t const rnumthreads
	) : processBuffer(rprocessBuffer), BAPC(processBuffer->ca),
	    sortControl(new sort_type(
	    	processBuffer->pc, // current
	    	processBuffer->pa, // end
	    	processBuffer->pc-(processBuffer->pa-processBuffer->pc),
	    	processBuffer->pc,
	    	BAPC,
	    	rnumthreads,
	    	true /* copy back */
	    ))
	{
		assert ( sortControl->context.ae-sortControl->context.aa == sortControl->context.be-sortControl->context.ba );

		#if 0
		for ( uint64_t * pc = processBuffer->pc; pc != processBuffer->pa; ++pc )
		{
			uint8_t const * cp = processBuffer->ca + *pc + 4;
			std::cerr << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(cp) << std::endl;
		}
		#endif
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamSortPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolDecodeBamSortPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	enum sort_package_type {
		sort_package_type_base,
		sort_package_type_merge_level,
		sort_package_type_merge_sublevel,
	};

	BamThreadPoolDecodeContextBase<order_type> * contextbase;
	uint64_t baseid;
	uint64_t blockid;
	typedef BamSortInfo<order_type> bam_sort_info_type;
	typename bam_sort_info_type::shared_ptr_type sortinfo;
	sort_package_type packagetype;

	uint64_t sort_base_id;
	uint64_t sort_merge_id;
	uint64_t sort_submerge_id;

	BamThreadPoolDecodeBamSortPackage() : contextbase(0), packagetype(sort_package_type_base) {}
	BamThreadPoolDecodeBamSortPackage(
		uint64_t const rpackageid,
		BamThreadPoolDecodeContextBase<order_type> * rcontextbase,
		uint64_t rbaseid,
		uint64_t rblockid,
		typename bam_sort_info_type::shared_ptr_type rsortinfo,
		sort_package_type rpackagetype,
		uint64_t rsort_base_id,
		uint64_t rsort_merge_id,
		uint64_t rsort_submerge_id
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_bamsort,
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_bamsort,
		rpackageid
	), contextbase(rcontextbase), baseid(rbaseid), blockid(rblockid), sortinfo(rsortinfo), packagetype(rpackagetype),
	   sort_base_id(rsort_base_id), sort_merge_id(rsort_merge_id), sort_submerge_id(rsort_submerge_id)
	{
		assert (
			packagetype == sort_package_type_base ||
			packagetype == sort_package_type_merge_level ||
			packagetype == sort_package_type_merge_sublevel
		);
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolDecodeBamSortPackage";
	}
};

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

	BamProcessBuffer * processBuffer;
	std::vector < std::pair<BamProcessBuffer::pointer_type const *, BamProcessBuffer::pointer_type const *> > packets;
	libmaus2::parallel::SynchronousCounter<uint64_t> packetsWritten;

	BamBlockWriteInfo() : processBuffer(0), packetsWritten(0) {}
	BamBlockWriteInfo(
		BamProcessBuffer * rprocessBuffer,
		std::vector < std::pair<BamProcessBuffer::pointer_type const *, BamProcessBuffer::pointer_type const *> > const & rpackets
	) : processBuffer(rprocessBuffer), packets(rpackets), packetsWritten(0) {}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamWritePackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolDecodeBamWritePackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolDecodeContextBase<order_type> * contextbase;
	uint64_t baseid;
	uint64_t blockid;
	BamBlockWriteInfo::shared_ptr_type writeinfo;
	uint64_t write_block_id;

	BamThreadPoolDecodeBamWritePackage() : contextbase(0) {}
	BamThreadPoolDecodeBamWritePackage(
		uint64_t const rpackageid,
		BamThreadPoolDecodeContextBase<order_type> * rcontextbase,
		uint64_t rbaseid,
		uint64_t rblockid,
		BamBlockWriteInfo::shared_ptr_type rwriteinfo,
		uint64_t rwrite_block_id
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_bamwrite,
		BamThreadPoolDecodeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_bamwrite,
		rpackageid
	), contextbase(rcontextbase), baseid(rbaseid), blockid(rblockid), writeinfo(rwriteinfo),
	   write_block_id(rwrite_block_id)
	{
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolDecodeBamWritePackage";
	}

	bool operator<(BamThreadPoolDecodeBamWritePackage const & o) const
	{
		return baseid > o.baseid;
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamWritePackageHeapComparator
{
	typedef _order_type order_type;

	bool operator()(BamThreadPoolDecodeBamWritePackage<order_type> const * A, BamThreadPoolDecodeBamWritePackage<order_type> const * B)
	{
		//return A->baseid > B->baseid;
		return A->blockid > B->blockid;
	}
};

struct MergeInfo
{
	std::vector< std::vector<uint64_t> > tmpfileblockcnts;
	std::vector< std::string > tmpfilenames;
	std::vector< std::vector< libmaus2::lz::SimpleCompressedStreamInterval > > tmpfileblocks;
	std::vector< std::vector< libmaus2::lz::SimpleCompressedStreamNamedInterval > > tmpfilenamedblocks;
	std::vector< uint64_t > tmpfileblockcntsums;
	libmaus2::bambam::BamHeader::shared_ptr_type sheader;

	MergeInfo() {}
	MergeInfo(
		std::vector< std::vector<uint64_t> > rtmpfileblockcnts,
		std::vector< std::string > rtmpfilenames,
		std::vector< std::vector< libmaus2::lz::SimpleCompressedStreamInterval > > rtmpfileblocks,
		std::vector< std::vector< libmaus2::lz::SimpleCompressedStreamNamedInterval > > rtmpfilenamedblocks,
		std::vector< uint64_t > rtmpfileblockcntsums,
		libmaus2::bambam::BamHeader const & header
	)
	: tmpfileblockcnts(rtmpfileblockcnts),
	  tmpfilenames(rtmpfilenames),
	  tmpfileblocks(rtmpfileblocks),
	  tmpfilenamedblocks(rtmpfilenamedblocks),
	  tmpfileblockcntsums(rtmpfileblockcntsums),
	  sheader(header.sclone())
	{

	}
};


template<typename _order_type>
struct BamThreadPoolDecodeContextBase : public BamThreadPoolDecodeContextBaseConstantsBase
{
	typedef _order_type order_type;

	static uint64_t const alperpackalign = 1024;

	libmaus2::parallel::StdSpinLock cerrlock;

	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolDecodeReadPackage<order_type> > readFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolDecodeDecompressPackage<order_type> > decompressFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolDecodeBamParsePackage<order_type> > bamParseFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolDecodeBamProcessPackage<order_type> > bamProcessFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolDecodeBamSortPackage<order_type> > bamSortFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolDecodeBamWritePackage<order_type> > bamWriteFreeList;

	libmaus2::parallel::StdSpinLock inputLock;
	libmaus2::timing::RealTimeClock inputRtc;
	std::istream & in;
	libmaus2::parallel::SynchronousCounter<uint64_t> readCnt;
	libmaus2::parallel::SynchronousCounter<uint64_t> readCompCnt;
	libmaus2::parallel::LockedBool readComplete;
	libmaus2::parallel::StdSemaphore readSem;
	libmaus2::autoarray::AutoArray< libmaus2::lz::BgzfInflateBase::unique_ptr_type > inflateBases;
	libmaus2::autoarray::AutoArray< char > inflateDecompressSpace;
	libmaus2::parallel::StdSynchronousQueue<uint64_t> inflateBasesFreeList;
	libmaus2::parallel::SynchronousCounter<uint64_t> nextInputBlockId;

	libmaus2::parallel::StdSpinLock decompressLock;
	uint64_t decompressCnt;
	libmaus2::parallel::LockedBool decompressComplete;

	libmaus2::parallel::StdSpinLock bamParseQueueLock;
	std::priority_queue<BamThreadPoolDecodeBamParseQueueInfo> bamparseQueue;
	std::priority_queue<BamThreadPoolDecodeBamParseQueueInfo> bamparseStall;
	uint64_t bamParseNextBlock;

	libmaus2::parallel::StdSpinLock bamParseLock;
	uint64_t bamParseCnt;
	libmaus2::parallel::LockedBool bamParseComplete;

	libmaus2::parallel::LockedBool haveheader;
	::libmaus2::bambam::BamHeaderParserState bamheaderparsestate;
	libmaus2::bambam::BamHeader header;

	enum bam_parser_state_type {
		bam_parser_state_read_blocklength,
		bam_parser_state_read_blockdata
	};

	bam_parser_state_type bamParserState;
	unsigned int bamBlockSizeRead;
	uint32_t bamBlockSizeValue;
	uint32_t bamBlockDataRead;
	libmaus2::bambam::BamAlignment bamGatherBuffer;

	libmaus2::autoarray::AutoArray<BamProcessBuffer::unique_ptr_type> processBuffers;
	libmaus2::parallel::LockedQueue<uint64_t> processBuffersFreeList;
	uint64_t nextProcessBufferIdIn;
	libmaus2::parallel::StdSpinLock nextProcessBufferIdOutLock;
	uint64_t nextProcessBufferIdOut;

	libmaus2::parallel::LockedBool bamProcessComplete;

	libmaus2::parallel::StdSpinLock buffersSortedLock;
	uint64_t buffersSorted;
	libmaus2::parallel::LockedBool bamSortComplete;

	std::vector<std::string> tmpfilenames;
	libmaus2::autoarray::AutoArray<libmaus2::aio::OutputStreamInstance::unique_ptr_type> tmpfiles;
	libmaus2::lz::ZlibCompressorObjectFactory compressorFactory;
	libmaus2::autoarray::AutoArray<libmaus2::lz::SimpleCompressedOutputStream<std::ostream>::unique_ptr_type> compressedTmpFiles;
	libmaus2::parallel::StdSpinLock tmpfileblockslock;
	std::vector< std::vector< libmaus2::lz::SimpleCompressedStreamInterval > > tmpfileblocks;
	std::vector< std::vector<uint64_t> > tmpfileblockcnts;
	std::vector< uint64_t > tmpfileblockcntsums;
	libmaus2::parallel::StdSpinLock tmpfileblockcntsumsacclock;
	uint64_t tmpfileblockcntsumsacc;

	MergeInfo getMergeInfo() const
	{
		std::vector< std::vector< libmaus2::lz::SimpleCompressedStreamNamedInterval > > tmpfilenamedblocks;

		for ( uint64_t i = 0; i < tmpfileblocks.size(); ++i )
		{
			std::vector< libmaus2::lz::SimpleCompressedStreamNamedInterval > subblocks(
				tmpfileblocks[i].size()
			);
			for ( uint64_t j = 0; j < tmpfileblocks[i].size(); ++j )
			{
				subblocks[j] = libmaus2::lz::SimpleCompressedStreamNamedInterval(
					tmpfileblocks[i][j].start,
					tmpfileblocks[i][j].end,
					tmpfilenames[j]
				);
			}

			tmpfilenamedblocks.push_back(subblocks);
		}

		return MergeInfo(tmpfileblockcnts,tmpfilenames,tmpfileblocks,tmpfilenamedblocks,tmpfileblockcntsums,header);
	}

	libmaus2::parallel::StdSpinLock writesPendingLock;
	std::vector< uint64_t > writesNext;
	std::vector<
		std::priority_queue<
			BamThreadPoolDecodeBamWritePackage<order_type> *,
			std::vector<BamThreadPoolDecodeBamWritePackage<order_type> *>,
			BamThreadPoolDecodeBamWritePackageHeapComparator<order_type>
		>
	> writesPending;

	libmaus2::parallel::StdSpinLock buffersWrittenLock;
	uint64_t buffersWritten;
	libmaus2::parallel::LockedBool bamWriteComplete;

	libmaus2::parallel::SimpleThreadPool & TP;

	BamThreadPoolDecodeContextBase(
		std::istream & rin,
		uint64_t const numInflateBases,
		uint64_t const numProcessBuffers,
		uint64_t const processBufferSize,
		std::string const & tmpfilenamebase,
		uint64_t const numthreads,
		libmaus2::parallel::SimpleThreadPool & rTP
	)
	:
	  inputLock(),
	  in(rin),
	  readCnt(0),
	  readCompCnt(0),
	  readComplete(false),
	  inflateBases(numInflateBases),
	  inflateDecompressSpace(numInflateBases * libmaus2::lz::BgzfConstants::getBgzfMaxBlockSize()),
	  inflateBasesFreeList(),
	  nextInputBlockId(0),
	  decompressLock(),
	  decompressCnt(0),
	  decompressComplete(false),
	  bamParseQueueLock(),
	  bamparseQueue(),
	  bamparseStall(),
	  bamParseNextBlock(0),
	  bamParseLock(),
	  bamParseCnt(0),
	  bamParseComplete(false),
	  haveheader(false),
	  bamheaderparsestate(),
	  header(),
	  bamParserState(bam_parser_state_read_blocklength),
	  bamBlockSizeRead(0),
	  bamBlockSizeValue(0),
	  bamBlockDataRead(0),
	  processBuffers(numProcessBuffers),
	  processBuffersFreeList(),
	  nextProcessBufferIdIn(0),
	  nextProcessBufferIdOut(0),
	  bamProcessComplete(false),
	  buffersSorted(0),
	  bamSortComplete(false),
	  tmpfilenames(numthreads),
	  tmpfiles(numthreads),
	  compressorFactory(libmaus2::lz::DeflateDefaults::getDefaultLevel()),
	  compressedTmpFiles(numthreads),
	  tmpfileblockcntsumsacc(0),
	  writesNext(numthreads,0),
	  writesPending(numthreads),
	  buffersWritten(0),
	  bamWriteComplete(false),
	  TP(rTP)
	{
		for ( uint64_t i = 0; i < inflateBases.size(); ++i )
		{
			libmaus2::lz::BgzfInflateBase::unique_ptr_type tptr(new libmaus2::lz::BgzfInflateBase);
			inflateBases[i] = std::move(tptr);
			inflateBasesFreeList.enque(i);
		}

		for ( uint64_t i = 0; i < processBuffers.size(); ++i )
		{
			BamProcessBuffer::unique_ptr_type tptr(new BamProcessBuffer(processBufferSize));
			processBuffers[i] = std::move(tptr);
			processBuffersFreeList.push_back(i);
		}

		for ( uint64_t i = 0; i < numthreads; ++i )
		{
			std::ostringstream ostr;
			ostr << tmpfilenamebase << "_" << std::setw(6) << std::setfill('0') << i << std::setw(0) << ".sorttmp";
			std::string const fn = ostr.str();
			tmpfilenames[i] = fn;
			libmaus2::util::TempFileRemovalContainer::addTempFile(fn);
			libmaus2::aio::OutputStreamInstance::unique_ptr_type tptr(
				new libmaus2::aio::OutputStreamInstance(fn)
			);
			tmpfiles[i] = std::move(tptr);

			libmaus2::lz::SimpleCompressedOutputStream<std::ostream>::unique_ptr_type tcptr(
				new libmaus2::lz::SimpleCompressedOutputStream<std::ostream>(*tmpfiles[i],compressorFactory)
			);

			compressedTmpFiles[i] = std::move(tcptr);
		}
	}

	char * getDecompressSpace(uint64_t const rbaseid)
	{
		return inflateDecompressSpace.begin() + rbaseid * libmaus2::lz::BgzfConstants::getBgzfMaxBlockSize();
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeReadPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolDecodeReadPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolDecodeReadPackage<order_type> *>(P) != 0 );

		BamThreadPoolDecodeReadPackage<order_type> & RP = *dynamic_cast<BamThreadPoolDecodeReadPackage<order_type> *>(P);
		BamThreadPoolDecodeContextBase<order_type> & contextbase = *(RP.contextbase);

		// handle empty input file
		if ( contextbase.readCnt.get() == 0 )
		{
			libmaus2::parallel::ScopeStdSpinLock slock(contextbase.inputLock);

			if ( contextbase.in.peek() == std::istream::traits_type::eof() )
			{
				contextbase.readComplete.set(true);
				contextbase.decompressComplete.set(true);
				contextbase.bamParseComplete.set(true);
				contextbase.bamProcessComplete.set(true);
				contextbase.bamSortComplete.set(true);
				tpi.terminate();
			}
		}

		if ( ! contextbase.readComplete.get() )
		{
			assert ( contextbase.in.peek() != std::istream::traits_type::eof() );

			uint64_t baseid;

			while (  contextbase.inflateBasesFreeList.trydeque(baseid) )
			{
				uint64_t const nextInputBlockId = (contextbase.nextInputBlockId++);

				#if 0
				uint64_t readCnt;
				uint64_t readCompCnt;
				#endif
				libmaus2::lz::BgzfInflateBase::BaseBlockInfo blockmeta;

				{
					libmaus2::parallel::ScopeStdSpinLock slock(contextbase.inputLock);
					blockmeta = contextbase.inflateBases[baseid]->readBlock(contextbase.in);

					#if 0
					readCnt =
					#endif
						contextbase.readCnt++;

					contextbase.readCompCnt += blockmeta.payloadsize;

					#if 0
					readCompCnt = contextbase.readCompCnt.get();
					#endif

					if ( contextbase.in.peek() == std::istream::traits_type::eof() )
					{
						contextbase.readComplete.set(true);
						#if 0
						{
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
							std::cerr << "read complete." << std::endl;
						}
						#endif
					}
				}

				#if 0
				if ( readCnt % 16384 == 0 )
				{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "[D] input rate " << (readCompCnt / contextbase.inputRtc.getElapsedSeconds())/(1024.0*1024.0) << std::endl;
					contextbase.TP.printStateHistogram(std::cerr);
				}
				#endif

				BamThreadPoolDecodeDecompressPackage<order_type> * pBTPDDP = RP.contextbase->decompressFreeList.getPackage();
				*pBTPDDP = BamThreadPoolDecodeDecompressPackage<order_type>(0,RP.contextbase,blockmeta,baseid,nextInputBlockId);

				tpi.enque(pBTPDDP);
			}

			contextbase.readSem.post();
		}

		RP.contextbase->readFreeList.returnPackage(dynamic_cast<BamThreadPoolDecodeReadPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeDecompressPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolDecodeDecompressPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolDecodeDecompressPackage<order_type> *>(P) != 0 );

		BamThreadPoolDecodeDecompressPackage<order_type> & RP = *dynamic_cast<BamThreadPoolDecodeDecompressPackage<order_type> *>(P);
		BamThreadPoolDecodeContextBase<order_type> & contextbase = *(RP.contextbase);

		char * const decompressSpace = contextbase.getDecompressSpace(RP.baseid);
		contextbase.inflateBases[RP.baseid]->decompressBlock(decompressSpace,RP.blockmeta);

		libmaus2::parallel::ScopeStdSpinLock ldecompressLock(contextbase.decompressLock);
		contextbase.decompressCnt += 1;
		if ( contextbase.readComplete.get() && (contextbase.decompressCnt == contextbase.readCnt) )
		{
			contextbase.decompressComplete.set(true);

			#if 0
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "decompress complete." << std::endl;
			#endif
		}

		{
			// generate parse info object
			BamThreadPoolDecodeBamParseQueueInfo qinfo(0,RP.blockmeta,RP.baseid,RP.blockid);

			// enque the parse info object
			libmaus2::parallel::ScopeStdSpinLock lbamParseQueueLock(contextbase.bamParseQueueLock);
			contextbase.bamparseQueue.push(qinfo);

			// check whether this is the next block for parsing
			if ( contextbase.bamParseNextBlock == contextbase.bamparseQueue.top().blockid )
			{
				BamThreadPoolDecodeBamParseQueueInfo const bqinfo = contextbase.bamparseQueue.top();
				contextbase.bamparseQueue.pop();

				BamThreadPoolDecodeBamParsePackage<order_type> * pBTPDBPP = RP.contextbase->bamParseFreeList.getPackage();
				*pBTPDBPP = BamThreadPoolDecodeBamParsePackage<order_type>(
					bqinfo.packageid,
					RP.contextbase,
					bqinfo.blockmeta,
					bqinfo.baseid,
					bqinfo.blockid
				);


				tpi.enque(pBTPDBPP);
			}
		}

		RP.contextbase->decompressFreeList.returnPackage(dynamic_cast<BamThreadPoolDecodeDecompressPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamParsePackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	template<unsigned int len>
	static uint64_t decodeLittleEndianInteger(uint8_t const * pa)
	{
		#if defined(LIBMAUS2_HAVE_i386)
		if ( len == 4 )
		{
			return *reinterpret_cast<uint32_t const *>(pa);
		}
		#else
		uint64_t v = 0;
		for ( unsigned int shift = 0; shift < 8*len; shift += 8 )
			v |= static_cast<uint64_t>(*(pa++)) << shift;
		return v;
		#endif
	}

	virtual ~BamThreadPoolDecodeBamParsePackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolDecodeBamParsePackage<order_type> *>(P) != 0 );

		BamThreadPoolDecodeBamParsePackage<order_type> & RP = *dynamic_cast<BamThreadPoolDecodeBamParsePackage<order_type> *>(P);
		BamThreadPoolDecodeContextBase<order_type> & contextbase = *(RP.contextbase);

		char * const decompressSpace = contextbase.getDecompressSpace(RP.baseid);

		uint8_t const * pa = reinterpret_cast<uint8_t const *>(decompressSpace);
		uint8_t const * pc = pa + RP.blockmeta.uncompdatasize;

		if ( (! contextbase.haveheader.get()) && (pa != pc) )
		{
			::libmaus2::util::GetCObject<uint8_t const *> G(pa);
			std::pair<bool,uint64_t> const P = contextbase.bamheaderparsestate.parseHeader(G,pc-pa);

			// header complete?
			if ( P.first )
			{
				contextbase.header.init(contextbase.bamheaderparsestate);
				contextbase.haveheader.set(true);
				pa += P.second;

				#if 0
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << contextbase.header.text;
				#endif
			}
			else
			{
				pa += P.second;
			}
		}

		bool bufferAvailable = !(contextbase.processBuffersFreeList.empty());
		int64_t freeBufferId = bufferAvailable ? contextbase.processBuffersFreeList.dequeFront() : -1;
		std::vector<uint64_t> finishedBuffers;

		bool stall = freeBufferId < 0;
		bool running = (pa != pc) && (!stall);

		while ( running )
		{
			assert ( contextbase.haveheader.get() );
			BamProcessBuffer * bamProcessBuffer = contextbase.processBuffers[freeBufferId].get();

			switch ( contextbase.bamParserState )
			{
				case BamThreadPoolDecodeContextBase<order_type>::bam_parser_state_read_blocklength:
				{
					if ( contextbase.bamBlockSizeRead == 0 )
					{
						uint32_t lblocksize;
						while ( (pc-pa >= static_cast<std::ptrdiff_t>(4)) && ((pc-pa) >= static_cast<std::ptrdiff_t>(4+(lblocksize=decodeLittleEndianInteger<4>(pa)) )) )
						{
							if ( bamProcessBuffer->put(pa+4,lblocksize) )
							{
								pa += 4 + lblocksize;
							}
							else
							{
								finishedBuffers.push_back(freeBufferId);

								bufferAvailable = !(contextbase.processBuffersFreeList.empty());
								freeBufferId = bufferAvailable ? contextbase.processBuffersFreeList.dequeFront() : -1;

								if ( freeBufferId < 0 )
								{
									running = false;
									stall = true;
									break;
								}
								else
								{
									bamProcessBuffer = contextbase.processBuffers[freeBufferId].get();
								}
							}
						}
					}
					while ( running && (pa != pc) && (contextbase.bamBlockSizeRead < 4) )
					{
						contextbase.bamBlockSizeValue |= static_cast<uint32_t>(*(pa++)) << (8*((contextbase.bamBlockSizeRead++)));
					}
					if ( running && (contextbase.bamBlockSizeRead == 4) )
					{
						contextbase.bamParserState = contextbase.bam_parser_state_read_blockdata;
						contextbase.bamBlockDataRead = 0;

						if ( contextbase.bamBlockSizeValue > contextbase.bamGatherBuffer.D.size() )
							contextbase.bamGatherBuffer.D = libmaus2::bambam::BamAlignment::D_array_type(contextbase.bamBlockSizeValue);
					}
					break;
				}
				case BamThreadPoolDecodeContextBase<order_type>::bam_parser_state_read_blockdata:
				{
					assert ( pa != pc );

					uint64_t const skip =
						std::min(
							static_cast<uint64_t>(pc-pa),
							static_cast<uint64_t>(contextbase.bamBlockSizeValue - contextbase.bamBlockDataRead)
						);

					std::copy(pa,pa+skip,contextbase.bamGatherBuffer.D.begin()+contextbase.bamBlockDataRead);

					contextbase.bamBlockDataRead += skip;
					pa += skip;

					if ( contextbase.bamBlockDataRead == contextbase.bamBlockSizeValue )
					{
						contextbase.bamGatherBuffer.blocksize = contextbase.bamBlockSizeValue;

						if ( bamProcessBuffer->put(
							contextbase.bamGatherBuffer.D.begin(),
							contextbase.bamGatherBuffer.blocksize)
						)
						{
							contextbase.bamBlockSizeRead = 0;
							contextbase.bamBlockSizeValue = 0;
							contextbase.bamParserState = contextbase.bam_parser_state_read_blocklength;
						}
						else
						{
							// queue finished buffer
							finishedBuffers.push_back(freeBufferId);

							// rewind to process this data again
							pa -= skip;
							contextbase.bamBlockDataRead -= skip;

							// check for next free buffer
							bufferAvailable = !(contextbase.processBuffersFreeList.empty());
							freeBufferId = bufferAvailable ? contextbase.processBuffersFreeList.dequeFront() : -1;

							// no more free buffers, stall
							if ( freeBufferId < 0 )
							{
								stall = true;
								running = false;
							}
							else
								bamProcessBuffer = contextbase.processBuffers[freeBufferId].get();
						}
					}
					break;
				}
			}

			running = running && (pa != pc) && (!stall);
		}

		if ( ! stall )
		{
			// add inflate object to free list if it is no longer required
			contextbase.inflateBasesFreeList.enque(RP.baseid);

			// enque next read package when stream is ready for reading
			if ( contextbase.readSem.trywait() )
			{
				BamThreadPoolDecodeReadPackage<order_type> * pBTPDRP = RP.contextbase->readFreeList.getPackage();
				*pBTPDRP = BamThreadPoolDecodeReadPackage<order_type>(0,RP.contextbase);
				tpi.enque(pBTPDRP);
			}
		}

		if ( ! stall )
		{

			libmaus2::parallel::ScopeStdSpinLock lbamParseLock(contextbase.bamParseLock);
			contextbase.bamParseCnt += 1;
			if ( contextbase.decompressComplete.get() && (contextbase.bamParseCnt == contextbase.decompressCnt) )
			{
				#if 0
				{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "bamParse complete." << std::endl;
				}
				#endif
				contextbase.bamParseComplete.set(true);

				// queue final finished buffer
				finishedBuffers.push_back(freeBufferId);
			}
			else
			{
				// reinsert unfinished buffer
				contextbase.processBuffersFreeList.push_front(freeBufferId);
			}
		}

		if ( stall )
		{
			// move remaining data to start of buffer
			memmove(reinterpret_cast<uint8_t *>(decompressSpace),pa,pc-pa);

			RP.blockmeta.uncompdatasize = pc-pa;

			#if 0
			{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "stalling, rest " << pc-pa << std::endl;
			}
			#endif

			// stall package
			BamThreadPoolDecodeBamParseQueueInfo qinfo(
				RP.packageid,
				RP.blockmeta,
				RP.baseid,
				RP.blockid
			);

			// enque the parse info object in the stall queue
			libmaus2::parallel::ScopeStdSpinLock lbamParseQueueLock(contextbase.bamParseQueueLock);
			contextbase.bamparseStall.push(qinfo);
		}

		// queue finished buffers
		if ( finishedBuffers.size() )
		{
			for ( uint64_t i = 0; i < finishedBuffers.size(); ++i )
			{
				#if 0
				{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "queueing process block." << std::endl;
				}
				#endif

				BamThreadPoolDecodeBamProcessQueueInfo qinfo(
					0,
					finishedBuffers[i],
					contextbase.nextProcessBufferIdIn++
				);

				BamThreadPoolDecodeBamProcessPackage<order_type> * pprocpack = RP.contextbase->bamProcessFreeList.getPackage();
				*pprocpack = BamThreadPoolDecodeBamProcessPackage<order_type>(
					qinfo.packageid,
					RP.contextbase,
					qinfo.baseid,
					qinfo.blockid
				);

				tpi.enque(pprocpack);
			}
		}

		if ( ! stall )
		{
			// process next bam parse object if any is available
			{
				libmaus2::parallel::ScopeStdSpinLock lbamParseQueueLock(contextbase.bamParseQueueLock);
				contextbase.bamParseNextBlock += 1;

				if ( contextbase.bamParseNextBlock == contextbase.bamparseQueue.top().blockid )
				{
					BamThreadPoolDecodeBamParseQueueInfo const bqinfo = contextbase.bamparseQueue.top();
					contextbase.bamparseQueue.pop();

					BamThreadPoolDecodeBamParsePackage<order_type> * pBTPDBPP = RP.contextbase->bamParseFreeList.getPackage();
					*pBTPDBPP = BamThreadPoolDecodeBamParsePackage<order_type>(
						bqinfo.packageid,
						RP.contextbase,
						bqinfo.blockmeta,
						bqinfo.baseid,
						bqinfo.blockid
					);

					tpi.enque(pBTPDBPP);
				}
			}
		}

		RP.contextbase->bamParseFreeList.returnPackage(dynamic_cast<BamThreadPoolDecodeBamParsePackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamProcessPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolDecodeBamProcessPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolDecodeBamProcessPackage<order_type> *>(P) != 0 );

		BamThreadPoolDecodeBamProcessPackage<order_type> & RP = *dynamic_cast<BamThreadPoolDecodeBamProcessPackage<order_type> *>(P);
		BamThreadPoolDecodeContextBase<order_type> & contextbase = *(RP.contextbase);

		BamProcessBuffer * processBuffer = contextbase.processBuffers[RP.baseid].get();
		std::reverse(processBuffer->pc,processBuffer->pa);

		typename BamSortInfo<order_type>::shared_ptr_type sortinfo(new BamSortInfo<order_type>(processBuffer,contextbase.TP.threads.size()));

		for ( uint64_t i = 0; i < contextbase.TP.threads.size(); ++i )
		{
			BamThreadPoolDecodeBamSortPackage<order_type> * spack = RP.contextbase->bamSortFreeList.getPackage();
			*spack =
				BamThreadPoolDecodeBamSortPackage<order_type>(
					0, RP.contextbase, RP.baseid, RP.blockid, sortinfo,
					BamThreadPoolDecodeBamSortPackage<order_type>::sort_package_type_base,
					i,0,0
				);
			tpi.enque(spack);
		}

		// increment number of processed buffers
		{
		libmaus2::parallel::ScopeStdSpinLock lnextProcessBufferIdOutLock(contextbase.nextProcessBufferIdOutLock);
		contextbase.nextProcessBufferIdOut += 1;
		}

		// check whether processing of package type is complete
		{
			libmaus2::parallel::ScopeStdSpinLock lnextProcessBufferIdOutLock(contextbase.nextProcessBufferIdOutLock);
			if (
				contextbase.bamParseComplete.get() && (contextbase.nextProcessBufferIdOut == contextbase.nextProcessBufferIdIn)
			)
			{
				contextbase.bamProcessComplete.set(true);
				#if 0
				std::cerr << "bamProcess complete" << std::endl;
				#endif
			}
		}

		// return package
		RP.contextbase->bamProcessFreeList.returnPackage(dynamic_cast<BamThreadPoolDecodeBamProcessPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamSortPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolDecodeBamSortPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolDecodeBamSortPackage<order_type> *>(P) != 0 );

		BamThreadPoolDecodeBamSortPackage<order_type> & RP = *dynamic_cast<BamThreadPoolDecodeBamSortPackage<order_type> *>(P);
		BamThreadPoolDecodeContextBase<order_type> & contextbase = *(RP.contextbase);
		typename BamSortInfo<order_type>::sort_type & sortControl = *(RP.sortinfo->sortControl);

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "BamThreadPoolDecodeBamSortPackageDispatcher "
			<< "packagetype=" << static_cast<int>(RP.packagetype)
			<< ",sort_base_id=" << RP.sort_base_id
			<< ",sort_merge_id=" << RP.sort_merge_id
			<< ",sort_submerge_id=" << RP.sort_submerge_id
			<< std::endl;
		}
		#endif

		switch ( RP.packagetype )
		{
			case BamThreadPoolDecodeBamSortPackage<order_type>::sort_package_type_base:
			{
				libmaus2::sorting::ParallelStableSort::BaseSortRequestSet<uint64_t *,typename BamSortInfo<order_type>::order_type> & baseSortRequests =
					sortControl.baseSortRequests;

				baseSortRequests.baseSortRequests[RP.sort_base_id].dispatch();

				uint64_t const finished = ++(baseSortRequests.requestsFinished);

				if ( finished == baseSortRequests.baseSortRequests.size() )
				{
					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "BamThreadPoolDecodeBamSortPackageDispatcher done" << std::endl;
					}
					#endif

					BamThreadPoolDecodeBamSortPackage<order_type> * spack = RP.contextbase->bamSortFreeList.getPackage();
					*spack = BamThreadPoolDecodeBamSortPackage<order_type>(0, RP.contextbase, RP.baseid, RP.blockid,
						RP.sortinfo, BamThreadPoolDecodeBamSortPackage<order_type>::sort_package_type_merge_level,0,0,0);
					tpi.enque(spack);
				}
				break;
			}
			case BamThreadPoolDecodeBamSortPackage<order_type>::sort_package_type_merge_level:
			{
				if ( sortControl.mergeLevels.levelsFinished.get() == sortControl.mergeLevels.levels.size() )
				{
					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "sorted buffer " << contextbase.processBuffers[RP.baseid]->pa-contextbase.processBuffers[RP.baseid]->pc << std::endl;
					}
					#endif

					BamProcessBuffer * processBuffer = contextbase.processBuffers[RP.baseid].get();
					order_type BAPC(processBuffer->ca);

					BamProcessBuffer::pointer_type * pa =
						sortControl.needCopyBack ?
						( processBuffer->pc - (processBuffer->pa-processBuffer->pc) )
						:
						processBuffer->pc;
					BamProcessBuffer::pointer_type * pe =
						sortControl.needCopyBack ? processBuffer->pc : processBuffer->pa;

					uint64_t const numthreads = contextbase.TP.threads.size();
					std::vector < std::pair<BamProcessBuffer::pointer_type const *, BamProcessBuffer::pointer_type const *> > writepackets(numthreads);
					uint64_t const numalgn = pe-pa;
					uint64_t const prealperpack = (numalgn + numthreads - 1)/numthreads;
					uint64_t const alperpack =
						((prealperpack + contextbase.alperpackalign - 1)/contextbase.alperpackalign) * contextbase.alperpackalign;
					uint64_t allow = 0;

					for ( uint64_t i = 0; i < numthreads; ++i )
					{
						uint64_t const alhigh = std::min(allow+alperpack,numalgn);

						writepackets[i] = std::pair<BamProcessBuffer::pointer_type const *, BamProcessBuffer::pointer_type const *>(
							pa + allow, pa + alhigh
						);

						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "writepacket " << allow << " " << alhigh << std::endl;
						}
						#endif

						allow = alhigh;
					}

					BamBlockWriteInfo::shared_ptr_type blockWriteInfo(new BamBlockWriteInfo(processBuffer,writepackets));

					{
						libmaus2::parallel::ScopeStdSpinLock ltmpfileblockslock(contextbase.tmpfileblockslock);

						while ( ! (RP.blockid < contextbase.tmpfileblocks.size() ) )
							contextbase.tmpfileblocks.push_back(
								std::vector<
									libmaus2::lz::SimpleCompressedStreamInterval
								>(
									numthreads
								)
							);

						while ( ! (RP.blockid < contextbase.tmpfileblockcnts.size() ) )
						{
							contextbase.tmpfileblockcnts.push_back(
								std::vector<uint64_t>(numthreads)
							);
						}

						while ( ! (RP.blockid < contextbase.tmpfileblockcntsums.size() ) )
						{
							contextbase.tmpfileblockcntsums.push_back(0);
						}
					}

					libmaus2::parallel::ScopeStdSpinLock lwritesPendingLock(contextbase.writesPendingLock);
					for ( uint64_t i = 0; i < numthreads; ++i )
					{
						BamThreadPoolDecodeBamWritePackage<order_type> * pBTPDBPP = RP.contextbase->bamWriteFreeList.getPackage();
						*pBTPDBPP = BamThreadPoolDecodeBamWritePackage<order_type>(
							0,RP.contextbase,RP.baseid,RP.blockid,
							blockWriteInfo,
							i
						);
						contextbase.writesPending[i].push(pBTPDBPP);

						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "queuing " << RP.blockid << "," << i << " as pending." << std::endl;
						}
						#endif
					}
					for ( uint64_t i = 0; i < numthreads; ++i )
					{
						assert ( ! contextbase.writesPending[i].empty() );

						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "checking queue for thread " << i << " top is " << contextbase.writesPending[i].top()->blockid << std::endl;
						}
						#endif

						if (
							contextbase.writesPending[i].top()->blockid ==
							contextbase.writesNext[i]
						)
						{
							BamThreadPoolDecodeBamWritePackage<order_type> * pBTPDBPP =
								contextbase.writesPending[i].top();
							contextbase.writesPending[i].pop();

							tpi.enque(pBTPDBPP);
						}
					}

					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "needCopyBack " << sortControl.needCopyBack << std::endl;
					}

					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					uint8_t const * prev = 0;
					bool ok = true;
					for ( BamProcessBuffer::pointer_type * pc = pa ; pc != pe; ++pc )
					{
						uint8_t const * c = processBuffer->ca + (*pc)+4;
						#if 0
						std::cerr
							<< libmaus2::bambam::BamAlignmentDecoderBase::getReadName(c)
							<< "\t"
							<< libmaus2::bambam::BamAlignmentDecoderBase::getRefID(c)
							<< "\t"
							<< libmaus2::bambam::BamAlignmentDecoderBase::getPos(c)
							<< std::endl;
						#endif

						if ( prev )
						{
							ok = ok && ( ! (BAPC( *pc , *(pc-1) )) );
							// assert ( ! (BAPC( *pc , *(pc-1) )) );
							// std::cerr << BAPC( *pc , *(pc-1) ) << std::endl;
						}

						prev = c;
					}
					std::cerr << "order: " << (ok?"ok":"failed") << std::endl;
					}
					#endif

					#if 0
					// return buffer to free list
					contextbase.processBuffers[RP.baseid]->reset();
					contextbase.processBuffersFreeList.push_back(RP.baseid);

					// reinsert stalled parse package
					{
						libmaus2::parallel::ScopeStdSpinLock lbamParseQueueLock(contextbase.bamParseQueueLock);

						if ( contextbase.bamparseStall.size() )
						{
							BamThreadPoolDecodeBamParseQueueInfo const bqinfo = contextbase.bamparseStall.top();
							contextbase.bamparseStall.pop();

							BamThreadPoolDecodeBamParsePackage * pBTPDBPP = RP.contextbase->bamParseFreeList.getPackage();
							*pBTPDBPP = BamThreadPoolDecodeBamParsePackage(
								bqinfo.packageid,
								RP.contextbase,
								bqinfo.blockmeta,
								bqinfo.baseid,
								bqinfo.blockid
							);

							{
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
							std::cerr << "reinserting stalled block " << bqinfo.blockid << std::endl;
							}

							tpi.enque(pBTPDBPP);
						}
					}
					#endif

					libmaus2::parallel::ScopeStdSpinLock lnextProcessBufferIdOutLock(contextbase.nextProcessBufferIdOutLock);
					libmaus2::parallel::ScopeStdSpinLock lbuffersSortedLock(contextbase.buffersSortedLock);
					contextbase.buffersSorted += 1;
					if (
						contextbase.bamProcessComplete.get()
						&&
						contextbase.nextProcessBufferIdOut == contextbase.buffersSorted
					)
					{
						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "bamSort complete" << std::endl;
						}
						#endif

						contextbase.bamSortComplete.set(true);
						// tpi.terminate();
					}
				}
				else
				{
					// search split points
					sortControl.mergeLevels.levels[RP.sort_merge_id]->dispatch();

					// call merging
					for ( uint64_t i = 0; i < sortControl.mergeLevels.levels[RP.sort_merge_id]->mergeRequests.size(); ++i )
					{
						BamThreadPoolDecodeBamSortPackage<order_type> * spack = RP.contextbase->bamSortFreeList.getPackage();
						*spack = BamThreadPoolDecodeBamSortPackage<order_type>(0, RP.contextbase, RP.baseid, RP.blockid,
							RP.sortinfo,
							BamThreadPoolDecodeBamSortPackage<order_type>::sort_package_type_merge_sublevel,
							0,RP.sort_merge_id,i);
						tpi.enque(spack);
					}
				}
				break;
			}
			case BamThreadPoolDecodeBamSortPackage<order_type>::sort_package_type_merge_sublevel:
			{
				sortControl.mergeLevels.levels[RP.sort_merge_id]->mergeRequests[RP.sort_submerge_id].dispatch();

				uint64_t const finished = ++(sortControl.mergeLevels.levels[RP.sort_merge_id]->requestsFinished);

				if ( finished == sortControl.mergeLevels.levels[RP.sort_merge_id]->mergeRequests.size() )
				{
					sortControl.mergeLevels.levelsFinished++;

					BamThreadPoolDecodeBamSortPackage<order_type> * spack = RP.contextbase->bamSortFreeList.getPackage();
					*spack = BamThreadPoolDecodeBamSortPackage<order_type>(0, RP.contextbase, RP.baseid, RP.blockid,
						RP.sortinfo,
						BamThreadPoolDecodeBamSortPackage<order_type>::sort_package_type_merge_level,
						0,sortControl.mergeLevels.levelsFinished.get(),0
					);
					tpi.enque(spack);
				}

				break;
			}
		}

		RP.sortinfo.reset();
		RP.contextbase->bamSortFreeList.returnPackage(dynamic_cast<BamThreadPoolDecodeBamSortPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeBamWritePackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolDecodeBamWritePackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolDecodeBamWritePackage<order_type> *>(P) != 0 );

		BamThreadPoolDecodeBamWritePackage<order_type> & RP = *dynamic_cast<BamThreadPoolDecodeBamWritePackage<order_type> *>(P);
		BamThreadPoolDecodeContextBase<order_type> & contextbase = *(RP.contextbase);
		BamBlockWriteInfo * writeinfo = RP.writeinfo.get();
		BamProcessBuffer * buffer = writeinfo->processBuffer;

		uint8_t const * ca = buffer->ca;
		BamProcessBuffer::pointer_type const * pa = writeinfo->packets[RP.write_block_id].first;
		BamProcessBuffer::pointer_type const * pe = writeinfo->packets[RP.write_block_id].second;
		libmaus2::lz::SimpleCompressedOutputStream<std::ostream> * compout =
			contextbase.compressedTmpFiles[RP.write_block_id].get();

		#if 0
		order_type BAPC(buffer->ca);
		BamProcessBuffer::pointer_type const * prev = 0;
		bool gok = true;
		#endif

		#if 0
		if ( pa != pe )
		{
			::libmaus2::bambam::BamFormatAuxiliary aux;

			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "first "
				<<
					libmaus2::bambam::BamAlignmentDecoderBase::formatAlignment(
						ca + *pa + 4,
						(static_cast<uint32_t>((ca + *pa)[0]) << 0) |
						(static_cast<uint32_t>((ca + *pa)[1]) << 8) |
						(static_cast<uint32_t>((ca + *pa)[2]) << 16) |
						(static_cast<uint32_t>((ca + *pa)[3]) << 24),
						contextbase.header,
						aux
					)
				<< std::endl;
		}
		#endif

		std::pair<uint64_t,uint64_t> const preoff = compout->getOffset();
		#if 0
		assert ( preoff.second == 0 );
		#endif
		for ( BamProcessBuffer::pointer_type const * pc = pa; pc != pe; ++pc )
		{
			uint64_t const off = *pc;
			uint8_t const * c = ca + off;

			uint32_t len = 0;
			for ( unsigned int i = 0; i < 4; ++i )
				len |= (static_cast<uint32_t>(c[i]) << (i*8));

			#if 0
			if ( (pc-pa) % contextbase.alperpackalign == 0 )
			{
				compout->flush();
				assert ( compout->getOffset().second == 0 );
			}
			#endif
			compout->write(reinterpret_cast<char const *>(c),len+4);

			#if 0
			if ( prev )
			{
				bool const ok = !BAPC(*pc,*prev);
				if ( ! ok )
				{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "comp failed" << std::endl;
				}

				gok = gok && ok;
			}

			prev = pc;
			#endif
		}
		std::pair<uint64_t,uint64_t> const postoff = compout->getOffset();
		compout->flush();

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "interval comp " << (gok?"ok":"failed") << std::endl;
		}

		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "BamWrite "
			<< RP.blockid << "," << RP.write_block_id
			<< "," << (writeinfo->packets[RP.write_block_id].second-writeinfo->packets[RP.write_block_id].first)
			<< std::endl;
		}
		#endif

		{
			libmaus2::parallel::ScopeStdSpinLock ltmpfileblockslock(contextbase.tmpfileblockslock);
			contextbase.tmpfileblocks[RP.blockid][RP.write_block_id] =
				libmaus2::lz::SimpleCompressedStreamInterval(preoff,postoff);
			contextbase.tmpfileblockcnts[RP.blockid][RP.write_block_id] = (pe-pa);
			contextbase.tmpfileblockcntsums[RP.blockid] += (pe-pa);
		}

		// enque next block for stream writing (if any is present)
		{
			libmaus2::parallel::ScopeStdSpinLock lwritesPendingLock(contextbase.writesPendingLock);
			contextbase.writesNext[RP.write_block_id]++;

			if (
				contextbase.writesPending[RP.write_block_id].size()
				&&
				contextbase.writesPending[RP.write_block_id].top()->blockid ==
				contextbase.writesNext[RP.write_block_id]
			)
			{
				BamThreadPoolDecodeBamWritePackage<order_type> * pBTPDBPP =
					contextbase.writesPending[RP.write_block_id].top();
				contextbase.writesPending[RP.write_block_id].pop();

				tpi.enque(pBTPDBPP);
			}
		}

		uint64_t const packetsWritten = ++(writeinfo->packetsWritten);

		if ( packetsWritten == writeinfo->packets.size() )
		{
			{
				libmaus2::parallel::ScopeStdSpinLock ltmpfileblockcntsumsacclock(contextbase.tmpfileblockcntsumsacclock);
				contextbase.tmpfileblockcntsumsacc += contextbase.tmpfileblockcntsums[RP.blockid];

				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "[V] " << contextbase.tmpfileblockcntsumsacc << std::endl;
			}

			// return buffer to free list
			contextbase.processBuffers[RP.baseid]->reset();
			contextbase.processBuffersFreeList.push_back(RP.baseid);

			// reinsert stalled parse package
			{
				libmaus2::parallel::ScopeStdSpinLock lbamParseQueueLock(contextbase.bamParseQueueLock);

				if ( contextbase.bamparseStall.size() )
				{
					BamThreadPoolDecodeBamParseQueueInfo const bqinfo = contextbase.bamparseStall.top();
					contextbase.bamparseStall.pop();

					BamThreadPoolDecodeBamParsePackage<order_type> * pBTPDBPP = RP.contextbase->bamParseFreeList.getPackage();
					*pBTPDBPP = BamThreadPoolDecodeBamParsePackage<order_type>(
						bqinfo.packageid,
						RP.contextbase,
						bqinfo.blockmeta,
						bqinfo.baseid,
						bqinfo.blockid
					);

					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "reinserting stalled block " << bqinfo.blockid << std::endl;
					}
					#endif

					tpi.enque(pBTPDBPP);
				}
			}

			libmaus2::parallel::ScopeStdSpinLock lbuffersWrittenLock(contextbase.buffersWrittenLock);
			libmaus2::parallel::ScopeStdSpinLock lbuffersSortedLock(contextbase.buffersSortedLock);
			contextbase.buffersWritten += 1;

			if (
				contextbase.bamSortComplete.get()
				&&
				contextbase.buffersWritten == contextbase.buffersSorted
			)
			{
				for ( uint64_t i = 0; i < contextbase.compressedTmpFiles.size(); ++i )
				{
					contextbase.compressedTmpFiles[i]->flush();
					contextbase.compressedTmpFiles[i].reset();
					contextbase.tmpfiles[i]->flush();
					contextbase.tmpfiles[i].reset();
				}

				#if 0
				{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "bamWrite done." << std::endl;
				}
				#endif

				contextbase.bamWriteComplete.set(true);
				tpi.terminate();
			}
		}

		// return package to free list
		RP.contextbase->bamWriteFreeList.returnPackage(dynamic_cast<BamThreadPoolDecodeBamWritePackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolDecodeContext : public BamThreadPoolDecodeContextBase<_order_type>
{
	typedef _order_type order_type;

	libmaus2::parallel::SimpleThreadPool & TP;

	BamThreadPoolDecodeContext(
		std::istream & rin,
		uint64_t const numInflateBases,
		uint64_t const numProcessBuffers,
		uint64_t const processBufferSize,
		std::string const & tmpfilenamebase,
		uint64_t const numthreads,
		libmaus2::parallel::SimpleThreadPool & rTP
	)
	: BamThreadPoolDecodeContextBase<order_type>(rin,numInflateBases,numProcessBuffers,processBufferSize,tmpfilenamebase,numthreads,rTP), TP(rTP)
	{

	}

	void startup()
	{
		BamThreadPoolDecodeContextBase<order_type>::inputRtc.start();

		assert ( BamThreadPoolDecodeContextBase<order_type>::inflateBases.size() );

		BamThreadPoolDecodeReadPackage<order_type> * pBTPDRP = BamThreadPoolDecodeContextBase<order_type>::readFreeList.getPackage();
		*pBTPDRP = BamThreadPoolDecodeReadPackage<order_type>(0,this);
		TP.enque(pBTPDRP);
	}
};

#include <libmaus2/aio/PosixFdInputStream.hpp>

template<typename _order_type>
MergeInfo produceSortedBlocks(libmaus2::util::ArgInfo const & arginfo)
{
	typedef _order_type order_type;

	std::string const tmpfilenamebase = arginfo.getDefaultTmpFileName();

	#if defined(_OPENMP)
	uint64_t const numthreads = omp_get_max_threads();
	#else
	uint64_t const numthreads = 1;
	#endif

	// thread pool
	libmaus2::parallel::SimpleThreadPool TP(numthreads);

	// package dispatchers
	BamThreadPoolDecodeReadPackageDispatcher<order_type> readdispatcher;
	TP.registerDispatcher(BamThreadPoolDecodeContext<order_type>::bamthreadpooldecodecontextbase_dispatcher_id_read,&readdispatcher);
	BamThreadPoolDecodeDecompressPackageDispatcher<order_type> decompressdispatcher;
	TP.registerDispatcher(BamThreadPoolDecodeContext<order_type>::bamthreadpooldecodecontextbase_dispatcher_id_decompress,&decompressdispatcher);
	BamThreadPoolDecodeBamParsePackageDispatcher<order_type> bamparsedispatcher;
	TP.registerDispatcher(BamThreadPoolDecodeContext<order_type>::bamthreadpooldecodecontextbase_dispatcher_id_bamparse,&bamparsedispatcher);
	BamThreadPoolDecodeBamProcessPackageDispatcher<order_type> bamprocessdispatcher;
	TP.registerDispatcher(BamThreadPoolDecodeContext<order_type>::bamthreadpooldecodecontextbase_dispatcher_id_bamprocess,&bamprocessdispatcher);
	BamThreadPoolDecodeBamSortPackageDispatcher<order_type> bamsortdispatcher;
	TP.registerDispatcher(BamThreadPoolDecodeContext<order_type>::bamthreadpooldecodecontextbase_dispatcher_id_bamsort,&bamsortdispatcher);
	BamThreadPoolDecodeBamWritePackageDispatcher<order_type> bamwritedispatcher;
	TP.registerDispatcher(BamThreadPoolDecodeContext<order_type>::bamthreadpooldecodecontextbase_dispatcher_id_bamwrite,&bamwritedispatcher);

	uint64_t numProcessBuffers = 4; // 2*numthreads;
	uint64_t processBufferMemory = arginfo.getValueUnsignedNumeric<uint64_t>("mem",16*1024ull*1024ull*1024ull);
	uint64_t processBufferSize = (processBufferMemory + numProcessBuffers-1)/numProcessBuffers;
	assert ( processBufferSize <= std::numeric_limits<BamProcessBuffer::pointer_type>::max() );

	libmaus2::aio::PosixFdInputStream PFIS(STDIN_FILENO,64*1024);
	BamThreadPoolDecodeContext<order_type> context(PFIS,16*numthreads /* inflate bases */,numProcessBuffers,processBufferSize,tmpfilenamebase,numthreads,TP);
	context.startup();

	TP.join();


	std::cerr << "[V] produced " << context.tmpfileblockcntsums.size() << " blocks: " << std::endl;
	for ( uint64_t i = 0; i < context.tmpfileblockcntsums.size(); ++i )
		std::cerr << "[V] block[" << i << "]=" << context.tmpfileblockcntsums[i] << std::endl;

	MergeInfo const mergeinfo = context.getMergeInfo();

	return mergeinfo;
}

template<typename _order_type>
void mergeSortedBlocks(libmaus2::util::ArgInfo const & arginfo, MergeInfo const & mergeinfo, std::string const & neworder)
{
	typedef _order_type order_type;

	#if defined(_OPENMP)
	uint64_t const numthreads = omp_get_max_threads();
	#else
	uint64_t const numthreads = 1;
	#endif

	libmaus2::autoarray::AutoArray<libmaus2::aio::InputStreamInstance::unique_ptr_type> inputfiles(mergeinfo.tmpfilenames.size());
	libmaus2::lz::ZlibDecompressorObjectFactory decfact;
	for ( uint64_t i = 0; i < inputfiles.size(); ++i )
	{
		libmaus2::aio::InputStreamInstance::unique_ptr_type tptr(new libmaus2::aio::InputStreamInstance(mergeinfo.tmpfilenames[i]));
		inputfiles[i] = std::move(tptr);
	}

	libmaus2::autoarray::AutoArray<libmaus2::lz::SimpleCompressedConcatInputStream<std::istream>::unique_ptr_type> concfiles(mergeinfo.tmpfileblocks.size());
	for ( uint64_t i = 0; i < mergeinfo.tmpfileblocks.size(); ++i )
	{
		std::vector<libmaus2::lz::SimpleCompressedConcatInputStreamFragment<std::istream> > fragments;

		for ( uint64_t j = 0; j < mergeinfo.tmpfileblocks[i].size(); ++j )
			fragments.push_back(
				libmaus2::lz::SimpleCompressedConcatInputStreamFragment<std::istream>(
					mergeinfo.tmpfileblocks[i][j],
					inputfiles[j].get()
				)
			);

		libmaus2::lz::SimpleCompressedConcatInputStream<std::istream>::unique_ptr_type tptr(
			new libmaus2::lz::SimpleCompressedConcatInputStream<std::istream>(fragments,decfact)
		);
		concfiles[i] = std::move(tptr);
	}

	std::vector<uint64_t> tmpoutcnts = mergeinfo.tmpfileblockcntsums;
	order_type BAPC(0);
	::libmaus2::autoarray::AutoArray< ::libmaus2::bambam::BamAlignment > algns(mergeinfo.tmpfileblocks.size());
	::libmaus2::bambam::BamAlignmentHeapComparator<order_type> heapcmp(BAPC,algns.begin());

	::std::priority_queue<
		uint64_t,
		std::vector<uint64_t>,
		::libmaus2::bambam::BamAlignmentHeapComparator<order_type> > Q(heapcmp);
	for ( uint64_t i = 0; i < tmpoutcnts.size(); ++i )
		if ( tmpoutcnts[i]-- )
		{
			::libmaus2::bambam::BamDecoder::readAlignmentGz(*concfiles[i],algns[i],0 /* no header for validation */,false /* no validation */);
			Q.push(i);
		}

	uint64_t outcnt = 0;
	bool const verbose = true;
	mergeinfo.sheader->changeSortOrder(neworder);
	int const level = arginfo.getValue<int>("level",-1);;
	libmaus2::bambam::BamParallelWriter writer(std::cout,std::max(1,static_cast<int>(numthreads-1)),*(mergeinfo.sheader),level);

	while ( Q.size() )
	{
		uint64_t const t = Q.top(); Q.pop();
		algns[t].serialise(writer.getStream());
		// writer.writeAlgn(algns[t]);

		if ( verbose && (++outcnt % (1024*1024) == 0) )
			std::cerr << "[V] " << outcnt/(1024*1024) << "M" << std::endl;

		if ( tmpoutcnts[t]-- )
		{
			::libmaus2::bambam::BamDecoder::readAlignmentGz(*concfiles[t],algns[t],0 /* bamheader */, false /* do not validate */);
			Q.push(t);
		}
	}
}

struct BamThreadPoolMergeContextBaseConstantsBase
{
	enum bamthreadpoolmergecontextbase_dispatcher_ids
	{
		bamthreadpooldecodecontextbase_dispatcher_id_read = 0,
		bamthreadpooldecodecontextbase_dispatcher_id_decompress = 1,
		bamthreadpooldecodecontextbase_dispatcher_id_process = 2,
		bamthreadpooldecodecontextbase_dispatcher_id_merge = 3,
		bamthreadpooldecodecontextbase_dispatcher_id_compress = 4,
		bamthreadpooldecodecontextbase_dispatcher_id_write = 5
	};

	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_read = 0;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_decompress = 0;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_process = 0;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_merge = 0;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_compress = 0;
	static unsigned int const bamthreadpooldecodecontextbase_dispatcher_priority_write = 0;
};

template<typename _order_type>
struct BamThreadPoolMergeContextBase;

template<typename _order_type>
struct BamThreadPoolMergeReadPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolMergeReadPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolMergeContextBase<order_type> * contextbase;
	uint64_t blockid;

	BamThreadPoolMergeReadPackage() : contextbase(0) {}
	BamThreadPoolMergeReadPackage(
		BamThreadPoolMergeContextBase<order_type> * rcontextbase,
		uint64_t rblockid
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_read,
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_read,
		0 /* package id */
	), contextbase(rcontextbase), blockid(rblockid)
	{
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolMergeReadPackage";
	}
};

template<typename _order_type>
struct BamThreadPoolMergeDecompressPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolMergeDecompressPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolMergeContextBase<order_type> * contextbase;
	uint64_t blockid;
	uint64_t baseid;
	uint64_t seqid;

	BamThreadPoolMergeDecompressPackage() : contextbase(0) {}
	BamThreadPoolMergeDecompressPackage(
		BamThreadPoolMergeContextBase<order_type> * rcontextbase,
		uint64_t rblockid,
		uint64_t rbaseid,
		uint64_t rseqid
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_decompress,
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_decompress,
		0 /* package id */
	), contextbase(rcontextbase), blockid(rblockid), baseid(rbaseid), seqid(rseqid)
	{
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolMergeDecompressPackage";
	}
};

template<typename _order_type>
struct BamThreadPoolMergeProcessPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolMergeProcessPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolMergeContextBase<order_type> * contextbase;
	uint64_t blockid;
	uint64_t baseid;
	uint64_t seqid;

	BamThreadPoolMergeProcessPackage() : contextbase(0) {}
	BamThreadPoolMergeProcessPackage(
		BamThreadPoolMergeContextBase<order_type> * rcontextbase,
		uint64_t rblockid,
		uint64_t rbaseid,
		uint64_t rseqid
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_process,
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_process,
		0 /* package id */
	), contextbase(rcontextbase), blockid(rblockid), baseid(rbaseid), seqid(rseqid)
	{
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolMergeProcessPackage";
	}
};

template<typename _order_type>
struct BamThreadPoolMergeMergePackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolMergeMergePackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolMergeContextBase<order_type> * contextbase;

	BamThreadPoolMergeMergePackage() : contextbase(0) {}
	BamThreadPoolMergeMergePackage(BamThreadPoolMergeContextBase<order_type> * rcontextbase)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_merge,
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_merge,
		0 /* package id */
	), contextbase(rcontextbase)
	{
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolMergeMergePackage";
	}
};

template<typename _order_type>
struct BamThreadPoolMergeCompressPackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolMergeCompressPackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolMergeContextBase<order_type> * contextbase;
	uint64_t baseid;
	uint64_t seqid;

	BamThreadPoolMergeCompressPackage() : contextbase(0), baseid(0), seqid(0) {}
	BamThreadPoolMergeCompressPackage(
		BamThreadPoolMergeContextBase<order_type> * rcontextbase,
		uint64_t const rbaseid,
		uint64_t const rseqid
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_compress,
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_compress,
		0 /* package id */
	), contextbase(rcontextbase), baseid(rbaseid), seqid(rseqid)
	{
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolMergeCompressPackage";
	}
};

struct BamThreadPoolMergeWritePendingInfo
{
	uint64_t baseid;
	uint64_t seqid;
	libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo flushinfo;

	BamThreadPoolMergeWritePendingInfo() : baseid(0), seqid(0), flushinfo() {}
	BamThreadPoolMergeWritePendingInfo(
		uint64_t const rbaseid,
		uint64_t const rseqid,
		libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo const & rflushinfo)
	: baseid(rbaseid), seqid(rseqid), flushinfo(rflushinfo) {}
};

struct BamThreadPoolMergeWritePendingInfoHeapComparator
{
	bool operator()(
		BamThreadPoolMergeWritePendingInfo const & A,
		BamThreadPoolMergeWritePendingInfo const & B
	)
	{
		return A.seqid > B.seqid;
	}
};

template<typename _order_type>
struct BamThreadPoolMergeWritePackage : public ::libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef _order_type order_type;
	typedef BamThreadPoolMergeWritePackage<order_type> this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamThreadPoolMergeContextBase<order_type> * contextbase;
	BamThreadPoolMergeWritePendingInfo info;

	BamThreadPoolMergeWritePackage() : contextbase(0), info() {}
	BamThreadPoolMergeWritePackage(
		BamThreadPoolMergeContextBase<order_type> * rcontextbase,
		BamThreadPoolMergeWritePendingInfo const & rinfo
	)
	: ::libmaus2::parallel::SimpleThreadWorkPackage(
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_priority_write,
		BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_write,
		0 /* package id */
	), contextbase(rcontextbase), info(rinfo)
	{
	}
	virtual char const * getPackageName() const
	{
		return "BamThreadPoolMergeWritePackage";
	}
};

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

	uint64_t blockid;
	uint64_t seqid;
	uint64_t bufferid;
	bool eof;
	BamProcessBuffer * buffer;

	BamProcessBuffer::pointer_type * pc;
	BamProcessBuffer::pointer_type * pe;

	BamThreadPoolMergeProcessBufferInfo()
	: blockid(0), seqid(0), bufferid(0), eof(false) {}
	BamThreadPoolMergeProcessBufferInfo(
		uint64_t rblockid,
		uint64_t rseqid,
		uint64_t rbufferid,
		bool reof,
		BamProcessBuffer * rbuffer
	)
	: blockid(rblockid), seqid(rseqid), bufferid(rbufferid), eof(reof), buffer(rbuffer),
	  pc(buffer->pc), pe(buffer->pa)
	{
		std::reverse(buffer->pc,buffer->pa);
	}

	template<typename _order_type>
	bool checkOrder(libmaus2::bambam::BamHeader const & header) const
	{
		BamProcessBuffer::pointer_type * prev = 0;
		bool ok = true;

		::libmaus2::bambam::BamFormatAuxiliary aux;

		for ( BamProcessBuffer::pointer_type * tc = pc; tc != pe; ++tc )
		{
			if ( prev )
			{
				uint8_t const * ca = buffer->ca + (*prev) + 4;
				uint8_t const * cb = buffer->ca + (*tc) + 4;
				uint32_t const la =
					(static_cast<uint32_t>(ca[0]) << 0) |
					(static_cast<uint32_t>(ca[1]) << 8) |
					(static_cast<uint32_t>(ca[2]) << 16) |
					(static_cast<uint32_t>(ca[3]) << 24);
				uint32_t const lb =
					(static_cast<uint32_t>(cb[0]) << 0) |
					(static_cast<uint32_t>(cb[1]) << 8) |
					(static_cast<uint32_t>(cb[2]) << 16) |
					(static_cast<uint32_t>(cb[3]) << 24);

				int const r = _order_type::compareInt(ca,cb);

				if ( r > 0 )
				{
					std::cerr << "*order broken* " << std::endl;
					std::cerr << libmaus2::bambam::BamAlignmentDecoderBase::formatAlignment(
						ca,la,header,aux) << std::endl;
					std::cerr << libmaus2::bambam::BamAlignmentDecoderBase::formatAlignment(
						cb,lb,header,aux) << std::endl;
				}
			}

			prev = tc;
		}

		return ok;
	}
};



struct BamThreadPoolMergeProcessBufferInfoHeapComparator
{
	bool operator()(BamThreadPoolMergeProcessBufferInfo const & A, BamThreadPoolMergeProcessBufferInfo const & B)
	{
		return A.seqid > B.seqid;
	}
};

template<typename _order_type>
struct BamThreadPoolMergeProcessPackageHeapComparator
{
	typedef _order_type order_type;

	bool operator()(BamThreadPoolMergeProcessPackage<order_type> const & A, BamThreadPoolMergeProcessPackage<order_type> const & B) const
	{
		return A.seqid > B.seqid;
	}
	bool operator()(BamThreadPoolMergeProcessPackage<order_type> const * A, BamThreadPoolMergeProcessPackage<order_type> const * B) const
	{
		return A->seqid > B->seqid;
	}
};

#include <libmaus2/lz/SimpleCompressedInputBlockConcatBlock.hpp>
#include <libmaus2/lz/SimpleCompressedInputBlockConcat.hpp>

struct BamThreadPoolMergeParserState
{
	enum bam_thread_pool_merge_parser_state
	{
		bam_thread_pool_merge_parser_state_reading_blocklen = 0,
		bam_thread_pool_merge_parser_state_reading_block = 1
	};

	bam_thread_pool_merge_parser_state state;
	unsigned int blocklenred;
	uint32_t blocklen;
	uint32_t blockred;
	libmaus2::autoarray::AutoArray<uint8_t> gatherbuffer;

	BamThreadPoolMergeParserState()
	: state(bam_thread_pool_merge_parser_state_reading_blocklen), blocklenred(0), blocklen(0), gatherbuffer()
	{

	}
};

template<typename _order_type>
struct BamThreadPoolMergeHeapComparator
{
	typedef _order_type order_type;

	BamThreadPoolMergeHeapComparator() {}

	bool operator()(
		std::pair<uint64_t,uint8_t const *> const & A,
		std::pair<uint64_t,uint8_t const *> const & B
	)
	{
		int const r = order_type::compareInt(A.second+4,libmaus2::bambam::DecoderBase::getLEInteger(A.second,4),B.second+4,libmaus2::bambam::DecoderBase::getLEInteger(B.second,4));

		if ( r )
			return r > 0;
		else
			return A.first > B.first;
	}
};

template<typename _order_type>
struct BamThreadPoolMergeContextBase : public BamThreadPoolMergeContextBaseConstantsBase
{
	typedef _order_type order_type;
	typedef BamThreadPoolMergeContextBase<order_type> this_type;

	libmaus2::parallel::StdSpinLock cerrlock;

	libmaus2::parallel::SimpleThreadPool & TP;

	MergeInfo const & mergeinfo;
	uint64_t const numblocks;

	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolMergeReadPackage<order_type> > readFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolMergeDecompressPackage<order_type> > decompressFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolMergeProcessPackage<order_type> > processFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolMergeMergePackage<order_type> > mergeFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolMergeCompressPackage<order_type> > compressFreeList;
	libmaus2::parallel::SimpleThreadPoolWorkPackageFreeList<BamThreadPoolMergeWritePackage<order_type> > writeFreeList;

	typedef libmaus2::lz::SimpleCompressedInputBlockConcatBlock read_block_type;
	typedef read_block_type::unique_ptr_type read_block_ptr_type;


	libmaus2::autoarray::AutoArray<libmaus2::parallel::StdSpinLock::shared_ptr_type> readQueueLocks;
	libmaus2::autoarray::AutoArray<libmaus2::parallel::StdMutex::shared_ptr_type> readLocks;
	libmaus2::autoarray::AutoArray<libmaus2::parallel::StdSynchronousQueue<uint64_t>::unique_ptr_type> readBlocksFreeLists;
	libmaus2::autoarray::AutoArray<read_block_ptr_type> readBlocks;
	libmaus2::autoarray::AutoArray<uint64_t> readSeqIds;
	libmaus2::autoarray::AutoArray<libmaus2::lz::SimpleCompressedInputBlockConcat::unique_ptr_type> blockReaders;
	libmaus2::parallel::SynchronousCounter<uint64_t> blockReadersFinished;
	libmaus2::bitio::SynchronousBitVector blockReadersFinishedVector;
	libmaus2::parallel::LockedBool readFinished;

	typedef std::priority_queue<
		BamThreadPoolMergeProcessPackage<order_type> *,
		std::vector< BamThreadPoolMergeProcessPackage<order_type> * >,
		BamThreadPoolMergeProcessPackageHeapComparator<order_type>
	> process_pending_type;
	libmaus2::parallel::StdSpinLock processPendingLock;
	libmaus2::autoarray::AutoArray<process_pending_type> processPending;
	libmaus2::parallel::StdSpinLock processPendingNextLock;
	libmaus2::autoarray::AutoArray<uint64_t> processPendingNext;
	libmaus2::autoarray::AutoArray<BamThreadPoolMergeParserState> parserStates;
	libmaus2::autoarray::AutoArray<BamProcessBuffer::unique_ptr_type> processBuffers;
	libmaus2::autoarray::AutoArray<libmaus2::parallel::LockedQueue<uint64_t>::unique_ptr_type> processBuffersFreeLists;
	libmaus2::autoarray::AutoArray<
		typename libmaus2::parallel::StdSynchronousQueue < BamThreadPoolMergeProcessPackage<order_type> * >::unique_ptr_type
	> processStallList;
	libmaus2::parallel::SynchronousCounter<uint64_t> blockParsersFinished;
	libmaus2::parallel::LockedBool parsingFinished;

	libmaus2::parallel::StdSpinLock bufferInfoLock;
	std::vector<
		std::priority_queue<
			BamThreadPoolMergeProcessBufferInfo,
			std::vector<BamThreadPoolMergeProcessBufferInfo>,
			BamThreadPoolMergeProcessBufferInfoHeapComparator
		>
	> bufferInfoPending;
	std::vector<uint64_t> bufferInfoSeq;
	std::vector<uint64_t> bufferInfoNext;
	libmaus2::parallel::SynchronousCounter<uint64_t> bufferInfoInitComplete;

	libmaus2::parallel::StdSpinLock missingLock;
	libmaus2::parallel::LockedBool missingSet;
	uint64_t missing;

	libmaus2::parallel::StdSpinLock alPerBlockLock;
	std::vector< uint64_t > alPerBlockFinished;
	uint64_t alFinished;
	uint64_t alTotal;
	std::vector < BamThreadPoolMergeProcessBufferInfo > activeMergeBuffers;

	std::priority_queue<
		std::pair<uint64_t,uint8_t const *>,
		std::vector<
			std::pair<uint64_t,uint8_t const *>
		>,
		BamThreadPoolMergeHeapComparator<order_type>
	> mergeQ;

	uint64_t outputMerged;


	libmaus2::autoarray::AutoArray< libmaus2::lz::BgzfDeflateBase::unique_ptr_type > deflateBuffers;
	libmaus2::parallel::LockedQueue<uint64_t> deflateBuffersFreeList;
	uint64_t deflateBufferSeq;
	libmaus2::parallel::StdSpinLock deflateBufferNextLock;
	uint64_t deflateBufferNext;
	libmaus2::parallel::StdSpinLock writePendingLock;
	std::priority_queue<
		BamThreadPoolMergeWritePendingInfo,
		std::vector< BamThreadPoolMergeWritePendingInfo >,
		BamThreadPoolMergeWritePendingInfoHeapComparator
	> writePending;
	libmaus2::parallel::StdSynchronousQueue< BamThreadPoolMergeMergePackage<order_type> * > mergeStallList;
	libmaus2::autoarray::AutoArray<uint8_t> mergeWritePending;
	uint64_t mergeWritePendingFill;

	libmaus2::parallel::LockedBool mergeFinished;

	std::ostream & out;

	#if 0
	libmaus2::bambam::BamAlignment thisAlgn;
	uint64_t thisAlgnBlock;
	libmaus2::bambam::BamAlignment prevAlgn;
	uint64_t prevAlgnBlock;
	#endif

	libmaus2::parallel::SynchronousCounter<uint64_t> mergeWriteIn;
	uint64_t mergeWriteOut;

	static libmaus2::autoarray::AutoArray<libmaus2::parallel::StdSpinLock::shared_ptr_type> getSpinLockArray(uint64_t const n)
	{
		libmaus2::autoarray::AutoArray<libmaus2::parallel::StdSpinLock::shared_ptr_type> A(n);
		for ( uint64_t i = 0; i < n; ++i )
		{
			libmaus2::parallel::StdSpinLock::shared_ptr_type tptr(new libmaus2::parallel::StdSpinLock);
			A[i] = tptr;
		}

		return A;
	}

	static libmaus2::autoarray::AutoArray<libmaus2::parallel::StdMutex::shared_ptr_type> getMutexArray(uint64_t const n)
	{
		libmaus2::autoarray::AutoArray<libmaus2::parallel::StdMutex::shared_ptr_type> A(n);
		for ( uint64_t i = 0; i < n; ++i )
		{
			libmaus2::parallel::StdMutex::shared_ptr_type tptr(new libmaus2::parallel::StdMutex);
			A[i] = tptr;
		}

		return A;
	}


	BamThreadPoolMergeContextBase(
		libmaus2::parallel::SimpleThreadPool & rTP,
		MergeInfo const & rmergeinfo,
		libmaus2::lz::DecompressorObjectFactory & decfact,
		uint64_t const inputBlocksPerBlock,
		uint64_t const processBuffersPerBlock,
		uint64_t const processBuffersSize,
		uint64_t const numDeflateBuffers,
		int const level,
		std::ostream & rout
	)
	:
		cerrlock(),
		TP(rTP),
		mergeinfo(rmergeinfo),
		numblocks(mergeinfo.tmpfilenamedblocks.size()),
		readFreeList(),
		decompressFreeList(),
		readQueueLocks(getSpinLockArray(numblocks)),
		readLocks(getMutexArray(numblocks)),
		readBlocksFreeLists(numblocks),
		readBlocks(inputBlocksPerBlock * numblocks),
		readSeqIds(numblocks),
		blockReaders(numblocks),
		blockReadersFinished(0),
		blockReadersFinishedVector(numblocks),
		readFinished(false),
		processPending(numblocks),
		processPendingNextLock(),
		processPendingNext(numblocks),
		parserStates(numblocks),
		processBuffers(numblocks * processBuffersPerBlock),
		processBuffersFreeLists(numblocks),
		processStallList(numblocks),
		blockParsersFinished(0),
		parsingFinished(false),
		bufferInfoLock(),
		bufferInfoPending(numblocks),
		bufferInfoSeq(numblocks,0),
		bufferInfoNext(numblocks,0),
		bufferInfoInitComplete(0),
		missingLock(),
		missingSet(false),
		missing(0),
		alPerBlockFinished(numblocks,0),
		alFinished(0),
		alTotal(std::accumulate(mergeinfo.tmpfileblockcntsums.begin(),mergeinfo.tmpfileblockcntsums.end(),0ull)),
		activeMergeBuffers(numblocks),
		mergeQ(),
		outputMerged(0),
		deflateBuffers(numDeflateBuffers),
		deflateBuffersFreeList(),
		deflateBufferSeq(0),
		deflateBufferNext(0),
		writePendingLock(),
		writePending(),
		mergeStallList(),
		mergeWritePendingFill(0),
		mergeFinished(false),
		out(rout),
		mergeWriteIn(0),
		mergeWriteOut(0)
	{
		assert ( inputBlocksPerBlock );

		for ( uint64_t i = 0; i < readBlocksFreeLists.size(); ++i )
		{
			libmaus2::parallel::StdSynchronousQueue<uint64_t>::unique_ptr_type tptr(
				new libmaus2::parallel::StdSynchronousQueue<uint64_t>()
			);
			readBlocksFreeLists[i] = std::move(tptr);
		}
		for ( uint64_t i = 0; i < readBlocks.size(); ++i )
		{
			read_block_ptr_type tptr(new read_block_type(decfact));
			readBlocks[i] = std::move(tptr);
			readBlocksFreeLists[i/inputBlocksPerBlock]->enque(i);
		}
		for ( uint64_t i = 0; i < blockReaders.size(); ++i )
		{
			libmaus2::lz::SimpleCompressedInputBlockConcat::unique_ptr_type tptr(
				new libmaus2::lz::SimpleCompressedInputBlockConcat(mergeinfo.tmpfilenamedblocks[i])
			);
			blockReaders[i] = std::move(tptr);
		}
		for ( uint64_t i = 0; i < numblocks; ++i )
		{
			blockReadersFinishedVector.set(i,false);
		}
		for ( uint64_t i = 0; i < processBuffers.size(); ++i )
		{
			BamProcessBuffer::unique_ptr_type tptr(new BamProcessBuffer(processBuffersSize));
			processBuffers[i] = std::move(tptr);
		}
		for ( uint64_t i = 0; i < processBuffersFreeLists.size(); ++i )
		{
			libmaus2::parallel::LockedQueue<uint64_t>::unique_ptr_type tptr(new libmaus2::parallel::LockedQueue<uint64_t>);
			processBuffersFreeLists[i] = std::move(tptr);
		}
		for ( uint64_t i = 0; i < processBuffers.size(); ++i )
			processBuffersFreeLists[i / processBuffersPerBlock]->push_back(i);

		for ( uint64_t i = 0; i < processStallList.size(); ++i )
		{
			typename libmaus2::parallel::StdSynchronousQueue < BamThreadPoolMergeProcessPackage<order_type> * >::unique_ptr_type
				tptr(new libmaus2::parallel::StdSynchronousQueue < BamThreadPoolMergeProcessPackage<order_type> * >);
			processStallList[i] = std::move(tptr);
		}
		for ( uint64_t i = 0; i < deflateBuffers.size(); ++i )
		{
			libmaus2::lz::BgzfDeflateBase::unique_ptr_type tptr(
				new libmaus2::lz::BgzfDeflateBase(level,true)
			);
			deflateBuffers[i] = std::move(tptr);
			deflateBuffersFreeList.push_back(i);
		}
	}
};


template<typename _order_type>
struct BamThreadPoolMergeReadPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolMergeReadPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolMergeReadPackage<order_type> *>(P) != 0 );

		BamThreadPoolMergeReadPackage<order_type> & RP = *dynamic_cast<BamThreadPoolMergeReadPackage<order_type> *>(P);
		BamThreadPoolMergeContextBase<order_type> & contextbase = *(RP.contextbase);

		uint64_t const blockid = RP.blockid;

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "read " << blockid << std::endl;
		}
		#endif

		bool finishedLoop = (contextbase.blockReadersFinishedVector.get(blockid));

		libmaus2::parallel::ScopeStdMutex SPM(*(contextbase.readLocks[blockid]));

		while ( !finishedLoop )
		{
			uint64_t baseid = 0;
			bool baseidok = false;
			{
				libmaus2::parallel::ScopeStdSpinLock llock(*(contextbase.readQueueLocks[blockid]));
				baseidok = contextbase.readBlocksFreeLists[blockid]->peek(baseid);
			}

			#if 0
			{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "read " << blockid << " baseidok=" << baseidok << " list size " << contextbase.readBlocksFreeLists[blockid]->getFillState() << std::endl;
			}
			#endif

			if ( baseidok )
			{
				contextbase.blockReaders[blockid]->readBlock(*(contextbase.readBlocks[baseid]));

				if ( contextbase.readBlocks[baseid]->eof )
				{
					contextbase.blockReadersFinishedVector.set(blockid,true);
					uint64_t const numfinished = ++(contextbase.blockReadersFinished);

					if ( numfinished == contextbase.numblocks )
					{
						contextbase.readFinished.set(true);
						finishedLoop = true;
					}
				}

				uint64_t const rbaseid = contextbase.readBlocksFreeLists[blockid]->deque();
				assert ( baseid == rbaseid );

				BamThreadPoolMergeDecompressPackage<order_type> * decpack =
					contextbase.decompressFreeList.getPackage();
				*decpack = BamThreadPoolMergeDecompressPackage<order_type>(
					RP.contextbase,RP.blockid,baseid,contextbase.readSeqIds[blockid]++
				);

				tpi.enque(decpack);
			}
			else
			{
				finishedLoop = true;
			}
		}

		// return package to free list
		contextbase.readFreeList.returnPackage(dynamic_cast<BamThreadPoolMergeReadPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolMergeDecompressPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolMergeDecompressPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolMergeDecompressPackage<order_type> *>(P) != 0 );

		BamThreadPoolMergeDecompressPackage<order_type> & RP = *dynamic_cast<BamThreadPoolMergeDecompressPackage<order_type> *>(P);
		BamThreadPoolMergeContextBase<order_type> & contextbase = *(RP.contextbase);

		uint64_t const blockid = RP.blockid;
		uint64_t const baseid = RP.baseid;

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "decompress " << blockid << "," << baseid << "," << RP.seqid << std::endl;
		}
		#endif

		contextbase.readBlocks[baseid]->uncompressBlock();

		BamThreadPoolMergeProcessPackage<order_type> * procpack = contextbase.processFreeList.getPackage();
		*procpack = BamThreadPoolMergeProcessPackage<order_type>(
			RP.contextbase,RP.blockid,RP.baseid,RP.seqid
		);

		{
			libmaus2::parallel::ScopeStdSpinLock llprocessPendingLock(contextbase.processPendingLock);
			libmaus2::parallel::ScopeStdSpinLock llprocessPendingNextLock(contextbase.processPendingNextLock);
			contextbase.processPending[blockid].push(procpack);

			if ( contextbase.processPending[blockid].top()->seqid == contextbase.processPendingNext[blockid] )
			{
				BamThreadPoolMergeProcessPackage<order_type> * nextprocpack =
					contextbase.processPending[blockid].top();
				contextbase.processPending[blockid].pop();

				tpi.enque(nextprocpack);
			}
		}

		// return package to free list
		contextbase.decompressFreeList.returnPackage(dynamic_cast<BamThreadPoolMergeDecompressPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolMergeProcessPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	uint32_t decodeLEInteger4(uint8_t const * pc)
	{
		return
			(static_cast<uint32_t>(pc[0]) << 0) |
			(static_cast<uint32_t>(pc[1]) << 8) |
			(static_cast<uint32_t>(pc[2]) << 16) |
			(static_cast<uint32_t>(pc[3]) << 24);
	}

	virtual ~BamThreadPoolMergeProcessPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolMergeProcessPackage<order_type> *>(P) != 0 );

		BamThreadPoolMergeProcessPackage<order_type> & RP = *dynamic_cast<BamThreadPoolMergeProcessPackage<order_type> *>(P);
		BamThreadPoolMergeContextBase<order_type> & contextbase = *(RP.contextbase);

		uint64_t const blockid = RP.blockid;
		uint64_t const baseid = RP.baseid;

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "process blockid=" << blockid << ",baseid=" << baseid << ",seqid=" << RP.seqid << std::endl;
		}
		#endif

		BamThreadPoolMergeParserState & parserState = contextbase.parserStates[blockid];

		typename BamThreadPoolMergeContextBase<order_type>::read_block_type & datablock = *(contextbase.readBlocks[baseid]);
		uint8_t * const pa = datablock.O.begin();
		uint8_t * const pe = pa + datablock.uncompsize;
		uint8_t * pc = pa;

		uint64_t processBufferId = 0;
		bool stalled = ! (contextbase.processBuffersFreeLists[blockid]->tryDequeFront(processBufferId));
		bool running = (!stalled) && (pc != pe);
		BamProcessBuffer * processBuffer = (!stalled) ? contextbase.processBuffers[processBufferId].get() : 0;
		std::deque<uint64_t> finishedBuffers;

		#if 0
		if ( processBuffer )
		{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "process blockid=" << blockid << ",baseid=" << baseid << ",seqid=" << RP.seqid << " buffer fill "
				<< processBuffer->getFill()
				<< " running=" << running
				<< " stalled=" << stalled
				<< " pe-pc=" << (pe-pc)
				<< " blocklen=" << parserState.blocklen
				<< " blocklenred=" << parserState.blocklenred
				<< std::endl;
		}
		#endif

		while ( running )
		{
			switch ( parserState.state )
			{
				case BamThreadPoolMergeParserState::bam_thread_pool_merge_parser_state_reading_blocklen:
				{
					if (
						(!parserState.blocklenred)
						&&
						(pe-pc) >= 4
						&&
						(pe-pc) >= static_cast<std::ptrdiff_t>(4+(parserState.blocklen=decodeLEInteger4(pc)))
					)
					{
						while (
							running
							&&
							(pe-pc) >= 4
							&&
							(pe-pc) >= static_cast<std::ptrdiff_t>(4+(parserState.blocklen=decodeLEInteger4(pc)))
						)
						{
							if ( processBuffer->put(pc+4,parserState.blocklen) )
							{
								pc += 4+parserState.blocklen;
							}
							else
							{
								#if 0
								{
								libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
								std::cerr << "block " << blockid << " in block push for id " << processBufferId << std::endl;
								}
								#endif

								finishedBuffers.push_back(processBufferId);
								stalled = ! (contextbase.processBuffersFreeLists[blockid]->tryDequeFront(processBufferId));
								running = running && (!stalled);
								processBuffer = (!stalled) ? contextbase.processBuffers[processBufferId].get() : 0;
							}
						}

						assert ( ! parserState.blocklenred );
						parserState.blocklen = 0;
					}
					else
					{
						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "rest bl blockid=" << blockid << " seqid=" << RP.seqid
							<< " blocklen=" << parserState.blocklen
							<< " pe-pc=" << (pe-pc)
							<< " blocklenred=" << parserState.blocklenred
							<< std::endl;
						}
						#endif

						while ( pc != pe && parserState.blocklenred < 4 )
						{
							parserState.blocklen |= (static_cast<uint32_t>(*(pc++)) << (8*(parserState.blocklenred++)));
						}
						if ( parserState.blocklenred == 4 )
						{
							parserState.state = BamThreadPoolMergeParserState::bam_thread_pool_merge_parser_state_reading_block;
							parserState.blockred = 0;

							#if 0
							{
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
							std::cerr << "bl blockid=" << blockid << " seqid=" << RP.seqid << " blocklen=" << parserState.blocklen << std::endl;
							}
							#endif

							if ( parserState.gatherbuffer.size() < parserState.blocklen )
								parserState.gatherbuffer = libmaus2::autoarray::AutoArray<uint8_t>(parserState.blocklen,false);
						}
					}
					break;
				}
				case BamThreadPoolMergeParserState::bam_thread_pool_merge_parser_state_reading_block:
				{
					assert ( pe >= pc );
					uint32_t const av = pe-pc;
					uint32_t const tocopy = std::min(
						av,
						static_cast<uint32_t>(parserState.blocklen - parserState.blockred)
					);
					std::copy(pc,pc+tocopy,parserState.gatherbuffer.begin()+parserState.blockred);

					parserState.blockred += tocopy;
					pc += tocopy;

					if ( parserState.blockred == parserState.blocklen )
					{
						if ( processBuffer->put(parserState.gatherbuffer.begin(),parserState.blocklen) )
						{
							parserState.blocklen = 0;
							parserState.blocklenred = 0;
							parserState.state = BamThreadPoolMergeParserState::bam_thread_pool_merge_parser_state_reading_blocklen;
						}
						else
						{
							parserState.blockred -= tocopy;
							pc -= tocopy;

							#if 0
							{
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
							std::cerr << "block " << blockid << " ext block push for id " << processBufferId << std::endl;
							}
							#endif

							finishedBuffers.push_back(processBufferId);
							stalled = ! (contextbase.processBuffersFreeLists[blockid]->tryDequeFront(processBufferId));
							running = running && (!stalled);
							processBuffer = (!stalled) ? contextbase.processBuffers[processBufferId].get() : 0;
						}
					}
					break;
				}
			}

			running = running && (pc != pe);
		}

		bool const eof = datablock.eof;

		if ( eof && !stalled )
		{
			// insert last finished buffer
			finishedBuffers.push_back(processBufferId);

			#if 0
			{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "parsing finished for block " << blockid << std::endl;
			}
			#endif

			if ( ++contextbase.blockParsersFinished == contextbase.numblocks )
			{
				contextbase.parsingFinished.set(true);

				#if 1
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "parsing finished for all blocks." << std::endl;
				#endif
			}

		}

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "process blockid=" << blockid << ",baseid=" << baseid << ",seqid=" << RP.seqid << " finished " << finishedBuffers.size() << std::endl;
		}
		#endif

		// queue finished blocks
		for ( uint64_t i = 0; i < finishedBuffers.size(); ++i )
		{
			libmaus2::parallel::ScopeStdSpinLock lbufferInfoLock(contextbase.bufferInfoLock);
			BamThreadPoolMergeProcessBufferInfo const info(
				blockid,
				contextbase.bufferInfoSeq[blockid]++,
				finishedBuffers[i],
				datablock.eof && // input buffer is marked as eof
				(!stalled) && // no stall
				(i==finishedBuffers.size()-1), // this is the last queue buffer
				contextbase.processBuffers[finishedBuffers[i]].get()
			);
			contextbase.bufferInfoPending[blockid].push(info);

			#if 0
			{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);

			std::cerr << "checking blockid="
				<< blockid << " seqid " << info.seqid
				<< " bufferid " << finishedBuffers[i]
				<< std::endl;
			}

			info.checkOrder<order_type>(*(contextbase.mergeinfo.sheader));
			#endif

			if ( info.seqid == 0 )
				if ( (++contextbase.bufferInfoInitComplete) == contextbase.numblocks )
				{
					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "ready to run " << std::endl;
					}
					#endif

					//
					libmaus2::parallel::ScopeStdSpinLock lalPerBlockLock(contextbase.alPerBlockLock);

					for ( uint64_t j = 0; j < contextbase.numblocks; ++j )
					{
						if ( contextbase.alPerBlockFinished[j] != contextbase.mergeinfo.tmpfileblockcntsums[j] )
						{
							BamThreadPoolMergeProcessBufferInfo info = contextbase.bufferInfoPending[j].top();

							contextbase.activeMergeBuffers[j] = info;

							assert ( info.pc != info.pe );

							contextbase.mergeQ.push(
								std::pair<uint64_t,uint8_t const *>(
									j,info.buffer->ca + (*(info.pc++))
								)
							);

							contextbase.bufferInfoPending[j].pop();

							#if 0
							{
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
							std::cerr << "ready to run for " << j
								<< " " << contextbase.mergeinfo.tmpfileblockcntsums[j]
								<< " " << info.blockid
								<< " " << info.seqid
								<< " " << info.bufferid
								<< " " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(info.buffer->ca + info.pc[-1] + 4)
								<< std::endl;
							}
							#endif
						}
						else
						{
							#if 0
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
							std::cerr << "empty to run for " << j << " " << contextbase.mergeinfo.tmpfileblockcntsums[j] << std::endl;
							#endif
						}
					}

					BamThreadPoolMergeMergePackage<order_type> * mergepack =
						contextbase.mergeFreeList.getPackage();
					*mergepack = BamThreadPoolMergeMergePackage<order_type>(RP.contextbase);
					tpi.enque(mergepack);
				}
		}

		if ( ! stalled )
		{
			// reinsert process buffer if it is unfinished
			if ( ! eof )
				contextbase.processBuffersFreeLists[blockid]->push_front(processBufferId);

			// return read buffer
			{
				libmaus2::parallel::ScopeStdSpinLock llock(*(contextbase.readQueueLocks[blockid]));
				// check if list is empty
				bool const listempty = contextbase.readBlocksFreeLists[blockid]->empty();

				#if 0
				{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "parsed " << baseid << " for " << blockid << " without stall." << std::endl;
				}
				#endif

				// return buffer
				contextbase.readBlocksFreeLists[blockid]->enque(baseid);

				// queue read
				if ( listempty )
				{
					BamThreadPoolMergeReadPackage<order_type> * ptr = contextbase.readFreeList.getPackage();
					*ptr = BamThreadPoolMergeReadPackage<order_type>(RP.contextbase,blockid);
					tpi.enque(ptr);
				}
			}

			libmaus2::parallel::ScopeStdSpinLock llprocessPendingLock(contextbase.processPendingLock);
			libmaus2::parallel::ScopeStdSpinLock llprocessPendingNextLock(contextbase.processPendingNextLock);

			// ready for next block
			contextbase.processPendingNext[blockid] += 1;

			if (
				(!contextbase.processPending[blockid].empty())
				&&
				contextbase.processPending[blockid].top()->seqid == contextbase.processPendingNext[blockid]
			)
			{
				BamThreadPoolMergeProcessPackage<order_type> * nextprocpack =
					contextbase.processPending[blockid].top();
				contextbase.processPending[blockid].pop();

				tpi.enque(nextprocpack);
			}
		}
		else
		{
			#if 0
			{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "stalling blockid " << blockid << std::endl;
			}
			#endif

			// move rest of block to front of buffer
			std::memmove(pa,pc,pe-pc);
			datablock.uncompsize = (pe-pc);

			// insert buffer in stall list
			contextbase.processStallList[blockid]->enque(dynamic_cast<BamThreadPoolMergeProcessPackage<order_type> *>(P));
			assert (
				blockid == dynamic_cast<BamThreadPoolMergeProcessPackage<order_type> *>(P)->blockid
			);
		}

		{
			libmaus2::parallel::ScopeStdSpinLock lbufferInfoLock(contextbase.bufferInfoLock);
			libmaus2::parallel::ScopeStdSpinLock lmissingLock(contextbase.missingLock);
			if ( contextbase.missingSet.get() )
			{
				uint64_t const missing = contextbase.missing;

				if (
					contextbase.bufferInfoPending[missing].size()
					&&
					(
						contextbase.bufferInfoPending[missing].top().seqid ==
						contextbase.bufferInfoNext[missing]
					)
				)
				{
					BamThreadPoolMergeProcessBufferInfo info = contextbase.bufferInfoPending[missing].top();
					contextbase.activeMergeBuffers[missing] = info;
					contextbase.missingSet.set(false);

					assert ( info.pc != info.pe );

					contextbase.mergeQ.push(
						std::pair<uint64_t,uint8_t const *>(
							missing,info.buffer->ca + (*(info.pc++))
						)
					);

					contextbase.bufferInfoPending[missing].pop();

					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "process() adding missing for " << missing << std::endl;
					}
					#endif

					BamThreadPoolMergeMergePackage<order_type> * mergepack =
						contextbase.mergeFreeList.getPackage();
					*mergepack = BamThreadPoolMergeMergePackage<order_type>(RP.contextbase);
					tpi.enque(mergepack);

				}
			}
		}

		if ( ! stalled )
			// return package to free list
			contextbase.processFreeList.returnPackage(dynamic_cast<BamThreadPoolMergeProcessPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolMergeMergePackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	uint32_t decodeLE4(uint8_t const * v)
	{
		return
			(static_cast<uint32_t>(v[0]) << 0) |
			(static_cast<uint32_t>(v[1]) << 8) |
			(static_cast<uint32_t>(v[2]) << 16) |
			(static_cast<uint32_t>(v[3]) << 24) ;
	}

	static void enqueCompress(
		BamThreadPoolMergeMergePackage<order_type> & RP,
		uint64_t const deflatebufferid,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		BamThreadPoolMergeCompressPackage<order_type> * ccpack =
			RP.contextbase->compressFreeList.getPackage();
		*ccpack = BamThreadPoolMergeCompressPackage<order_type>(
			RP.contextbase,
			deflatebufferid,
			RP.contextbase->deflateBufferSeq++
		);

		tpi.enque(ccpack);
	}

	virtual ~BamThreadPoolMergeMergePackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolMergeMergePackage<order_type> *>(P) != 0 );

		BamThreadPoolMergeMergePackage<order_type> & RP = *dynamic_cast<BamThreadPoolMergeMergePackage<order_type> *>(P);
		BamThreadPoolMergeContextBase<order_type> & contextbase = *(RP.contextbase);

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "MERGE, mergeQ.size()=" << contextbase.mergeQ.size() << std::endl;
		}
		#endif

		libmaus2::parallel::ScopeStdSpinLock lalPerBlockLock(contextbase.alPerBlockLock);

		uint64_t deflatebufferid = 0;
		bool stalled = !contextbase.deflateBuffersFreeList.tryDequeFront(deflatebufferid);
		libmaus2::lz::BgzfDeflateBase * deflateBuffer =
			stalled ? 0 : contextbase.deflateBuffers[deflatebufferid].get();
		assert ( deflateBuffer == 0 || deflateBuffer->pc != deflateBuffer->pe );
		std::deque<uint64_t> compressPendingBuffer;

		// handle pending write data
		while ( (!stalled) && (contextbase.mergeWritePendingFill) )
		{
			uint64_t const spaceinbuffer = (deflateBuffer->pe - deflateBuffer->pc);
			uint64_t const bufferwrite = std::min(spaceinbuffer,contextbase.mergeWritePendingFill);

			std::copy(
				contextbase.mergeWritePending.begin(),
				contextbase.mergeWritePending.begin()+bufferwrite,
				deflateBuffer->pc
			);

			deflateBuffer->pc += bufferwrite;

			if ( bufferwrite != contextbase.mergeWritePendingFill )
				// move rest to front of pending buffer
				std::memmove(
					contextbase.mergeWritePending.begin(),
					contextbase.mergeWritePending.begin() + bufferwrite,
					contextbase.mergeWritePendingFill - bufferwrite
				);

			contextbase.mergeWritePendingFill -= bufferwrite;

			// check whether buffer is full now
			if ( deflateBuffer->pc == deflateBuffer->pe )
			{
				// pass to compression
				contextbase.mergeWriteIn += 1;
				enqueCompress(RP,deflatebufferid,tpi);
				// compressPendingBuffer.push_back(deflatebufferid);
				// try to get next buffer
				stalled = !contextbase.deflateBuffersFreeList.tryDequeFront(deflatebufferid);
				// set new buffer pointer
				deflateBuffer = stalled ? 0 : contextbase.deflateBuffers[deflatebufferid].get();
			}
		}

		bool running = (contextbase.mergeQ.size() != 0) && (!stalled);
		bool finishflag = false;

		while ( running )
		{
			std::pair<uint64_t,uint8_t const *> const P = contextbase.mergeQ.top();
			contextbase.mergeQ.pop();

			uint32_t const blocklen = decodeLE4(P.second);
			uint8_t const * pa = P.second;
			uint8_t const * pc = pa;
			uint8_t const * pe = pa + blocklen + 4;

			#if 0
			if ( contextbase.thisAlgn.D.size() < blocklen )
				contextbase.thisAlgn.D = libmaus2::bambam::BamAlignment::D_array_type(blocklen,false);
			contextbase.thisAlgn.blocksize = blocklen;
			contextbase.thisAlgnBlock = P.first;
			std::copy(pa+4,pe,contextbase.thisAlgn.D.begin());

			if ( contextbase.prevAlgn.blocksize )
			{
				if (
					order_type::compareInt(contextbase.prevAlgn.D.begin(),contextbase.thisAlgn.D.begin()) > 0
				)
				{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "order wrong " << contextbase.prevAlgnBlock << " " << contextbase.thisAlgnBlock << std::endl;
					std::cerr << contextbase.prevAlgn.formatAlignment(*(contextbase.mergeinfo.sheader)) << std::endl;
					std::cerr << contextbase.thisAlgn.formatAlignment(*(contextbase.mergeinfo.sheader)) << std::endl;
				}
			}

			contextbase.prevAlgn.copyFrom(contextbase.thisAlgn);
			contextbase.prevAlgnBlock = contextbase.thisAlgnBlock;
			#endif

			while ( pc != pe )
			{
				// number of bytes still pending
				uint64_t const writepending = pe-pc;
				// amount of space in buffer
				uint64_t const bufferspace = deflateBuffer->pe - deflateBuffer->pc;

				if ( bufferspace )
				{
					// copy as much as possible to output buffer
					uint64_t const tocopy = std::min(writepending,bufferspace);
					// copy
					std::copy(pc,pc+tocopy,deflateBuffer->pc);

					// advance pointers
					pc += tocopy;
					deflateBuffer->pc += tocopy;
				}

				// if buffer is full
				if ( deflateBuffer->pc == deflateBuffer->pe )
				{
					// pass to compression
					contextbase.mergeWriteIn += 1;
					enqueCompress(RP,deflatebufferid,tpi);
					// compressPendingBuffer.push_back(deflatebufferid);
					// try to get next buffer
					stalled = !contextbase.deflateBuffersFreeList.tryDequeFront(deflatebufferid);
					// set new buffer pointer
					deflateBuffer = stalled ? 0 : contextbase.deflateBuffers[deflatebufferid].get();

					// no more buffers?
					if ( stalled )
					{
						// copy pending data to pending buffer
						uint64_t const writepending = pe-pc;

						// check buffer size
						if ( contextbase.mergeWritePending.size() < writepending )
							contextbase.mergeWritePending = libmaus2::autoarray::AutoArray<uint8_t>(writepending,false);

						// copy data
						std::copy(pc,pe,contextbase.mergeWritePending.begin());
						// set number of bytes in buffer
						contextbase.mergeWritePendingFill = writepending;

						// quit loop, there is no more buffer space available
						running = false;
						// quit inner loop
						pc += writepending;
						// sanity check
						assert ( pc == pe );
					}
				}
			}

			if ( ((++contextbase.outputMerged) & (1024*1024-1)) == 0 )
			{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "[V] " << contextbase.outputMerged << std::endl;
			}

			#if 0
			{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "NAME " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(P.second+4)
				<< " " << P.first
				<< " " << libmaus2::bambam::BamAlignmentDecoderBase::getRefID(P.second+4)
				<< " " << libmaus2::bambam::BamAlignmentDecoderBase::getPos(P.second+4)
				<< std::endl;
			}
			#endif

			contextbase.alPerBlockFinished[P.first]++;

			if (
				contextbase.alPerBlockFinished[P.first] !=
				contextbase.mergeinfo.tmpfileblockcntsums[P.first]
			)
			{
				BamThreadPoolMergeProcessBufferInfo & info = contextbase.activeMergeBuffers[P.first];

				if ( info.pc != info.pe )
				{
					contextbase.mergeQ.push(
						std::pair<uint64_t,uint8_t const *>(
							P.first,
							info.buffer->ca + (*(info.pc++))
						)
					);
				}
				else
				{
					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "out of data for block " << P.first << std::endl;
					}
					#endif

					// reset buffer
					info.buffer->reset();
					//
					uint64_t const freebufferid = info.bufferid;

					libmaus2::parallel::ScopeStdSpinLock lbufferInfoLock(contextbase.bufferInfoLock);
					libmaus2::parallel::ScopeStdSpinLock lmissingLock(contextbase.missingLock);

					// switch to next
					contextbase.bufferInfoNext[P.first]++;

					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "pending size " << contextbase.bufferInfoPending[P.first].size() << std::endl;
					if ( contextbase.bufferInfoPending[P.first].size() )
						std::cerr << "top " << contextbase.bufferInfoPending[P.first].top().seqid << std::endl;
					std::cerr << "expecting " << contextbase.bufferInfoNext[P.first] << std::endl;
					}
					#endif

					#if 0
					assert (
						contextbase.bufferInfoPending[P.first].size()
						&&
						contextbase.bufferInfoNext[P.first] == contextbase.bufferInfoPending[P.first].top().seqid
					);

					contextbase.bufferInfoPending[P.first].pop();
					#endif


					// check if next block is already in queue
					if (
						contextbase.bufferInfoPending[P.first].size()
						&&
						(
							contextbase.bufferInfoPending[P.first].top().seqid ==
							contextbase.bufferInfoNext[P.first]
						)
					)
					{
						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "next block already waiting." << std::endl;
						}
						#endif

						contextbase.activeMergeBuffers[P.first] =
							contextbase.bufferInfoPending[P.first].top();
						contextbase.bufferInfoPending[P.first].pop();

						BamThreadPoolMergeProcessBufferInfo & info = contextbase.activeMergeBuffers[P.first];

						contextbase.mergeQ.push(
							std::pair<uint64_t,uint8_t const *>(
								P.first,
								info.buffer->ca + (*(info.pc++))
							)
						);

						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "new queue size is " << contextbase.mergeQ.size() << std::endl;
						}
						#endif
					}
					// otherwise note that we are waiting for it
					else
					{
						#if 0
						std::cerr << "marking " << P.first << " as missing." << std::endl;
						std::cerr << "contextbase.bufferInfoPending[P.first].size()=" << contextbase.bufferInfoPending[P.first].size() <<std::endl;
						std::cerr << "expecting " << contextbase.bufferInfoNext[P.first] << std::endl;
						if ( contextbase.bufferInfoPending[P.first].size() )
							std::cerr << "top is " << contextbase.bufferInfoPending[P.first].top().seqid << std::endl;
						#endif

						contextbase.missing = P.first;
						contextbase.missingSet.set(true);

						// we are out of data for this block
						running = false;
					}

					#if 0
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "adding " << freebufferid << " to freelist of " << P.first << std::endl;
					}
					#endif

					// add it to the free list
					contextbase.processBuffersFreeLists[P.first]->push_back(freebufferid);

					// reinsert stalled package if there is any
					BamThreadPoolMergeProcessPackage<order_type> * stalled = 0;
					bool ok = contextbase.processStallList[P.first]->trydeque(stalled);
					if ( ok )
					{
						assert ( stalled->blockid == P.first );

						#if 0
						{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "reinserting stalled block " << stalled->blockid << std::endl;
						}
						#endif

						tpi.enque(stalled);
					}
					else
					{
						#if 0
						libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "stall list is empty." << std::endl;
						#endif

					}
				}
			}
			else
			{
				#if 0
				{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "all data for " << P.first << " seen." << std::endl;
				}
				#endif

				if ( contextbase.mergeQ.size() == 0 )
				{
					{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "queue is empty, terminating loop." << std::endl;
					}

					running = false;

					finishflag = true;

					// tpi.terminate();
				}
			}
		}

		if ( finishflag )
			contextbase.mergeFinished.set(true);

		if ( ! stalled )
		{
			if ( finishflag )
			{
				contextbase.mergeWriteIn += 1;
				enqueCompress(RP,deflatebufferid,tpi);
				// compressPendingBuffer.push_back(deflatebufferid);

				{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "should write last buffer" << std::endl;
				}
			}
			else
			{
				{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "no stall, reinsering buffer" << std::endl;
				}

				contextbase.deflateBuffersFreeList.push_front(deflatebufferid);
			}
		}

		#if 0
		for ( uint64_t i = 0; i < compressPendingBuffer.size(); ++i )
			enqueCompress(RP,compressPendingBuffer[i],tpi);
		#endif

		if ( stalled )
			contextbase.mergeStallList.enque(dynamic_cast<BamThreadPoolMergeMergePackage<order_type> *>(P));
		else
			contextbase.mergeFreeList.returnPackage(dynamic_cast<BamThreadPoolMergeMergePackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolMergeCompressPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolMergeCompressPackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolMergeCompressPackage<order_type> *>(P) != 0 );

		BamThreadPoolMergeCompressPackage<order_type> & RP = *dynamic_cast<BamThreadPoolMergeCompressPackage<order_type> *>(P);
		BamThreadPoolMergeContextBase<order_type> & contextbase = *(RP.contextbase);

		// compress
		libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo const FI =
			contextbase.deflateBuffers[RP.baseid]->flush(true);

		// pending info
		BamThreadPoolMergeWritePendingInfo const pendinf(RP.baseid,RP.seqid,FI);

		// queue write
		{
		libmaus2::parallel::ScopeStdSpinLock swritePendingLock(contextbase.writePendingLock);
		contextbase.writePending.push(pendinf);
		}

		// check if next write in line is available
		{
			libmaus2::parallel::ScopeStdSpinLock ldeflateBufferNextLock(contextbase.deflateBufferNextLock);
			libmaus2::parallel::ScopeStdSpinLock swritePendingLock(contextbase.writePendingLock);
			if (
				contextbase.writePending.size()
				&&
				contextbase.deflateBufferNext == contextbase.writePending.top().seqid
			)
			{
				BamThreadPoolMergeWritePackage<order_type> * wpack = contextbase.writeFreeList.getPackage();
				*wpack = BamThreadPoolMergeWritePackage<order_type>(
					RP.contextbase,
					contextbase.writePending.top()
				);

				contextbase.writePending.pop();

				tpi.enque(wpack);
			}
		}

		contextbase.compressFreeList.returnPackage(dynamic_cast<BamThreadPoolMergeCompressPackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolMergeWritePackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef _order_type order_type;

	virtual ~BamThreadPoolMergeWritePackageDispatcher() {}
	virtual void dispatch(
		libmaus2::parallel::SimpleThreadWorkPackage * P,
		libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & tpi
	)
	{
		assert ( dynamic_cast<BamThreadPoolMergeWritePackage<order_type> *>(P) != 0 );

		BamThreadPoolMergeWritePackage<order_type> & RP = *dynamic_cast<BamThreadPoolMergeWritePackage<order_type> *>(P);
		BamThreadPoolMergeContextBase<order_type> & contextbase = *(RP.contextbase);

		BamThreadPoolMergeWritePendingInfo const & pendinf = RP.info;

		#if 0
		{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "write " << RP.info.seqid << std::endl;
		}
		#endif

		uint64_t const compsize = pendinf.flushinfo.getCompressedSize();
		contextbase.out.write(
			reinterpret_cast<char const *>(contextbase.deflateBuffers[pendinf.baseid]->outbuf.begin()),
			compsize
		);

		contextbase.mergeWriteOut += 1;

		{
			libmaus2::parallel::ScopeStdSpinLock ldeflateBufferNextLock(contextbase.deflateBufferNextLock);
			libmaus2::parallel::ScopeStdSpinLock swritePendingLock(contextbase.writePendingLock);

			contextbase.deflateBufferNext += 1;

			#if 0
			{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "pending in write " << contextbase.writePending.size() << std::endl;
			if ( contextbase.writePending.size() )
				std::cerr << "top " << contextbase.writePending.top().seqid << " next " << contextbase.deflateBufferNext << std::endl;
			}
			#endif

			if (
				contextbase.writePending.size()
				&&
				contextbase.deflateBufferNext == contextbase.writePending.top().seqid
			)
			{
				BamThreadPoolMergeWritePackage<order_type> * wpack = contextbase.writeFreeList.getPackage();
				*wpack = BamThreadPoolMergeWritePackage<order_type>(
					RP.contextbase,
					contextbase.writePending.top()
				);

				contextbase.writePending.pop();

				tpi.enque(wpack);
			}
		}

		// mark buffer as free
		contextbase.deflateBuffersFreeList.push_back(pendinf.baseid);

		// unstall merging if stalled
		BamThreadPoolMergeMergePackage<order_type> * mergepack = 0;
		if ( contextbase.mergeStallList.trydeque(mergepack) )
			tpi.enque(mergepack);

		if (
			contextbase.mergeFinished.get() &&
			(contextbase.mergeWriteIn.get() == contextbase.mergeWriteOut)
		)
		{
			{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "writing done, terminating" << std::endl;
			}

			tpi.terminate();
		}

		contextbase.writeFreeList.returnPackage(dynamic_cast<BamThreadPoolMergeWritePackage<order_type> *>(P));
	}
};

template<typename _order_type>
struct BamThreadPoolMergeContext : public BamThreadPoolMergeContextBase<_order_type>
{
	typedef _order_type order_type;

	BamThreadPoolMergeContext(
		libmaus2::parallel::SimpleThreadPool & TP,
		MergeInfo const & mergeinfo,
		libmaus2::lz::DecompressorObjectFactory	& decfact,
		uint64_t const inputBlocksPerBlock,
		uint64_t const processBuffersPerBlock,
		uint64_t const processBuffersSize,
		uint64_t const numDeflateBuffers,
		int const level,
		std::ostream & out
	)
	: BamThreadPoolMergeContextBase<order_type>(
		TP,mergeinfo,decfact,inputBlocksPerBlock,
		processBuffersPerBlock,processBuffersSize,numDeflateBuffers,level,out)
	{

	}

	void startup()
	{
		for ( uint64_t i = 0; i < BamThreadPoolMergeContextBase<_order_type>::numblocks; ++i )
		{
			BamThreadPoolMergeReadPackage<order_type> * ptr = BamThreadPoolMergeContextBase<_order_type>::readFreeList.getPackage();
			*ptr = BamThreadPoolMergeReadPackage<order_type>(this,i);
			BamThreadPoolMergeContextBase<_order_type>::TP.enque(ptr);
		}
	}
};

template<typename _order_type>
void bamparsort(libmaus2::util::ArgInfo const & arginfo, std::string const & neworder)
{
	typedef _order_type order_type;
	MergeInfo const mergeinfo = produceSortedBlocks<order_type>(arginfo);

	#if 0
	std::cerr << "CHECKING SORTING OF BLOCKS..." << std::endl;
	{
	libmaus2::autoarray::AutoArray<libmaus2::aio::InputStreamInstance::unique_ptr_type> inputfiles(mergeinfo.tmpfilenames.size());
	libmaus2::lz::ZlibDecompressorObjectFactory decfact;
	for ( uint64_t i = 0; i < inputfiles.size(); ++i )
	{
		libmaus2::aio::InputStreamInstance::unique_ptr_type tptr(new libmaus2::aio::InputStreamInstance(mergeinfo.tmpfilenames[i]));
		inputfiles[i] = std::move(tptr);
	}

	libmaus2::autoarray::AutoArray<libmaus2::lz::SimpleCompressedConcatInputStream<std::istream>::unique_ptr_type> concfiles(mergeinfo.tmpfileblocks.size());
	for ( uint64_t i = 0; i < mergeinfo.tmpfileblocks.size(); ++i )
	{
		std::vector<libmaus2::lz::SimpleCompressedConcatInputStreamFragment<std::istream> > fragments;

		for ( uint64_t j = 0; j < mergeinfo.tmpfileblocks[i].size(); ++j )
			fragments.push_back(
				libmaus2::lz::SimpleCompressedConcatInputStreamFragment<std::istream>(
					mergeinfo.tmpfileblocks[i][j],
					inputfiles[j].get()
				)
			);

		libmaus2::lz::SimpleCompressedConcatInputStream<std::istream>::unique_ptr_type tptr(
			new libmaus2::lz::SimpleCompressedConcatInputStream<std::istream>(fragments,decfact)
		);
		concfiles[i] = std::move(tptr);
	}

	for ( uint64_t i = 0; i < mergeinfo.tmpfileblocks.size(); ++i )
	{
		libmaus2::bambam::BamAlignment prev, cur;

		for ( uint64_t j = 0; j < mergeinfo.tmpfileblockcntsums[i]; ++j )
		{
			::libmaus2::bambam::BamDecoder::readAlignmentGz(*concfiles[i],cur,0 /* no header for validation */,false /* no validation */);

			if ( prev.blocksize )
			{
				int const r = _order_type::compareInt(
					prev.D.begin(),
					cur.D.begin()
				);

				if ( r > 0 )
				{
					std::cerr << "NO" << std::endl;
				}
			}

			prev.copyFrom(cur);
		}
	}

	}
	std::cerr << "CHECKING SORTING OF BLOCKS DONE." << std::endl;

	std::cerr << "CHECKING SORTING OF BLOCKS BY BLOCK CONCAT." << std::endl;
	{
		for ( uint64_t i = 0; i < mergeinfo.tmpfilenamedblocks.size(); ++i )
		{
			std::cerr << "block " << (i) << "/" << mergeinfo.tmpfilenamedblocks.size() << std::endl;

			libmaus2::lz::SimpleCompressedInputBlockConcat conc(mergeinfo.tmpfilenamedblocks[i]);
			libmaus2::lz::ZlibDecompressorObjectFactory decfact;
			libmaus2::lz::SimpleCompressedInputBlockConcatBlock block(decfact);
			std::ostringstream ostr;

			while ( conc.readBlock(block) )
			{
				block.uncompressBlock();
				ostr.write(
					reinterpret_cast<char const *>(block.O.begin()),
					block.uncompsize
				);

				if ( block.eof )
					break;
			}

			std::string const sdata = ostr.str();
			char const * cdata = sdata.c_str();
			uint8_t const * udata = reinterpret_cast<uint8_t const *>(cdata);
			uint64_t datalen = sdata.size();
			uint8_t const * udataend = udata + datalen;

			uint8_t const * prev = 0;
			uint32_t prevlen = 0;
			uint64_t alcnt = 0;
			uint64_t failcnt = 0;
			int64_t firstfail = -1;

			while ( udata != udataend )
			{
				uint32_t const len =
					(static_cast<uint32_t>(udata[0]) << 0) |
					(static_cast<uint32_t>(udata[1]) << 8) |
					(static_cast<uint32_t>(udata[2]) << 16) |
					(static_cast<uint32_t>(udata[3]) << 24);

				if ( prev )
				{
					int const r = _order_type::compareInt(
						prev+4,
						udata+4
					);

					if ( r > 0 )
					{
						if ( firstfail < 0 )
							firstfail = alcnt;
						// std::cerr << "NO" << std::endl;
						++failcnt;
					}
				}
				alcnt += 1;

				prevlen = len;
				prev = udata;
				udata += len+4;
			}

			std::cerr << "block " << (i) << "/" << mergeinfo.tmpfilenamedblocks[i].size() << " done, alcnt=" << alcnt << " failcnt=" << failcnt << " first fail=" << firstfail << std::endl;
		}
	}
	std::cerr << "CHECKING SORTING OF BLOCKS DONE BY BLOCK CONCAT DONE." << std::endl;
	#endif

	{

	// write bam header
	{
		std::ostringstream headerostr;
		libmaus2::lz::BgzfOutputStream writer(headerostr);
		mergeinfo.sheader->changeSortOrder(neworder);
		mergeinfo.sheader->serialise(writer);
		writer.flush();
		std::string const & sheader = headerostr.str();
		std::cout.write(sheader.c_str(),sheader.size());
	}

	std::string const tmpfilenamebase = arginfo.getDefaultTmpFileName();

	#if defined(_OPENMP)
	uint64_t const numthreads = omp_get_max_threads();
	#else
	uint64_t const numthreads = 1;
	#endif

	// thread pool
	libmaus2::parallel::SimpleThreadPool TP(numthreads);

	// package dispatchers
	BamThreadPoolMergeReadPackageDispatcher<order_type> readdispatcher;
	TP.registerDispatcher(BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_read,&readdispatcher);
	BamThreadPoolMergeDecompressPackageDispatcher<order_type> decompressdispatcher;
	TP.registerDispatcher(BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_decompress,&decompressdispatcher);
	BamThreadPoolMergeProcessPackageDispatcher<order_type> processdispatcher;
	TP.registerDispatcher(BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_process,&processdispatcher);
	BamThreadPoolMergeMergePackageDispatcher<order_type> mergedispatcher;
	TP.registerDispatcher(BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_merge,&mergedispatcher);
	BamThreadPoolMergeCompressPackageDispatcher<order_type> compressdispatcher;
	TP.registerDispatcher(BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_compress,&compressdispatcher);
	BamThreadPoolMergeWritePackageDispatcher<order_type> writedispatcher;
	TP.registerDispatcher(BamThreadPoolMergeContextBaseConstantsBase::bamthreadpooldecodecontextbase_dispatcher_id_write,&writedispatcher);

	libmaus2::lz::ZlibDecompressorObjectFactory decfact;
	uint64_t const numblocks = mergeinfo.tmpfilenamedblocks.size();
	uint64_t const mem = arginfo.getValueUnsignedNumeric<uint64_t>("mem",16*1024ull*1024ull*1024ull);
	// input blocks, output blocks, process buffers
	uint64_t const inputBlockMemory = 0.1 * mem;
	uint64_t const inputMemoryPerBlock = std::max ( static_cast<uint64_t>(1), (inputBlockMemory + numblocks-1)/numblocks);
	uint64_t const inputBlocksPerBlock = std::max ( static_cast<uint64_t>(1), (inputMemoryPerBlock + (2*64*1024-1))/(2*64*1024));

	uint64_t const processBlockMemory = 0.6 * mem;
	uint64_t const processBuffersPerBlock = 16;
	uint64_t const processBuffersSize = ( processBlockMemory + (processBuffersPerBlock*numblocks) - 1 ) / (processBuffersPerBlock*numblocks);

	uint64_t const deflateMemory = 0.1 * mem;
	uint64_t const numDeflateBlocks = std::max ( static_cast<uint64_t>(1), (deflateMemory + (2*64*1024-1)) / (2*64*1024) );

	std::cerr << "input blocks per block: " << inputBlocksPerBlock << std::endl;
	std::cerr << "processBuffersPerBlock: " << processBuffersPerBlock << std::endl;
	std::cerr << "processBuffersSize:     " << processBuffersSize << std::endl;
	std::cerr << "numDeflateBlocks:       " << numDeflateBlocks << std::endl;

	BamThreadPoolMergeContext<order_type> mergecontext(TP,mergeinfo,decfact,
		inputBlocksPerBlock,
		processBuffersPerBlock,processBuffersSize,
		numDeflateBlocks,
		libmaus2::lz::DeflateDefaults::getDefaultLevel(),
		std::cout);

	mergecontext.startup();
	TP.join();

	// write bam footer
	{
		std::ostringstream footerostr;
		libmaus2::lz::BgzfOutputStream writer(footerostr);
		writer.flush();
		writer.addEOFBlock();
		std::string const & sfooter = footerostr.str();
		std::cout.write(sfooter.c_str(),sfooter.size());
	}


	std::cout.flush();

	exit(0);
	}

	mergeSortedBlocks<order_type>(arginfo,mergeinfo,neworder);
}


int main(int argc, char * argv[])
{
	try
	{
		libmaus2::util::ArgInfo const arginfo(argc,argv);

		std::string const sortorder = arginfo.getValue<std::string>("SO","coordinate");

		if ( sortorder == "coordinate" )
			bamparsort<libmaus2::bambam::BamAlignmentPosComparator>(arginfo,"coordinate");
		else if ( sortorder == "queryname" )
			bamparsort<libmaus2::bambam::BamAlignmentNameComparator>(arginfo,"queryname");
		else
		{
			std::cerr << "[E] unknown sort order " << sortorder << std::endl;
			return EXIT_FAILURE;
		}
	}
	catch(std::exception const & ex)
	{
		std::cerr << ex.what() << std::endl;
		return EXIT_FAILURE;
	}
}
