/**
    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/BamNumericalIndexGenerator.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/bambam/AdapterFilter.hpp>
#include <libmaus2/util/Histogram.hpp>
#include <biobambam2/KmerPoisson.hpp>

#include <biobambam2/RgInfo.hpp>
#include <biobambam2/BamBamConfig.hpp>
#include <biobambam2/Licensing.hpp>
#include <biobambam2/RunEOFFilter.hpp>

#include <config.h>

struct AdapterOverlapParams
{
	uint64_t const seedlength;
	double const mismatchrate;
	uint64_t const maxseedmismatches;
	uint64_t const minoverlap;
	uint64_t const almax;

	AdapterOverlapParams(
		uint64_t const rseedlength,
		double const rmismatchrate,
		uint64_t const rmaxseedmismatches,
		uint64_t const rminoverlap,
		uint64_t const ralmax
	) : seedlength(rseedlength), mismatchrate(rmismatchrate), maxseedmismatches(rmaxseedmismatches),
	    minoverlap(rminoverlap), almax(ralmax)
	{

	}
};


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

	uint64_t const adpmatchminscore;
	double   const adpmatchminfrac;
	double   const adpmatchminpfrac;
	uint64_t const reflen;
	double   const pA;
	double   const pC;
	double   const pG;
	double   const pT;

	AdapterMatchParameters(
		uint64_t const radpmatchminscore,
		double   const radpmatchminfrac,
		double   const radpmatchminpfrac,
		uint64_t const rreflen,
		double   const rpA,
		double   const rpC,
		double   const rpG,
		double   const rpT
	) :
		adpmatchminscore(radpmatchminscore),
		adpmatchminfrac(radpmatchminfrac),
		adpmatchminpfrac(radpmatchminpfrac),
		reflen(rreflen),
		pA(rpA),
		pC(rpC),
		pG(rpG),
		pT(rpT)
	{

	}
};


enum fastq_name_scheme_type {
	fastq_name_scheme_generic,
	fastq_name_scheme_casava18_single,
	fastq_name_scheme_casava18_paired_end,
	fastq_name_scheme_pairedfiles
};

std::ostream & operator<<(std::ostream & out, fastq_name_scheme_type const namescheme)
{
	switch ( namescheme )
	{
		case fastq_name_scheme_generic:
			out << "generic";
			break;
		case fastq_name_scheme_casava18_single:
			out << "c18s";
			break;
		case fastq_name_scheme_casava18_paired_end:
			out << "c18pe";
			break;
		case fastq_name_scheme_pairedfiles:
			out << "pairedfiles";
			break;
		default:
			out << "unknown";
			break;
	}
	return out;
}

static fastq_name_scheme_type parseNameScheme(std::string const & schemename)
{
	if ( schemename == "generic" )
		return fastq_name_scheme_generic;
	else if ( schemename == "c18s" )
		return fastq_name_scheme_casava18_single;
	else if ( schemename == "c18pe" )
		return fastq_name_scheme_casava18_paired_end;
	else if ( schemename == "pairedfiles" )
		return fastq_name_scheme_pairedfiles;
	else
	{
		libmaus2::exception::LibMausException lme;
		lme.getStream() << "unknown read name scheme " << schemename << std::endl;
		lme.finish();
		throw lme;
	}
}

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 FastQPtr
{
	typedef FastQPtr this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	uint64_t namestart;
	uint64_t nameend;
	uint64_t basestart;
	uint64_t baseend;
	uint64_t qstart;
	uint64_t qend;

	FastQPtr() {}
	FastQPtr(
		uint64_t rnamestart,
		uint64_t rnameend,
		uint64_t rbasestart,
		uint64_t rbaseend,
		uint64_t rqstart,
		uint64_t rqend
	) : namestart(rnamestart), nameend(rnameend), basestart(rbasestart), baseend(rbaseend), qstart(rqstart), qend(rqend)
	{

	}
};

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

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

	FastQPtrList() : A(0), Ao(0), Ap(0) {}

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

struct FastQPtrListTypeInfo
{
	typedef FastQPtrList 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 FastQPtrListAllocator
{
	FastQPtrListAllocator() {}

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


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

	uint64_t const numthreads;
	libmaus2::autoarray::AutoArray<uint64_t> aLineCount;
	libmaus2::autoarray::AutoArray<uint64_t> aLinePtr;
	uint64_t * uLinePtr;

	uint64_t volatile countCompleted;
	uint64_t volatile storeCompleted;

	libmaus2::parallel::StdSpinLock completeLock;

	uint64_t const newlineputback;

	NewlineInfo(uint64_t const rnumthreads, uint64_t rnewlineputback)
	: numthreads(rnumthreads), aLineCount(numthreads+1), aLinePtr(), countCompleted(0), storeCompleted(0), newlineputback(rnewlineputback)
	{

	}

	uint64_t getNumLines() const
	{
		return aLineCount[numthreads];
	}

	void reset()
	{
		std::fill(aLineCount.begin(),aLineCount.end(),0ull);
		countCompleted = 0;
		storeCompleted = 0;
	}

	void ensurePutBackSpace(uint64_t const n)
	{
		if ( n > newlineputback )
		{
			uint64_t const numnl = aLineCount[numthreads];

			libmaus2::autoarray::AutoArray<uint64_t> nLinePtr(numnl + n);

			std::copy(
				uLinePtr,
				uLinePtr + numnl,
				nLinePtr.begin() + n
			);

			aLinePtr = nLinePtr;
			uLinePtr = aLinePtr.begin() + n;
		}

		assert ( uLinePtr >= aLinePtr.begin()+n );
	}
};

struct NewlineInfoTypeInfo
{
	typedef NewlineInfo 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 NewlineInfoAllocator
{
	uint64_t numthreads;
	uint64_t putbackspace;

	NewlineInfoAllocator() : numthreads(0) {}
	NewlineInfoAllocator(uint64_t const rnumthreads, uint64_t const rputbackspace) : numthreads(rnumthreads), putbackspace(rputbackspace) {}

	NewlineInfoTypeInfo::pointer_type operator()() const
	{
		NewlineInfoTypeInfo::pointer_type ptr(new NewlineInfoTypeInfo::element_type(numthreads,putbackspace));
		return ptr;
	}
};

struct NewlineTable
{
	libmaus2::autoarray::AutoArray<unsigned char> A;

	NewlineTable()
	: A(256)
	{
		std::fill(A.begin(),A.end(),0);
		A['\n'] = 1;
	}

	uint64_t operator[](char const c) const
	{
		return static_cast<uint64_t>(A[static_cast<int>(static_cast<unsigned char>(c))]);
	}
};

typedef std::pair<uint8_t const *,uint8_t const *> fragment_pointer_pair;
typedef libmaus2::autoarray::AutoArray< fragment_pointer_pair > fragment_pointer_pair_array;
typedef fragment_pointer_pair_array::shared_ptr_type fragment_pointer_pair_array_pointer;

struct FragmentPointerPairArrayTypeInfo
{
	typedef fragment_pointer_pair_array 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 FragmentPointerPairArrayAllocator
{
	FragmentPointerPairArrayAllocator() {}

	FragmentPointerPairArrayTypeInfo::pointer_type operator()() const
	{
		FragmentPointerPairArrayTypeInfo::pointer_type ptr(new FragmentPointerPairArrayTypeInfo::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;
	}
};


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

	uint64_t streamid;
	libmaus2::autoarray::AutoArray<char> A;
	char * c;
	uint64_t n;
	bool eof;
	uint64_t seqid;
	uint64_t putbackspace;
	NewlineInfoTypeInfo::pointer_type newLineInfo;
	FastQPtrListTypeInfo::pointer_type fastQPtrList;
	uint64_t volatile bamencodefinished;
	uint64_t volatile bamencodestored;
	uint64_t volatile bamencodedcurrent;
	uint64_t volatile bamencodemergecnt;
	uint64_t volatile bamencodemergecntall;
	uint64_t volatile bamencodemergeproc;
	libmaus2::parallel::StdSpinLock bamencodefinishedLock;
	libmaus2::bambam::parallel::FragmentAlignmentBuffer::shared_ptr_type fragmentBuffer;
	std::vector<FragmentPointerPairArrayTypeInfo::pointer_type> fragmentBufferPointers;
	FragmentPointerPairArrayTypeInfo::pointer_type fragmentBufferPointersMerged;


	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), newLineInfo(), fastQPtrList(),
	    bamencodefinished(0), bamencodestored(0), bamencodedcurrent(0), bamencodemergecnt(0), bamencodemergecntall(0), bamencodemergeproc(0), fragmentBuffer(), fragmentBufferPointersMerged()
	{
		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;
		newLineInfo = NewlineInfoTypeInfo::pointer_type();
		fastQPtrList = FastQPtrListTypeInfo::pointer_type();
		fragmentBuffer = libmaus2::bambam::parallel::FragmentAlignmentBuffer::shared_ptr_type();
		fragmentBufferPointers.resize(0);
		fragmentBufferPointersMerged = FragmentPointerPairArrayTypeInfo::pointer_type();
		eof = false;
		seqid = 0;
		bamencodefinished = 0;
		bamencodestored = 0;
		bamencodedcurrent = 0;
		bamencodemergecnt = 0;
		bamencodemergecntall = 0;
		bamencodemergeproc = 0;
	}

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

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

			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 BamMergeInfo
{
	uint64_t start;
	uint64_t end;
	DataBlockTypeInfo::pointer_type ptr;

	BamMergeInfo() : start(0), end(0), ptr()
	{
	}
	BamMergeInfo(
		uint64_t rstart,
		uint64_t rend,
		DataBlockTypeInfo::pointer_type rptr
	) : start(rstart), end(rend), ptr(rptr)
	{}
};

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;
	libmaus2::parallel::StdSpinLock 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;
	libmaus2::parallel::StdSpinLock 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;
	libmaus2::parallel::StdSpinLock deFSHlock;

	uint64_t volatile decompNextOut;
	libmaus2::parallel::StdSpinLock decompNextOutLock;

	NewlineInfoAllocator newLineInfoAlloc;
	libmaus2::parallel::LockedGrowingFreeList<
		NewlineInfo,
		NewlineInfoAllocator,
		NewlineInfoTypeInfo
	> newLineInfoFreeList;

	NewlineTable const NLT;

	uint64_t volatile numlines;
	libmaus2::parallel::StdSpinLock numlinesLock;

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

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

	uint64_t volatile deLineNext;

	libmaus2::parallel::LockedGrowingFreeList<
		FastQPtrList,
		FastQPtrListAllocator,
		FastQPtrListTypeInfo
	> fastQPtrListFreeList;

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

	libmaus2::parallel::LockedGrowingFreeList<
		FragmentPointerPairArrayTypeInfo::element_type,
		FragmentPointerPairArrayAllocator,
		FragmentPointerPairArrayTypeInfo
	> fragmentPointerFreeList;

	libmaus2::util::FiniteSizeHeap<DataBlockTypeInfo::pointer_type,DataBlockComparator> bamEncodedFSH;
	libmaus2::parallel::StdSpinLock 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;
	libmaus2::parallel::StdSpinLock preBamEncodeFSHlock;
	uint64_t volatile preBamEncodeLow;

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

		{
			libmaus2::parallel::ScopeStdSpinLock slock(FSHlock);
			libmaus2::parallel::ScopeStdSpinLock 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)
	{
		libmaus2::parallel::ScopeStdSpinLock slock(FSHlock);
		FSH.push(ptr);
	}

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

	void incrementNextProcId()
	{
		{
			libmaus2::parallel::ScopeStdSpinLock 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,
		uint64_t const newlineputback
	)
	: 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),
	  lock(),
	  eof(0),
	  B(numblocks),
	  nextid(0),
	  FSH(numblocks),
	  FSHlock(),
	  nextprocid(0),
	  zintf(libmaus2::lz::ZlibInterface::construct()),
	  deFSH(numblocks),
	  deFSHlock(),
	  decompNextOut(0),
	  newLineInfoAlloc(numthreads,newlineputback),
	  newLineInfoFreeList(newLineInfoAlloc),
	  numlines(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)
	{
		init();
	}

	BlockReadStreamInfo(
		uint64_t const rstreamid,
		std::string const & rfn,
		uint64_t const blocksize,
		uint64_t const numblocks,
		uint64_t const numthreads,
		uint64_t const newlineputback
	)
	: 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),
	  newLineInfoAlloc(numthreads,newlineputback),
	  newLineInfoFreeList(newLineInfoAlloc),
	  numlines(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)
	{
		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,
		uint64_t const newlineputback
	) : 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,newlineputback
				)
			);
			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];
			libmaus2::parallel::ScopeStdSpinLock 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 BlockProcessNewlineCountPackage : 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 i;
	DataBlockTypeInfo::pointer_type ptr;

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


	BlockProcessNewlineCountPackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), i(0), ptr()
	{

	}

	BlockProcessNewlineCountPackage(BlockProcessNewlineCountPackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), i(O.i), ptr(O.ptr)
	{

	}

	BlockProcessNewlineCountPackage(
		uint64_t const ri,
		DataBlockTypeInfo::pointer_type rptr,
		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), i(ri), ptr(rptr)
	{

	}

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

struct BlockProcessNewlineStorePackage : 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 i;
	DataBlockTypeInfo::pointer_type ptr;

	BlockProcessNewlineStorePackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), i(0), ptr()
	{

	}

	BlockProcessNewlineStorePackage(BlockProcessNewlineCountPackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), i(O.i), ptr(O.ptr)
	{

	}

	BlockProcessNewlineStorePackage(
		uint64_t const ri,
		DataBlockTypeInfo::pointer_type rptr,
		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), i(ri), ptr(rptr)
	{

	}

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

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 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;
	BlockLineCountEnqueEvent & BLCEE;

	BlockProcessDispatcher(
		uint64_t const rnumproc,
		BlockReadStreamInfoVector * rBRSIV,
		ReturnDecompressedBlockEvent & rRDBE,
		BlockProcessReturnEvent & rBPRE,
		BlockLineCountEnqueEvent & rBLCEE
	) : numproc(rnumproc), BRSIV(rBRSIV), RDBE(rRDBE), BPRE(rBPRE), BLCEE(rBLCEE)
	{

	}

	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;

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.deFSHlock);
			libmaus2::parallel::ScopeStdSpinLock sdlock(info.decompNextOutLock);

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

				tptr->newLineInfo = info.newLineInfoFreeList.get();
				tptr->newLineInfo->reset();

				for ( uint64_t i = 0; i < tptr->newLineInfo->numthreads; ++i )
					BLCEE.blockLineCountEnqueEvent(tptr,i);

				info.decompNextOut++;
			}
		}

		BPRE.blockProcessReturnEvent(BP);
	}
};

struct BlockProcessNewlineCountPackageReturnEvent
{
	virtual ~BlockProcessNewlineCountPackageReturnEvent() {}
	virtual void blockProcessNewlineCountPackageReturnEvent(BlockProcessNewlineCountPackage * p) = 0;
};

struct BlockProcessNewlineStorePackageReturnEvent
{
	virtual ~BlockProcessNewlineStorePackageReturnEvent() {}
	virtual void blockProcessNewlineStorePackageReturnEvent(BlockProcessNewlineStorePackage * p) = 0;
};

struct BlockProcessLineParseCheckEvent
{
	virtual ~BlockProcessLineParseCheckEvent() {}
	virtual void blockProcessLineParseCheckEvent(uint64_t const streamid) = 0;
};

struct BlockProcessNewlineCountPackageDispatcher : 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;
	BlockProcessNewlineCountPackageReturnEvent & BPNCP;
	BlockProcessNewlineCountCompleteEvent & BPNCC;

	BlockProcessNewlineCountPackageDispatcher(
		BlockReadStreamInfoVector * rBRSIV,
		BlockProcessNewlineCountPackageReturnEvent & rBPNCP,
		BlockProcessNewlineCountCompleteEvent & rBPNCC
	) : BRSIV(rBRSIV), BPNCP(rBPNCP), BPNCC(rBPNCC)
	{

	}

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

		DataBlockTypeInfo::pointer_type ptr = BP->ptr;
		NewlineInfo & NI = *(ptr->newLineInfo);
		uint64_t const i = BP->i;
		uint64_t const n = ptr->n;
		uint64_t const streamid = ptr->streamid;
		uint64_t const numthreads = NI.numthreads;
		BlockReadStreamInfo & info = *(BRSIV->A[streamid]);

		uint64_t const packsize = (n+numthreads-1)/numthreads;
		uint64_t const from = std::min(i*packsize,n);
		uint64_t const to   = std::min(from+packsize,n);

		NewlineTable const & NLT = info.NLT;
		char const * cp = ptr->c + from;
		char const * ce = ptr->c + to;

		uint64_t lc = 0;
		while ( cp != ce )
			lc += NLT[*(cp++)];

		NI.aLineCount[i] = lc;

		#if 0
		{
		libmaus2::parallel::ScopeStdSpinLock slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "streamid=" << streamid << " i=" << i << " lc=" << lc << std::endl;
		}
		#endif

		uint64_t lcountComplete;
		{
			libmaus2::parallel::ScopeStdSpinLock slock(NI.completeLock);
			lcountComplete = ++NI.countCompleted;
		}

		if ( lcountComplete == numthreads )
		{
			libmaus2::util::PrefixSums::prefixSums(NI.aLineCount.begin(),NI.aLineCount.end());
			NI.aLinePtr.ensureSize(
				NI.getNumLines()
				+
				NI.newlineputback
			);
			NI.uLinePtr = NI.aLinePtr.begin() + NI.newlineputback;

			BPNCC.blockProcessNewlineCountCompleteEvent(ptr);
		}

		BPNCP.blockProcessNewlineCountPackageReturnEvent(BP);
	}
};

struct BlockProcessNewlineStorePackageDispatcher : 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;
	BlockProcessNewlineStorePackageReturnEvent & BPNCP;
	BlockProcessNewlineStoreCompleteEvent & BPNCC;

	BlockProcessNewlineStorePackageDispatcher(
		BlockReadStreamInfoVector * rBRSIV,
		BlockProcessNewlineStorePackageReturnEvent & rBPNCP,
		BlockProcessNewlineStoreCompleteEvent & rBPNCC
	) : BRSIV(rBRSIV), BPNCP(rBPNCP), BPNCC(rBPNCC)
	{

	}

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

		DataBlockTypeInfo::pointer_type ptr = BP->ptr;
		NewlineInfo & NI = *(ptr->newLineInfo);
		uint64_t const i = BP->i;
		uint64_t const n = ptr->n;
		uint64_t const streamid = ptr->streamid;
		uint64_t const numthreads = NI.numthreads;
		BlockReadStreamInfo & info = *(BRSIV->A[streamid]);

		uint64_t const packsize = (n+numthreads-1)/numthreads;
		uint64_t const from = std::min(i*packsize,n);
		uint64_t const to   = std::min(from+packsize,n);

		NewlineTable const & NLT = info.NLT;
		char const * cp = ptr->c;
		uint64_t uptr = NI.aLineCount[i];

		#if 0
		{
		libmaus2::parallel::ScopeStdSpinLock slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "streamid=" << streamid << " seqid=" << ptr->seqid << " i=" << i << " from=" << from << " to=" << to << std::endl;
		}
		#endif

		for ( uint64_t j = from; j < to; ++j )
			if ( NLT[cp[j]] )
			{
				assert ( cp[j] == '\n' );
				NI.uLinePtr[uptr++] = j;
			}

		assert ( uptr == NI.aLineCount[i+1] );

		uint64_t lstoreComplete;
		{
			libmaus2::parallel::ScopeStdSpinLock slock(NI.completeLock);
			lstoreComplete = ++NI.storeCompleted;
		}

		if ( lstoreComplete == numthreads )
		{
			BPNCC.blockProcessNewlineStoreCompleteEvent(ptr);
		}

		BPNCP.blockProcessNewlineStorePackageReturnEvent(BP);
	}
};

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

	DataBlockTypeInfo::pointer_type ptr;

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

	BlockProcessLineParsePackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), ptr()
	{

	}

	BlockProcessLineParsePackage(BlockProcessLineParsePackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), ptr(O.ptr)
	{

	}

	BlockProcessLineParsePackage(
		DataBlockTypeInfo::pointer_type rptr,
		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), ptr(rptr)
	{

	}

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

struct BlockProcessLineParsePackageReturnEvent
{
	virtual ~BlockProcessLineParsePackageReturnEvent() {}
	virtual void blockProcessLineParsePackageReturnEvent(BlockProcessLineParsePackage * p) = 0;
};

struct BlockProcessLineParseCompleteEvent
{
	virtual ~BlockProcessLineParseCompleteEvent() {}
	virtual void blockProcessLineParseCompleteEvent(DataBlockTypeInfo::pointer_type ptr) = 0;
};

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

	BlockReadStreamInfoVector * BRSIV;
	BlockProcessLineParsePackageReturnEvent & BPLPPRE;
	BlockProcessLineParseCompleteEvent & BPLPCE;

	BlockProcessLineParsePackageDispatcher(
		BlockReadStreamInfoVector * rBRSIV,
		BlockProcessLineParsePackageReturnEvent & rBPLPPRE,
		BlockProcessLineParseCompleteEvent & rBPLPCE
	) : BRSIV(rBRSIV), BPLPPRE(rBPLPPRE), BPLPCE(rBPLPCE)
	{

	}

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

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

		NewlineInfo & NL = *(ptr->newLineInfo);
		uint64_t const inNumLines = NL.getNumLines();

		// update newline positions
		for ( uint64_t i = 0; i < inNumLines; ++i )
			NL.uLinePtr[i] += info.parsePutbackSpaceUsed;

		// count new lines in put back space
		uint64_t pbnl = 0;
		for ( uint64_t i = 0; i < info.parsePutbackSpaceUsed; ++i )
			if ( info.parsePutbackSpace[i] == '\n' )
				pbnl += 1;

		// make sure we have enough space for newline pointers
		ptr->newLineInfo->ensurePutBackSpace(pbnl);

		// put new line positions in put back space
		uint64_t uLinePtrO = 0;
		ptr->newLineInfo->uLinePtr -= pbnl;
		for ( uint64_t i = 0; i < info.parsePutbackSpaceUsed; ++i )
			if ( info.parsePutbackSpace[i] == '\n' )
				ptr->newLineInfo->uLinePtr[uLinePtrO++] = i;
		assert ( uLinePtrO == pbnl );

		// copy put back space data
		ptr->insertPutBack(
			info.parsePutbackSpace.begin(),
			info.parsePutbackSpaceUsed
		);

		// update number of line markers
		NL.aLineCount[NL.numthreads] += pbnl;

		uint64_t const numnl = NL.aLineCount[NL.numthreads];

		for ( uint64_t i = 0; i < numnl; ++i )
			assert (
				ptr->c[NL.uLinePtr[i]] == '\n'
			);

		#if 0
		{
			libmaus2::parallel::ScopeStdSpinLock slock(libmaus2::aio::StreamLock::cerrlock);

			uint64_t linestart = 0;
			for ( uint64_t lid = 0; lid < numnl; ++lid )
			{
				uint64_t const lineend = ptr->newLineInfo->uLinePtr[lid];

				std::cerr << std::string(
					ptr->c + linestart,
					ptr->c + lineend
				) << std::endl;

				linestart = lineend + 1;
			}
		}
		#endif

		ptr->fastQPtrList = info.fastQPtrListFreeList.get();
		ptr->fastQPtrList->reset();

		uint64_t lid = 0;
		uint64_t linestart = 0;
		uint64_t fullrecordend = 0;
		while ( lid < numnl )
		{
			uint64_t const recordstart = linestart;
			// uint64_t const recordlidstart = lid;

			if ( ptr->c[recordstart] != '@' )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream() << "[E] FastQ record does not start with @ sign" << std::endl;
				lme.finish();
				throw lme;
			}

			uint64_t nextlinestart = ptr->newLineInfo->uLinePtr[lid++]+1;

			uint64_t const namestart = recordstart;
			uint64_t const nameend = nextlinestart-1;

			// std::cerr << std::string(ptr->c + namestart, ptr->c + nameend) << std::endl;

			uint64_t const basestart = nextlinestart;

			uint64_t baselen = 0;

			while ( lid < numnl && ptr->c[nextlinestart] != '+' )
			{
				uint64_t const nextnl = ptr->newLineInfo->uLinePtr[lid++];

				baselen += nextnl - nextlinestart;

				nextlinestart = nextnl+1;
			}

			if ( lid >= numnl )
				break;

			uint64_t baseend = nextlinestart-1;

			uint64_t baseptr = basestart;
			for ( uint64_t i = basestart; i < baseend; ++i )
				if ( ptr->c[i] != '\n' )
					ptr->c[baseptr++] = ptr->c[i];
			baseend = baseptr;

			// std::cerr << std::string(ptr->c + basestart, ptr->c + baseend) << std::endl;

			assert ( ptr->c[nextlinestart] == '+' );

			nextlinestart = ptr->newLineInfo->uLinePtr[lid++]+1;

			uint64_t numq = 0;

			uint64_t const qstart = nextlinestart;

			while ( lid < numnl && numq < baselen )
			{
				uint64_t const nextnl = ptr->newLineInfo->uLinePtr[lid++];

				numq += nextnl - nextlinestart;

				nextlinestart = nextnl+1;
			}

			if ( lid >= numnl && numq < baselen )
			{
				break;
			}
			else if ( numq != baselen )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream()
					<< "[E] FastQ record base length mismatch with quality length for name "
					<< std::string(ptr->c+namestart,ptr->c+nameend)
					<< " baselen=" << baselen
					<< " numq=" << numq
					<< std::endl;
				lme.finish();
				throw lme;
			}

			uint64_t qend = nextlinestart-1;


			uint64_t qptr = qstart;
			for ( uint64_t i = qstart; i < qend; ++i )
				if ( ptr->c[i] != '\n' )
					ptr->c[qptr++] = ptr->c[i];
			qend = qptr;

			// std::cerr << std::string(ptr->c + qstart, ptr->c + qend) << std::endl;

			FastQPtr const P(namestart,nameend,basestart,baseend,qstart,qend);
			ptr->fastQPtrList->A.push(ptr->fastQPtrList->Ao,P);

			linestart = nextlinestart;
			fullrecordend = linestart;
		}

		uint64_t const tocopy = ptr->n - fullrecordend;
		info.parsePutbackSpace.ensureSize(tocopy);
		std::copy(
			ptr->c + fullrecordend,
			ptr->c + ptr->n,
			info.parsePutbackSpace.begin()
		);

		info.parsePutbackSpaceUsed = tocopy;

		if ( ptr->eof && tocopy )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] FastQ file seems to be truncated on stream id " << streamid << std::endl;
			lme.finish();
			throw lme;
		}

		BPLPCE.blockProcessLineParseCompleteEvent(ptr);
		BPLPPRE.blockProcessLineParsePackageReturnEvent(BP);
	}
};

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

	DataBlockTypeInfo::pointer_type ptr;
	uint64_t i;
	uint64_t low;

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

	BlockProcessBamEncodePackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), ptr(), i(0), low(0)
	{

	}

	BlockProcessBamEncodePackage(BlockProcessBamEncodePackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), ptr(O.ptr), i(O.i), low(O.low)
	{

	}

	BlockProcessBamEncodePackage(
		DataBlockTypeInfo::pointer_type rptr,
		uint64_t const ri,
		uint64_t const rlow,
		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), ptr(rptr), i(ri), low(rlow)
	{

	}

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

struct BlockProcessBamEncodePackageReturnEvent
{
	virtual ~BlockProcessBamEncodePackageReturnEvent() {}
	virtual void blockProcessBamEncodePackageReturnEvent(BlockProcessBamEncodePackage * p) = 0;
};

struct BlockProcessBamEncodePackageFinishedEvent
{
	virtual ~BlockProcessBamEncodePackageFinishedEvent() {}
	virtual void blockProcessBamEncodePackageFinishedEvent(DataBlockTypeInfo::pointer_type ptr) = 0;
};

struct NumberEncodeTable
{
	// A-Z, a-z
	uint64_t const base2;
	uint64_t const base;
	uint64_t const l0;

	libmaus2::autoarray::AutoArray<char> T;

	NumberEncodeTable()
	: base2('Z'-'A'+1), base(2*base2), l0(12), T(base,false)
	{
		uint64_t j = 0;

		for ( uint64_t i = 0; i < base2; ++i )
			T[j++] = 'A' + i;
		for ( uint64_t i = 0; i < base2; ++i )
			T[j++] = 'a' + i;

		assert ( j == base );
	}

	uint64_t setup(libmaus2::autoarray::AutoArray<char> & A) const
	{
		A.ensureSize(l0+1);
		A[l0] = 0;

		return l0;
	}


	void encode(libmaus2::autoarray::AutoArray<char> & A, uint64_t z) const
	{
		char * p1 = A.begin() + l0;

		for ( uint64_t i = 0; i < l0; ++i )
		{
			*(--p1) = T[z % base];
			z /= base;
		}
	}
};

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

	BlockReadStreamInfoVector * BRSIV;
	BlockProcessBamEncodePackageReturnEvent & BPBEPRE;
	BlockProcessBamEncodePackageFinishedEvent & BPBEPFE;
	RgInfo const & rginfo;
	std::string const rgid;
	libmaus2::parallel::LockedGrowingFreeList<
		ChecksumsTypeInfo::element_type,
		ChecksumsAllocator,
		ChecksumsTypeInfo
	> & checksumsFreeList;
	libmaus2::bambam::AdapterFilter const & AF;
	AdapterMatchParameters const & AMP;
	NumberEncodeTable const NET;
	bool const zz;
	bool const adaptercheck;
	bool const clipslashid;
	bool const patchne;

	BlockProcessBamEncodePackageDispatcher(
		libmaus2::parallel::LockedGrowingFreeList<
			ChecksumsTypeInfo::element_type,
			ChecksumsAllocator,
			ChecksumsTypeInfo
		> & rchecksumsFreeList,
		RgInfo const & rrginfo,
		libmaus2::bambam::AdapterFilter const & rAF,
		AdapterMatchParameters const & rAMP,
		bool const rzz,
		bool const radaptercheck,
		bool const rclipslashid,
		bool const rpatchne,
		BlockReadStreamInfoVector * rBRSIV,
		BlockProcessBamEncodePackageReturnEvent & rBPBEPRE,
		BlockProcessBamEncodePackageFinishedEvent & rBPBEPFE
	) : BRSIV(rBRSIV), BPBEPRE(rBPBEPRE),
	    BPBEPFE(rBPBEPFE), rginfo(rrginfo), rgid(rginfo.ID),
	    checksumsFreeList(rchecksumsFreeList), AF(rAF), AMP(rAMP), NET(), zz(rzz), adaptercheck(radaptercheck),
	    clipslashid(rclipslashid), patchne(rpatchne)
	{

	}

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

		DataBlockTypeInfo::pointer_type ptr = BP->ptr;
		uint64_t const streamid = ptr->streamid;
		// BlockReadStreamInfo & info = *(BRSIV->A[streamid]);

		FastQPtrListTypeInfo::pointer_type fqptrlist = ptr->fastQPtrList;
		libmaus2::bambam::parallel::FragmentAlignmentBuffer & fragmentBuffer = *(ptr->fragmentBuffer);
		libmaus2::bambam::parallel::FragmentAlignmentBufferFragment & fragment = *(fragmentBuffer[BP->i]);
		fragment.reset();
		std::string sne("1:N:0:AAAAAAAA");

		uint64_t const packagelow = BP->low;
		uint64_t const numfq = fqptrlist->Ao;
		uint64_t const numthreads = ptr->newLineInfo->numthreads;
		uint64_t const fqperthread = (numfq + numthreads - 1)/numthreads;
		uint64_t const fqstart = std::min(BP->i * fqperthread,numfq);
		uint64_t const fqend   = std::min(fqstart + fqperthread,numfq);

		::libmaus2::fastx::UCharBuffer ubuffer;
		libmaus2::bambam::BamSeqEncodeTable const seqenc;
		libmaus2::autoarray::AutoArray<char> AZ;

		uint64_t lzz = 0;
		if ( zz )
			lzz = NET.setup(AZ);

		libmaus2::bambam::cigar_operation const * cnp = NULL;
		uint64_t const mapflags =
			libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FUNMAP
			|
			(
				(BRSIV->A.size() == 2)
				?
				(
					(
						(streamid == 0)
						?
						libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD1
						:
						libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD2
					)
					|
					libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FMUNMAP
					|
					libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FPAIRED
				)
				:
				0
			)
			;
		bool const isread1 = (mapflags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD1) ? 1 : 0;
		bool const isread2 = (mapflags & libmaus2::bambam::BamFlagBase::LIBMAUS2_BAMBAM_FREAD2) ? 1 : 0;

		if ( isread1 )
			sne[0] = '1';
		else
			sne[0] = '2';

		ChecksumsTypeInfo::pointer_type chksums = checksumsFreeList.get();
		libmaus2::autoarray::AutoArray<char> Aextra;
		libmaus2::util::PushBuffer<libmaus2::bambam::AdapterOffsetStrand> AOSPB;
		libmaus2::fastx::AutoArrayWordPutObject<uint64_t> TA;

		for ( uint64_t i = fqstart; i < fqend; ++i )
		{
			FastQPtr const & FQP = fqptrlist->A[i];

			char const * namestart = ptr->c + FQP.namestart + 1;
			char const * nameend   = ptr->c + FQP.nameend;
			char const * namestop  = namestart;

			uint64_t const bamid = packagelow+i;

			while ( namestop < nameend && !::isspace(*namestop) )
				++namestop;

			char const *nameextrastart = namestop;
			while ( nameextrastart < nameend && ::isspace(*nameextrastart) )
				++nameextrastart;

			if (
				clipslashid
				&&
				(namestop-namestart >= 2)
				&&
				(namestop[-2] == '/')
				&&
				(
					(isread1 && namestop[-1] == '1')
					||
					(isread2 && namestop[-1] == '2')
				)
			)
			{
				namestop -= 2;
			}

			libmaus2::bambam::BamAlignmentEncoderBase::encodeAlignment(
				ubuffer,
				seqenc,
				namestart,
				(namestop-namestart),
				-1, /* chrom */
				-1, /* pos */
				0 /* mapq */,
				mapflags,
				cnp /* cigop */,
				0 /* ncig */,
				-1,
				-1,
				0 /* tlen */,
				ptr->c + FQP.basestart,
				FQP.baseend-FQP.basestart,
				ptr->c + FQP.qstart,
				33,
				true /* reset */
			);
			if ( rgid.size() )
				libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
					ubuffer,
					"RG",
					rgid.c_str()
				);

			libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
				ubuffer,
				"OQ",
				ptr->c + FQP.qstart,
				ptr->c + (FQP.qstart + (FQP.baseend-FQP.basestart))
			);

			if ( nameextrastart < nameend )
			{
				Aextra.ensureSize((nameend - nameextrastart) + 1);

				std::copy(
					nameextrastart,
					nameend,
					Aextra.begin()
				);
				Aextra[nameend - nameextrastart] = 0;

				libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
					ubuffer,
					"ne",
					Aextra.begin()
				);
			}
			else if ( patchne )
			{
				libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
					ubuffer,
					"ne",
					sne.c_str()
				);
			}

			bool const foundadapter = adaptercheck && AF.searchAdapters(
				reinterpret_cast<uint8_t const *>(ptr->c + FQP.basestart),
				FQP.baseend-FQP.basestart,
				2 /* maximum number of mismatches */,
				AOSPB,
				AMP.adpmatchminscore,
				AMP.adpmatchminfrac,
				AMP.adpmatchminpfrac,
				TA,
				0 /* verbose */
                        );

                        if ( foundadapter )
                        {
                        	std::sort(AOSPB.begin(),AOSPB.end(),libmaus2::bambam::AdapterOffsetStrandMatchStartComparator());
	                        uint64_t const clip = (FQP.baseend-FQP.basestart) - AOSPB.begin()[0].getMatchStart();

	                        uint64_t fA, fC, fG, fT;
	                        AF.getAdapterMatchFreqs((FQP.baseend-FQP.basestart),AOSPB.begin()[0],fA,fC,fG,fT);

	                        // confidence that this is not a random event
	                        double const randconfid = kmerPoisson(AMP.reflen,fA,fC,fG,fT,0/*n*/,AMP.pA,AMP.pC,AMP.pG,AMP.pT);

	                        #if 0
	                        {
	                        	std::cerr << "\n" << std::string(80,'-') << "\n\n";

	                        	std::cerr << "read length " << (FQP.baseend-FQP.basestart) << " clip " << clip << " randconfig=" << randconfid << " fA=" << fA << " fC=" << fC << " fG=" << fG << " fT=" << fT << std::endl;

	                        	for ( libmaus2::bambam::AdapterOffsetStrand const * it = AOSPB.begin(); it != AOSPB.end(); ++it )
	                        		AF.printAdapterMatch(reinterpret_cast<uint8_t const *>(ptr->c + FQP.basestart),(FQP.baseend-FQP.basestart),*it);
				}
				#endif

				libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(ubuffer,"as",'i',clip);
				libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(ubuffer,"aa",AF.getAdapterName(AOSPB.begin()[0]));
				libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(ubuffer,"af",'i',AOSPB.begin()[0].pfrac);
				libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(ubuffer,"ar",'i',randconfid);
                        }

			chksums->update(ubuffer.buffer,ubuffer.length);

			if ( zz )
			{
				NET.encode(AZ,bamid);

				ubuffer.length = libmaus2::bambam::BamAlignmentDecoderBase::replaceName(
					ubuffer.abuffer,
					ubuffer.length,
					AZ.begin(),
					lzz
				);
				ubuffer.buffer = ubuffer.abuffer.begin();
				ubuffer.buffersize = ubuffer.abuffer.size();

				libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
					ubuffer,
					"nn",
					namestart,
					namestop
				);
			}

			fragment.pushAlignmentBlock(ubuffer.buffer,ubuffer.length);
		}

		checksumsFreeList.put(chksums);

		fragment.getPointers(*(ptr->fragmentBufferPointers[BP->i]));

		uint64_t lbamencodefinished;
		{
			libmaus2::parallel::ScopeStdSpinLock slock(ptr->bamencodefinishedLock);
			lbamencodefinished = ++ptr->bamencodefinished;
			ptr->bamencodestored += fragment.f;
		}

		if ( lbamencodefinished == ptr->newLineInfo->numthreads )
			BPBEPFE.blockProcessBamEncodePackageFinishedEvent(ptr);

		BPBEPRE.blockProcessBamEncodePackageReturnEvent(BP);
	}
};

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

	uint64_t mergeid;
	std::vector<BamMergeInfo> VBMI;
	FragmentPointerPairArrayTypeInfo::pointer_type merge;
	FragmentAlignmentBufferFragmentTypeInfo::pointer_type fragment;

	BamStreamMergePackage & operator=(BamStreamMergePackage const & O)
	{
		if ( this != &O )
		{
			libmaus2::parallel::SimpleThreadWorkPackage::operator=(O);
			mergeid = O.mergeid;
			VBMI = O.VBMI;
			merge = O.merge;
			fragment = O.fragment;
		}
		return *this;
	}

	BamStreamMergePackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), mergeid(0), VBMI(), merge(), fragment()
	{

	}

	BamStreamMergePackage(BamStreamMergePackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), mergeid(0), VBMI(O.VBMI), merge(O.merge), fragment(O.fragment)
	{

	}

	BamStreamMergePackage(
		uint64_t const rmergeid,
		std::vector<BamMergeInfo> const & rVBMI,
		FragmentPointerPairArrayTypeInfo::pointer_type rmerge,
		FragmentAlignmentBufferFragmentTypeInfo::pointer_type rfragment,
		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), mergeid(rmergeid), VBMI(rVBMI), merge(rmerge), fragment(rfragment)
	{

	}

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

struct BamStreamMergePackageReturnEvent
{
	virtual ~BamStreamMergePackageReturnEvent() {}
	virtual void bamStreamMergePackageReturnEvent(BamStreamMergePackage * p) = 0;
};

struct BamStreamMergePackageFinishedEvent
{
	virtual ~BamStreamMergePackageFinishedEvent() {}
	virtual void bamStreamMergePackageFinishedEvent(
		uint64_t const mergeid,
		std::vector<BamMergeInfo> & VBMI,
		FragmentPointerPairArrayTypeInfo::pointer_type merge,
		FragmentAlignmentBufferFragmentTypeInfo::pointer_type fragment,
		std::vector<std::pair<uint8_t *,uint8_t *> > linfrag
	) = 0;
};

struct BamStreamMergePackageStatsUpdate
{
	virtual ~BamStreamMergePackageStatsUpdate() {}
	virtual void bamStreamMergePackageStatsUpdate(
		libmaus2::util::Histogram const & roverlaphist,
		libmaus2::util::Histogram const & radapterhist0,
		libmaus2::util::Histogram const & radapterhist1,
		uint64_t adptcnt
	) = 0;
};

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

	BlockReadStreamInfoVector * BRSIV;
	BamStreamMergePackageReturnEvent & BSMPR;
	BamStreamMergePackageFinishedEvent & BSMPF;
	BamStreamMergePackageStatsUpdate & BSMPSU;
	AdapterOverlapParams const & AOP;

	libmaus2::autoarray::AutoArray<uint8_t> S;
	libmaus2::autoarray::AutoArray<uint8_t> R;
	uint64_t const mmask;
	libmaus2::bambam::BamSeqEncodeTable const seqenc;

	bool const adaptercheck;

	static uint64_t getMMask()
	{
		static uint64_t const mmask =
			(1ull << 0) |
			(1ull << 3) |
			(1ull << 6) |
			(1ull << 9) |
			(1ull << 12) |
			(1ull << 15) |
			(1ull << 18) |
			(1ull << 21) |
			(1ull << 24) |
			(1ull << 27) |
			(1ull << 30) |
			(1ull << 33) |
			(1ull << 36) |
			(1ull << 39) |
			(1ull << 42) |
			(1ull << 45) |
			(1ull << 48) |
			(1ull << 51) |
			(1ull << 54) |
			(1ull << 57) |
			(1ull << 60) |
			(1ull << 63);
		return mmask;
	}

	BamStreamMergePackageDispatcher(
		AdapterOverlapParams const & rAOP,
		BlockReadStreamInfoVector * rBRSIV,
		BamStreamMergePackageReturnEvent & rBSMPR,
		BamStreamMergePackageFinishedEvent & rBSMPF,
		BamStreamMergePackageStatsUpdate & rBSMPSU,
		bool const radaptercheck
	) : BRSIV(rBRSIV), BSMPR(rBSMPR), BSMPF(rBSMPF), BSMPSU(rBSMPSU), AOP(rAOP), S(256,false), R(256,false), mmask(getMMask()), adaptercheck(radaptercheck)
	{
		std::fill(S.begin(),S.end(),4);
		S['a'] = S['A'] = 0;
		S['c'] = S['C'] = 1;
		S['g'] = S['G'] = 2;
		S['t'] = S['T'] = 3;

		std::fill(R.begin(),R.end(),5);
		R['a'] = R['A'] = 0;
		R['c'] = R['C'] = 1;
		R['g'] = R['G'] = 2;
		R['t'] = R['T'] = 3;
	}

	virtual ~BamStreamMergePackageDispatcher() {}
	virtual void dispatch(libmaus2::parallel::SimpleThreadWorkPackage * P, libmaus2::parallel::SimpleThreadPoolInterfaceEnqueTermInterface & /* tpi */)
	{

		BamStreamMergePackage * BP = dynamic_cast<BamStreamMergePackage *>(P);
		assert ( BP );

		std::vector<BamMergeInfo> & VBMI = BP->VBMI;
		FragmentPointerPairArrayTypeInfo::element_type & merge = *(BP->merge);
		FragmentAlignmentBufferFragmentTypeInfo::pointer_type mergefragment = BP->fragment;
		uint64_t merge_o = 0;
		mergefragment->reset();
		uint64_t const numstreams = VBMI.size();

		libmaus2::util::Histogram overlaphist;
		libmaus2::util::Histogram adapterhist0;
		libmaus2::util::Histogram adapterhist1;
		uint64_t adptcnt = 0;
		libmaus2::autoarray::AutoArray<char> ASEQ;
		libmaus2::autoarray::AutoArray<char> AQUAL;

		if ( numstreams )
		{
			uint64_t const bamsize = VBMI[0].end - VBMI[0].start;

			uint64_t const merge_e = bamsize * numstreams;
			merge.ensureSize(merge_e);

			for ( uint64_t i = 0; i < bamsize; ++i )
				for ( uint64_t streamid = 0; streamid < VBMI.size(); ++streamid )
				{
					uint64_t const li = VBMI[streamid].start + i;

					std::pair<uint8_t const *,uint8_t const *> const UP = (*(VBMI[streamid].ptr->fragmentBufferPointersMerged))[li];

					merge[merge_o++] = UP;
				}

			assert ( merge_o == merge_e );

			libmaus2::autoarray::AutoArray<char> seqs[2];
			libmaus2::fastx::UCharBuffer ubuffer;
			libmaus2::fastx::UCharBuffer ubufferclip;

			for ( uint64_t z = 0; z < bamsize; ++z )
			{
				uint64_t const ifrom = z * numstreams;
				uint64_t const ito   = ifrom + numstreams;

				int64_t ah = std::numeric_limits<int64_t>::min();
				int64_t a30 = std::numeric_limits<int64_t>::min();
				int64_t a31 = std::numeric_limits<int64_t>::min();

				if ( adaptercheck && (ito - ifrom == 2) )
				{
					// get read1 data pointers
					std::pair<uint8_t const *,uint8_t const *> const UP0(merge[ifrom+0].first,merge[ifrom+0].second);
					// get read2 data pointers
					std::pair<uint8_t const *,uint8_t const *> const UP1(merge[ifrom+1].first,merge[ifrom+1].second);

					uint64_t const l0 = libmaus2::bambam::BamAlignmentDecoderBase::getLseq(UP0.first);
					uint64_t const l1 = libmaus2::bambam::BamAlignmentDecoderBase::getLseq(UP1.first);

					#if 0
					// XXX: remove this
					char const * name0 = libmaus2::bambam::BamAlignmentDecoderBase::getReadName(UP0.first);
					char const * name1 = libmaus2::bambam::BamAlignmentDecoderBase::getReadName(UP1.first);
					bool const ok = strcmp(name0,name1) == 0;

					if ( ! ok )
					{
						libmaus2::parallel::ScopeStdSpinLock slock(libmaus2::aio::StreamLock::cerrlock);
						std::cerr << "name0=" << name0 << " != name1=" << name1 << std::endl;
						std::cerr << VBMI[0].ptr->streamid << "[" << VBMI[0].start << "," << VBMI[0].end << std::endl;
						std::cerr << VBMI[1].ptr->streamid << "[" << VBMI[1].start << "," << VBMI[1].end << std::endl;


						for ( uint64_t i = 0; i < bamsize; ++i )
							for ( uint64_t streamid = 0; streamid < VBMI.size(); ++streamid )
							{
								uint64_t const li = VBMI[streamid].start + i;

								std::pair<uint8_t const *,uint8_t const *> const UP = (*(VBMI[streamid].ptr->fragmentBufferPointersMerged))[li];

								std::cerr << i << "," << streamid << " " << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(UP.first) << std::endl;
							}

						assert ( ok );
					}
					#endif

					seqs[0].ensureSize(l0);
					seqs[1].ensureSize(l1);

					libmaus2::bambam::BamAlignmentDecoderBase::decodeRead(UP0.first,seqs[0].begin(),l0);
					libmaus2::bambam::BamAlignmentDecoderBase::decodeReadRC(UP1.first,seqs[1].begin(),l1);

					uint64_t const lm = std::min(l0,l1);
					uint64_t const lseedlength = std::min(AOP.seedlength,lm);
					uint64_t const lseedmask = libmaus2::math::lowbits(3*lseedlength);

					// compute seed from last lseedlength bases of second read
					uint64_t seed = 0;
					uint8_t const * p = reinterpret_cast<uint8_t const *>(seqs[1].begin() + l1);
					for ( uint64_t i = 0; i < lseedlength; ++i )
					{
						seed <<= 3;
						seed  |= S[*(--p)];
					}

					// compute mask of length lseedlength-1 from back of first read
					uint64_t query = 0;
					uint8_t const * const qe = reinterpret_cast<uint8_t const *>(seqs[0].begin());
					uint8_t const * q = qe + l0;

					// position of match on l0
					uint64_t matchpos = l0-lseedlength;

					for ( uint64_t i = 0; i < lseedlength-1; ++i )
					{
						query <<= 3;
						query |= R[*(--q)];
					}

					// try to find seed in first read
					do
					{
						// add next base (step backward from end)
						query <<= 3;
						query |= R[*(--q)];
						query &= lseedmask;

						// compute number of mismatches
						uint64_t dif = (query ^ seed);
						dif = (dif | (dif >> 1) | (dif >> 2)) & mmask;
						unsigned int const difcnt = libmaus2::rank::PopCnt8<sizeof(unsigned long)>::popcnt8(dif);

						#if defined(DIFCNTDEBUG)
						unsigned int debdifcnt = 0;
						for ( unsigned int i = 0; i < lseedlength; ++i )
							if ( S[*(seqs[0].begin()+matchpos+i)] != R[*(seqs[1].begin()+l1-lseedlength + i)] )
								debdifcnt++;
						assert ( debdifcnt == difcnt );
						#endif

						// if the seed matches, then look at the rest
						if ( difcnt <= AOP.maxseedmismatches )
						{
							// end of seed match (and total match) on read 1
							uint64_t const end0 = matchpos + lseedlength;
							// end of seed match (and total match) on read 2
							uint64_t const end1 = l1;

							// position of seed on read 1
							uint64_t const seedp0 = end0 - lseedlength;
							// position of seed on read 2
							uint64_t const seedp1 = end1 - lseedlength;

							// length of match between read 1 and read 2 (no indels allowed)
							uint64_t const restoverlap = std::min(seedp0,seedp1);

							// if length of overlap is sufficient
							if ( lseedlength + restoverlap >= AOP.minoverlap )
							{
								// iterator range of non seed overlap on read 1
								uint8_t const * check0  = reinterpret_cast<uint8_t const *>(seqs[0].begin()+seedp0-restoverlap);
								uint8_t const * check0e = check0 + restoverlap;
								// start iterator of non seed overlap on read 2
								uint8_t const * check1  = reinterpret_cast<uint8_t const *>(seqs[1].begin()+seedp1-restoverlap);

								// maximum number of mismatches allowed
								uint64_t const maxmis = (restoverlap+lseedlength) * AOP.mismatchrate;

								// compute number of mismatches
								uint64_t nummis = difcnt;
								while ( nummis <= maxmis && check0 != check0e  )
									if ( S[*check0++] != R[*check1++] )
										nummis++;

								// if match is within the maximal number of mismatches
								if ( check0 == check0e && nummis <= maxmis )
								{
									// update overlap histogram
									overlaphist(lseedlength + restoverlap);

									// adapter length for read 1
									uint64_t const al0 = (l0-end0);
									// adapter length for read 2
									uint64_t const al1 = end1-(restoverlap+lseedlength);
									// number of bases compared in this case
									uint64_t const alcmp = std::min(AOP.almax,std::min(al0,al1));

									// adapter range on read 1
									char const * ap0 = seqs[0].begin()+end0;
									char const * ap0e = ap0 + alcmp;
									// adapter start on read 2 (actually adapter end, we will read it backwards)
									char const * ap1 = seqs[1].begin()+al1;
									// number of mismatches on adapter (up to a length of almax)
									unsigned int aldif = 0;

									// calculate mismatches in adapter
									while ( ap0 != ap0e )
										if ( S[*(ap0++)] != R[libmaus2::fastx::invertUnmapped(*(--ap1))] )
											aldif++;

									// if number of mismatches is within the tolerated range (0 at this time)
									if ( (ap0 == ap0e) && ! aldif )
									{
										#if 0
										if ( al0 || al1 )
										{
											char const * name0 = libmaus2::bambam::BamAlignmentDecoderBase::getReadName(UP0.first);

											std::cerr
												<< "[V2] overlap: " << name0
												<< " mismatchrate=" <<
													nummis << "/" << (restoverlap+lseedlength) << "=" <<
													static_cast<double>(nummis)/(restoverlap+lseedlength)
												<< " al0=" << al0
												<< " al1=" << al1
												<< " aldif=" << aldif
												<< " alcmp=" << alcmp
												<< "\n" <<
												"[V2] " << std::string(
													seqs[0].begin()+seedp0-restoverlap,
													seqs[0].begin()+end0) << "\n" <<
												"[V2] " << std::string(
													seqs[1].begin()+seedp1-restoverlap,
													seqs[1].begin()+end1) << "\n";

											std::cerr << "[V2] assumed adapter on read 1 ["<<al0<<"]: "
												<< std::string(
													seqs[0].begin()+end0,
													seqs[0].begin()+l0
												) << std::endl;

											std::cerr << "[V2] assumed adapter on read 2 ["<<al1<<"]: "
												<< libmaus2::fastx::reverseComplementUnmapped(std::string(
													seqs[1].begin(),
													seqs[1].begin()+al1)) << std::endl;
										}
										#endif

										a30 = al0;
										a31 = al1;
										ah = (a30 || a31);

										if ( ah )
										{
											adptcnt += 1;
											adapterhist0(al0);
											adapterhist0(al1);
											break;
										}
									}
								}
							}
						}

						--matchpos;
					} while ( q != qe );
				}

				for ( uint64_t i = ifrom; i < ito; ++i )
				{
					std::pair<uint8_t const *,uint8_t const *> UP(merge[i].first,merge[i].second);
					uint64_t ul = UP.second-UP.first;

					if ( ah == 1 )
					{
						ubuffer.reset();
						ubuffer.put(UP.first,UP.second-UP.first);

						libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(ubuffer,"ah",'i',1);
						libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(ubuffer,"a3",'i',(i==ifrom)?a30:a31);

						UP.first = ubuffer.buffer;
						UP.second = UP.first + ubuffer.length;
						ul = ubuffer.length;
					}

					int64_t asclip = 0;
					int64_t a3clip = 0;

					libmaus2::bambam::BamAlignmentDecoderBase::getAuxAsNumber(UP.first,ul,"as",asclip);
					libmaus2::bambam::BamAlignmentDecoderBase::getAuxAsNumber(UP.first,ul,"a3",a3clip);

					int64_t const aclip = std::max(asclip,a3clip);

					if ( aclip > 0 )
					{
						int64_t const lseq = libmaus2::bambam::BamAlignmentDecoderBase::decodeRead(UP.first,ASEQ);
						libmaus2::bambam::BamAlignmentDecoderBase::decodeQual(UP.first,AQUAL);

						if ( aclip < lseq )
						{
							ubufferclip.reset();
							ubufferclip.put(UP.first,ul);

							ubufferclip.length = libmaus2::bambam::BamAlignmentEncoderBase::replaceSequence(
								ubufferclip.abuffer,
								ubufferclip.length,
								seqenc,
								ASEQ.begin(),
								lseq - aclip,
								AQUAL.begin()
							);

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

							libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
								ubufferclip,
								"qs",
								ASEQ.begin() + (lseq-aclip),
								ASEQ.begin() + lseq
							);
							libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
								ubufferclip,
								"qq",
								AQUAL.begin() + (lseq-aclip),
								AQUAL.begin() + lseq
							);

							UP.first = ubufferclip.buffer;
							UP.second = UP.first + ubufferclip.length;
							ul = ubufferclip.length;
						}
					}

					mergefragment->pushAlignmentBlock(UP.first,ul);
				}
			}
		}

		std::vector<std::pair<uint8_t *,uint8_t *> > linfrag;
		mergefragment->getLinearOutputFragments(libmaus2::lz::BgzfConstants::getBgzfMaxBlockSize(),linfrag);

		BSMPSU.bamStreamMergePackageStatsUpdate(overlaphist,adapterhist0,adapterhist1,adptcnt);
		BSMPF.bamStreamMergePackageFinishedEvent(BP->mergeid,VBMI,BP->merge,BP->fragment,linfrag);
		BSMPR.bamStreamMergePackageReturnEvent(BP);
	}
};

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

	uint64_t mergeid;
	std::vector<BamMergeInfo> VBMI;
	FragmentPointerPairArrayTypeInfo::pointer_type merge;
	FragmentAlignmentBufferFragmentTypeInfo::pointer_type fragment;
	std::vector<std::pair<uint8_t *,uint8_t *> > linfrag;
	uint64_t volatile linfragfinished;
	libmaus2::parallel::StdSpinLock linfragfinishedlock;

	MergeQueueElement(
		uint64_t rmergeid,
		std::vector<BamMergeInfo> rVBMI,
		FragmentPointerPairArrayTypeInfo::pointer_type rmerge,
		FragmentAlignmentBufferFragmentTypeInfo::pointer_type rfragment,
		std::vector<std::pair<uint8_t *,uint8_t *> > const & rlinfrag
	) : mergeid(rmergeid), VBMI(rVBMI), merge(rmerge), fragment(rfragment), linfrag(rlinfrag), linfragfinished(0)
	{

	}

	bool operator<(MergeQueueElement const & M) const
	{
		return mergeid < M.mergeid;
	}
};

struct MergeQueueElementComparator
{
	bool operator()(MergeQueueElement::shared_ptr_type A, MergeQueueElement::shared_ptr_type B) const
	{
		return (*A) < (*B);
	}
};

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

	uint64_t bgzfid;
	MergeQueueElement::shared_ptr_type MQE;
	uint64_t linfragid;
	libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type outbuf;

	BamBgzfCompressionPackage & operator=(BamBgzfCompressionPackage const & O)
	{
		if ( this != &O )
		{
			libmaus2::parallel::SimpleThreadWorkPackage::operator=(O);
			bgzfid = O.bgzfid;
			MQE = O.MQE;
			linfragid = O.linfragid;
			outbuf = O.outbuf;
		}
		return *this;
	}

	BamBgzfCompressionPackage()
	: libmaus2::parallel::SimpleThreadWorkPackage(), bgzfid(0), MQE(), linfragid(0), outbuf()
	{

	}

	BamBgzfCompressionPackage(BamBgzfCompressionPackage const & O)
	: libmaus2::parallel::SimpleThreadWorkPackage(O), bgzfid(O.bgzfid), MQE(O.MQE), linfragid(O.linfragid), outbuf(O.outbuf)
	{

	}

	BamBgzfCompressionPackage(
		uint64_t const rbgzfid,
		MergeQueueElement::shared_ptr_type rMQE,
		uint64_t const rlinfragid,
		libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type routbuf,
		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), bgzfid(rbgzfid), MQE(rMQE), linfragid(rlinfragid), outbuf(routbuf)
	{

	}

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

struct BamBgzfCompressionPackageReturnEvent
{
	virtual ~BamBgzfCompressionPackageReturnEvent() {}
	virtual void bamBgzfCompressionPackageReturnEvent(BamBgzfCompressionPackage * p) = 0;
};

struct BamBgzfCompressionPackageFinishedEvent
{
	virtual ~BamBgzfCompressionPackageFinishedEvent() {}
	virtual void bamBgzfCompressionPackageFinishedEvent(
		uint64_t const bgzfid,
		MergeQueueElement::shared_ptr_type MQE, uint64_t const linfragid, libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type routbuf,
		libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo rflushinfo
	) = 0;
};

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

	libmaus2::parallel::LockedGrowingFreeList<
		libmaus2::lz::BgzfDeflateZStreamBase,
		libmaus2::lz::BgzfDeflateZStreamBaseAllocator,
		libmaus2::lz::BgzfDeflateZStreamBaseTypeInfo
	> & deflateFreeList;
	BlockReadStreamInfoVector * BRSIV;
	BamBgzfCompressionPackageReturnEvent & BBCOR;
	BamBgzfCompressionPackageFinishedEvent & BBCPF;

	BamBgzfCompressionPackageDispatcher(
		libmaus2::parallel::LockedGrowingFreeList<
			libmaus2::lz::BgzfDeflateZStreamBase,
			libmaus2::lz::BgzfDeflateZStreamBaseAllocator,
			libmaus2::lz::BgzfDeflateZStreamBaseTypeInfo
		> & rdeflateFreeList,
		BlockReadStreamInfoVector * rBRSIV,
		BamBgzfCompressionPackageReturnEvent & rBBCOR,
		BamBgzfCompressionPackageFinishedEvent & rBBCPF
	) : deflateFreeList(rdeflateFreeList), BRSIV(rBRSIV), BBCOR(rBBCOR), BBCPF(rBBCPF)
	{
	}

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

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

		libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo const flushinfo = compressor->flush(
			BP->MQE->linfrag[BP->linfragid].first,
			BP->MQE->linfrag[BP->linfragid].second,
			*(BP->outbuf)
		);

		deflateFreeList.put(compressor);

		BBCPF.bamBgzfCompressionPackageFinishedEvent(BP->bgzfid,BP->MQE,BP->linfragid,BP->outbuf,flushinfo);
		BBCOR.bamBgzfCompressionPackageReturnEvent(BP);
	}
};

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

	uint64_t bgzfid;
	MergeQueueElement::shared_ptr_type MQE;
	uint64_t linfragid;
	libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type outbuf;
	libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo flushinfo;

	BgzfWriteQueueEvent(
		uint64_t rbgzfid,
		MergeQueueElement::shared_ptr_type rMQE,
		uint64_t rlinfragid,
		libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type routbuf,
		libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo rflushinfo
	) : bgzfid(rbgzfid), MQE(rMQE), linfragid(rlinfragid), outbuf(routbuf), flushinfo(rflushinfo)
	{

	}

	bool operator<(BgzfWriteQueueEvent const & B) const
	{
		return bgzfid < B.bgzfid;
	}
};

struct BgzfWriteQueueEventComparator
{
	bool operator()(BgzfWriteQueueEvent::shared_ptr_type const & A, BgzfWriteQueueEvent::shared_ptr_type const & B) const
	{
		return *A < *B;
	}
};

struct BlockReadControl :
	public BlockReadQueueEvent,
	public BlockReadReturnEvent,
	public BlockProcessReturnEvent,
	public ReturnDecompressedBlockEvent,
	public BlockLineCountEnqueEvent,
	public BlockProcessNewlineCountPackageReturnEvent,
	public BlockProcessNewlineCountCompleteEvent,
	public BlockProcessNewlineStoreCompleteEvent,
	public BlockProcessNewlineStorePackageReturnEvent,
	public BlockProcessLineParseCheckEvent,
	public BlockProcessLineParsePackageReturnEvent,
	public BlockProcessLineParseCompleteEvent,
	public BlockProcessBamEncodePackageReturnEvent,
	public BlockProcessBamEncodePackageFinishedEvent,
	public BamStreamMergePackageReturnEvent,
	public BamStreamMergePackageFinishedEvent,
	public BamBgzfCompressionPackageReturnEvent,
	public BamBgzfCompressionPackageFinishedEvent,
	public BamStreamMergePackageStatsUpdate
{
	std::ostream & out;

	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::bambam::BamNumericalIndexGenerator * numindexgen;

	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 blockprocesslinecountdispatcherid;
	uint64_t const blockprocesslinestoredispatcherid;
	uint64_t const blockprocesslineparsepackagedispatcherid;
	uint64_t const blockprocessbamencodepackagedispatcherid;
	uint64_t const bamstreammergepackagedispatcherid;
	uint64_t const bambgzfcompressionpackagedispatcherid;
	BlockReadStreamInfoVector BRSIV;

	libmaus2::parallel::LockedGrowingFreeList<BlockReadPackage> blockReadPackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockProcessPackage> blockProcessPackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockProcessNewlineCountPackage> blockProcessNewlineCountPackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockProcessNewlineStorePackage> blockProcessNewlineStorePackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockProcessLineParsePackage> blockProcessLineParsePackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BlockProcessBamEncodePackage> blockProcessBamEncodePackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BamStreamMergePackage> bamStreamMergePackageFreeList;
	libmaus2::parallel::LockedGrowingFreeList<BamBgzfCompressionPackage> bamBgzfCompressionPackageFreeList;

	BlockReadDispatcher BRD;
	BlockProcessDispatcher BPD;
	BlockProcessNewlineCountPackageDispatcher BPNCP;
	BlockProcessNewlineStorePackageDispatcher BPNSP;
	BlockProcessLineParsePackageDispatcher BPLPP;
	BlockProcessBamEncodePackageDispatcher BPBEP;
	BamStreamMergePackageDispatcher BSMP;
	BamBgzfCompressionPackageDispatcher BBCP;

	uint64_t volatile eofcnt;
	libmaus2::parallel::StdSpinLock eoflock;

	libmaus2::autoarray::AutoArray<uint64_t> Ainput;
	libmaus2::parallel::StdSpinLock Ainputlock;

	libmaus2::autoarray::AutoArray<uint64_t> Adecomp;
	libmaus2::parallel::StdSpinLock Adecomplock;

	uint64_t volatile bammergenextid;

	libmaus2::parallel::LockedGrowingFreeList<
		FragmentPointerPairArrayTypeInfo::element_type,
		FragmentPointerPairArrayAllocator,
		FragmentPointerPairArrayTypeInfo
	> mergeFragmentPointerFreeList;

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


	libmaus2::util::FiniteSizeHeap<MergeQueueElement::shared_ptr_type,MergeQueueElementComparator> mergeQueueHeap;
	libmaus2::parallel::StdSpinLock mergeQueueHeapLock;
	uint64_t volatile mergeQueueNext;

	std::map<uint64_t,MergeQueueElement::shared_ptr_type> mergeQueueActiveMap;
	libmaus2::parallel::StdSpinLock mergeQueueActiveMapLock;
	uint64_t volatile bgzfnextid;

	libmaus2::util::FiniteSizeHeap<BgzfWriteQueueEvent::shared_ptr_type,BgzfWriteQueueEventComparator> bgzfWriteQueue;
	libmaus2::parallel::StdSpinLock bgzfWriteQueueLock;
	uint64_t volatile bgzfnextwrite;

	libmaus2::util::Histogram overlaphist;
	libmaus2::util::Histogram adapterhist0;
	libmaus2::util::Histogram adapterhist1;
	uint64_t volatile adptcnt;
	libmaus2::parallel::StdSpinLock adpstatslock;

	uint64_t volatile numbam;
	uint64_t volatile numbamprinted;
	libmaus2::parallel::StdSpinLock numbamlock;

	bool isEOF()
	{
		bool iseof;

		{
			libmaus2::parallel::ScopeStdSpinLock slock(eoflock);
			iseof = (eofcnt == BRSIV.A.size());
		}

		return iseof;
	}

	BlockReadControl(
		std::ostream & rout,
		libmaus2::parallel::SimpleThreadPool & rSTP,
		std::vector<std::string> const & Vfn,
		int const rlevel,
		RgInfo const & rginfo,
		std::string const & hash,
		libmaus2::bambam::BamHeader const * bamheader,
		libmaus2::digest::DigestInterface & rfiledigest,
		libmaus2::bambam::BamNumericalIndexGenerator * rnumindexgen,
		libmaus2::bambam::AdapterFilter const & rAF,
		AdapterMatchParameters const & rAMP,
		bool const rzz,
		bool const radaptercheck,
		bool const rclipslashid,
		bool const rpatchne,
		AdapterOverlapParams const & rAOP,
		uint64_t const newlineputback,
		uint64_t const rblocksize,
		uint64_t const rnumblocks,
		uint64_t const rreaddiv = 2
	) :
	    out(rout),
	    STP(rSTP),
	    blocksize(rblocksize),
	    numblocks(rnumblocks),
	    readdiv(rreaddiv),
	    readthres( (numblocks + readdiv - 1)/readdiv ),
	    level(rlevel),
	    filedigest(rfiledigest),
	    numindexgen(rnumindexgen),
	    bgzfOutAlloc(level),
	    bgzfOutBaseFreeList(bgzfOutAlloc),
	    deflateAlloc(level),
	    deflateFreeList(deflateAlloc),
	    checksumsAlloc(hash,bamheader),
	    checksumsFreeList(checksumsAlloc),
	    nextdispatcherid(0),
	    blockreaddispatcherid(nextdispatcherid++),
	    blockprocessdispatcherid(nextdispatcherid++),
	    blockprocesslinecountdispatcherid(nextdispatcherid++),
	    blockprocesslinestoredispatcherid(nextdispatcherid++),
	    blockprocesslineparsepackagedispatcherid(nextdispatcherid++),
	    blockprocessbamencodepackagedispatcherid(nextdispatcherid++),
	    bamstreammergepackagedispatcherid(nextdispatcherid++),
	    bambgzfcompressionpackagedispatcherid(nextdispatcherid++),
	    BRSIV(Vfn,blocksize,numblocks,STP.getNumThreads(),newlineputback),
	    BRD(&BRSIV,*this,*this),
	    BPD(STP.getNumThreads(),&BRSIV,*this,*this,*this),
	    BPNCP(&BRSIV,*this,*this),
	    BPNSP(&BRSIV,*this,*this),
	    BPLPP(&BRSIV,*this,*this),
	    BPBEP(checksumsFreeList,rginfo,rAF,rAMP,rzz,radaptercheck,rclipslashid,rpatchne,&BRSIV,*this,*this),
	    BSMP(rAOP,&BRSIV,*this,*this,*this,radaptercheck),
	    BBCP(deflateFreeList,&BRSIV,*this,*this),
	    eofcnt(0),
	    eoflock(),
	    Ainput(Vfn.size()),
	    Adecomp(Vfn.size()),
	    bammergenextid(0),
	    mergeQueueHeap(1),
	    mergeQueueNext(0),
	    bgzfnextid(0),
	    bgzfWriteQueue(1),
	    bgzfnextwrite(0),
	    adptcnt(0),
	    numbam(0),
	    numbamprinted(0)
	{
		STP.registerDispatcher(blockreaddispatcherid, &BRD);
		STP.registerDispatcher(blockprocessdispatcherid, &BPD);
		STP.registerDispatcher(blockprocesslinecountdispatcherid, &BPNCP);
		STP.registerDispatcher(blockprocesslinestoredispatcherid, &BPNSP);
		STP.registerDispatcher(blockprocesslineparsepackagedispatcherid, &BPLPP);
		STP.registerDispatcher(blockprocessbamencodepackagedispatcherid, &BPBEP);
		STP.registerDispatcher(bamstreammergepackagedispatcherid, &BSMP);
		STP.registerDispatcher(bambgzfcompressionpackagedispatcherid,&BBCP);
		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;

		{
			libmaus2::parallel::ScopeStdSpinLock 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
		{
		libmaus2::parallel::ScopeStdSpinLock 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);

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

					assert ( av_in_before );
					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 << "=" << zError(info.ret) << " on stream id " << streamid << " file " << info.fn << 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);
					}

					{
						libmaus2::parallel::ScopeStdSpinLock 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
		{
		libmaus2::parallel::ScopeStdSpinLock 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 blockProcessNewlineCountPackageReturnEvent(BlockProcessNewlineCountPackage * p)
	{
		blockProcessNewlineCountPackageFreeList.put(p);
	}

	virtual void blockProcessNewlineStorePackageReturnEvent(BlockProcessNewlineStorePackage * p)
	{
		blockProcessNewlineStorePackageFreeList.put(p);
	}

	virtual void blockProcessLineParsePackageReturnEvent(BlockProcessLineParsePackage * p)
	{
		blockProcessLineParsePackageFreeList.put(p);
	}

	virtual void blockProcessBamEncodePackageReturnEvent(BlockProcessBamEncodePackage * p)
	{
		blockProcessBamEncodePackageFreeList.put(p);
	}

	virtual void bamStreamMergePackageReturnEvent(BamStreamMergePackage * p)
	{
		bamStreamMergePackageFreeList.put(p);
	}

	virtual void bamBgzfCompressionPackageReturnEvent(BamBgzfCompressionPackage * p)
	{
		bamBgzfCompressionPackageFreeList.put(p);
	}


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

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

		{
			libmaus2::parallel::ScopeStdSpinLock slock(numbamlock);
			numbam += lnumbam;

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

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

		{
			libmaus2::parallel::ScopeStdSpinLock slock(Adecomplock);
			Adecomp[streamid] += block->n;
		}


		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] << " out=" << Adecomp[streamid]
				<< " lines " << info.numlines << " entries " << numbam << std::endl;
		}

		if ( block->eof )
		{
			libmaus2::parallel::ScopeStdSpinLock slock(eoflock);
			eofcnt += 1;

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

		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);

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

	virtual void blockLineCountEnqueEvent(DataBlockTypeInfo::pointer_type ptr, uint64_t const i)
	{
		// std::cerr << "enque " << ptr->streamid << "/" << i << std::endl;

		BlockProcessNewlineCountPackage * p = blockProcessNewlineCountPackageFreeList.get();

		*p = BlockProcessNewlineCountPackage(i,ptr,0/* prio */,blockprocesslinecountdispatcherid);

		STP.enque(p);
	}

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

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.numlinesLock);
			info.numlines += ptr->newLineInfo->aLineCount[ptr->newLineInfo->numthreads];
		}

		for ( uint64_t i = 0; i < ptr->newLineInfo->numthreads; ++i )
		{
			BlockProcessNewlineStorePackage * p = blockProcessNewlineStorePackageFreeList.get();

			*p = BlockProcessNewlineStorePackage(i,ptr,0/* prio */,blockprocesslinestoredispatcherid);

			STP.enque(p);
		}

	}

	virtual void blockProcessLineParseCheckEvent(uint64_t const streamid)
	{
		BlockReadStreamInfo & info = *(BRSIV.A[streamid]);

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.deLineFSHlock);

			if (
				!info.deLineFSH.empty()
				&&
				info.deLineFSH.top()->seqid == info.deLineNext
			)
			{
				DataBlockTypeInfo::pointer_type ptr = info.deLineFSH.pop();

				BlockProcessLineParsePackage * p = blockProcessLineParsePackageFreeList.get();

				*p = BlockProcessLineParsePackage(
					ptr,
					0 /* priority */,
					blockprocesslineparsepackagedispatcherid
				);

				STP.enque(p);
			}
		}
	}

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

		#if defined(LINESTOREDEBUG)
		uint64_t const numnl = ptr->newLineInfo->aLineCount[ptr->newLineInfo->numthreads];

		for ( uint64_t i = 0; i < numnl; ++i )
		{
			uint64_t const uptr = ptr->newLineInfo->uLinePtr[i];

			bool const ok = ptr->c[uptr] == '\n';

			assert ( ok );
		}
		#endif

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.deLineFSHlock);
			info.deLineFSH.push(ptr);
		}

		blockProcessLineParseCheckEvent(streamid);
	}

	// YYY change here
	virtual void blockProcessLineParseCompleteEvent(DataBlockTypeInfo::pointer_type ptr)
	{
		uint64_t const streamid = ptr->streamid;
		BlockReadStreamInfo & info = *(BRSIV.A[streamid]);

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.preBamEncodeFSHlock);
			info.preBamEncodeFSH.push(ptr);
		}

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.preBamEncodeFSHlock);

			while ( ! info.preBamEncodeFSH.empty() && info.preBamEncodeFSH.top()->seqid == info.preBamEncodeFSHnext )
			{
				DataBlockTypeInfo::pointer_type ptr = info.preBamEncodeFSH.pop();

				FastQPtrListTypeInfo::pointer_type fastQPtrList = ptr->fastQPtrList;
				uint64_t const fastqnum = fastQPtrList->Ao;

				ptr->fragmentBuffer = info.fragmentFreeList.get();

				for ( uint64_t i = 0; i < ptr->fragmentBuffer->size(); ++i )
				{
					ptr->fragmentBufferPointers.push_back(
						info.fragmentPointerFreeList.get()
					);
				}

				for ( uint64_t i = 0; i < STP.getNumThreads(); ++i )
				{
					BlockProcessBamEncodePackage * p = blockProcessBamEncodePackageFreeList.get();

					*p = BlockProcessBamEncodePackage(ptr,i,info.preBamEncodeLow,0/* prio */,blockprocessbamencodepackagedispatcherid);

					STP.enque(p);
				}

				info.preBamEncodeLow += fastqnum;

				info.preBamEncodeFSHnext += 1;
			}
		}

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.deLineFSHlock);
			info.deLineNext += 1;
		}

		blockProcessLineParseCheckEvent(streamid);
	}

	virtual void checkBamEncodedQueues()
	{
		uint64_t const numstreams = BRSIV.A.size();
		std::vector<libmaus2::parallel::ScopeStdSpinLock::shared_ptr_type> L(numstreams);
		std::vector<DataBlockTypeInfo::pointer_type> V(numstreams);

		// get locks
		for ( uint64_t i = 0; i < numstreams; ++i )
		{
			BlockReadStreamInfo & info = *(BRSIV.A[i]);

			libmaus2::parallel::ScopeStdSpinLock::shared_ptr_type tptr(
				new libmaus2::parallel::ScopeStdSpinLock(
					info.bamEncodedFSHLock
				)
			);

			L[i] = tptr;
		}

		bool running = true;

		while ( running )
		{
			// check whether all stream have a block
			bool ok = true;
			for ( uint64_t i = 0; i < numstreams; ++i )
			{
				BlockReadStreamInfo & info = *(BRSIV.A[i]);

				if (
					info.bamEncodedFSH.empty()
					||
					info.bamEncodedFSH.top()->seqid != info.bamEncodedNext
				)
					ok = false;
			}

			if ( ok )
			{
				uint64_t const maxavail = 8192;
				// number of available entries
				uint64_t avail = numstreams ? maxavail : 0;

				for ( uint64_t i = 0; i < numstreams; ++i )
				{
					BlockReadStreamInfo & info = *(BRSIV.A[i]);

					assert (
						!info.bamEncodedFSH.empty()
						&&
						info.bamEncodedFSH.top()->seqid == info.bamEncodedNext
					);

					V[i] = info.bamEncodedFSH.pop();

					uint64_t const lavail = V[i]->bamencodestored - V[i]->bamencodedcurrent;

					avail = std::min(lavail,avail);
				}

				std::vector<BamMergeInfo> VBMI(numstreams);

				for ( uint64_t i = 0; i < numstreams; ++i )
				{
					BlockReadStreamInfo & info = *(BRSIV.A[i]);

					uint64_t const bamstart = V[i]->bamencodedcurrent;
					uint64_t const bamend = bamstart + avail;

					V[i]->bamencodedcurrent = bamend;
					V[i]->bamencodemergecnt += 1;
					if ( bamend != V[i]->bamencodestored )
						info.bamEncodedFSH.push(V[i]);
					else
					{
						V[i]->bamencodemergecntall = 1;
						info.bamEncodedNext += 1;
					}

					VBMI[i] = BamMergeInfo(bamstart,bamend,V[i]);
				}

				FragmentPointerPairArrayTypeInfo::pointer_type fragmergeptr =
					mergeFragmentPointerFreeList.get();

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

				uint64_t const lbammergeid = bammergenextid++;

				BamStreamMergePackage BSMP(lbammergeid,VBMI,fragmergeptr,mergefragment,0 /* priority */,bamstreammergepackagedispatcherid /*dispid */,0 /*packid*/,lbammergeid/*subid*/);

				BamStreamMergePackage * p = bamStreamMergePackageFreeList.get();

				*p = BSMP;

				STP.enque(p);
			}
			else
			{
				running = false;
			}
		}
	}

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

		ptr->fragmentBufferPointersMerged = info.fragmentPointerFreeList.get();

		ptr->fragmentBufferPointersMerged->ensureSize(ptr->bamencodestored);
		uint64_t o = 0;
		for ( uint64_t i = 0; i < ptr->fragmentBufferPointers.size(); ++i )
		{
			uint64_t const f = (*(ptr->fragmentBuffer))[i]->f;
			FragmentPointerPairArrayTypeInfo::element_type const & A = *(ptr->fragmentBufferPointers[i]);

			for ( uint64_t j = 0; j < f; ++j )
				(*(ptr->fragmentBufferPointersMerged))[o++] = A[j];
		}
		assert ( o == ptr->bamencodestored );

		{
			libmaus2::parallel::ScopeStdSpinLock slock(info.bamEncodedFSHLock);
			info.bamEncodedFSH.push(ptr);
		}

		checkBamEncodedQueues();

		// XXX
		// returnDecompressedBlockEvent(ptr);
	}


	virtual void checkMergeQueue()
	{
		libmaus2::parallel::ScopeStdSpinLock slock(mergeQueueHeapLock);

		while ( ! mergeQueueHeap.empty() && mergeQueueHeap.top()->mergeid == mergeQueueNext )
		{
			MergeQueueElement::shared_ptr_type MQE = mergeQueueHeap.pop();

			{
				libmaus2::parallel::ScopeStdSpinLock slock(mergeQueueActiveMapLock);
				mergeQueueActiveMap[MQE->mergeid] = MQE;
			}

			for ( uint64_t i = 0; i < MQE->linfrag.size(); ++i )
			{
				uint64_t const lbgzfid = bgzfnextid++;
				BamBgzfCompressionPackage BBCP(
					lbgzfid,
					MQE,
					i,
					bgzfOutBaseFreeList.get(),
					0, /* priority */
					bambgzfcompressionpackagedispatcherid,
					0 /* package id */,
					lbgzfid
				);

				BamBgzfCompressionPackage * p = bamBgzfCompressionPackageFreeList.get();
				*p = BBCP;

				STP.enque(p);
			}

			mergeQueueNext += 1;

		}
	}

	virtual void bamStreamMergePackageFinishedEvent(
		uint64_t const mergeid,
		std::vector<BamMergeInfo> & VBMI,
		FragmentPointerPairArrayTypeInfo::pointer_type merge,
		FragmentAlignmentBufferFragmentTypeInfo::pointer_type fragment,
		std::vector<std::pair<uint8_t *,uint8_t *> > linfrag
	)
	{
		{
			MergeQueueElement::shared_ptr_type ptr(new MergeQueueElement(mergeid,VBMI,merge,fragment,linfrag));

			libmaus2::parallel::ScopeStdSpinLock slock(mergeQueueHeapLock);
			mergeQueueHeap.pushBump(ptr);
		}

		checkMergeQueue();
	}

	BgzfWriteQueueEvent::shared_ptr_type bgzfGetNext()
	{
		BgzfWriteQueueEvent::shared_ptr_type ptr;

		{
			libmaus2::parallel::ScopeStdSpinLock slock(bgzfWriteQueueLock);
			if ( ! bgzfWriteQueue.empty() && bgzfWriteQueue.top()->bgzfid == bgzfnextwrite )
				ptr = bgzfWriteQueue.pop();
		}

		return ptr;
	}

	virtual void checkBgzfWriteQueue()
	{
		// libmaus2::parallel::ScopeStdSpinLock slock(bgzfWriteQueueLock);
		BgzfWriteQueueEvent::shared_ptr_type ptr;

		while ( ptr = bgzfGetNext() )
		{
			// std::cerr << ptr->bgzfid << std::endl;

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

			assert ( ptr->flushinfo.blocks == 1 || ptr->flushinfo.blocks == 2 );

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

			out.write(outp,n);

			filedigest.vupdate(reinterpret_cast<uint8_t const *>(outp),n);

			// add indexing here
			{
				uint64_t const llinfragid = ptr->linfragid;
				std::pair<uint8_t const *,uint8_t const *> llinfrag = ptr->MQE->linfrag.at(llinfragid);

				if ( numindexgen )
					numindexgen->addBlock(llinfrag.first,n,llinfrag.second-llinfrag.first);
			}

			bgzfOutBaseFreeList.put(ptr->outbuf);

			MergeQueueElement::shared_ptr_type MQE = ptr->MQE;

			uint64_t llinfragdone;

			{
				libmaus2::parallel::ScopeStdSpinLock sllock(MQE->linfragfinishedlock);
				llinfragdone = ++MQE->linfragfinished;
			}

			if ( llinfragdone == MQE->linfrag.size() )
			{
				//
				uint64_t const numstreams = BRSIV.A.size();
				std::vector<libmaus2::parallel::ScopeStdSpinLock::shared_ptr_type> L(numstreams);

				// get locks
				for ( uint64_t i = 0; i < numstreams; ++i )
				{
					BlockReadStreamInfo & info = *(BRSIV.A[i]);

					libmaus2::parallel::ScopeStdSpinLock::shared_ptr_type tptr(
						new libmaus2::parallel::ScopeStdSpinLock(
							info.bamEncodedFSHLock
						)
					);

					L[i] = tptr;
				}

				for ( uint64_t i = 0; i < numstreams; ++i )
				{
					BamMergeInfo & BMI = MQE->VBMI[i];
					// BlockReadStreamInfo & info = *(BRSIV.A[i]);

					BMI.ptr->bamencodemergeproc += 1;

					if ( BMI.ptr->bamencodemergecntall && BMI.ptr->bamencodemergeproc == BMI.ptr->bamencodemergecnt )
					{
						returnDecompressedBlockEvent(BMI.ptr);
					}
				}

				mergeFragmentPointerFreeList.put(MQE->merge);
				mergeFragmentsFreeList.put(MQE->fragment);

			}

			{
				libmaus2::parallel::ScopeStdSpinLock slock(bgzfWriteQueueLock);
				bgzfnextwrite += 1;
			}
		}
	}

	virtual void bamBgzfCompressionPackageFinishedEvent(
		uint64_t const bgzfid,
		MergeQueueElement::shared_ptr_type MQE, uint64_t const linfragid, libmaus2::lz::BgzfDeflateOutputBufferBase::shared_ptr_type outbuf,
		libmaus2::lz::BgzfDeflateZStreamBaseFlushInfo flushinfo)
	{
		BgzfWriteQueueEvent::shared_ptr_type event(
			new BgzfWriteQueueEvent(
				bgzfid,
				MQE,
				linfragid,
				outbuf,
				flushinfo
			)
		);

		{
			libmaus2::parallel::ScopeStdSpinLock slock(bgzfWriteQueueLock);
			bgzfWriteQueue.pushBump(event);
		}

		checkBgzfWriteQueue();
	}

	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 bamStreamMergePackageStatsUpdate(
		libmaus2::util::Histogram const & roverlaphist,
		libmaus2::util::Histogram const & radapterhist0,
		libmaus2::util::Histogram const & radapterhist1,
		uint64_t radptcnt
	)
	{
		libmaus2::parallel::ScopeStdSpinLock slock(adpstatslock);
		adptcnt += radptcnt;
		overlaphist.merge(roverlaphist);
		adapterhist0.merge(radapterhist0);
		adapterhist1.merge(radapterhist1);
	}
};

static int getDefaultLevel()
{
	return Z_DEFAULT_COMPRESSION;
}

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 std::string getDefaultNameScheme(uint64_t const i)
{
	if ( i == 1 )
		return "c18s";
	else if ( i == 2 )
		return "c18pe";
	else
	{
		libmaus2::exception::LibMausException lme;
		lme.getStream() << "[E] no default name scheme for " << i << " input files" << std::endl;
		lme.finish();
		throw lme;
	}
}

static uint64_t getDefaultMatchMinScore() { return 16; }
static double getDefaultMatchMinFrac() { return 0.75; }
static double getDefaultMatchMinPFrac() { return 0.8; }
static uint64_t getDefaultRefLen() { return 3000000000ull; }
static double getDefaultpA() { return 0.25; }
static double getDefaultpC() { return 0.25; }
static double getDefaultpG() { return 0.25; }
static double getDefaultpT() { return 0.25; }
// static int getDefaultClip() { return 0; }

static uint64_t getDefaultPCT_MISMATCH() { return 10; }
static unsigned int getDefaultSEED_LENGTH() { return 12; }
static uint64_t getDefaultMIN_OVERLAP() { return 32; }
static uint64_t getDefaultADAPTER_MATCH() { return 12; }


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

	std::string const hash = arginfo.getValue<std::string>("hash",getDefaultHash());
	std::string const filehash = arginfo.getValue<std::string>("filehash",getDefaultFileHash());
	std::string const chksumfn = arginfo.getUnparsedValue("chksumfn",std::string());
	std::string const filehashfn = arginfo.getUnparsedValue("filehashfn",std::string());
	std::string const numerical = arginfo.getValue<std::string>("numerical",std::string());

	bool const zz = arginfo.getValue<int>("zz",0);
	bool const adaptercheck = arginfo.getValue<int>("adaptercheck",1);
	bool const clipslashid = arginfo.getValue<int>("clipslashid",0);
	bool const patchne = arginfo.getValue<int>("patchne",0);

	uint64_t const adpmatchminscore  = arginfo.getValue<uint64_t>("adpmatchminscore",getDefaultMatchMinScore());
	double   const adpmatchminfrac   = arginfo.getValue<double>("adpmatchminfrac",getDefaultMatchMinFrac());
	double   const adpmatchminpfrac  = arginfo.getValue<double>("adpmatchminpfrac",getDefaultMatchMinPFrac());
	uint64_t const reflen = arginfo.getValue<uint64_t>("reflen",getDefaultRefLen());
	double   const pA = arginfo.getValue<double>("pA",getDefaultpA());
	double   const pC = arginfo.getValue<double>("pC",getDefaultpC());
	double   const pG = arginfo.getValue<double>("pG",getDefaultpG());
	double   const pT = arginfo.getValue<double>("pT",getDefaultpT());

	uint64_t const seedlength = std::min(
		static_cast<unsigned int>((8*sizeof(uint64_t))/3),
		std::max(arginfo.getValue<unsigned int>("SEED_LENGTH",getDefaultSEED_LENGTH()),1u)
	);

	double const mismatchrate = std::min(100u,arginfo.getValue<unsigned int>("PCT_MISMATCH",getDefaultPCT_MISMATCH()))/100.0;
	// maximum number of mismatches in seed
	unsigned int const maxseedmismatches = arginfo.getValue<unsigned int>("MAX_SEED_MISMATCHES",static_cast<unsigned int>(std::ceil(seedlength * mismatchrate)));
	// minimum length of overlap between reads
	uint64_t const minoverlap = arginfo.getValue<uint64_t>("MIN_OVERLAP",getDefaultMIN_OVERLAP());
	// maximum number of adapter bases to be compared
	uint64_t const almax = arginfo.getValue<uint64_t>("ADAPTER_MATCH",getDefaultADAPTER_MATCH());

	libmaus2::bambam::AdapterFilter::unique_ptr_type pAF;

	if ( arginfo.hasArg("adaptersbam") )
	{
		libmaus2::aio::InputStreamInstance adapterCIS(arginfo.getUnparsedValue("adaptersbam","adapters.bam"));
		libmaus2::bambam::AdapterFilter::unique_ptr_type tAF(
			new libmaus2::bambam::AdapterFilter(adapterCIS,12 /* seed length */)
		);
		pAF = std::move(tAF);
	}
	else
	{
		std::string const builtinAdapters = libmaus2::bambam::BamDefaultAdapters::getDefaultAdapters();
		std::istringstream builtinAdaptersStr(builtinAdapters);
		libmaus2::bambam::AdapterFilter::unique_ptr_type tAF(
			new libmaus2::bambam::AdapterFilter(builtinAdaptersStr,12 /* seed length */)
		);
		pAF = std::move(tAF);
	}

	libmaus2::bambam::AdapterFilter const & AF = *pAF;
	AdapterMatchParameters const AMP(adpmatchminscore,adpmatchminfrac,adpmatchminpfrac,reflen,pA,pC,pG,pT);
	AdapterOverlapParams const AOP(seedlength,mismatchrate,maxseedmismatches,minoverlap,almax);

	if ( numthreads == 0 )
		numthreads = getDefaultNumThreads();

	int const level = getLevel(arginfo);
	RgInfo const rginfo(arginfo);
	std::string const rgid = rginfo.ID;

	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();

		libmaus2::bambam::BamNumericalIndexGenerator::unique_ptr_type pnumindexgen;
		libmaus2::bambam::BamNumericalIndexGenerator * numindexgen = NULL;

		if ( numerical.size() )
		{
			libmaus2::bambam::BamNumericalIndexGenerator::unique_ptr_type tnumindexgen(
				new libmaus2::bambam::BamNumericalIndexGenerator(numerical,1024)
			);
			pnumindexgen = std::move(tnumindexgen);
			numindexgen = pnumindexgen.get();
		}

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

		fastq_name_scheme_type const namescheme =
			parseNameScheme
			(
				arginfo.getUnparsedValue
				(
					"namescheme",
					getDefaultNameScheme(Vfn.size())
				)
			)
		;

		bool const schemesupported =
			(Vfn.size() && namescheme == fastq_name_scheme_casava18_single)
			||
			(Vfn.size() && namescheme == fastq_name_scheme_casava18_paired_end);

		if (! schemesupported )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] name scheme " << arginfo.getUnparsedValue("namescheme",getDefaultNameScheme(Vfn.size()))
				<< " is not supported for " << Vfn.size() << " input files" << 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:" << "fastqtobam2" << "\t"
			<< "PN:" << "fastqtobam2" << "\t"
			<< "CL:" << arginfo.commandline << "\t"
			<< "VN:" << std::string(PACKAGE_VERSION)
			<< std::endl;
		if ( rginfo.ID.size() )
			headstr << rginfo.toString();

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

		libmaus2::bambam::BamHeader header(head);
		std::ostringstream headerstr;
		header.serialise(headerstr);
		std::string const headerser = headerstr.str();

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

		std::ostringstream defstr;
		libmaus2::lz::BgzfDeflate<std::ostream> def(defstr,level);
		def.write(headerser.c_str(),headerser.size());
		def.flush();
		std::string const bgzfhead = biobambam2::runEOFFilter(defstr.str());

		if ( numindexgen )
			numindexgen->addBlock(reinterpret_cast<uint8_t const *>(headerser.c_str()), bgzfhead.size(), headerser.size());

		out.write(bgzfhead.c_str(),bgzfhead.size());
		filedigest.vupdate(
			reinterpret_cast<uint8_t const *>(bgzfhead.c_str()),bgzfhead.size()
		);

		BlockReadControl BRC(out,STP,Vfn,level /* level */,rginfo,hash,&header,filedigest,numindexgen,AF,AMP,zz,adaptercheck,clipslashid,patchne,AOP,16 /* newline putback */, 1024*1024,100);

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

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

		std::string const eofblock = libmaus2::lz::BgzfDeflate<std::ostream>::getEOFBlock();
		out.write(
			eofblock.c_str(),
			eofblock.size()
		);
		filedigest.vupdate(
			reinterpret_cast<uint8_t const *>(eofblock.c_str()),eofblock.size()
		);
		if ( numindexgen )
			numindexgen->addBlock(
				NULL,
				eofblock.size(),
				0
			);

		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();
		}

		if ( numindexgen )
		{
			numindexgen->flush();
			pnumindexgen.reset();
		}

		std::cerr << "[V] number of adapters detected: " << BRC.adptcnt << std::endl;

		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);

		for ( uint64_t i = 0; i < arginfo.restargs.size(); ++i )
			if (
				arginfo.restargs[i] == "-v"
				||
				arginfo.restargs[i] == "--version"
			)
			{
				std::cerr << ::biobambam2::Licensing::license();
				return EXIT_SUCCESS;
			}
			else if (
				arginfo.restargs[i] == "-h"
				||
				arginfo.restargs[i] == "--help"
			)
			{
				std::cerr << ::biobambam2::Licensing::license() << std::endl;
				std::cerr << "Key=Value pairs:" << std::endl;
				std::cerr << std::endl;

				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

				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=<fastqtobam2>", "CN field of RG header line if RGID is set (default: fastqtobam2)" ) );
				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)" ) );
				#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> ( "numerical=<>", "file name for numerical index of output file (default: do not produce numerical index)" ) );

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

				std::cerr << std::endl;

				std::cerr << "The I key can be given twice for a pair of synced FastQ files." << std::endl;
				std::cerr << "Any none key=value arguments will be considered as input file names." << std::endl;

				return EXIT_SUCCESS;
			}


		return fastqtobam2(arginfo);
	}
	catch(std::exception const & ex)
	{
		std::cerr << ex.what() << std::endl;
		return EXIT_FAILURE;
	}
}
