/**
    biobambam2
    Copyright (C) 2009-2019 German Tischler

    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/parallel/SimpleThreadPool.hpp>
#include <libmaus2/parallel/SimpleThreadWorkPackageDispatcher.hpp>
#include <libmaus2/util/ArgParser.hpp>
#include <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/util/FiniteSizeHeap.hpp>
#include <libmaus2/util/PrefixSums.hpp>
#include <libmaus2/parallel/NumCpus.hpp>
#include <libmaus2/parallel/LockedFreeList.hpp>
#include <libmaus2/parallel/LockedGrowingFreeList.hpp>
#include <libmaus2/lz/ZlibInterface.hpp>
#include <libmaus2/lz/GzipHeader.hpp>
#include <libmaus2/bambam/BamAlignmentEncoderBase.hpp>
#include <libmaus2/bambam/parallel/FragmentAlignmentBuffer.hpp>
#include <libmaus2/bambam/parallel/FragmentAlignmentBufferAllocator.hpp>
#include <libmaus2/bambam/parallel/FragmentAlignmentBufferTypeInfo.hpp>
#include <libmaus2/lz/BgzfDeflateOutputBufferBase.hpp>
#include <libmaus2/lz/BgzfDeflateOutputBufferBaseAllocator.hpp>
#include <libmaus2/lz/BgzfDeflateOutputBufferBaseTypeInfo.hpp>
#include <libmaus2/lz/BgzfDeflateZStreamBaseAllocator.hpp>
#include <libmaus2/lz/BgzfDeflateZStreamBaseTypeInfo.hpp>
#include <libmaus2/lz/BgzfDeflateZStreamBase.hpp>
#include <libmaus2/lz/BgzfDeflate.hpp>
#include <libmaus2/bambam/BamBlockWriterBaseFactory.hpp>
#include <libmaus2/bambam/ChecksumsFactory.hpp>
#include <libmaus2/digest/DigestFactory.hpp>
#include <libmaus2/util/Histogram.hpp>
#include <biobambam2/KmerPoisson.hpp>
#include <libmaus2/bambam/BamHeaderParserState.hpp>
#include <libmaus2/util/GetCObject.hpp>
#include <libmaus2/util/LELoad.hpp>
#include <libmaus2/bambam/ProgramHeaderLineSet.hpp>
#include <libmaus2/fastx/FastAReader.hpp>
#include <libmaus2/bambam/StrCmpNum.hpp>

#include <biobambam2/BamBamConfig.hpp>
#include <biobambam2/Licensing.hpp>
#include <biobambam2/Reference.hpp>

#include <config.h>


struct StringComparator
{
	bool operator()(char const * A, char const * B) const
	{
		return libmaus2::bambam::StrCmpNum::strcmpnum(A,B) < 0;
	}
};

StringComparator const comp;

static uint64_t getDefaultNumThreads()
{
	return libmaus2::parallel::NumCpus::getNumLogicalProcessors();
}

struct ChecksumsTypeInfo
{
	typedef libmaus2::bambam::ChecksumsInterface element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct ChecksumsAllocator
{
	std::string hash;
	libmaus2::bambam::BamHeader const * bamheader;

	ChecksumsAllocator() {}
	ChecksumsAllocator(std::string const & rhash, libmaus2::bambam::BamHeader const * rbamheader) : hash(rhash), bamheader(rbamheader) {}

	ChecksumsTypeInfo::pointer_type operator()() const
	{
		ChecksumsTypeInfo::pointer_type ptr(libmaus2::bambam::ChecksumsFactory::constructShared(hash,*bamheader));
		return ptr;
	}
};

struct AlignmentTraceContainerTypeInfo
{
	typedef libmaus2::lcs::AlignmentTraceContainer element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct AlignmentTraceContainerAllocator
{
	std::string hash;
	libmaus2::bambam::BamHeader const * bamheader;

	AlignmentTraceContainerAllocator() {}
	AlignmentTraceContainerAllocator(std::string const & rhash, libmaus2::bambam::BamHeader const * rbamheader) : hash(rhash), bamheader(rbamheader) {}

	AlignmentTraceContainerTypeInfo::pointer_type operator()() const
	{
		AlignmentTraceContainerTypeInfo::pointer_type ptr(new libmaus2::lcs::AlignmentTraceContainer);
		return ptr;
	}
};

struct UPointerPair
{
	typedef UPointerPair this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;

	uint8_t const * pa;
	uint8_t const * pe;

	UPointerPair()
	{

	}
	UPointerPair(uint8_t const * rpa, uint8_t const * rpe) : pa(rpa), pe(rpe) {}
};

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

	libmaus2::autoarray::AutoArray<UPointerPair> A;
	uint64_t Ao;
	uint64_t Ap;

	UPointerPairList() {}

	void reset()
	{
		Ao = 0;
		Ap = 0;
	}
};

struct UPointerPairListTypeInfo
{
	typedef UPointerPairList element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct UPointerPairListAllocator
{
	UPointerPairListAllocator() {}

	UPointerPairListTypeInfo::pointer_type operator()() const
	{
		UPointerPairListTypeInfo::pointer_type ptr(new UPointerPairListTypeInfo::element_type);
		return ptr;
	}
};

struct FragmentAlignmentBufferFragmentTypeInfo
{
	typedef libmaus2::bambam::parallel::FragmentAlignmentBufferFragment element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}

};

struct FragmentAlignmentBufferFragmentAllocator
{
	FragmentAlignmentBufferFragmentAllocator() {}

	FragmentAlignmentBufferFragmentTypeInfo::pointer_type operator()() const
	{
		FragmentAlignmentBufferFragmentTypeInfo::pointer_type ptr(new FragmentAlignmentBufferFragmentTypeInfo::element_type);
		return ptr;
	}
};

#if 0
typedef libmaus2::parallel::StdSpinLock quick_lock_type;
typedef libmaus2::parallel::ScopeStdSpinLock quick_lock_wrapper_type;
#else
typedef libmaus2::parallel::StdMutex quick_lock_type;
typedef libmaus2::parallel::ScopeStdMutex quick_lock_wrapper_type;
#endif

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

	// stream identifier
	uint64_t streamid;
	// buffer
	libmaus2::autoarray::AutoArray<char> A;
	// current pointer
	char * c;
	// number of bytes in buffer
	uint64_t n;
	// true if end of file has been reached
	bool eof;
	uint64_t seqid;
	// space reserved for putback
	uint64_t putbackspace;

	libmaus2::bambam::parallel::FragmentAlignmentBuffer::shared_ptr_type fragmentBuffer;

	UPointerPairListTypeInfo::pointer_type bamPointers;

	uint64_t volatile bamMergeNext;
	uint64_t volatile bamMergeNumPackages;
	uint64_t volatile bamMergeNumPackagesProcessed;
	uint64_t volatile bamMergeNumPackagesAll;
	quick_lock_type bamMergeNumPackagesLock;

	void bump()
	{
		uint64_t const off = c - A.begin();
		A.bump();
		c = A.begin() + off;
	}

	DataBlock(
		uint64_t const rstreamid,
		uint64_t const rn,
		uint64_t const rputbackspace
	) : streamid(rstreamid), A(rn+rputbackspace,false), c(NULL), n(0), eof(false), seqid(0), putbackspace(rputbackspace), fragmentBuffer()
	{
		reset();
	}

	bool operator<(DataBlock const & D) const
	{
		return seqid < D.seqid;
	}

	uint64_t capacity()
	{
		return A.end()-c;
	}

	void reset()
	{
		c = A.begin()+putbackspace;
		n = 0;
		fragmentBuffer = libmaus2::bambam::parallel::FragmentAlignmentBuffer::shared_ptr_type();
		eof = false;
		seqid = 0;
	}

	void insertPutBack(char const * p, uint64_t const m)
	{
		assert ( c == A.begin() + putbackspace );

		if ( m > putbackspace )
		{
			// allocate new buffer
			libmaus2::autoarray::AutoArray<char> B(n+m,false);

			// copy old buffer
			std::copy(c,c+n,B.begin()+m);

			A = B;
			c = A.begin() + m;
			assert ( c + n == A.end() );
			putbackspace = m;
		}

		assert ( c-A.begin() >= static_cast< ::std::ptrdiff_t >(m) );

		c -= m;
		n += m;

		std::copy(p,p+m,c);
	}
};

struct DataBlockComparator
{
	bool operator()(DataBlock const & A, DataBlock const & B) const
	{
		return A < B;
	}

	bool operator()(DataBlock::shared_ptr_type const & A, DataBlock::shared_ptr_type const & B) const
	{
		return operator()(*A,*B);
	}
};

struct DataBlockTypeInfo
{
	typedef DataBlock element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct DataBlockAllocator
{
	uint64_t streamid;
	uint64_t blocksize;
	uint64_t putbackspace;

	DataBlockAllocator() : streamid(0), blocksize(0) {}
	DataBlockAllocator(uint64_t const rstreamid, uint64_t const rblocksize, uint64_t const rputbackspace) : streamid(rstreamid), blocksize(rblocksize), putbackspace(rputbackspace) {}

	DataBlockTypeInfo::pointer_type operator()() const
	{
		DataBlockTypeInfo::pointer_type ptr(new DataBlockTypeInfo::element_type(streamid,blocksize,putbackspace));
		return ptr;
	}
};

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

	typedef libmaus2::parallel::LockedFreeList<DataBlock,DataBlockAllocator,DataBlockTypeInfo> data_free_list_type;
	typedef data_free_list_type::shared_ptr_type data_free_list_ptr_type;

	uint64_t streamid;
	std::string fn;
	libmaus2::aio::InputStreamInstance::shared_ptr_type Pstream;
	std::istream * stream;

	DataBlockAllocator p_block_alloc;
	DataBlockAllocator d_block_alloc;

	data_free_list_ptr_type Pfreelist;
	data_free_list_ptr_type Dfreelist;

	uint64_t volatile Dnextid;
	DataBlockTypeInfo::pointer_type Dstallslot;
	quick_lock_type Dstallslotlock;

	libmaus2::parallel::StdMutex lock;

	int volatile eof;

	libmaus2::autoarray::AutoArray<DataBlock::shared_ptr_type> B;

	uint64_t volatile nextid;

	libmaus2::util::FiniteSizeHeap<DataBlockTypeInfo::pointer_type,DataBlockComparator> FSH;
	quick_lock_type FSHlock;

	uint64_t volatile nextprocid;

	// libmaus2::lz::GzipHeaderParser gzhp;
	libmaus2::lz::ZlibInterface::unique_ptr_type zintf;

	int volatile ret;

	libmaus2::util::FiniteSizeHeap<DataBlockTypeInfo::pointer_type,DataBlockComparator> deFSH;
	quick_lock_type deFSHlock;

	uint64_t volatile decompNextOut;
	quick_lock_type decompNextOutLock;

	libmaus2::autoarray::AutoArray<char> parsePutbackSpace;
	uint64_t volatile parsePutbackSpaceUsed;

	libmaus2::util::FiniteSizeHeap<DataBlockTypeInfo::pointer_type,DataBlockComparator> deLineFSH;
	quick_lock_type deLineFSHlock;

	uint64_t volatile deLineNext;

	libmaus2::bambam::parallel::FragmentAlignmentBufferAllocator fragmentAlloc;
	libmaus2::parallel::LockedGrowingFreeList<
		libmaus2::bambam::parallel::FragmentAlignmentBuffer,
		libmaus2::bambam::parallel::FragmentAlignmentBufferAllocator,
		libmaus2::bambam::parallel::FragmentAlignmentBufferTypeInfo
	> fragmentFreeList;

	libmaus2::util::FiniteSizeHeap<DataBlockTypeInfo::pointer_type,DataBlockComparator> bamEncodedFSH;
	quick_lock_type bamEncodedFSHLock;
	uint64_t volatile bamEncodedNext;

	int volatile needheader;
	int volatile needfooter;
	libmaus2::lz::GzipHeaderParser headerparser;
	libmaus2::autoarray::AutoArray<char> footerData;
	char * footerDataP;
	libmaus2::digest::CRC32 crc32;
	uint32_t volatile crc32length;
	uint32_t crc32digest;

	libmaus2::util::FiniteSizeHeap<DataBlockTypeInfo::pointer_type,DataBlockComparator> preBamEncodeFSH;
	uint64_t volatile preBamEncodeFSHnext;
	quick_lock_type preBamEncodeFSHlock;
	uint64_t volatile preBamEncodeLow;

	libmaus2::bambam::BamHeaderParserState BHPS;
	libmaus2::autoarray::AutoArray<char> bamParsePutBackSpace;
	uint64_t volatile bamParsePutBackSpaceUsed;

	libmaus2::parallel::LockedGrowingFreeList<
		UPointerPairListTypeInfo::element_type,
		UPointerPairListAllocator,
		UPointerPairListTypeInfo
	> bamPointerFreeList;

	libmaus2::util::FiniteSizeHeap<DataBlockTypeInfo::pointer_type,DataBlockComparator> bamMergeQueueFSH;
	quick_lock_type bamMergeQueueFSHLock;
	uint64_t volatile bamMergeQueueFSHnext;

	int volatile bamMergeEOF;

	std::pair<
		DataBlockTypeInfo::pointer_type,
		DataBlockTypeInfo::pointer_type
	>
		getNextBlock()
	{
		DataBlockTypeInfo::pointer_type cptr;
		DataBlockTypeInfo::pointer_type dptr;

		{
			quick_lock_wrapper_type slock(FSHlock);
			quick_lock_wrapper_type dslock(Dstallslotlock);

			if ( (!FSH.empty()) && (FSH.top()->seqid == nextprocid) )
			{
				if ( Dstallslot )
				{
					cptr = FSH.pop();
					dptr = Dstallslot;
					Dstallslot = DataBlockTypeInfo::pointer_type();
				}
				else if ( !Dfreelist->empty() )
				{
					cptr = FSH.pop();
					dptr = Dfreelist->get();
					dptr->seqid = Dnextid++;
				}
			}
		}

		return std::pair<
			DataBlockTypeInfo::pointer_type,
			DataBlockTypeInfo::pointer_type
			>(cptr,dptr);
	}

	void putBackCompressedBlock(DataBlockTypeInfo::pointer_type ptr)
	{
		quick_lock_wrapper_type slock(FSHlock);
		FSH.push(ptr);
	}

	void putBackUncompressedBlock(DataBlockTypeInfo::pointer_type ptr)
	{
		quick_lock_wrapper_type dslock(Dstallslotlock);
		assert ( !Dstallslot );
		Dstallslot = ptr;
	}

	void incrementNextProcId()
	{
		{
			quick_lock_wrapper_type slock(FSHlock);
			++nextprocid;
		}
	}

	void init(int const windowSizeLog = -15)
	{
		crc32length = 0;
		crc32.init();

		zintf->eraseContext();
		zintf->setZAlloc(Z_NULL);
		zintf->setZFree(Z_NULL);
		zintf->setOpaque(Z_NULL);
		zintf->setAvailIn(0);
		zintf->setNextIn(Z_NULL);

		if ( windowSizeLog )
			ret = zintf->z_inflateInit2(windowSizeLog);
		else
			ret = zintf->z_inflateInit();

		if (ret != Z_OK)
		{
			::libmaus2::exception::LibMausException se;
			se.getStream() << "inflate failed in inflateInit";
			se.finish();
			throw se;
		}
	}

	void zreset()
	{
		if ( (ret=zintf->z_inflateReset()) != Z_OK )
		{
			::libmaus2::exception::LibMausException se;
			se.getStream() << "Inflate::zreset(): inflateReset failed";
			se.finish();
			throw se;
		}

		ret = Z_OK;

		crc32.init();
 	}

	~BlockReadStreamInfo()
	{
		zintf->z_inflateEnd();
		zintf.reset();
	}

	BlockReadStreamInfo(
		uint64_t const rstreamid,
		std::istream & rin,
		uint64_t const blocksize,
		uint64_t const numblocks,
		uint64_t const numthreads
	)
	: streamid(rstreamid), fn(), Pstream(), stream(&rin),
	  p_block_alloc(streamid,blocksize,0),
	  d_block_alloc(streamid,blocksize,16*1024),
	  Pfreelist(new data_free_list_type(numblocks,p_block_alloc)),
	  Dfreelist(new data_free_list_type(numblocks,d_block_alloc)),
	  Dnextid(0),
	  Dstallslot(),
	  lock(),
	  eof(0),
	  B(numblocks),
	  nextid(0),
	  FSH(numblocks),
	  FSHlock(),
	  nextprocid(0),
	  zintf(libmaus2::lz::ZlibInterface::construct()),
	  deFSH(numblocks),
	  deFSHlock(),
	  decompNextOut(0),
	  parsePutbackSpaceUsed(0),
	  deLineFSH(numblocks),
	  deLineNext(0),
	  fragmentAlloc(numthreads,1),
	  fragmentFreeList(fragmentAlloc),
	  bamEncodedFSH(numblocks),
	  bamEncodedNext(0),
	  needheader(1),
	  needfooter(0),
	  footerData(8),
	  footerDataP(NULL),
	  crc32length(0),
	  preBamEncodeFSH(numblocks),
	  preBamEncodeFSHnext(0),
	  preBamEncodeLow(0),
	  bamParsePutBackSpaceUsed(0),
	  bamMergeQueueFSH(numblocks),
	  bamMergeQueueFSHnext(0),
	  bamMergeEOF(0)
	{
		init();
	}

	BlockReadStreamInfo(
		uint64_t const rstreamid,
		std::string const & rfn,
		uint64_t const blocksize,
		uint64_t const numblocks,
		uint64_t const numthreads
	)
	: streamid(rstreamid),
	  fn(rfn),
	  Pstream(new libmaus2::aio::InputStreamInstance(fn)), stream(Pstream.get()),
	  p_block_alloc(streamid,blocksize,0),
	  d_block_alloc(streamid,blocksize,16*1024),
	  Pfreelist(new data_free_list_type(numblocks,p_block_alloc)),
	  Dfreelist(new data_free_list_type(numblocks,d_block_alloc)),
	  Dnextid(0),
	  lock(), eof(0),
	  B(numblocks),
	  nextid(0),
	  FSH(numblocks),
	  FSHlock(),
	  nextprocid(0),
	  zintf(libmaus2::lz::ZlibInterface::construct()),
	  deFSH(numblocks),
	  deFSHlock(),
	  decompNextOut(0),
	  parsePutbackSpaceUsed(0),
	  deLineFSH(numblocks),
	  deLineNext(0),
	  fragmentAlloc(numthreads,1),
	  fragmentFreeList(fragmentAlloc),
	  bamEncodedFSH(numblocks),
	  bamEncodedNext(0),
	  needheader(1),
	  needfooter(0),
	  footerData(8),
	  footerDataP(NULL),
	  crc32length(0),
	  preBamEncodeFSH(numblocks),
	  preBamEncodeFSHnext(0),
	  preBamEncodeLow(0),
	  bamParsePutBackSpaceUsed(0),
	  bamMergeQueueFSH(numblocks),
	  bamMergeQueueFSHnext(0),
	  bamMergeEOF(0)
	{
		init();
	}
};

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

	libmaus2::autoarray::AutoArray<BlockReadStreamInfo::shared_ptr_type> A;

	BlockReadStreamInfoVector() : A() {}
	BlockReadStreamInfoVector(
		std::vector<std::string> const & Vfn,
		uint64_t const blocksize,
		uint64_t const numblocks,
		uint64_t const numthreads
	) : A(Vfn.size())
	{
		for ( uint64_t i = 0; i < Vfn.size(); ++i )
		{
			BlockReadStreamInfo::shared_ptr_type ptr(
				new BlockReadStreamInfo(
					i /* stream id */,Vfn[i],blocksize,numblocks,numthreads
				)
			);
			A[i] = ptr;

			// std::cerr << Vfn[i] << std::endl;
		}
	}
};

struct BlockReadPackage : public libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef BlockReadPackage this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	uint64_t streamid;

	BlockReadPackage & operator=(BlockReadPackage const & O)
	{
		if ( this != &O )
		{
			libmaus2::parallel::SimpleThreadWorkPackage::operator=(O);
			streamid = O.streamid;
		}
		return *this;
	}

	BlockReadPackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), streamid(0)
	{

	}

	BlockReadPackage(BlockReadPackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), streamid(O.streamid)
	{

	}

	BlockReadPackage(
		uint64_t const rstreamid,
		uint64_t const rpriority,
		uint64_t const rdispatcherid,
		uint64_t const rpackageid = 0,
		uint64_t const rsubid = 0
	) : libmaus2::parallel::SimpleThreadWorkPackage(rpriority,rdispatcherid,rpackageid,rsubid), streamid(rstreamid)
	{

	}

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

struct BlockReadPackageTypeInfo
{
	typedef BlockReadPackage element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct BlockReadPackageAllocator
{
	BlockReadPackageAllocator() {}

	BlockReadPackageTypeInfo::pointer_type operator()() const
	{
		BlockReadPackageTypeInfo::pointer_type ptr(new BlockReadPackageTypeInfo::element_type);
		return ptr;
	}
};

struct BlockReadQueueEvent
{
	virtual ~BlockReadQueueEvent() {}
	virtual void blockReadQueueEvent(uint64_t const rstreamid) = 0;
};

struct BlockReadReturnEvent
{
	virtual ~BlockReadReturnEvent() {}
	virtual void blockReadReturnEvent(BlockReadPackage * p) = 0;
};

struct ReturnDecompressedBlockEvent
{
	virtual ~ReturnDecompressedBlockEvent() {}
	virtual void returnDecompressedBlockEvent(DataBlock::shared_ptr_type block) = 0;
};

struct BlockReadDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef BlockReadDispatcher this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BlockReadStreamInfoVector * BRSIV;
	BlockReadQueueEvent & BRQE;
	BlockReadReturnEvent & BRRE;

	BlockReadDispatcher(
		BlockReadStreamInfoVector * rBRSIV,
		BlockReadQueueEvent & rBRQE,
		BlockReadReturnEvent & rBRRE
	) : BRSIV(rBRSIV), BRQE(rBRQE), BRRE(rBRRE) {

	}

	virtual ~BlockReadDispatcher() {}
	virtual void dispatch(libmaus2::parallel::SimpleThreadWorkPackage * P, libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & /* tpi */)
	{
		BlockReadPackage * BP = dynamic_cast<BlockReadPackage *>(P);
		assert ( BP );

		uint64_t const streamid = BP->streamid;

		assert ( streamid < BRSIV->A.size() );

		BlockReadStreamInfo & info = *(BRSIV->A[streamid]);

		uint64_t o = 0;

		if ( info.lock.trylock() )
		{
			libmaus2::parallel::ScopeStdMutex slock(info.lock,true);
			BlockReadStreamInfo::data_free_list_type & datafreelist =
				*(info.Pfreelist);

			while (
				(!info.eof)
				&&
				(!datafreelist.empty())
			)
			{
				DataBlock::shared_ptr_type block = datafreelist.get();
				block->reset();

				uint64_t const blockid = info.nextid++;

				char * c         = block->c;
				uint64_t const s = block->capacity();

				block->seqid = blockid;
				block->streamid = streamid;

				if ( info.stream->peek() == std::istream::traits_type::eof() )
				{
					block->n = 0;
					block->c = NULL;
					block->eof = true;
					info.eof = true;
				}
				else
				{
					info.stream->read(c,s);

					block->n = info.stream->gcount();
					block->eof = false;
				}

				info.B[o++] = block;
			}
		}

		for ( uint64_t i = 0; i < o; ++i )
		{
			DataBlock::shared_ptr_type block = info.B[i];
			quick_lock_wrapper_type slock(info.FSHlock);
			info.FSH.push(block);
		}

		if ( o )
		{
			// std::cerr << "pushed " << o << " for " << streamid << std::endl;
			BRQE.blockReadQueueEvent(streamid);
		}

		BRRE.blockReadReturnEvent(BP);
	}
};

struct BlockProcessPackage : public libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef BlockReadPackage this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	uint64_t streamid;

	BlockProcessPackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), streamid(0)
	{

	}

	BlockProcessPackage(BlockReadPackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), streamid(O.streamid)
	{

	}

	BlockProcessPackage(
		uint64_t const rstreamid,
		uint64_t const rpriority,
		uint64_t const rdispatcherid,
		uint64_t const rpackageid = 0,
		uint64_t const rsubid = 0
	) : libmaus2::parallel::SimpleThreadWorkPackage(rpriority,rdispatcherid,rpackageid,rsubid), streamid(rstreamid)
	{

	}

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

struct BlockProcessReturnEvent
{
	virtual ~BlockProcessReturnEvent() {}
	virtual void blockProcessReturnEvent(BlockProcessPackage * p) = 0;
};

struct BlockLineCountEnqueEvent
{
	virtual ~BlockLineCountEnqueEvent() {}
	virtual void blockLineCountEnqueEvent(DataBlockTypeInfo::pointer_type ptr, uint64_t const i) = 0;
};

struct BlockProcessNewlineCountCompleteEvent
{
	virtual ~BlockProcessNewlineCountCompleteEvent() {}
	virtual void blockProcessNewlineCountCompleteEvent(DataBlockTypeInfo::pointer_type ptr) = 0;
};

struct BlockProcessNewlineStoreCompleteEvent
{
	virtual ~BlockProcessNewlineStoreCompleteEvent() {}
	virtual void blockProcessNewlineStoreCompleteEvent(DataBlockTypeInfo::pointer_type ptr) = 0;
};

struct BlockProcessBamHeaderFinishedEvent
{
	virtual ~BlockProcessBamHeaderFinishedEvent() {}
	virtual void blockProcessBamHeaderFinishedEvent(DataBlockTypeInfo::pointer_type ptr) = 0;
};

struct BlockProcessEnqueParsedBamBlockEvent
{
	virtual ~BlockProcessEnqueParsedBamBlockEvent() {}
	virtual void blockProcessEnqueParsedBamBlockEvent() = 0;
};

struct BlockProcessDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef BlockReadDispatcher this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	uint64_t const numproc;
	BlockReadStreamInfoVector * BRSIV;
	ReturnDecompressedBlockEvent & RDBE;
	BlockProcessReturnEvent & BPRE;
	BlockProcessBamHeaderFinishedEvent & BPBF;
	BlockProcessEnqueParsedBamBlockEvent & BPEPBB;

	BlockProcessDispatcher(
		uint64_t const rnumproc,
		BlockReadStreamInfoVector * rBRSIV,
		ReturnDecompressedBlockEvent & rRDBE,
		BlockProcessReturnEvent & rBPRE,
		BlockProcessBamHeaderFinishedEvent & rBPBF,
		BlockProcessEnqueParsedBamBlockEvent & rBPEPBB
	) : numproc(rnumproc), BRSIV(rBRSIV), RDBE(rRDBE), BPRE(rBPRE), BPBF(rBPBF), BPEPBB(rBPEPBB)
	{

	}

	virtual ~BlockProcessDispatcher() {}
	virtual void dispatch(libmaus2::parallel::SimpleThreadWorkPackage * P, libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & /* tpi */)
	{
		BlockProcessPackage * BP = dynamic_cast<BlockProcessPackage *>(P);
		assert ( BP );

		uint64_t const streamid = BP->streamid;
		BlockReadStreamInfo & info = *(BRSIV->A[streamid]);

		std::vector<DataBlockTypeInfo::pointer_type> Vout;

		{
			quick_lock_wrapper_type slock(info.deFSHlock);
			quick_lock_wrapper_type sdlock(info.decompNextOutLock);

			while ( !info.deFSH.empty() && info.deFSH.top()->seqid == info.decompNextOut )
			{
				DataBlockTypeInfo::pointer_type tptr = info.deFSH.pop();

				libmaus2::bambam::BamHeaderParserState & BHPS = info.BHPS;
				libmaus2::util::GetCObject<uint8_t const *> GO(reinterpret_cast<uint8_t const *>(tptr->c));

				std::pair<bool,uint64_t> const HPB = BHPS.parseHeader(GO, 0);
				std::pair<bool,uint64_t> const HP = BHPS.parseHeader(GO, tptr->n);

				tptr->c += HP.second;
				tptr->n -= HP.second;
				tptr->putbackspace += HP.second;

				if ( (! HPB.first) && HP.first )
				{
					// std::cerr << "got header on stream " << streamid << std::endl;
					BPBF.blockProcessBamHeaderFinishedEvent(tptr);
				}

				tptr->insertPutBack(
					info.bamParsePutBackSpace.begin(),
					info.bamParsePutBackSpaceUsed
				);

				UPointerPairListTypeInfo::pointer_type bamPointers =
					info.bamPointerFreeList.get();

				assert ( bamPointers );

				bamPointers->reset();

				tptr->bamPointers = bamPointers;
				tptr->bamMergeNext = 0;
				tptr->bamMergeNumPackages = 0;
				tptr->bamMergeNumPackagesProcessed = 0;
				tptr->bamMergeNumPackagesAll = 0;

				uint64_t l;
				uint64_t c = 0;

				char const * prevname = NULL;
				char * prev_c = tptr->c;
				uint64_t prev_n = tptr->n;
				uint64_t prev_Ao = 0;

				if (
					tptr->n >= sizeof(uint32_t)
					&&
					tptr->n >= sizeof(uint32_t) + (l=libmaus2::util::loadValueLE4(reinterpret_cast<uint8_t const *>(tptr->c)))
				)
				{
					uint8_t const * p = reinterpret_cast<uint8_t const *>(tptr->c);
					uint8_t const * pa = p + sizeof(uint32_t);
					prevname = libmaus2::bambam::BamAlignmentDecoderBase::getReadName(pa);
				}


				while (
					tptr->n >= sizeof(uint32_t)
					&&
					tptr->n >= sizeof(uint32_t) + (l=libmaus2::util::loadValueLE4(reinterpret_cast<uint8_t const *>(tptr->c)))
				)
				{
					uint64_t const e = sizeof(uint32_t) + l;

					uint8_t const * p = reinterpret_cast<uint8_t const *>(tptr->c);
					uint8_t const * pa = p + sizeof(uint32_t);
					uint8_t const * pe = pa + l;

					char const * name = libmaus2::bambam::BamAlignmentDecoderBase::getReadName(pa);
					if ( comp(prevname,name) )
					{
						prev_c = tptr->c;
						prev_n = tptr->n;
						prev_Ao = bamPointers->Ao;
						prevname = name;
					}

					bamPointers->A.push(bamPointers->Ao,UPointerPair(pa,pe));

					#if 0
					std::cerr << "name =" << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(
						reinterpret_cast<uint8_t const *>(tptr->c + sizeof(uint32_t))
					) << std::endl;
					#endif

					tptr->n -= e;
					tptr->c += e;
					c += 1;
				}

				// pull back to avoid splitting through read name boundary
				// unless this is the last block in the stream
				if ( ! tptr->eof )
				{
					// std::cerr << "moving " << tptr->n << " to " << prev_n << std::endl;

					tptr->c = prev_c;
					tptr->n = prev_n;
					bamPointers->Ao = prev_Ao;
				}

				// std::cerr << "c=" << c << " rest=" << tptr->n << " " << HP.second << std::endl;

				info.bamParsePutBackSpace.ensureSize(tptr->n);
				info.bamParsePutBackSpaceUsed = tptr->n;
				std::copy(tptr->c,tptr->c + tptr->n,info.bamParsePutBackSpace.begin());

				if ( tptr->eof && info.bamParsePutBackSpaceUsed )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] found unprocessed data at end of stream for stream id " << streamid << std::endl;
					lme.finish(),
					throw lme;
				}

				Vout.push_back(tptr);

				info.decompNextOut++;
			}
		}

		for ( uint64_t i = 0; i < Vout.size(); ++i )
		{
			quick_lock_wrapper_type slock(info.bamMergeQueueFSHLock);
			info.bamMergeQueueFSH.push(Vout[i]);

			// RDBE.returnDecompressedBlockEvent(Vout[i]);
		}

		BPEPBB.blockProcessEnqueParsedBamBlockEvent();

		BPRE.blockProcessReturnEvent(BP);
	}
};

struct BamMergeEntry
{
	DataBlockTypeInfo::pointer_type ptr;
	uint64_t from;
	uint64_t to;

	BamMergeEntry()
	{
	}

	BamMergeEntry(
		DataBlockTypeInfo::pointer_type rptr,
		uint64_t rfrom,
		uint64_t rto
	) : ptr(rptr), from(rfrom), to(rto)
	{}
};

std::ostream & operator<<(std::ostream & out, BamMergeEntry const & B)
{
	out << "BamMergeEntry(" << B.ptr->streamid << "," << B.ptr->seqid << "," << B.from << "," << B.to << ")";
	return out;
}

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

	uint64_t blockid;
	std::vector<BamMergeEntry> V;
	FragmentAlignmentBufferFragmentTypeInfo::pointer_type fragment;
	std::vector<std::pair<uint8_t *,uint8_t *> > linfrag;
	uint64_t linfragencoded;
	quick_lock_type::shared_ptr_type linfragencodedlock;
	std::vector < libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type > Voutbuf;
	std::vector < libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo > Vflushinfo;

	BamMergeEntryInfo(
		uint64_t const rblockid,
		std::vector<BamMergeEntry> const & rV,
		FragmentAlignmentBufferFragmentTypeInfo::pointer_type rfragment
	) : blockid(rblockid), V(rV), fragment(rfragment),
	    linfragencoded(0), linfragencodedlock(new quick_lock_type)
	{

	}
};


std::ostream & operator<<(std::ostream & out, BamMergeEntryInfo const & info)
{
	for ( uint64_t i = 0; i < info.V.size(); ++i )
	{
		BamMergeEntry const & BME = info.V[i];
		DataBlockTypeInfo::pointer_type ptr = BME.ptr;

		out << "V[" << i << "]=" << BME
			<< " first " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(ptr->bamPointers->A[BME.from].pa)
			<< " last " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(ptr->bamPointers->A[BME.to-1].pa)
		<< std::endl;
	}
	return out;
}

struct BamMergeEntryInfoComparator
{
	bool operator()(BamMergeEntryInfo::shared_ptr_type const & A, BamMergeEntryInfo::shared_ptr_type const & B) const
	{
		return A->blockid < B->blockid;
	}
};


struct NameGet
{
	// typedef char const * value_type;

	DataBlockTypeInfo::pointer_type ptr;

	NameGet() {}
	NameGet(DataBlockTypeInfo::pointer_type rptr) : ptr(rptr) {}

	char const * get(uint64_t const i) const
	{
		return
			libmaus2::bambam::BamAlignmentDecoderBase::getReadName(ptr->bamPointers->A[i].pa);
	}
};
typedef libmaus2::util::ConstIterator<NameGet,char const *> name_it_type;

struct FlagGet
{
	// typedef char const * value_type;

	DataBlockTypeInfo::pointer_type ptr;

	FlagGet() {}
	FlagGet(DataBlockTypeInfo::pointer_type rptr) : ptr(rptr) {}

	uint64_t get(uint64_t const i) const
	{
		return
			libmaus2::bambam::BamAlignmentDecoderBase::getFlags(ptr->bamPointers->A[i].pa);
	}
};


struct HeapEntry
{
	DataBlockTypeInfo::pointer_type ptr;
	uint64_t from;
	uint64_t to;

	HeapEntry()
	{

	}
	HeapEntry(
		DataBlockTypeInfo::pointer_type rptr,
		uint64_t rfrom,
		uint64_t rto
	) : ptr(rptr), from(rfrom), to(rto) {}

	std::pair<uint8_t const *,uint8_t const *> getPointers() const
	{
		return
			std::pair<uint8_t const *,uint8_t const *>(
				ptr->bamPointers->A[from].pa,
				ptr->bamPointers->A[from].pe
			)
			;
	}

	char const * get() const
	{
		return NameGet(ptr).get(from);
	}

	uint64_t getFlags() const
	{
		return FlagGet(ptr).get(from);
	}

	static int flagsToRank(uint64_t const flags)
	{
		if ( (flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD1) )
			return 0;
		else if ( (flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD2) )
			return 1;
		else
			return 2;
	}

	int getRank() const
	{
		return flagsToRank(getFlags());
	}

	bool next()
	{
		return ++from < to;
	}
};

struct HeapEntryComparator
{
	StringComparator const * pcomp;

	HeapEntryComparator() : pcomp(0) {}
	HeapEntryComparator(StringComparator const & comp) : pcomp(&comp) {}


	bool operator()(HeapEntry const & A, HeapEntry const & B) const
	{
		if ( (*pcomp)(A.get(),B.get()) )
			return true;
		else if ( (*pcomp)(B.get(),A.get()) )
			return false;
		else
		{
			uint64_t rankA = A.getRank();
			uint64_t rankB = B.getRank();

			if ( rankA != rankB )
				return rankA < rankB;
			else
				return A.ptr->streamid < B.ptr->streamid;
		}
	}
};

HeapEntryComparator const HEC(comp);

struct BlockBamMergePackage : public libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef BlockBamMergePackage this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamMergeEntryInfo::shared_ptr_type info;

	BlockBamMergePackage & operator=(BlockBamMergePackage const & O)
	{
		if ( this != &O )
		{
			libmaus2::parallel::SimpleThreadWorkPackage::operator=(O);
			info = O.info;
		}
		return *this;
	}

	BlockBamMergePackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), info()
	{

	}

	BlockBamMergePackage(BlockBamMergePackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), info(O.info)
	{

	}

	BlockBamMergePackage(
		BamMergeEntryInfo::shared_ptr_type rinfo,
		uint64_t const rpriority,
		uint64_t const rdispatcherid,
		uint64_t const rpackageid = 0,
		uint64_t const rsubid = 0
	) : libmaus2::parallel::SimpleThreadWorkPackage(rpriority,rdispatcherid,rpackageid,rsubid), info(rinfo)
	{
	}

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

struct BlockBamMergePackageReturnEvent
{
	virtual ~BlockBamMergePackageReturnEvent() {}
	virtual void blockBamMergePackageReturnEvent(BlockBamMergePackage * p) = 0;
};

struct BlockBamMergePackageFinishedEvent
{
	virtual ~BlockBamMergePackageFinishedEvent() {}
	virtual void blockBamMergePackageFinishedEvent(BamMergeEntryInfo::shared_ptr_type info) = 0;
};

struct AuxFilterTypeInfo
{
	typedef libmaus2::bambam::BamAuxFilterVector element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct AuxFilterAllocator
{
	AuxFilterAllocator() {}

	AuxFilterTypeInfo::pointer_type operator()() const
	{
		AuxFilterTypeInfo::pointer_type ptr(new AuxFilterTypeInfo::element_type);
		return ptr;
	}
};

struct MdStringComputationContextTypeInfo
{
	typedef libmaus2::bambam::MdStringComputationContext element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct MdStringComputationContextAllocator
{
	MdStringComputationContextAllocator() {}

	MdStringComputationContextTypeInfo::pointer_type operator()() const
	{
		MdStringComputationContextTypeInfo::pointer_type ptr(new MdStringComputationContextTypeInfo::element_type);
		return ptr;
	}
};


struct BlockBamMergePackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef BlockBamMergePackageDispatcher this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BlockReadStreamInfoVector * BRSIV;
	BlockBamMergePackageReturnEvent & BBMPR;
	BlockBamMergePackageFinishedEvent & BBMPF;
	libmaus2::bambam::BamHeader const & header;

	libmaus2::parallel::LockedGrowingFreeList<
		AuxFilterTypeInfo::element_type,
		AuxFilterAllocator,
		AuxFilterTypeInfo
	> auxFilterFreeList;

	libmaus2::parallel::LockedGrowingFreeList<
		ChecksumsTypeInfo::element_type,
		ChecksumsAllocator,
		ChecksumsTypeInfo
	> & checksumsFreeList;

	libmaus2::parallel::LockedGrowingFreeList<
		MdStringComputationContextTypeInfo::element_type,
		MdStringComputationContextAllocator,
		MdStringComputationContextTypeInfo
	> mdFreeList;

	bool const zz;

	Reference::shared_ptr_type reference;
	bool const mdwarnchange;
	bool const replacecigar;

	libmaus2::bambam::BamSeqEncodeTable const seqenc;
	libmaus2::bambam::BamAuxFilterVector qsqqfilter;

	libmaus2::parallel::LockedGrowingFreeList<
		AlignmentTraceContainerTypeInfo::element_type,
		AlignmentTraceContainerAllocator,
		AlignmentTraceContainerTypeInfo
	> atcFreeList;

	BlockBamMergePackageDispatcher(
		BlockReadStreamInfoVector * rBRSIV,
		BlockBamMergePackageReturnEvent & rBBMPR,
		BlockBamMergePackageFinishedEvent & rBBMPF,
		libmaus2::bambam::BamHeader const & rheader,
		libmaus2::parallel::LockedGrowingFreeList<
			ChecksumsTypeInfo::element_type,
			ChecksumsAllocator,
			ChecksumsTypeInfo
		> & rchecksumsFreeList,
		bool const rzz,
		Reference::shared_ptr_type rreference,
		bool const rmdwarnchange,
		bool const rreplacecigar
	) : BRSIV(rBRSIV), BBMPR(rBBMPR), BBMPF(rBBMPF), header(rheader), checksumsFreeList(rchecksumsFreeList), zz(rzz), reference(rreference), mdwarnchange(rmdwarnchange),
	    replacecigar(rreplacecigar)
	{
		qsqqfilter.set('q','s');
		qsqqfilter.set('q','q');
	}


	void reinsertClip(
		libmaus2::fastx::UCharBuffer & buffer,
		libmaus2::autoarray::AutoArray<char> & ASEQ,
		libmaus2::autoarray::AutoArray<char> & AQUAL,
		libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> & cigop,
		uint16_t const flags
	)
	{
		// get qs tag (if any)
		char const * qs = libmaus2::bambam::BamAlignmentDecoderBase::getAuxString(buffer.buffer,buffer.length,"qs");

		if ( qs )
		{
			// get qq tag
			char const * qq = libmaus2::bambam::BamAlignmentDecoderBase::getAuxString(buffer.buffer,buffer.length,"qq");

			uint64_t const lseqin = libmaus2::bambam::BamAlignmentDecoderBase::decodeRead(buffer.buffer, ASEQ);
			libmaus2::bambam::BamAlignmentDecoderBase::decodeQual(buffer.buffer, AQUAL);

			uint64_t const cliplen = strlen(qs);

			if ( (! qq) || (strlen(qq) != cliplen) )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream() << "[E] size of qs and qq tags is incompatible in reinsertClip" << std::endl;
				lme.finish();
				throw lme;
			}

			// lenght of output sequence
			uint64_t const lseqout = lseqin + cliplen;

			uint64_t ncigin = libmaus2::bambam::BamAlignmentDecoderBase::getCigarOperations(buffer.buffer,cigop);

			if ( (flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREVERSE) )
			{
				std::reverse(AQUAL.begin(),AQUAL.begin()+lseqin);
				std::reverse(ASEQ.begin(),ASEQ.begin()+lseqin);
				for ( uint64_t i = 0; i < lseqin; ++i )
					ASEQ[i] = libmaus2::fastx::invertUnmapped(ASEQ[i]);
				std::reverse(cigop.begin(),cigop.begin()+ncigin);
			}

			if ( ncigin && cigop[ncigin-1].first == libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CHARD_CLIP )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream() << "[E] alignment for read " <<
					libmaus2::bambam::BamAlignmentDecoderBase::getReadName(buffer.buffer)
					<< " contains hard clipping event, cannot reinsert clipped adapter" << std::endl;
				lme.finish();
				throw lme;
			}

			if ( ncigin && cigop[ncigin-1].first == libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CSOFT_CLIP )
				cigop[ncigin-1].second += cliplen;
			else
				cigop.push(ncigin,libmaus2::bambam::cigar_operation(static_cast<int32_t>(libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CSOFT_CLIP),cliplen));

			ASEQ.ensureSize(lseqout);
			AQUAL.ensureSize(lseqout);

			std::copy(qs,qs+cliplen,ASEQ.begin()+lseqin);
			std::copy(qq,qq+cliplen,AQUAL.begin()+lseqin);

			if ( (flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREVERSE) )
			{
				std::reverse(AQUAL.begin(),AQUAL.begin()+lseqout);
				std::reverse(ASEQ.begin(),ASEQ.begin()+lseqout);
				for ( uint64_t i = 0; i < lseqout; ++i )
					ASEQ[i] = libmaus2::fastx::invertUnmapped(ASEQ[i]);
				std::reverse(cigop.begin(),cigop.begin()+ncigin);
			}

			buffer.length = libmaus2::bambam::BamAlignmentEncoderBase::replaceSequence(
				buffer.abuffer,
				buffer.length,
				seqenc,
				ASEQ.begin(),
				lseqout,
				AQUAL.begin()
			);

			buffer.buffer = buffer.abuffer.begin();
			buffer.buffersize = buffer.abuffer.size();

			buffer.length = libmaus2::bambam::BamAlignmentDecoderBase::filterOutAux(
				buffer.buffer,
				buffer.length,
				qsqqfilter
			);

			if ( ! (flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FUNMAP) )
			{
				buffer.length = libmaus2::bambam::BamAlignmentEncoderBase::replaceCigar(
					buffer.abuffer,
					buffer.length,
					cigop,
					ncigin
				);
				buffer.buffer = buffer.abuffer.begin();
				buffer.buffersize = buffer.abuffer.size();
			}
		}

	}

	void reinsertClip(
		libmaus2::fastx::UCharBuffer & buffer,
		libmaus2::autoarray::AutoArray<char> & ASEQ,
		libmaus2::autoarray::AutoArray<char> & AQUAL,
		libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> & cigop
	)
	{
		reinsertClip(buffer,ASEQ,AQUAL,cigop,libmaus2::bambam::BamAlignmentDecoderBase::getFlags(buffer.buffer));
	}

	virtual ~BlockBamMergePackageDispatcher() {}
	virtual void dispatch(libmaus2::parallel::SimpleThreadWorkPackage * P, libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & /* tpi */)
	{
		BlockBamMergePackage * BP = dynamic_cast<BlockBamMergePackage *>(P);
		assert ( BP );

		BamMergeEntryInfo::shared_ptr_type pmergeinfo = BP->info;
		BamMergeEntryInfo & mergeinfo = *pmergeinfo;

		libmaus2::bambam::parallel::FragmentAlignmentBufferFragment::shared_ptr_type
			fragment = mergeinfo.fragment;
		fragment->reset();

		#if 0
		std::cerr << "processing" << std::endl;
		std::cerr << mergeinfo << std::endl;
		#endif

		libmaus2::util::FiniteSizeHeap<HeapEntry,HeapEntryComparator> FSH(mergeinfo.V.size(),HEC);
		libmaus2::autoarray::AutoArray<HeapEntry> AH;
		for ( uint64_t i = 0; i < mergeinfo.V.size(); ++i )
		{
			if ( mergeinfo.V[i].from < mergeinfo.V[i].to )
				FSH.push(
					HeapEntry(
						mergeinfo.V[i].ptr,
						mergeinfo.V[i].from,
						mergeinfo.V[i].to
					)
				);
		}

		libmaus2::autoarray::AutoArray < libmaus2::bambam::BamAlignmentDecoderBase::AuxInfo > AUX0;
		libmaus2::autoarray::AutoArray < libmaus2::bambam::BamAlignmentDecoderBase::AuxInfo > AUXi;
		libmaus2::bambam::BamAuxFilterVector::shared_ptr_type pauxfilter = auxFilterFreeList.get();
		libmaus2::bambam::BamAuxFilterVector & auxfilter = *pauxfilter;
		libmaus2::fastx::UCharBuffer buffer;
		libmaus2::fastx::UCharBuffer pairbuffer[2];

		ChecksumsTypeInfo::pointer_type checkptr = checksumsFreeList.get();
		MdStringComputationContextTypeInfo::pointer_type mdptr = mdFreeList.get();
		std::ostringstream mdstr;
		std::string const mdempty;
		libmaus2::autoarray::AutoArray<uint8_t> Acig;
		libmaus2::autoarray::AutoArray<char> ASEQ;
		libmaus2::autoarray::AutoArray<char> AQUAL;
		libmaus2::autoarray::AutoArray<int> ACIG;
		libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> cigop;
		libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> cigopOut;

		libmaus2::lcs::AlignmentTraceContainer::shared_ptr_type patc = atcFreeList.get();

		while ( ! FSH.empty() )
		{
			HeapEntry const OHE = FSH.top();
			char const * name = OHE.get();

			pairbuffer[0].reset();
			pairbuffer[1].reset();

			while (
				! FSH.empty()
				&&
				(! ( comp(name,FSH.top().get()) ))
			)
			{
				HeapEntry const HE = FSH.top();

				int const rank = HE.getRank();
				uint64_t o = 0;

				while (
					(! FSH.empty())
					&&
					(! ( comp(name,FSH.top().get()) ))
					&&
					(rank == FSH.top().getRank())
				)
				{
					HeapEntry LHE = FSH.pop();

					AH.push(o,LHE);

					if ( LHE.next() )
						FSH.push(LHE);
				}

				assert ( o );

				if ( AH[0].ptr->streamid != 0 )
				{

					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] unable to find name " << name << " in first/unmapped file" << std::endl;

					for ( uint64_t i = mergeinfo.V[0].from; i < mergeinfo.V[0].to; ++i )
					{
						uint8_t const * p = mergeinfo.V[0].ptr->bamPointers->A[i].pa;
						lme.getStream() << "name[" << i << "]=" << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(p) << std::endl;
					}

					lme.finish();
					throw lme;
				}
				if ( o > 1 )
				{
					if ( AH[1].ptr->streamid == 0 )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] more than one read named " << name << " in first/unmapped file" << std::endl;
						lme.finish();
						throw lme;
					}

					for ( uint64_t i = 1; i < o; ++i )
						assert ( AH[i].ptr->streamid > 0 );

					std::pair<uint8_t const *,uint8_t const *> const P0 = AH[0].getPointers();

					uint64_t const naux0 = libmaus2::bambam::BamAlignmentDecoderBase::enumerateAuxTags(
						P0.first,P0.second-P0.first,AUX0
					);

					#if 0
					std::cerr << std::string(80,'-') << std::endl;
					#endif

					for ( uint64_t i = 1; i < o; ++i )
					{
						HeapEntry const & LHE = AH[i];

						std::pair<uint8_t const *,uint8_t const *> const Pi = LHE.getPointers();

						uint64_t const nauxi = libmaus2::bambam::BamAlignmentDecoderBase::enumerateAuxTags(
							Pi.first,Pi.second-Pi.first,AUXi
						);

						for ( uint64_t j = 0; j < nauxi; ++j )
							auxfilter.set(AUXi[j].tag[0],AUXi[j].tag[1]);
						if ( zz )
							auxfilter.set('n','n');

						buffer.reset();

						buffer.put(Pi.first,Pi.second-Pi.first);

						for ( uint64_t j = 0; j < naux0; ++j )
							if ( ! auxfilter(AUX0[j].tag[0],AUX0[j].tag[1]) )
								buffer.put(P0.first + AUX0[j].o, AUX0[j].l);

						if ( zz )
						{
							char const * const nn = libmaus2::bambam::BamAlignmentDecoderBase::getAuxString(
								P0.first,
								P0.second-P0.first,
								"nn"
							);

							// std::cerr << "nn=" << nn << std::endl;

							if ( nn )
							{
								buffer.length = libmaus2::bambam::BamAlignmentDecoderBase::replaceName(
									buffer.abuffer,
									buffer.length,
									nn,
									strlen(nn)
								);
								buffer.buffersize = buffer.abuffer.size();
								buffer.buffer = buffer.abuffer.begin();
							}
						}

						if ( reference )
						{
							uint16_t const flags = libmaus2::bambam::BamAlignmentDecoderBase::getFlags(buffer.buffer);

							if ( !(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FUNMAP) )
							{
								int64_t const refid = libmaus2::bambam::BamAlignmentDecoderBase::getRefID(buffer.buffer);
								int64_t const pos =
									libmaus2::bambam::BamAlignmentDecoderBase::getPos(buffer.buffer) -
									libmaus2::bambam::BamAlignmentDecoderBase::getFrontDel(buffer.buffer)
									;
								std::string const & refseq = reference->getSequence(refid);

								if ( replacecigar )
								{
									#if 0
									std::cerr << "old cigar string" <<
										libmaus2::bambam::BamAlignmentDecoderBase::getCigarAsString(buffer.buffer)
										<< std::endl;
									#endif

									uint64_t ncigin = libmaus2::bambam::BamAlignmentDecoderBase::getCigarOperations(buffer.buffer,cigop);

									uint64_t cigrefpos = pos;
									uint64_t cigreadpos = 0;
									uint64_t const laseq = libmaus2::bambam::BamAlignmentDecoderBase::decodeRead(buffer.buffer,ASEQ);
									char const * cigreadptr = ASEQ.begin();
									uint64_t cigptr = 0;
									for ( uint64_t i = 0; i < ncigin; ++i )
									{
										int32_t const op = cigop[i].first;
										int32_t const len = cigop[i].second;

										switch ( op )
										{
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CMATCH:
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CEQUAL:
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CDIFF:
												for ( int64_t j = 0; j < len; ++j )
												{
													char const cref = refseq[cigrefpos++];
													char const cread = cigreadptr[cigreadpos++];

													if (
														cref == 'N'
														||
														cread == 'N'
														||
														cref != cread
													)
														ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CDIFF);
													else
														ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CEQUAL);
												}
												break;
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CINS:
												for ( int64_t j = 0; j < len; ++j )
												{
													cigreadpos++;
													ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CINS);
												}
												break;
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CDEL:
												for ( int64_t j = 0; j < len; ++j )
												{
													cigrefpos++;
													ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CDEL);
												}
												break;
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CPAD:
												for ( int64_t j = 0; j < len; ++j )
												{
													cigrefpos++;
													ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CPAD);
												}
												break;
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CSOFT_CLIP:
												for ( int64_t j = 0; j < len; ++j )
												{
													cigreadpos++;
													ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CSOFT_CLIP);
												}
												break;
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CHARD_CLIP:
												for ( int64_t j = 0; j < len; ++j )
												{
													ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CHARD_CLIP);
												}
												break;
											case libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CREF_SKIP:
												for ( int64_t j = 0; j < len; ++j )
												{
													cigrefpos++;
													ACIG.push(cigptr,libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_CREF_SKIP);
												}
												break;
											default:
												break;
										}
									}

									uint64_t ciglow = 0;
									uint64_t ncigout = 0;
									while ( ciglow < cigptr )
									{
										int32_t const op = ACIG[ciglow];
										uint64_t cighigh = ciglow+1;

										while ( cighigh < cigptr && ACIG[cighigh] == op )
											++cighigh;

										cigopOut.push(ncigout,std::pair<int32_t,int32_t>(op,cighigh-ciglow));

										ciglow = cighigh;
									}

									assert ( cigreadpos == laseq );

									buffer.length = libmaus2::bambam::BamAlignmentEncoderBase::replaceCigar(
										buffer.abuffer,
										buffer.length,
										cigopOut.begin(),
										ncigout
									);
									buffer.buffer = buffer.abuffer.begin();
									buffer.buffersize = buffer.abuffer.size();
								}

								#if 0
								std::cerr << "new cigar string" <<
									libmaus2::bambam::BamAlignmentDecoderBase::getCigarAsString(buffer.buffer)
									<< std::endl;
								#endif

								libmaus2::bambam::BamAlignmentDecoderBase::calculateMd(
									buffer.buffer,
									buffer.length,
									*mdptr,
									refseq.begin() + pos,
									mdwarnchange /* warn change */,
									&mdstr
								);

								if ( mdptr->diff )
								{
									if ( mdptr->eraseold )
									{
										buffer.length = libmaus2::bambam::BamAlignmentDecoderBase::filterOutAux(
											buffer.buffer,
											buffer.length,
											mdptr->auxvec
										);
									}

									libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
										buffer,
										"MD",
										mdptr->md.begin()
									);

									libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(
										buffer,
										"NM",
										'i',
										mdptr->nm
									);

									if ( mdstr.tellp() >= 16*1024 )
									{
										std::string const mdwarn = mdstr.str();
										mdstr.str(mdempty);
										libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
										std::cerr << mdwarn;
									}
								}
							}
						}

						uint16_t const flags = libmaus2::bambam::BamAlignmentDecoderBase::getFlags(buffer.buffer);

						if (
							(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FSUPPLEMENTARY)
							||
							(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FSECONDARY)
							||
							(!(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FPAIRED))
						)
						{
							if (
								(!(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FPAIRED))
							)
								reinsertClip(buffer,ASEQ,AQUAL,cigop,flags);

							fragment->pushAlignmentBlock(buffer.buffer,buffer.length);
							checkptr->update(buffer.buffer,buffer.length);
						}
						else
						{
							if (
								(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD1)
								||
								(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD2)
							)
							{
								libmaus2::fastx::UCharBuffer & fbuffer =
									(flags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD1)
									?
									pairbuffer[0]
									:
									pairbuffer[1];

								buffer.swap(fbuffer);
							}
							else
							{
								libmaus2::exception::LibMausException lme;
								lme.getStream() << "[E] found read with FPAIRED set but neither FREAD1 nor FREAD2" << std::endl;
								lme.finish();
								throw lme;
							}
						}


						if ( zz )
							auxfilter.clear('n','n');
						for ( uint64_t j = 0; j < nauxi; ++j )
							auxfilter.clear(AUXi[j].tag[0],AUXi[j].tag[1]);
					}
				}
			}

			if ( pairbuffer[0].length && pairbuffer[1].length )
			{
				reinsertClip(pairbuffer[0],ASEQ,AQUAL,cigop);
				reinsertClip(pairbuffer[1],ASEQ,AQUAL,cigop);

				// fix mate information
				std::pair<int16_t,int16_t> const MQ = libmaus2::bambam::BamAlignmentEncoderBase::fixMateInformation(
					pairbuffer[0].buffer,pairbuffer[1].buffer
				);

				// insert mate cigar information
				auxfilter.set('M','Q');
				auxfilter.set('M','C');
				auxfilter.set('M','S');

				pairbuffer[0].length = libmaus2::bambam::BamAlignmentDecoderBase::filterOutAux(pairbuffer[0].buffer,pairbuffer[0].length,auxfilter);
				pairbuffer[1].length = libmaus2::bambam::BamAlignmentDecoderBase::filterOutAux(pairbuffer[1].buffer,pairbuffer[1].length,auxfilter);

				auxfilter.clear('M','Q');
				auxfilter.clear('M','C');
				auxfilter.clear('M','S');

				if ( MQ.first >= 0 )
					libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(pairbuffer[0],"MQ",'i',MQ.first);
				if ( MQ.second >= 0 )
					libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(pairbuffer[1],"MQ",'i',MQ.second);

				if (
					!(
					libmaus2::bambam::BamAlignmentDecoderBase::getFlags(pairbuffer[0].buffer) & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FUNMAP
					)
				)
				{
					unsigned int const cigstringlen = libmaus2::bambam::BamAlignmentDecoderBase::getCigarStringLength(pairbuffer[0].buffer);
					Acig.ensureSize(cigstringlen+1);
					libmaus2::bambam::BamAlignmentDecoderBase::getCigarString(pairbuffer[0].buffer,Acig.begin());

					libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(pairbuffer[1],"MC",Acig.begin());

					uint64_t const score = libmaus2::bambam::BamAlignmentDecoderBase::getScore(pairbuffer[0].buffer);
					libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(pairbuffer[1],"MS",'i',score);
				}
				if (
					!(
					libmaus2::bambam::BamAlignmentDecoderBase::getFlags(pairbuffer[1].buffer) & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FUNMAP
					)
				)
				{
					unsigned int const cigstringlen = libmaus2::bambam::BamAlignmentDecoderBase::getCigarStringLength(pairbuffer[1].buffer);
					Acig.ensureSize(cigstringlen+1);
					libmaus2::bambam::BamAlignmentDecoderBase::getCigarString(pairbuffer[1].buffer,Acig.begin());

					libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(pairbuffer[0],"MC",Acig.begin());

					uint64_t const score = libmaus2::bambam::BamAlignmentDecoderBase::getScore(pairbuffer[1].buffer);
					libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(pairbuffer[0],"MS",'i',score);
				}

				fragment->pushAlignmentBlock(pairbuffer[0].buffer,pairbuffer[0].length);
				checkptr->update(pairbuffer[0].buffer,pairbuffer[0].length);

				fragment->pushAlignmentBlock(pairbuffer[1].buffer,pairbuffer[1].length);
				checkptr->update(pairbuffer[1].buffer,pairbuffer[1].length);
			}
			else
			{
				if ( pairbuffer[0].length )
				{
					reinsertClip(pairbuffer[0],ASEQ,AQUAL,cigop);
					fragment->pushAlignmentBlock(pairbuffer[0].buffer,pairbuffer[0].length);
					checkptr->update(pairbuffer[0].buffer,pairbuffer[0].length);
				}
				if ( pairbuffer[1].length )
				{
					reinsertClip(pairbuffer[1],ASEQ,AQUAL,cigop);
					fragment->pushAlignmentBlock(pairbuffer[1].buffer,pairbuffer[1].length);
					checkptr->update(pairbuffer[1].buffer,pairbuffer[1].length);
				}
			}
		}

		if ( mdstr.tellp() )
		{
			std::string const mdwarn = mdstr.str();
			mdstr.str(mdempty);
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << mdwarn;
		}

		auxFilterFreeList.put(pauxfilter);
		checksumsFreeList.put(checkptr);
		mdFreeList.put(mdptr);
		atcFreeList.put(patc);

		fragment->getLinearOutputFragments(libmaus2::lz::BgzfConstants::getBgzfMaxBlockSize(),mergeinfo.linfrag);
		mergeinfo.Voutbuf.resize(mergeinfo.linfrag.size());
		mergeinfo.Vflushinfo.resize(mergeinfo.linfrag.size());

		BBMPF.blockBamMergePackageFinishedEvent(BP->info);
		BBMPR.blockBamMergePackageReturnEvent(BP);
	}
};

struct BlockBamMergeCompressPackage : public libmaus2::parallel::SimpleThreadWorkPackage
{
	typedef BlockBamMergePackage this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BamMergeEntryInfo::shared_ptr_type info;
	uint64_t i;

	BlockBamMergeCompressPackage & operator=(BlockBamMergeCompressPackage const & O)
	{
		if ( this != &O )
		{
			libmaus2::parallel::SimpleThreadWorkPackage::operator=(O);
			info = O.info;
			i = O.i;
		}
		return *this;
	}


	BlockBamMergeCompressPackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), info(), i(0)
	{

	}

	BlockBamMergeCompressPackage(BlockBamMergeCompressPackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), info(O.info), i(O.i)
	{

	}

	BlockBamMergeCompressPackage(
		BamMergeEntryInfo::shared_ptr_type rinfo,
		uint64_t const ri,
		uint64_t const rpriority,
		uint64_t const rdispatcherid,
		uint64_t const rpackageid = 0,
		uint64_t const rsubid = 0
	) : libmaus2::parallel::SimpleThreadWorkPackage(rpriority,rdispatcherid,rpackageid,rsubid), info(rinfo), i(ri)
	{
	}

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

struct BlockBamMergeCompressPackageReturnEvent
{
	virtual ~BlockBamMergeCompressPackageReturnEvent() {}
	virtual void blockBamMergeCompressPackageReturnEvent(BlockBamMergeCompressPackage * p) = 0;
};

struct BlockBamMergeCompressPackageFinishedEvent
{
	virtual ~BlockBamMergeCompressPackageFinishedEvent() {}
	virtual void blockBamMergeCompressPackageFinishedEvent(BamMergeEntryInfo::shared_ptr_type info) = 0;
};

struct BlockBamMergeCompressPackageDispatcher : public libmaus2::parallel::SimpleThreadWorkPackageDispatcher
{
	typedef BlockBamMergePackageDispatcher this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	BlockReadStreamInfoVector * BRSIV;
	BlockBamMergeCompressPackageReturnEvent & BBMCPR;
	BlockBamMergeCompressPackageFinishedEvent & BBMCPF;

	libmaus2::parallel::LockedGrowingFreeList<
		libmaus2::lz::BgzfDeflateOutputBufferBase,
		libmaus2::lz::BgzfDeflateOutputBufferBaseAllocator,
		libmaus2::lz::BgzfDeflateOutputBufferBaseTypeInfo
	> & bgzfOutBaseFreeList;

	libmaus2::parallel::LockedGrowingFreeList<
		libmaus2::lz::BgzfDeflateZStreamBase,
		libmaus2::lz::BgzfDeflateZStreamBaseAllocator,
		libmaus2::lz::BgzfDeflateZStreamBaseTypeInfo
	> & deflateFreeList;

	BlockBamMergeCompressPackageDispatcher(
		BlockReadStreamInfoVector * rBRSIV,
		BlockBamMergeCompressPackageReturnEvent & rBBMCPR,
		BlockBamMergeCompressPackageFinishedEvent & rBBMCPF,
		libmaus2::parallel::LockedGrowingFreeList<
			libmaus2::lz::BgzfDeflateOutputBufferBase,
			libmaus2::lz::BgzfDeflateOutputBufferBaseAllocator,
			libmaus2::lz::BgzfDeflateOutputBufferBaseTypeInfo
		> & rbgzfOutBaseFreeList,
		libmaus2::parallel::LockedGrowingFreeList<
			libmaus2::lz::BgzfDeflateZStreamBase,
			libmaus2::lz::BgzfDeflateZStreamBaseAllocator,
			libmaus2::lz::BgzfDeflateZStreamBaseTypeInfo
		> & rdeflateFreeList
	) : BRSIV(rBRSIV), BBMCPR(rBBMCPR), BBMCPF(rBBMCPF), bgzfOutBaseFreeList(rbgzfOutBaseFreeList), deflateFreeList(rdeflateFreeList)
	{
	}

	virtual ~BlockBamMergeCompressPackageDispatcher() {}
	virtual void dispatch(libmaus2::parallel::SimpleThreadWorkPackage * P, libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & /* tpi */)
	{
		BlockBamMergeCompressPackage * BP = dynamic_cast<BlockBamMergeCompressPackage *>(P);
		assert ( BP );

		libmaus2::lz::BgzfDeflateZStreamBaseTypeInfo::pointer_type compressor =
			deflateFreeList.get();

		BP->info->Voutbuf[BP->i] = bgzfOutBaseFreeList.get();

		libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo const flushinfo = compressor->flush(
			BP->info->linfrag[BP->i].first,
			BP->info->linfrag[BP->i].second,
			*(BP->info->Voutbuf[BP->i])
		);

		BP->info->Vflushinfo[BP->i] = flushinfo;

		deflateFreeList.put(compressor);

		uint64_t lencoded;
		{
			quick_lock_wrapper_type slock(*(BP->info->linfragencodedlock));
			lencoded = ++BP->info->linfragencoded;
		}

		if ( lencoded == BP->info->linfrag.size() )
			BBMCPF.blockBamMergeCompressPackageFinishedEvent(BP->info);

		BBMCPR.blockBamMergeCompressPackageReturnEvent(BP);
	}
};


struct BlockReadControl :
	public BlockReadQueueEvent,
	public BlockReadReturnEvent,
	public BlockProcessReturnEvent,
	public ReturnDecompressedBlockEvent,
	public BlockProcessBamHeaderFinishedEvent,
	public BlockProcessEnqueParsedBamBlockEvent,
	public BlockBamMergePackageReturnEvent,
	public BlockBamMergePackageFinishedEvent,
	public BlockBamMergeCompressPackageReturnEvent,
	public BlockBamMergeCompressPackageFinishedEvent
{
	std::ostream & out;

	Reference::shared_ptr_type reference;

	libmaus2::parallel::SimpleThreadPool & STP;
	uint64_t const blocksize;
	uint64_t const numblocks;
	uint64_t const readdiv;
	uint64_t const readthres;

	int const level;

	libmaus2::digest::DigestInterface & filedigest;

	libmaus2::lz::BgzfDeflateOutputBufferBaseAllocator bgzfOutAlloc;
	libmaus2::parallel::LockedGrowingFreeList<
		libmaus2::lz::BgzfDeflateOutputBufferBase,
		libmaus2::lz::BgzfDeflateOutputBufferBaseAllocator,
		libmaus2::lz::BgzfDeflateOutputBufferBaseTypeInfo
	> bgzfOutBaseFreeList;

	libmaus2::lz::BgzfDeflateZStreamBaseAllocator deflateAlloc;
	libmaus2::parallel::LockedGrowingFreeList<
		libmaus2::lz::BgzfDeflateZStreamBase,
		libmaus2::lz::BgzfDeflateZStreamBaseAllocator,
		libmaus2::lz::BgzfDeflateZStreamBaseTypeInfo
	> deflateFreeList;

	ChecksumsAllocator checksumsAlloc;
	libmaus2::parallel::LockedGrowingFreeList<
		ChecksumsTypeInfo::element_type,
		ChecksumsAllocator,
		ChecksumsTypeInfo
	> checksumsFreeList;

	uint64_t nextdispatcherid;
	uint64_t const blockreaddispatcherid;
	uint64_t const blockprocessdispatcherid;
	uint64_t const blockprocessbamencodepackagedispatcherid;
	uint64_t const bamstreammergepackagedispatcherid;
	uint64_t const bambgzfcompressionpackagedispatcherid;
	uint64_t const blockbammergepackagedispatcherid;
	uint64_t const blockbammergecompresspackagedispatcherid;
	BlockReadStreamInfoVector BRSIV;

	libmaus2::parallel::LockedGrowingFreeList<BlockReadPackage> blockReadPackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockProcessPackage> blockProcessPackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockBamMergePackage> blockBamMergePackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockBamMergeCompressPackage> blockBamMergeCompressPackageFreeList;

	BlockReadDispatcher BRD;
	BlockProcessDispatcher BPD;
	BlockBamMergePackageDispatcher BBMP;
	BlockBamMergeCompressPackageDispatcher BBMCP;

	uint64_t volatile eofcnt;
	quick_lock_type eoflock;

	libmaus2::autoarray::AutoArray<uint64_t> Ainput;
	quick_lock_type Ainputlock;

	libmaus2::autoarray::AutoArray<uint64_t> Adecomp;
	quick_lock_type Adecomplock;

	uint64_t volatile bammergenextid;

	libmaus2::parallel::LockedGrowingFreeList<
		FragmentAlignmentBufferFragmentTypeInfo::element_type,
		FragmentAlignmentBufferFragmentAllocator,
		FragmentAlignmentBufferFragmentTypeInfo
	> mergeFragmentsFreeList;

	uint64_t volatile numbam;
	uint64_t volatile numbamprinted;
	quick_lock_type numbamlock;

	uint64_t volatile bamHeaderFinished;
	quick_lock_type bamHeaderFinishedLock;
	uint64_t volatile bamMergeSubId;

	libmaus2::util::FiniteSizeHeap<BamMergeEntryInfo::shared_ptr_type,BamMergeEntryInfoComparator> FSHBMEI;
	uint64_t volatile FSHBMEInext;
	uint64_t volatile FSHBMEIbase;
	quick_lock_type FSHBMEIlock;

	libmaus2::util::FiniteSizeHeap<BamMergeEntryInfo::shared_ptr_type,BamMergeEntryInfoComparator> FSHBMEIW;
	uint64_t volatile FSHBMEIWnext;
	quick_lock_type FSHBMEIWlock;

	libmaus2::util::ArgInfo const & arginfo;

	bool isEOF()
	{
		bool iseof;

		{
			quick_lock_wrapper_type slock(eoflock);
			iseof = (eofcnt == BRSIV.A.size());
		}

		return iseof;
	}

	BlockReadControl(
		std::ostream & rout,
		Reference::shared_ptr_type treference,
		bool const mdwarnchange,
		bool const replacecigar,
		libmaus2::parallel::SimpleThreadPool & rSTP,
		std::vector<std::string> const & Vfn,
		int const rlevel,
		std::string const & hash,
		libmaus2::bambam::BamHeader const * bamheader,
		libmaus2::digest::DigestInterface & rfiledigest,
		bool const rzz,
		libmaus2::util::ArgInfo const & rarginfo,
		uint64_t const rblocksize,
		uint64_t const rnumblocks,
		uint64_t const rreaddiv = 2
	) :
	    out(rout),
	    reference(treference),
	    STP(rSTP),
	    blocksize(rblocksize),
	    numblocks(rnumblocks),
	    readdiv(rreaddiv),
	    readthres( (numblocks + readdiv - 1)/readdiv ),
	    level(rlevel),
	    filedigest(rfiledigest),
	    bgzfOutAlloc(level),
	    bgzfOutBaseFreeList(bgzfOutAlloc),
	    deflateAlloc(level),
	    deflateFreeList(deflateAlloc),
	    checksumsAlloc(hash,bamheader),
	    checksumsFreeList(checksumsAlloc),
	    nextdispatcherid(0),
	    blockreaddispatcherid(nextdispatcherid++),
	    blockprocessdispatcherid(nextdispatcherid++),
	    blockprocessbamencodepackagedispatcherid(nextdispatcherid++),
	    bamstreammergepackagedispatcherid(nextdispatcherid++),
	    bambgzfcompressionpackagedispatcherid(nextdispatcherid++),
	    blockbammergepackagedispatcherid(nextdispatcherid++),
	    blockbammergecompresspackagedispatcherid(nextdispatcherid++),
	    BRSIV(Vfn,blocksize,numblocks,STP.getNumThreads()),
	    BRD(&BRSIV,*this,*this),
	    BPD(STP.getNumThreads(),&BRSIV,*this,*this,*this,*this),
	    BBMP(&BRSIV,*this,*this,*bamheader,checksumsFreeList,rzz,reference,mdwarnchange,replacecigar),
	    BBMCP(&BRSIV,*this,*this,bgzfOutBaseFreeList,deflateFreeList),
	    eofcnt(0),
	    eoflock(),
	    Ainput(Vfn.size()),
	    Adecomp(Vfn.size()),
	    bammergenextid(0),
	    numbam(0),
	    numbamprinted(0),
	    bamHeaderFinished(0),
	    bamMergeSubId(0),
	    FSHBMEI(0),
	    FSHBMEInext(0),
	    FSHBMEIbase(0),
	    FSHBMEIlock(),
	    FSHBMEIW(0),
	    FSHBMEIWnext(0),
	    FSHBMEIWlock(),
	    arginfo(rarginfo)
	{
		STP.registerDispatcher(blockreaddispatcherid, &BRD);
		STP.registerDispatcher(blockprocessdispatcherid, &BPD);
		STP.registerDispatcher(blockbammergepackagedispatcherid,&BBMP);
		STP.registerDispatcher(blockbammergecompresspackagedispatcherid,&BBMCP);
		enqueRead();
	}

	void enqueRead(uint64_t const i)
	{
		BlockReadPackage * p = blockReadPackageFreeList.get();

		*p =
			BlockReadPackage(
				i /* stream id */,
				256 /* priority */,
				blockreaddispatcherid
			);

		STP.enque(p);
	}

	void enqueRead()
	{
		for ( uint64_t i = 0; i < BRSIV.A.size(); ++i )
			enqueRead(i);
	}

	virtual void enqueDecompressedBlock(DataBlockTypeInfo::pointer_type dptr)
	{
		uint64_t const streamid = dptr->streamid;
		BlockReadStreamInfo & info = *(BRSIV.A[streamid]);

		// retract to start of data after decompression finished
		dptr->c -= dptr->n;

		{
			quick_lock_wrapper_type slock(info.deFSHlock);
			info.deFSH.push(dptr);
		}

		BlockProcessPackage * p = blockProcessPackageFreeList.get();

		*p =
			BlockProcessPackage(
				streamid /* stream id */,
				0 /* priority */,
				blockprocessdispatcherid
			);

		STP.enque(p);
	}

	virtual void blockReadQueueEvent(uint64_t const streamid)
	{
		#if 0
		{
		quick_lock_wrapper_type slock(libmaus2::aio::StreamLock::cerrlock);
		// if ( streamid > 0 )
			std::cerr << "block event for " << streamid << std::endl;
		}
		#endif

		BlockReadStreamInfo & info = *(BRSIV.A[streamid]);

		std::pair<
			DataBlockTypeInfo::pointer_type,
			DataBlockTypeInfo::pointer_type
		>
			blockptr;

		while ( (blockptr = info.getNextBlock()).first )
		{
			DataBlockTypeInfo::pointer_type cptr = blockptr.first;
			DataBlockTypeInfo::pointer_type	dptr = blockptr.second;

			assert ( cptr );
			assert ( dptr );

			bool const leof = cptr->eof;

			if ( leof )
			{
				if (
					!info.needheader
					||
					(info.headerparser.state != libmaus2::lz::GzipHeaderParser::gzip_header_parser_state_id1)
				)
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] gzip parser is not in an idle state at EOF" << std::endl;
					lme.finish();
					throw lme;
				}

				// std::cerr << "eof on stream " << streamid << " " << Ainput[streamid] << " " << dptr->n << std::endl;

				dptr->eof = true;

				enqueDecompressedBlock(dptr);
			}
			else
			{
				if ( info.needheader )
				{
					info.putBackUncompressedBlock(dptr);

					libmaus2::lz::GzipHeaderParser & ghp = info.headerparser;

					std::pair<bool,char const *> const ghpresult = ghp.parse(
						cptr->c,
						cptr->c + cptr->n
					);

					uint64_t const used = ghpresult.second - cptr->c;

					cptr->c += used;
					assert ( cptr->c == ghpresult.second );
					cptr->n -= used;


					if ( cptr->n )
					{
						info.putBackCompressedBlock(cptr);
					}
					else
					{
						uint64_t const fill = info.Pfreelist->putAndCount(cptr);

						if ( fill >= readthres )
							enqueRead(streamid);

						info.incrementNextProcId();
					}

					if ( ghpresult.first )
					{
						info.needheader = 0;
					}
				}
				else if ( info.needfooter )
				{
					info.putBackUncompressedBlock(dptr);

					uint64_t const av = cptr->n;
					uint64_t const need = info.needfooter;
					uint64_t const use = std::min(av,need);

					std::copy(
						cptr->c,
						cptr->c + use,
						info.footerDataP
					);

					cptr->c += use;
					cptr->n -= use;
					info.footerDataP += use;

					info.needfooter -= use;

					if ( cptr->n )
					{
						info.putBackCompressedBlock(cptr);
					}
					else
					{
						uint64_t const fill = info.Pfreelist->putAndCount(cptr);

						if ( fill >= readthres )
							enqueRead(streamid);

						info.incrementNextProcId();
					}

					if ( info.needfooter == 0 )
					{
						uint32_t const storedcrc32digest =
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[0])) << 0)
							|
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[1])) << 8)
							|
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[2])) << 16)
							|
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[3])) << 24)
							;

						if ( storedcrc32digest != info.crc32digest )
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "[E] crc32 mismatch in gzip decoding" << std::endl;
							lme.finish();
							throw lme;
						}

						uint32_t const storedlength =
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[4])) << 0)
							|
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[5])) << 8)
							|
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[6])) << 16)
							|
							(static_cast<uint32_t>(static_cast<unsigned char>(info.footerData[7])) << 24)
							;

						// std::cerr << "stored length " << storedlength << std::endl;

						if ( storedlength != info.crc32length )
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "[E] uncompressed length mismatch in gzip decoding: " << storedlength << " != " << info.crc32length << std::endl;
							lme.finish();
							throw lme;
						}

						info.needheader = 1;
						info.headerparser.reset();
						info.crc32length = 0;
					}

				}
				else
				{
					libmaus2::lz::ZlibInterface & zintf = *(info.zintf);

					if ( ! dptr->capacity() )
					{
						dptr->bump();
					}

					uint64_t const av_in_before  = cptr->n;
					uint64_t const av_out_before = dptr->capacity();

					if ( ! av_in_before )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] av_in_before=" << av_in_before << " streamid=" << streamid << std::endl;
						lme.finish();
						throw lme;
					}
					assert ( av_in_before );
					if ( ! av_out_before )
					{
						libmaus2::lz::GzipHeaderParser & ghp = info.headerparser;

						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] av_out_before=" << av_out_before << " streamid=" << streamid << std::endl;
						lme.getStream() << "[E] av_in_before=" << av_in_before << std::endl;
						lme.getStream() << "[E] decoded C " << (cptr->c - cptr->A.begin()) << std::endl;
						lme.getStream() << "[E] decoded D " << (dptr->c - dptr->A.begin()) << std::endl;
						assert ( ghp.state == ghp.gzip_header_parser_state_done );

						lme.getStream() << "[E] ghp.FLG=" << static_cast<int>(ghp.FLG) << std::endl;
						lme.getStream() << "[E] ghp.MTIME=" << ghp.MTIME << std::endl;
						lme.getStream() << "[E] ghp.XFL=" << static_cast<int>(ghp.XFL) << std::endl;
						lme.getStream() << "[E] ghp.OS=" << static_cast<int>(ghp.OS) << std::endl;
						lme.getStream() << "[E] ghp.XLEN=" << ghp.XLEN << std::endl;
						lme.getStream() << "[E] ghp.extralen=" << ghp.extralen << std::endl;
						lme.getStream() << "[E] ghp.extralenread=" << ghp.extralenread << std::endl;
						lme.getStream() << "[E] ghp.filename=" << ghp.filename << std::endl;
						lme.getStream() << "[E] ghp.comment=" << ghp.comment << std::endl;
						lme.getStream() << "[E] ghp.CRC16=" << ghp.CRC16 << std::endl;

						if ( ghp.extralen == 6 )
						{
							lme.getStream() << "[E] extra header " << ghp.extradata.substr(0,2) << std::endl;
							lme.getStream() << "[E] extra length "
								<<
									((static_cast<uint64_t>(static_cast<unsigned char>(ghp.extradata[2]))<<0)
									|
									(static_cast<uint64_t>(static_cast<unsigned char>(ghp.extradata[3]))<<8))
								<< std::endl;
							lme.getStream() << "[E] extra blocksize-1 "
								<<
									((static_cast<uint64_t>(static_cast<unsigned char>(ghp.extradata[4]))<<0)
									|
									(static_cast<uint64_t>(static_cast<unsigned char>(ghp.extradata[5]))<<8))
								<< std::endl;
						}
						lme.getStream() << "[E] ghp.extradata=" << ghp.extradata << std::endl;

						lme.finish();
						throw lme;
					}
					assert ( av_out_before );

					zintf.setAvailIn(av_in_before);
					zintf.setNextIn(reinterpret_cast<unsigned char *>(cptr->c));
					zintf.setAvailOut(av_out_before);
					zintf.setNextOut(reinterpret_cast<unsigned char *>(dptr->c));

					info.ret = zintf.z_inflate(Z_NO_FLUSH);

					if ( info.ret != Z_STREAM_END && info.ret != Z_OK )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] z_inflate failed " << info.ret << std::endl;
						lme.finish();
						throw lme;
					}

					uint64_t const av_in_after = zintf.getAvailIn();
					uint64_t const av_out_after = zintf.getAvailOut();

					uint64_t const consumed_in = av_in_before - av_in_after;
					uint64_t const consumed_out = av_out_before - av_out_after;

					info.crc32.update(reinterpret_cast<uint8_t const *>(dptr->c),consumed_out);
					info.crc32length += consumed_out;

					// std::cerr << "consumed_in=" << consumed_in << " consumed_out=" << consumed_out << std::endl;

					dptr->c += consumed_out;
					dptr->n += consumed_out;
					cptr->c += consumed_in;
					cptr->n -= consumed_in;

					if ( info.ret == Z_STREAM_END )
					{
						uint8_t udigest[4];
						info.crc32.digest(&udigest[0]);

						info.crc32digest =
							(static_cast<uint32_t>(udigest[3]) << 0)
							|
							(static_cast<uint32_t>(udigest[2]) << 8)
							|
							(static_cast<uint32_t>(udigest[1]) << 16)
							|
							(static_cast<uint32_t>(udigest[0]) << 24)
							;

						info.zreset();
						info.needfooter = 8;
						info.footerDataP = info.footerData.begin();
					}

					if ( av_out_after )
					{
						info.putBackUncompressedBlock(dptr);
						// info.Dfreelist->put(dptr);
					}
					else
					{
						enqueDecompressedBlock(dptr);
					}

					{
						quick_lock_wrapper_type slock(Ainputlock);
						Ainput[streamid] += consumed_in;
					}

					if ( av_in_after )
					{
						info.putBackCompressedBlock(cptr);
					}
					else
					{
						uint64_t const fill = info.Pfreelist->putAndCount(cptr);

						if ( fill >= readthres )
							enqueRead(streamid);

						info.incrementNextProcId();
					}
				}
			}
		}

		#if 0
		{
		quick_lock_wrapper_type slock(libmaus2::aio::StreamLock::cerrlock);
		if ( streamid > 0 )
			std::cerr << "left block event for " << streamid << std::endl;
		}
		#endif
	}

	virtual void blockReadReturnEvent(BlockReadPackage * p)
	{
		blockReadPackageFreeList.put(p);
	}

	virtual void blockProcessReturnEvent(BlockProcessPackage * p)
	{
		blockProcessPackageFreeList.put(p);
	}

	virtual void blockBamMergePackageReturnEvent(BlockBamMergePackage * p)
	{
		blockBamMergePackageFreeList.put(p);
	}

	virtual void blockBamMergeCompressPackageReturnEvent(BlockBamMergeCompressPackage * p)
	{
		blockBamMergeCompressPackageFreeList.put(p);
	}

	virtual void returnDecompressedBlockEvent(DataBlock::shared_ptr_type block)
	{
		uint64_t const streamid = block->streamid;
		BlockReadStreamInfo & info = *(BRSIV.A[streamid]);

		#if 0
		uint64_t const lnumbam = block->fastQPtrList->Ao;
		int64_t lnumbamprint = -1;

		{
			quick_lock_wrapper_type slock(numbamlock);
			numbam += lnumbam;

			if ( (numbam >> 20) != (numbamprinted >> 20) )
			{
				numbamprinted = numbam;
				lnumbamprint = numbam;
			}
		}

		if ( lnumbamprint >= 0 )
		{
			quick_lock_wrapper_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "[V] encoded " << lnumbamprint << std::endl;
		}

		{
			quick_lock_wrapper_type slock(Adecomplock);
			Adecomp[streamid] += block->n;
		}
		#endif

		if ( block->eof )
		{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "[V] found EOF on stream " << streamid << " in=" << Ainput[streamid] << std::endl;
		}

		if ( block->eof )
		{
			quick_lock_wrapper_type slock(eoflock);
			eofcnt += 1;

			if ( eofcnt == BRSIV.A.size() )
			{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				quick_lock_wrapper_type snlock(numbamlock);
				std::cerr << "[V] " << numbam << std::endl;
			}
		}

		#if 0
		info.newLineInfoFreeList.put(block->newLineInfo);
		info.fastQPtrListFreeList.put(block->fastQPtrList);
		info.fragmentFreeList.put(block->fragmentBuffer);

		for ( uint64_t i = 0; i < block->fragmentBufferPointers.size(); ++i )
			info.fragmentPointerFreeList.put(block->fragmentBufferPointers[i]);

		info.fragmentPointerFreeList.put(block->fragmentBufferPointersMerged);
		#endif

		info.bamPointerFreeList.put(block->bamPointers);

		block->reset();
		info.Dfreelist->put(block);
		blockReadQueueEvent(streamid);
	}

	virtual std::string getChecksums()
	{
		std::ostringstream ostr;
		std::vector < ChecksumsTypeInfo::pointer_type > V = checksumsFreeList.getAll();

		if ( V.size() )
		{
			for ( uint64_t i = 1; i < V.size(); ++i )
				V[0]->update(*V[i]);

			V[0]->printChecksumsForBamHeader(ostr);

			checksumsFreeList.put(V);
		}
		else
		{
			ChecksumsTypeInfo::pointer_type ptr = checksumsFreeList.get();
			ptr->printChecksumsForBamHeader(ostr);
			checksumsFreeList.put(ptr);
		}

		return ostr.str();
	}

	virtual void writeData(char const * p, uint64_t const l)
	{
		filedigest.vupdate(
			reinterpret_cast<uint8_t const *>(p),l
		);

		out.write(p,l);
	}

	virtual void blockProcessBamHeaderFinishedEvent(DataBlockTypeInfo::pointer_type /* ptr */)
	{
		uint64_t lbamHeaderFinished;
		{
			quick_lock_wrapper_type slock(bamHeaderFinishedLock);
			lbamHeaderFinished = ++bamHeaderFinished;

		}

		if (  lbamHeaderFinished == BRSIV.A.size() )
		{
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << "[V] all BAM headers obtained" << std::endl;

			if ( BRSIV.A.size() > 1 )
			{
				libmaus2::bambam::BamHeaderParserState const & BHPS_b = BRSIV.A[1]->BHPS;
				std::string const header_b = std::string(BHPS_b.text.begin(),BHPS_b.text.begin() + BHPS_b.l_text);
				std::vector<libmaus2::bambam::HeaderLine> VSQ = libmaus2::bambam::HeaderLine::extractLinesByType(header_b,"SQ");
				std::vector<libmaus2::bambam::HeaderLine> VPG = libmaus2::bambam::HeaderLine::extractLinesByType(header_b,"PG");

				libmaus2::bambam::BamHeaderParserState const & BHPS_a = BRSIV.A[0]->BHPS;
				std::string const header_a = std::string(BHPS_a.text.begin(),BHPS_a.text.begin() + BHPS_a.l_text);

				std::ostringstream outhdrostr;
				outhdrostr << header_a;

				for ( uint64_t i = 0; i < VSQ.size(); ++i )
					outhdrostr << VSQ[i].line << "\n";

				std::string headertext(
					libmaus2::bambam::BamHeader::filterOutChecksum(outhdrostr.str())
				);

				std::string const prevchain = ::libmaus2::bambam::ProgramHeaderLineSet(headertext).getLastIdInChain();

				for ( uint64_t i = 0; i < VPG.size(); ++i )
				{
					libmaus2::bambam::HeaderLine const & H = VPG[i];

					std::string const ID = H.hasKey("ID") ? H.getValue("ID") : std::string();
					std::string const PN = H.hasKey("PN") ? H.getValue("PN") : std::string();
					std::string const CL = H.hasKey("CL") ? H.getValue("CL") : std::string();
					std::string const VN = H.hasKey("VN") ? H.getValue("VN") : std::string();
					std::string const PP = H.hasKey("PP") ? H.getValue("PP") : prevchain;


					if ( ID.size() && PN.size() && CL.size() && VN.size() )
						headertext = ::libmaus2::bambam::ProgramHeaderLineSet::addProgramLine(
							headertext,
							ID, // ID
							PN, // PN
							CL, // CL
							PP, // PP
							VN // VN
						);
				}

				// add PG line to header
				headertext = ::libmaus2::bambam::ProgramHeaderLineSet::addProgramLine(
					headertext,
					"bamauxmerge2", // ID
					"bamauxmerge2", // PN
					arginfo.commandline, // CL
					::libmaus2::bambam::ProgramHeaderLineSet(headertext).getLastIdInChain(), // PP
					std::string(PACKAGE_VERSION) // VN
				);

				libmaus2::bambam::BamHeader header(headertext);

				if ( reference )
				{
					reference->computeMap(header);
				}

				std::ostringstream defstr;
				libmaus2::lz::BgzfDeflate<std::ostream> def(defstr,level);
				header.serialise(def);
				def.flush();
				std::string const bgzfhead = defstr.str();

				writeData(bgzfhead.c_str(),bgzfhead.size());
			}
		}
	}


	virtual void blockProcessEnqueParsedBamBlockEvent()
	{
		uint64_t const numstreams = BRSIV.A.size();
		std::vector<quick_lock_wrapper_type::shared_ptr_type> VL(numstreams);
		for ( uint64_t i = 0; i < numstreams; ++i )
		{
			quick_lock_wrapper_type::shared_ptr_type p(
				new quick_lock_wrapper_type(
					(*BRSIV.A[i]).bamMergeQueueFSHLock
				)
			);
			VL[i] = p;
		}

		#if 0
		for ( uint64_t i = 0; i < numstreams; ++i )
		{
			std::cerr << "stream " << i << " next " << BRSIV.A[i]->bamMergeQueueFSHnext << std::endl;
		}
		#endif

		bool enqueued = true;

		while ( enqueued )
		{
			enqueued = false;

			// handle empty input packages
			for ( uint64_t i = 0; i < numstreams; ++i )
				while (
					!BRSIV.A[i]->bamMergeQueueFSH.empty()
					&&
					BRSIV.A[i]->bamMergeQueueFSH.top()->seqid == BRSIV.A[i]->bamMergeQueueFSHnext
					&&
					BRSIV.A[i]->bamMergeQueueFSH.top()->bamMergeNext == BRSIV.A[i]->bamMergeQueueFSH.top()->bamPointers->Ao
				)
				{
					DataBlockTypeInfo::pointer_type ptr = BRSIV.A[i]->bamMergeQueueFSH.pop();

					std::vector<BamMergeEntry> V;
					V.push_back(BamMergeEntry(ptr,0,0));

					{
						quick_lock_wrapper_type slock(ptr->bamMergeNumPackagesLock);
						ptr->bamMergeNumPackages += 1;
						ptr->bamMergeNumPackagesAll = 1;
					}

					BRSIV.A[i]->bamMergeQueueFSHnext += 1;

					if ( ptr->eof )
						BRSIV.A[i]->bamMergeEOF = 1;

					// std::cerr << "queueing empty " << V.back() << std::endl;

					FragmentAlignmentBufferFragmentTypeInfo::pointer_type fragment =
						mergeFragmentsFreeList.get();

					uint64_t const lbamMergeSubId = bamMergeSubId++;

					BamMergeEntryInfo::shared_ptr_type info(new BamMergeEntryInfo(lbamMergeSubId,V,fragment));

					BlockBamMergePackage BBMP(info,0 /* prio */,blockbammergepackagedispatcherid,0 /* pack id */,lbamMergeSubId);
					BlockBamMergePackage * BP = blockBamMergePackageFreeList.get();
					*BP = BBMP;

					STP.enque(BP);
					enqueued = true;
				}

			// see if all packages are ready or have reached the end of their stream
			bool ok = true;
			for ( uint64_t i = 0; ok && i < numstreams; ++i )
			{
				bool const haveblock =
					!BRSIV.A[i]->bamMergeQueueFSH.empty()
					&&
					BRSIV.A[i]->bamMergeQueueFSH.top()->seqid == BRSIV.A[i]->bamMergeQueueFSHnext;

				bool const lok = haveblock || BRSIV.A[i]->bamMergeEOF;

				ok = ok && lok;
			}

			// std::cerr << "have all ok " << ok << std::endl;

			if ( ok )
			{
				std::vector<BamMergeEntry> V;

				std::vector<char const *> VN;

				for ( uint64_t i = 0; ok && i < numstreams; ++i )
				{
					bool const haveblock =
						!BRSIV.A[i]->bamMergeQueueFSH.empty()
						&&
						BRSIV.A[i]->bamMergeQueueFSH.top()->seqid == BRSIV.A[i]->bamMergeQueueFSHnext;

					if ( haveblock )
					{
						DataBlockTypeInfo::pointer_type ptr = BRSIV.A[i]->bamMergeQueueFSH.pop();
						uint64_t const from = ptr->bamMergeNext;
						uint64_t const to = ptr->bamPointers->Ao;
						assert ( to != from );
						V.push_back(BamMergeEntry(ptr,from,to));


						char const * lastname = libmaus2::bambam::BamAlignmentDecoderBase::getReadName(
							ptr->bamPointers->A[to-1].pa
						);

						#if 0
						std::cerr << "have block " << ptr->seqid << " for stream " << i << " " << V.back()
							<< " first " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(ptr->bamPointers->A[from].pa)
							<< " last " << lastname
							<< std::endl;
						#endif

						// std::cerr << "last name in " << i << " is " << lastname << std::endl;

						VN.push_back(lastname);
					}
				}

				std::sort(VN.begin(),VN.end(),comp);

				if ( VN.size() )
				{
					// std::cerr << "minimum name " << VN[0] << std::endl;

					// compute interval end points according to minimum name
					for ( uint64_t i = 0; i < V.size(); ++i )
					{
						NameGet const NG(V[i].ptr);
						name_it_type const it_a(&NG,V[i].from);
						name_it_type const it_e(&NG,V[i].to);
						name_it_type it_c = std::lower_bound(it_a,it_e,VN[0],comp);

						assert ( it_c != it_e );

						// move over entries equalling minimum name
						while ( it_c != it_e && ! comp(VN[0],*it_c) )
							++it_c;

						V[i].to = (it_c - it_a) + V[i].from;
					}

					#if 0
					uint64_t const numthreads = STP.getNumThreads();
					uint64_t const numsplitpoints =
						(numthreads > 1) ? (numthreads-1) : 0;

					uint64_t sizesum = 0;
					for ( uint64_t i = 0; i < V.size(); ++i )
						sizesum += V[i].to - V[i].from;

					for ( uint64_t i = 0; i < numsplitpoints; ++i )
					{
						uint64_t const size = V[0].to - V[0].from;
						uint64_t const sizeperthread = (size + numthreads - 1)/numthreads;
						uint64_t const tstart = (i+1)*sizeperthread;
					}
					#endif

					for ( uint64_t i = 0; i < V.size(); ++i )
					{
						{
							quick_lock_wrapper_type slock(V[i].ptr->bamMergeNumPackagesLock);
							V[i].ptr->bamMergeNumPackages += 1;
						}

						V[i].ptr->bamMergeNext = V[i].to;

						if ( V[i].ptr->bamMergeNext == V[i].ptr->bamPointers->Ao )
						{
							quick_lock_wrapper_type slock(V[i].ptr->bamMergeNumPackagesLock);
							V[i].ptr->bamMergeNumPackagesAll = 1;

							BRSIV.A[V[i].ptr->streamid]->bamMergeQueueFSHnext += 1;

							if ( V[i].ptr->eof )
								BRSIV.A[V[i].ptr->streamid]->bamMergeEOF = 1;
						}
						else
						{
							// reenter package
							BRSIV.A[V[i].ptr->streamid]->bamMergeQueueFSH.push(V[i].ptr);
						}
					}

					#if 0
					for ( uint64_t i = 0; i < V.size(); ++i )
					{
						BamMergeEntry const & BME = V[i];
						DataBlockTypeInfo::pointer_type ptr = BME.ptr;

						std::cerr << "have block " << ptr->seqid << " for stream " << i << " " << BME
							<< " first " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(ptr->bamPointers->A[BME.from].pa)
							<< " last " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(ptr->bamPointers->A[BME.to-1].pa)
							<< std::endl;
					}
					#endif


					// mergeFragmentsFreeList

					FragmentAlignmentBufferFragmentTypeInfo::pointer_type fragment =
						mergeFragmentsFreeList.get();

					uint64_t const lbamMergeSubId = bamMergeSubId++;
					BamMergeEntryInfo::shared_ptr_type info(new BamMergeEntryInfo(lbamMergeSubId,V,fragment));

					// enque here
					// blockbammergepackagedispatcherid
					BlockBamMergePackage BBMP(info,0 /* prio */,blockbammergepackagedispatcherid,0 /* pack id */,lbamMergeSubId);
					BlockBamMergePackage * BP = blockBamMergePackageFreeList.get();
					*BP = BBMP;

					STP.enque(BP);
					enqueued = true;
				}
			}
		}
	}

	virtual void blockBamMergePackageFinishedEvent(BamMergeEntryInfo::shared_ptr_type info)
	{
		{
			quick_lock_wrapper_type slock(FSHBMEIlock);
			FSHBMEI.pushBump(info);
		}

		{
			quick_lock_wrapper_type slock(FSHBMEIlock);

			while ( ! FSHBMEI.empty() && FSHBMEI.top()->blockid == FSHBMEInext )
			{
				BamMergeEntryInfo::shared_ptr_type ptr = FSHBMEI.pop();

				for ( uint64_t i = 0; i < ptr->linfrag.size(); ++i )
				{
					BlockBamMergeCompressPackage BBMCP(
						ptr,
						i,
						128 /* priority */,
						blockbammergecompresspackagedispatcherid,
						0 /* packageid */,
						FSHBMEIbase + i /* subid */
					);

					BlockBamMergeCompressPackage * BP = blockBamMergeCompressPackageFreeList.get();
					*BP = BBMCP;

					STP.enque(BP);
				}

				FSHBMEIbase += ptr->linfrag.size();
				FSHBMEInext += 1;
			}
		}
	}

	BamMergeEntryInfo::shared_ptr_type getNextWritePackage()
	{
		quick_lock_wrapper_type slock(FSHBMEIWlock);

		BamMergeEntryInfo::shared_ptr_type info;

		if ( ! FSHBMEIW.empty() && FSHBMEIW.top()->blockid == FSHBMEIWnext )
			info = FSHBMEIW.pop();

		return info;
	}

	virtual void blockBamMergeCompressPackageFinishedEvent(BamMergeEntryInfo::shared_ptr_type rinfo)
	{
		{
			quick_lock_wrapper_type slock(FSHBMEIWlock);
			FSHBMEIW.pushBump(rinfo);
		}

		{
			BamMergeEntryInfo::shared_ptr_type info;

			while (
				(info = getNextWritePackage())
			)
			{
				for ( uint64_t i = 0; i < info->Voutbuf.size(); ++i )
				{
					libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type outbuf = info->Voutbuf[i];
					libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo const flushinfo = info->Vflushinfo[i];

					assert ( flushinfo.blocks == 1 || flushinfo.blocks == 2 );

					uint64_t n;
					char const * outp = reinterpret_cast<char const *>(outbuf->outbuf.begin());

					if ( flushinfo.blocks == 1 )
					{
						n = flushinfo.block_a_c;
					}
					else
					{
						assert ( flushinfo.blocks == 2 );
						n = flushinfo.block_a_c + flushinfo.block_b_c;
					}

					writeData(outp,n);
				}

				uint64_t const lbamwritten = info->fragment->f;

				{
					quick_lock_wrapper_type slock(numbamlock);
					numbam += lbamwritten;

					if ( (numbam >> 20) != (numbamprinted >> 20) )
					{
						libmaus2::aio::StreamLock::lock_type::scope_lock_type splock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "[V] " << numbam << std::endl;
						numbamprinted = numbam;
					}
				}

				mergeFragmentsFreeList.put(info->fragment);
				for ( uint64_t i = 0; i < info->Voutbuf.size(); ++i )
					bgzfOutBaseFreeList.put(info->Voutbuf[i]);

				for ( uint64_t i = 0; i < info->V.size(); ++i )
				{
					BamMergeEntry & BME = info->V[i];

					bool complete;

					{
						quick_lock_wrapper_type slock(BME.ptr->bamMergeNumPackagesLock);
						BME.ptr->bamMergeNumPackagesProcessed += 1;

						complete =
							BME.ptr->bamMergeNumPackagesProcessed == BME.ptr->bamMergeNumPackages
							&&
							BME.ptr->bamMergeNumPackagesAll;
					}

					if ( complete )
						returnDecompressedBlockEvent(BME.ptr);
				}

				FSHBMEIWnext += 1;
			}
		}
	}
};

static int getDefaultLevel()
{
	return libmaus2::lz::DeflateDefaults::getDefaultLevel();
}

static int getLevel(libmaus2::util::ArgInfo const & arginfo)
{
	return libmaus2::bambam::BamBlockWriterBaseFactory::checkCompressionLevel(arginfo.getValue<int>("level",getDefaultLevel()));
}

static std::string getDefaultHash()
{
	return "crc32prod";
}

static std::string getDefaultFileHash()
{
	return "md5";
}

static int getDefaultCalMdNm() { return 0; }
static int getDefaultCalMdNmWarnChange() { return 0; }
static int getDefaultReplaceCigar() { return 1; }

int bamauxmerge2(libmaus2::util::ArgInfo const & arginfo)
{
	// number of threads
	unsigned int numthreads = arginfo.getValue<unsigned int>("threads",1);

	// seqchksum hash
	std::string const hash = arginfo.getValue<std::string>("hash",getDefaultHash());
	// output file hash
	std::string const filehash = arginfo.getValue<std::string>("filehash",getDefaultFileHash());
	// file name for seq chksum
	std::string const chksumfn = arginfo.getUnparsedValue("chksumfn",std::string());
	// file name for file hash
	std::string const filehashfn = arginfo.getUnparsedValue("filehashfn",std::string());

	// calculate MD and NM tags
	bool const calmdnm = arginfo.getValue<unsigned int>("calmdnm",getDefaultCalMdNm());
	// reference for MD and NM tags
	if ( calmdnm && (! arginfo.hasArg("calmdnmreference")) )
	{
		libmaus2::exception::LibMausException lme;
		lme.getStream() << "calmdnm is set but required calmdnmreference is not, aborting." << std::endl;
		lme.finish();
		throw lme;
	}
	std::string const calmdnmreference = arginfo.getUnparsedValue("calmdnmreference","");
	// warn if we change the NM and MD fields
	bool const calmdnmwarnchange = arginfo.getValue<unsigned int>("calmdnmwarnchange",getDefaultCalMdNmWarnChange());
	bool const replacecigar = arginfo.getValue<int>("replacecigar",getDefaultReplaceCigar());

	Reference::shared_ptr_type reference;
	if ( calmdnm )
	{
		Reference::shared_ptr_type treference(new Reference(calmdnmreference));
		reference = treference;
	}

	// if true then replace name by content of nn aux tag
	bool const zz = arginfo.getValue<int>("zz",0);

	// if number of threads is given as 0, then use number of hardware threads
	if ( numthreads == 0 )
		numthreads = getDefaultNumThreads();

	// output compression level
	int const level = getLevel(arginfo);

	libmaus2::parallel::SimpleThreadPool STP(numthreads);

	try
	{
		libmaus2::digest::DigestInterface::unique_ptr_type ufiledigest(
			libmaus2::digest::DigestFactory::constructStatic(filehash)
		);
		libmaus2::digest::DigestInterface & filedigest = *ufiledigest;
		filedigest.vinit();

		std::vector<std::string> Vfn;
		for ( uint64_t i = 0; i < arginfo.getNumRestArgs(); ++i )
			Vfn.push_back(arginfo.getUnparsedRestArg(i));

		if ( Vfn.size() != 2 )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] number of positional arguments is not two" << std::endl;
			lme.finish();
			throw lme;
		}

		std::ostringstream headstr;
		if ( zz )
			headstr << "@HD\tVN:1.5\tSO:queryname\n";
		else
			headstr << "@HD\tVN:1.5\tSO:unknown\n";
		headstr
			<< "@PG"<< "\t"
			<< "ID:" << "bamauxmerge2" << "\t"
			<< "PN:" << "bamauxmerge2" << "\t"
			<< "CL:" << arginfo.commandline << "\t"
			<< "VN:" << std::string(PACKAGE_VERSION)
			<< std::endl;

		std::string const head = headstr.str();

		libmaus2::bambam::BamHeader header(head);

		std::ostream & out = std::cout;

		uint64_t const blocksize = 1024*1024;
		uint64_t const numblocks = 200;
		BlockReadControl BRC(out,reference,calmdnmwarnchange,replacecigar,STP,Vfn,level /* level */,hash,&header,filedigest,zz,arginfo,blocksize,numblocks);

		while ( ! BRC.isEOF() && ! STP.isInPanicMode() )
		{
			sleep(1);
		}

		STP.terminate();
		STP.join();

		std::string const eofblock = libmaus2::lz::BgzfDeflate<std::ostream>::getEOFBlock();
		BRC.writeData(eofblock.c_str(),eofblock.size());

		if ( chksumfn.size() )
		{
			std::string const chkdata = BRC.getChecksums();
			libmaus2::aio::OutputStreamInstance OSI(chksumfn);
			OSI << chkdata;
			OSI.flush();
		}

		if ( filehashfn.size() )
		{
			std::string const chkdata = filedigest.vdigestAsString();
			libmaus2::aio::OutputStreamInstance OSI(filehashfn);
			OSI << chkdata << std::endl;
			OSI.flush();
		}

		return EXIT_SUCCESS;
	}
	catch(std::exception const &)
	{
		STP.terminate();
		STP.join();
		throw;
	}
}

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

		bool printversion = false;
		bool printhelp = arginfo.restargs.size() < 2;
		bool helparg = false;

		for ( uint64_t i = 0; i < arginfo.restargs.size(); ++i )
			if (
				arginfo.restargs[i] == "-v"
				||
				arginfo.restargs[i] == "--version"
			)
			{
				printversion = true;
			}
			else if (
				arginfo.restargs[i] == "-h"
				||
				arginfo.restargs[i] == "--help"
			)
			{
				printhelp = true;
				helparg = true;
			}

		if ( printversion )
		{
			std::cerr << ::biobambam2::Licensing::license();
			return EXIT_SUCCESS;
		}
		else if ( printhelp )
		{
			std::cerr << ::biobambam2::Licensing::license() << std::endl;
			std::cerr << "Key=Value pairs:" << std::endl;
			std::cerr << std::endl;

			std::cerr << "synopsis: " << argv[0] << " <key=value pairs> unmapped.bam mapped.bam > merged.bam\n\n";

			std::vector< std::pair<std::string,std::string> > V;

			V.push_back ( std::pair<std::string,std::string> ( "level=<[-1]>", libmaus2::bambam::BamBlockWriterBaseFactory::getBamOutputLevelHelpText() ) );

			#if 0
			V.push_back ( std::pair<std::string,std::string> ( "gz=<[0]>", "input is gzip compressed FastQ (default: 0)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "verbose=<[0]>", "print progress report (default: 0)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "md5=<["+::biobambam2::Licensing::formatNumber(getDefaultMD5())+"]>", "create md5 check sum (default: 0)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "md5filename=<filename>", "file name for md5 check sum" ) );
			V.push_back ( std::pair<std::string,std::string> ( "I=<[input file name]>", "input file names (standard input if unset)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "threads=<[1]>", "number of threads used (default: serial encoding)" ) );
			#endif

			#if 0
			V.push_back ( std::pair<std::string,std::string> ( "RGID=<>", "read group id for reads (default: do not set a read group id)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGCN=<>", "CN field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGDS=<>", "DS field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGDT=<>", "DT field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGFO=<>", "FO field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGKS=<>", "KS field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGLB=<>", "LB field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGPG=<bamauxmerge2>", "CN field of RG header line if RGID is set (default: bamauxmerge2)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGPI=<>", "PI field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGPL=<>", "PL field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGPU=<>", "PU field of RG header line if RGID is set (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "RGSM=<>", "SM field of RG header line if RGID is set (default: not present)" ) );
			#endif

			#if 0
			V.push_back ( std::pair<std::string,std::string> ( "qualityoffset=<["+::biobambam2::Licensing::formatNumber(getDefaultQualityOffset())+"]>", "quality offset (default: 33)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "qualitymax=<["+::biobambam2::Licensing::formatNumber(getDefaultQualityMaximum())+"]>", "maximum valid quality value (default: 41)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "checkquality=<["+::biobambam2::Licensing::formatNumber(getDefaultCheckQuality())+"]>", "check quality (default: true)" ) );
			V.push_back ( std::pair<std::string,std::string> ( std::string("namescheme=<[")+(getDefaultNameScheme())+"]>", "read name scheme (generic, c18s, c18pe, pairedfiles)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "qualityhist=<["+::biobambam2::Licensing::formatNumber(getDefaultQualityHist())+"]>", "compute quality histogram and print it on standard error" ) );
			#endif
			V.push_back ( std::pair<std::string,std::string> ( "chksumfn=<>", "file for storing bamseqchksum type information (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "hash=<crc32prod>", "hash used for computing bamseqchksum type information (default: crc32prod)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "filehashfn=<>", "file for storing output file checksum (default: not present)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "filehash=<md5>", "hash used for computing output file checksum (default: md5)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "zz=<0>", "use content of nn aux field to replace read name (default: 0)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "threads=<1>", "number of threads used (default: 1)"));
			V.push_back ( std::pair<std::string,std::string> ( "replacecigar=<1>", "replace M cigar operations by =/X (default: 1, requires calmdnmreference)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "calmdnm=<0>", "(re)compute NM and MD aux fields (default: 1, requires calmdnmreference)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "calmdnmwarnchange=<0>", "print a warning when changing any pre-existing MD/NM aux fields (default: 0)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "calmdnmreference=<>", "reference FastA file used for calmdnm (unset by default)" ) );

			::biobambam2::Licensing::printMap(std::cerr,V);

			std::cerr << std::endl;

			return helparg ? EXIT_SUCCESS : EXIT_FAILURE;
		}
		else
		{
			return bamauxmerge2(arginfo);
		}
	}
	catch(std::exception const & ex)
	{
		std::cerr << ex.what() << std::endl;
		return EXIT_FAILURE;
	}
}
