/*
    libmaus2
    Copyright (C) 2021-2022 German Tischler-Höhle

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#if ! defined(LIBMAUS2_PARALLEL_THREADPOOL_BAM_THREADPOOLBAMPARSEACCUMULATEHANDLER_HPP)
#define LIBMAUS2_PARALLEL_THREADPOOL_BAM_THREADPOOLBAMPARSEACCUMULATEHANDLER_HPP

#include <libmaus2/parallel/threadpool/bam/ThreadPoolBamParseControl.hpp>
#include <libmaus2/util/AtomicArray.hpp>
#include <libmaus2/bambam/BamAlignmentDecoderBase.hpp>
#include <libmaus2/bambam/StrCmpNum.hpp>
#include <libmaus2/sorting/RankSearch.hpp>
#include <libmaus2/lz/lz4.h>
#include <libmaus2/avl/AtomicAVLPtrValueMap.hpp>
#include <libmaus2/bambam/BamAlignmentNameComparator.hpp>

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bam
			{
				struct BlockAccum
				{
					std::atomic<ThreadPoolBamParseControl *> control;
					std::vector < libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> > Vblock;
					std::atomic<std::size_t> alignments;
					std::atomic<std::size_t> bytes;
					libmaus2::util::atomic_shared_ptr < libmaus2::util::AtomicArray<char const *> > P;
					libmaus2::util::atomic_shared_ptr < libmaus2::util::AtomicArray<char const *> > Q;
					std::atomic<std::size_t> id;

					BlockAccum()
					: Vblock(), alignments(0), bytes(0), id(0)
					{}

					bool operator<(BlockAccum const & O) const
					{
						return id.load() < O.id.load();
					}

					bool getEOF() const
					{
						if ( Vblock.size() )
							return Vblock.back()->eof;
						else
							return false;
					}

					libmaus2::util::AtomicArray<char const *> & getP(std::size_t const r_n)
					{
						if (
							! P.load()
							||
							P.load()->size() < r_n
						) {
							libmaus2::util::shared_ptr < libmaus2::util::AtomicArray<char const *> > T(
								// new libmaus2::util::AtomicArray<char const *>(r_n,0)
								libmaus2::util::AtomicArray<char const *>::allocateUnitiliazed(r_n)
							);
							P.store(T);
						}

						return *(P.load());
					}

					libmaus2::util::AtomicArray<char const *> & getQ(std::size_t const r_n)
					{
						if (
							! Q.load()
							||
							Q.load()->size() < r_n
						) {
							libmaus2::util::shared_ptr < libmaus2::util::AtomicArray<char const *> > T(
								libmaus2::util::AtomicArray<char const *>::allocateUnitiliazed(r_n)
							);
							Q.store(T);
						}

						return *(Q.load());
					}
				};

				struct BamSortInfo;

				struct BamSortInfoFinishedCallback
				{
					virtual ~BamSortInfoFinishedCallback() {}
					virtual void sortFinished(libmaus2::util::shared_ptr<BamSortInfo> info, libmaus2::util::shared_ptr<BlockAccum> l) = 0;
				};

				struct BamSortInfo : public libmaus2::parallel::threadpool::ThreadPoolDispatchable
				{
					libmaus2::parallel::threadpool::ThreadPool & TP;
					libmaus2::util::AtomicArray<char const *> & P;
					libmaus2::util::AtomicArray<char const *> & Q;
					std::size_t const n;
					std::size_t const threads;
					std::function<bool(std::atomic<char const *> const &,std::atomic<char const *> const &)> comp;
					std::atomic<std::size_t> baseBlocksNext;
					std::atomic<std::size_t> baseBlocksSorted;
					std::atomic<std::size_t> baseBlockArraySwap;
					std::atomic<std::size_t> level;
					std::atomic<std::size_t> next_thread_id;
					std::atomic<std::size_t> threads_finished;
					libmaus2::util::atomic_shared_ptr<BamSortInfo> selfptr;
					BamSortInfoFinishedCallback * const sortFinished;
					libmaus2::util::shared_ptr<BlockAccum> const blockaccum;
					std::vector < std::pair<std::size_t,std::size_t> > const V_copystart;

					static libmaus2::util::shared_ptr<BamSortInfo> allocate(
						libmaus2::parallel::threadpool::ThreadPool & r_TP,
						libmaus2::util::AtomicArray<char const *> & r_P,
						libmaus2::util::AtomicArray<char const *> & r_Q,
						std::size_t const r_n,
						std::size_t const r_threads,
						std::function<bool(
							std::atomic<char const *> const &,
							std::atomic<char const *> const &)> r_comp,
						BamSortInfoFinishedCallback * r_sortFinished,
						libmaus2::util::shared_ptr<BlockAccum> const r_blockaccum,
						std::vector < std::pair<std::size_t,std::size_t> > const & r_V_copystart
					)
					{
						libmaus2::util::shared_ptr<BamSortInfo> ptr(
							new BamSortInfo(
								r_TP,
								r_P,
								r_Q,
								r_n,
								r_threads,
								r_comp,
								r_sortFinished,
								r_blockaccum,
								r_V_copystart
							)
						);

						ptr->selfptr.store(ptr);

						return ptr;
					}

					void enquePackages()
					{
						for ( std::size_t i = 0; i < TP.threads; ++i )
						{
							auto pack = TP.getPackage<libmaus2::parallel::threadpool::ThreadPoolGenericPackageData>();
							libmaus2::parallel::threadpool::ThreadPoolGenericPackageData * data = dynamic_cast<libmaus2::parallel::threadpool::ThreadPoolGenericPackageData *>(pack->data.load().get());
							data->P.store(selfptr.load());
							TP.enqueue(pack);
						}
					}

					BamSortInfo(
						libmaus2::parallel::threadpool::ThreadPool & r_TP,
						libmaus2::util::AtomicArray<char const *> & r_P,
						libmaus2::util::AtomicArray<char const *> & r_Q,
						std::size_t const r_n,
						std::size_t const r_threads,
						std::function<bool(
							std::atomic<char const *> const &,
							std::atomic<char const *> const &)> r_comp,
						BamSortInfoFinishedCallback * r_sortFinished,
						libmaus2::util::shared_ptr<BlockAccum> const r_blockaccum,
						std::vector < std::pair<std::size_t,std::size_t> > const & r_V_copystart
					) : TP(r_TP), P(r_P), Q(r_Q), n(r_n), threads(r_threads), comp(r_comp), baseBlocksNext(0), baseBlocksSorted(0), baseBlockArraySwap(0), level(0), next_thread_id(0), threads_finished(0), sortFinished(r_sortFinished), blockaccum(r_blockaccum), V_copystart(r_V_copystart)
					{

					}

					enum dispatch_impl_result {
						dispatch_impl_result_noqueue,
						dispatch_impl_result_base_blocks_sorted,
						dispatch_impl_result_level_sorted,
						dispatch_impl_result_all_done
					};

					void dispatch()
					{
						dispatch_impl_result const r = dispatchImpl();

						switch ( r ) {
							case dispatch_impl_result_base_blocks_sorted:
							case dispatch_impl_result_level_sorted:
								// std::cerr << "requeue" << std::endl;
								enquePackages();
								break;
							case dispatch_impl_result_all_done:
								//std::cerr << "all done" << std::endl;
								sortFinished->sortFinished(selfptr.load(), blockaccum);
								break;
							case dispatch_impl_result_noqueue:
								break;
						}
					}

					static std::size_t getBaseBlockSize(std::size_t const n, std::size_t const threads)
					{
						std::size_t const base_block_size = (n + threads - 1)/threads;
						return base_block_size;
					}

					static std::tuple<std::size_t,std::size_t> getBaseBlockInterval(std::size_t const n, std::size_t const threads, std::size_t const baseblockid)
					{
						std::size_t const base_block_size = getBaseBlockSize(n,threads);
						std::size_t const block_low = std::min(baseblockid * base_block_size,n);
						std::size_t const block_high = std::min(block_low + base_block_size,n);
						return std::tuple<std::size_t,std::size_t>(block_low,block_high);
					}

					static std::vector < std::pair<std::size_t,std::size_t> > getBaseBlockIntervals(std::size_t const n, std::size_t const threads)
					{
						std::vector < std::pair<std::size_t,std::size_t> > V;
						for ( std::size_t i = 0; i < threads; ++i )
						{
							auto [block_low,block_high] = getBaseBlockInterval(n,threads,i);
							V.push_back(std::make_pair(block_low,block_high));
						}
						return V;
					}

					dispatch_impl_result dispatchImpl()
					{
						std::size_t const baseblockid = baseBlocksNext++;
						std::size_t const base_block_size = getBaseBlockSize(n,threads);

						// sort base block
						if ( baseblockid < threads )
						{
							auto [block_low,block_high] = getBaseBlockInterval(n,threads,baseblockid);

							std::size_t merge_loops = 0;

							if ( P.getInitRequired() )
							{
								P.initArray(block_low,block_high,nullptr);
								Q.initArray(block_low,block_high,nullptr);
							}

							if ( block_low < block_high )
							{
								auto block_it = blockaccum->Vblock.begin() + V_copystart[baseblockid].first;
								std::size_t j = V_copystart[baseblockid].second;

								std::size_t i = block_low;

								while ( i < block_high )
								{
									// end of BAM block?
									while ( j == (*block_it)->P.f.load() )
									{
										assert ( block_it != blockaccum->Vblock.end() );
										block_it++;
										j = 0;
									}

									std::size_t const av = (*block_it)->P.f.load() - j;
									std::size_t const need = block_high - i;
									std::size_t const use = std::min(av,need);
									auto const & LP = (*block_it)->P;

									for ( std::size_t z = 0; z < use; ++z )
										P[i++].store(LP.p.load()[j++]);
								}

								assert ( i == block_high );
							}

							for ( std::size_t level = 0; true; ++level )
							{
								std::size_t const blocksize = std::size_t(1) << level;

								if ( blocksize >= base_block_size )
									break;

								bool const level_even = (level % 2) == 0;

								libmaus2::util::AtomicArray<char const *> * pfrom = level_even ? &P : &Q;
								libmaus2::util::AtomicArray<char const *> * pto   = level_even ? &Q : &P;

								for ( std::size_t i = block_low; i < block_high; i += 2*blocksize )
								{
									std::size_t const left = i;
									std::size_t const middle = std::min(left+blocksize,block_high);
									std::size_t const right = std::min(middle+blocksize,block_high);

									auto ab = pfrom->begin() + left;
									auto ae = pfrom->begin() + middle;
									auto bb = pfrom->begin() + middle;
									auto be = pfrom->begin() + right;
									auto cb = pto->begin() + left;

									while ( ab != ae && bb != be )
									{
										char const * ap = ab->load();
										char const * bp = bb->load();

										if ( comp(*bb,*ab) )
										{
											(cb++)->store(bp);
											++bb;
										}
										else
										{
											(cb++)->store(ap);
											++ab;
										}
									}

									while ( ab != ae )
									{
										char const * ap = ab->load();
										(cb++)->store(ap);
										++ab;
									}
									while ( bb != be )
									{
										char const * bp = bb->load();
										(cb++)->store(bp);
										++bb;
									}
								}

								++merge_loops;
							}

							#if 0
							// check sorting
							bool const level_even = (merge_loops % 2) == 0;
							libmaus2::util::AtomicArray<char const *> const & pfrom = level_even ? P : Q;

							for ( std::size_t j = block_low+1; j < block_high; ++j )
							{
								std::atomic<char const *> const & v0 = pfrom[j-0];
								std::atomic<char const *> const & v1 = pfrom[j-1];

								bool const ok = !comp(v0,v1);
								assert ( ok );
							}
							#endif

							if ( baseblockid == 0 )
							{
								baseBlockArraySwap.store(merge_loops);
							}

							//std::cerr << "sorted block " << baseblockid << "/[" << block_low << "/" << block_high << ")" << std::endl;

							std::size_t const l_baseBlocksSorted = ++baseBlocksSorted;

							if ( l_baseBlocksSorted == threads )
							{
								P.clearInitRequired();
								Q.clearInitRequired();

								return dispatch_impl_result_base_blocks_sorted;
							}
						}
						// block merging
						else
						{
							std::size_t const l_level = level.load();
							std::size_t const blocksize = std::size_t(1) << l_level;
							std::size_t const mergesize = 2 * blocksize;
							std::size_t const nummerge = (threads + mergesize - 1) / mergesize;
							bool const level_even = ((baseBlockArraySwap.load() + l_level) % 2) == 0;

							libmaus2::util::AtomicArray<char const *> & pfrom = level_even ? P : Q;
							libmaus2::util::AtomicArray<char const *> & pto   = level_even ? Q : P;

							std::size_t const thread_id = next_thread_id++;

							for ( std::size_t z = 0; z < nummerge; ++z )
							{
								std::size_t const block_left   = z * mergesize;
								assert ( block_left < threads );

								// start of left block
								std::size_t const i_left   = block_left * base_block_size;
								// start of right block, end of left block
								std::size_t const i_middle = std::min(i_left + blocksize * base_block_size, n);
								// end of right block
								std::size_t const i_right  = std::min(i_middle + blocksize * base_block_size, n);
								// length of left plus length of right block
								std::size_t const i_range = i_right - i_left;
								std::size_t const i_range_per_thread = (i_range + threads - 1) / threads;
								std::size_t const i_start = std::min(thread_id * i_range_per_thread,i_range);
								std::size_t const i_end = std::min((thread_id+1) * i_range_per_thread,i_range);

								auto const P_rank_start =
									libmaus2::sorting::RankSearch::rankSearch(
										pfrom.begin() + i_left, pfrom.begin() + i_middle,
										pfrom.begin() + i_middle, pfrom.begin() + i_right,
										i_start,
										comp
									);
								auto const P_rank_end =
									libmaus2::sorting::RankSearch::rankSearch(
										pfrom.begin() + i_left, pfrom.begin() + i_middle,
										pfrom.begin() + i_middle, pfrom.begin() + i_right,
										i_end,
										comp
									);

								auto ab = P_rank_start.first;
								auto ae = P_rank_end.first;
								auto bb = P_rank_start.second;
								auto be = P_rank_end.second;
								auto cb = pto.begin() + i_left + i_start;

								while ( ab != ae && bb != be )
								{
									char const * ap = ab->load();
									char const * bp = bb->load();

									if ( comp(*bb,*ab) )
									{
										(cb++)->store(bp);
										++bb;
									}
									else
									{
										(cb++)->store(ap);
										++ab;
									}
								}

								while ( ab != ae )
								{
									char const * ap = ab->load();
									(cb++)->store(ap);
									++ab;
								}
								while ( bb != be )
								{
									char const * bp = bb->load();
									(cb++)->store(bp);
									++bb;
								}

								#if 0
								std::size_t const block_middle = std::min(block_left+blocksize,threads);
								std::size_t const block_right  = std::min(block_middle+blocksize,threads);

								std::cerr
									<< "(" << block_left << "," << block_middle << "," << block_right << ")"
									<< "(" << i_left << "," << i_middle << "," << i_right << ")"
									<< " " << i_range
									<< " " << i_start << "," << i_end
									<< " "
										<< "[" << P_rank_start.first - (pfrom.begin() + i_left)
										<< ","
										<< P_rank_end.first - (pfrom.begin() + i_left) << ")"
									<< " "
										<< "[" << P_rank_start.second - (pfrom.begin() + i_middle)
										<< ","
										<< P_rank_end.second - (pfrom.begin() + i_middle) << ")"
									<< " " << (i_left + i_start)
									<< std::endl;
								#endif
							}

							std::size_t const l_threads_finished = ++threads_finished;

							if ( l_threads_finished == threads )
							{
								// std::cerr << "finished level " << l_level << " blocksize=" << blocksize << " mergesize=" << mergesize << std::endl;

								next_thread_id.store(0);
								threads_finished.store(0);
								std::size_t const next_level = ++level;
								std::size_t const next_block_size = 1ull << next_level;

								if ( next_block_size < threads )
								{
									return dispatch_impl_result_level_sorted;
								}
								else
								{
									return dispatch_impl_result_all_done;
								}
							}
						}

						return dispatch_impl_result_noqueue;
					}

					libmaus2::util::AtomicArray<char const *> const & getFinal() const
					{
						bool const level_even = ((baseBlockArraySwap.load() + level.load()) % 2) == 0;

						libmaus2::util::AtomicArray<char const *> & pfrom = level_even ? P : Q;

						return pfrom;
					}

					char const * getFinalEntry(std::size_t const i) const
					{
						return getFinal()[i].load();
					}
				};

				#if 0
				struct MemoryBlock
				{
					// raw data
					std::shared_ptr<char[]> data;
					std::atomic<std::size_t> n;
					std::atomic<std::size_t> f;
					std::atomic<std::size_t> id;

					MemoryBlock() : data(), n(0), f(0), id(0) {}

					void allocate(std::size_t const r_n)
					{
						if ( r_n > n.load() )
						{
							std::shared_ptr<char[]> t_data(new char[r_n]);
							data = t_data;
							n.store(r_n);
						}
						f.store(0);
					}

					bool operator<(MemoryBlock const & M) const
					{
						return id.load() < M.id.load();
					}
				};
				#endif

				#if 0
				struct SortedBlockEncodePackageData
				{
					std::atomic<BlockAccum *> l_accum;
					libmaus2::util::atomic_shared_ptr<BamSortInfo> sort_info;
				};
				#endif

				struct EncodePart;

				struct EncodePartFinishedInterface
				{
					virtual ~EncodePartFinishedInterface() {}
					virtual void encodePartFinished(libmaus2::util::shared_ptr<EncodePart> ptr, bool const all_finished, libmaus2::util::shared_ptr<BlockAccum> accum) = 0;
				};

				struct EncodePart : public libmaus2::parallel::threadpool::ThreadPoolDispatchable
				{
					libmaus2::util::atomic_shared_ptr<EncodePart> self_ptr;

					std::atomic<std::size_t> super_block_id;
					std::atomic<std::size_t> i;
					std::atomic<std::size_t> n;
					// finished
					libmaus2::util::atomic_shared_ptr< std::atomic<std::size_t> > f;
					std::atomic<std::size_t> low;
					std::atomic<std::size_t> high;
					libmaus2::util::atomic_shared_ptr<BamSortInfo> bam_sort_info;

					libmaus2::util::atomic_shared_ptr<char[]> M;
					std::atomic<std::size_t> M_n;
					std::atomic<std::size_t> M_f;
					std::atomic<char const *> M_l;

					libmaus2::util::atomic_shared_ptr<char[]> C;
					std::atomic<std::size_t> C_n;
					std::atomic<std::size_t> C_f;

					std::atomic<EncodePartFinishedInterface *> finished;

					libmaus2::util::atomic_shared_ptr<BlockAccum> accum;

					bool operator<(EncodePart const & E) const
					{
						if ( super_block_id.load() != E.super_block_id.load() )
							return super_block_id.load() < E.super_block_id.load();
						else
							return i.load() < E.i.load();
					}

					EncodePart(EncodePartFinishedInterface * r_finished)
					: self_ptr(), super_block_id(0), i(0), n(0), f(), low(0), high(0), bam_sort_info(), M(), M_n(0), M_f(0),
					  C(), C_n(0), C_f(0), finished(r_finished), accum()
					{
					}

					void enqueuePackage(libmaus2::parallel::threadpool::ThreadPool & TP)
					{
						auto pack = TP.getPackage<libmaus2::parallel::threadpool::ThreadPoolGenericPackageData>();
						libmaus2::parallel::threadpool::ThreadPoolGenericPackageData * data = dynamic_cast<libmaus2::parallel::threadpool::ThreadPoolGenericPackageData *>(pack->data.load().get());
						data->P.store(self_ptr.load());
						TP.enqueue(pack);
					}

					static libmaus2::util::shared_ptr<EncodePart> allocate(EncodePartFinishedInterface * finished)
					{
						libmaus2::util::shared_ptr<EncodePart> ptr(new EncodePart(finished));
						ptr->self_ptr = ptr;
						return ptr;
					}

					void setup(
						std::size_t const r_super_block_id,
						std::size_t const r_i,
						std::size_t const r_n,
						libmaus2::util::shared_ptr<std::atomic<std::size_t> > r_f,
						libmaus2::util::shared_ptr<BamSortInfo> r_bam_sort_info,
						libmaus2::util::shared_ptr<BlockAccum> r_accum,
						std::size_t const r_low,
						std::size_t const r_high
					)
					{
						super_block_id.store(r_super_block_id);
						i.store(r_i);
						n.store(r_n);
						f.store(r_f);
						bam_sort_info.store(r_bam_sort_info);
						M_f.store(0);
						M_l.store(nullptr);
						C_f.store(0);
						accum.store(r_accum);
						low.store(r_low);
						high.store(r_high);
					}

					std::size_t computeUncompressedSize() const
					{
						libmaus2::util::AtomicArray<char const *> const & F = bam_sort_info.load()->getFinal();

						std::size_t const l_low = low.load();
						std::size_t const l_high = high.load();
						std::size_t s = 0;

						for ( std::size_t i = l_low; i < l_high; ++i ) {
							char const * c = F[i].load();
							std::size_t const blocklength = libmaus2::parallel::threadpool::bgzf::BgzfHeaderDecoderBase::get32(c,0 /* offset */);
							s += blocklength;
						}


						return s;
					}

					void ensureUncompressedSpace(std::size_t const s)
					{
						if ( s >= M_n.load() )
						{
							libmaus2::util::shared_ptr<char[]> t_M(new char[s]);
							M.store(t_M);
							M_n.store(s);
						}
					}

					void concatenateUncompressed(std::size_t const s)
					{
						libmaus2::util::AtomicArray<char const *> const & F = bam_sort_info.load()->getFinal();

						std::size_t const l_low = low.load();
						std::size_t const l_high = high.load();

						char * p = M.load().get();
						for ( std::size_t i = l_low; i < l_high; ++i ) {
							char const * c = F[i].load();
							std::size_t const blocklength = libmaus2::parallel::threadpool::bgzf::BgzfHeaderDecoderBase::get32(c,0 /* offset */);
							std::copy(c,c+blocklength,p);
							p += blocklength;
						}

						assert ( p - M.load().get() == static_cast<std::ptrdiff_t>(s) );

						if ( l_high > l_low )
						{
							char const * c = F[l_high-1].load();
							std::size_t const block_length = libmaus2::parallel::threadpool::bgzf::BgzfHeaderDecoderBase::get32(c,0 /* offset */);
							M_l.store(p - block_length);
						}

						M_f.store(s);
					}

					std::size_t ensureCompressedSpace(std::size_t const s)
					{
						std::size_t const lz4_bound = LZ4_compressBound(s);

						if ( lz4_bound > C_n.load() )
						{
							libmaus2::util::shared_ptr<char[]> t_C(new char[lz4_bound]);
							C.store(t_C);
							C_n.store(lz4_bound);
						}

						return lz4_bound;
					}

					void compressData(std::size_t const s, std::size_t const lz4_bound)
					{
						int const r_lz4 = LZ4_compress_limitedOutput (M.load().get(), C.load().get(), s, lz4_bound);

						if ( ! r_lz4 )
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "EncodePart::dispatch(): lz4 compression failed" << std::endl;
						}

						C_f.store(r_lz4);
					}

					void dispatch()
					{
						std::size_t const s = computeUncompressedSize();
						ensureUncompressedSpace(s);
						concatenateUncompressed(s);
						std::size_t const lz4_bound = ensureCompressedSpace(s);
						compressData(s,lz4_bound);

						// std::cerr << "s=" << s << " lz4_bound=" << lz4_bound << " actual=" << r_lz4 << std::endl;

						std::size_t const l_finished =
							++(*f.load());

						bool const all_finished = (l_finished == n.load());

						finished.load()->encodePartFinished(self_ptr.load(),all_finished,accum.load());
					}
				};

				struct BamSortFunctionsInterface
				{
					virtual ~BamSortFunctionsInterface() {}
					virtual std::function<bool(std::atomic<char const *> const &,std::atomic<char const *> const &)> getComparator() const = 0;
					virtual std::size_t getRecordLength(char const * ca) const = 0;
				};

				struct BamSortFunctionsQueryName : public BamSortFunctionsInterface
				{
					static bool compare(
						uint8_t const * uap,
						std::size_t const l_a,
						uint8_t const * ubp,
						std::size_t const l_b
					)
					{
						return libmaus2::bambam::BamAlignmentNameComparator::compare(uap,l_a,ubp,l_b);
					}

					std::size_t getRecordLength(char const * ca) const
					{
						return libmaus2::parallel::threadpool::bgzf::BgzfHeaderDecoderBase::get32(ca,0 /* offset */);
					}

					std::function<bool(std::atomic<char const *> const &,std::atomic<char const *> const &)> getComparator() const
					{
						return std::function<bool(std::atomic<char const *> const &,std::atomic<char const *> const &)>(
							[this](std::atomic<char const *> const & ap, std::atomic<char const *> const & bp){
								char const * ca = ap.load();
								char const * cb = bp.load();
								std::size_t const blocklength_a = getRecordLength(ca);
								std::size_t const blocklength_b = getRecordLength(cb);
								uint8_t const * uap = reinterpret_cast<uint8_t const *>(ca + sizeof(uint32_t));
								uint8_t const * ubp = reinterpret_cast<uint8_t const *>(cb + sizeof(uint32_t));
								return compare(uap,blocklength_a,ubp,blocklength_b);
							}
						);
					}
				};

				struct IndexSubEntry
				{
					std::size_t data_ptr;
					std::size_t comp_size;
					std::size_t uncomp_size;
					std::size_t record_length;
					std::size_t record_offset;
					char const * record;

					IndexSubEntry()
					{}
					IndexSubEntry(
						std::size_t const r_data_ptr,
						std::size_t const r_comp_size,
						std::size_t const r_uncomp_size,
						std::size_t const r_record_length,
						std::size_t const r_record_offset
					) : data_ptr(r_data_ptr), comp_size(r_comp_size),
					    uncomp_size(r_uncomp_size), record_length(r_record_length),
					    record_offset(r_record_offset)
					{

					}
				};

				struct IndexEntry
				{
					libmaus2::autoarray::AutoArray<char> A_record;
					std::vector < IndexSubEntry > V_sub;

					typedef std::vector < IndexSubEntry >::const_iterator const_iterator;

					const_iterator begin() const
					{
						return V_sub.begin();
					}

					const_iterator end() const
					{
						return V_sub.end();
					}

					std::size_t size() const
					{
						return V_sub.size();
					}

					IndexEntry(std::istream & in)
					{
						std::size_t const num_rec = libmaus2::util::NumberSerialisation::deserialiseNumber(in);

						if ( num_rec )
						{
							std::size_t o = 0;

							for ( std::size_t i = 0; i < num_rec; ++i )
							{

								std::size_t const data_ptr = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
								std::size_t const comp_size = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
								std::size_t const uncomp_size = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
								std::size_t const record_length = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
								std::size_t const record_offset = o;

								#if 0
								std::cerr << "i=" << i << std::endl;
								std::cerr << "data_ptr=" << data_ptr << std::endl;
								std::cerr << "comp_size=" << comp_size << std::endl;
								std::cerr << "uncomp_size=" << uncomp_size << std::endl;
								std::cerr << "record_length=" << record_length << std::endl;
								std::cerr << "record_offset=" << record_offset << std::endl;
								#endif

								A_record.ensureSize(o + record_length);

								in.read(A_record.begin() + o,record_length);
								o += record_length;

								V_sub.push_back(IndexSubEntry(data_ptr,comp_size,uncomp_size,record_length,record_offset));
							}

							std::size_t const last_record_length = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
							[[maybe_unused]] std::size_t const last_record_offset = o;
							A_record.ensureSize(o + last_record_length);
							in.read(A_record.begin() + o,last_record_length);
							o += last_record_length;

							#if 0
							// push dummy element representing last record in entry
							V_sub.push_back(IndexSubEntry(0 /* data_ptr */,0 /* comp_size */,0 /* uncomp_size*/,last_record_length,last_record_offset));
							#endif

							for ( std::size_t i = 0; i < V_sub.size(); ++i )
								V_sub[i].record = A_record.begin() + V_sub[i].record_offset;

							#if 0
							for ( auto const & E : V_sub ) {
								char const * ca = E.record;
								uint8_t const * ua = reinterpret_cast<uint8_t const *>(ca);
								std::cerr << libmaus2::bambam::BamAlignmentDecoderBase::getReadName(ua + sizeof(uint32_t)) << std::endl;
							}
							#endif
						}
					}
				};

				struct IndexEntrySet
				{
					std::vector < std::shared_ptr<IndexEntry> > V_entry;
					std::vector < std::pair<IndexEntry::const_iterator,IndexEntry::const_iterator> > V_split;
					std::size_t n;

					std::size_t size() const
					{
						return n;
					}

					IndexEntrySet(std::string const & index_fn)
					: n(0)
					{
						libmaus2::aio::InputStreamInstance ISI(index_fn);

						while ( ISI.peek() != std::istream::traits_type::eof() )
						{
							std::shared_ptr<IndexEntry> ptr(new IndexEntry(ISI));
							V_entry.push_back(ptr);
							V_split.push_back(
								std::make_pair(ptr->begin(),ptr->end())
							);
							n += ptr->size();
						}

						std::cerr << "V_entry.size()=" << V_entry.size() << std::endl;
					}

					std::vector < IndexEntry::const_iterator > getSplit(BamSortFunctionsInterface const & BSFI, std::size_t const s) const
					{
						auto l_comp = BSFI.getComparator();
						auto comp = [&l_comp](auto const & A, auto const & B) { return l_comp(A.record,B.record); };
						return libmaus2::sorting::RankSearch::rankSearchMulti(V_split,s,comp);
					}
				};

				struct ThreadPoolBamParseAccumulateHandler : public ThreadPoolBamParseHandler, BamSortInfoFinishedCallback, EncodePartFinishedInterface
				{
					BamSortFunctionsInterface & BSFI;
					std::string const fn;
					std::map < std::size_t, std::pair<std::string,std::string> > Mfn;
					std::mutex Mfn_lock;

					libmaus2::avl::AtomicAVLPtrValueMap<std::size_t, libmaus2::aio::OutputStreamInstance > Mdata;
					libmaus2::avl::AtomicAVLPtrValueMap<std::size_t, libmaus2::aio::OutputStreamInstance > Mindex;

					std::size_t const buffersize;
					std::size_t const num_buffers;

					std::atomic<std::size_t> next_accum_id;
					std::atomic<std::size_t> next_accum_process_id;
					libmaus2::avl::AtomicAVLPtrValueMap<
						ThreadPoolBamParseControl *,
						libmaus2::parallel::AtomicPtrHeap<BlockAccum>
					> block_accum_free;
					std::mutex accum_lock;

					std::size_t const encode_pack_size;
					libmaus2::parallel::AtomicPtrStack<EncodePart> part_stack;

					libmaus2::parallel::AtomicPtrHeap<EncodePart> part_heap;

					typedef std::function<bool(std::atomic<std::size_t> const &, std::atomic<std::size_t> const &)> encode_finished_heap_comp_type;
					encode_finished_heap_comp_type encode_finished_heap_comp;
					libmaus2::parallel::AtomicPtrHeap< std::atomic<std::size_t>, encode_finished_heap_comp_type > encode_finished_heap;
					std::atomic<std::size_t> encode_finished_heap_next;
					std::mutex encode_finished_heap_lock;

					std::atomic<bool> encode_finished_heap_eof_set;
					std::atomic<std::size_t> encode_finished_heap_eof;
					std::mutex encode_finished_heap_eof_set_lock;

					void flushStreams()
					{
						for ( auto it = Mdata.begin(); it != Mdata.end(); ++it )
							it->second.load().get()->flush();
						for ( auto it = Mindex.begin(); it != Mindex.end(); ++it )
							it->second.load().get()->flush();
					}

					void closeStreams()
					{
						for ( auto it = Mdata.begin(); it != Mdata.end(); ++it )
							it->second.load().get()->flush();
						for ( auto it = Mindex.begin(); it != Mindex.end(); ++it )
							it->second.load().get()->flush();
					}

					std::pair<std::string,std::string> getFileNames(std::size_t const streamid)
					{
						std::lock_guard<std::mutex> slock(Mfn_lock);

						auto it = Mfn.find(streamid);

						if ( it == Mfn.end() )
						{
							std::string const d_fn = getDataFileName(streamid);
							std::string const i_fn = getIndexFileName(streamid);
							Mfn[streamid] = std::make_pair(d_fn,i_fn);
							it = Mfn.find(streamid);
						}

						assert ( it != Mfn.end() );

						return it->second;
					}

					libmaus2::aio::OutputStreamInstance & getDataStream(std::size_t const streamid)
					{
						std::lock_guard<std::mutex> slock(Mfn_lock);
						auto it = Mdata.find(streamid);
						if ( it == Mdata.end() )
						{
							libmaus2::util::shared_ptr<libmaus2::aio::OutputStreamInstance> ptr(new libmaus2::aio::OutputStreamInstance(getDataFileName(streamid)));
							ptr->exceptions(std::ios::badbit);
							Mdata.insert(streamid,ptr);
							it = Mdata.find(streamid);
						}
						assert ( it != Mdata.end() );

						return *(it->second.load());
					}

					libmaus2::aio::OutputStreamInstance & getIndexStream(std::size_t const streamid)
					{
						std::lock_guard<std::mutex> slock(Mfn_lock);
						auto it = Mindex.find(streamid);
						if ( it == Mindex.end() )
						{
							libmaus2::util::shared_ptr<libmaus2::aio::OutputStreamInstance> ptr(new libmaus2::aio::OutputStreamInstance(getIndexFileName(streamid)));
							ptr->exceptions(std::ios::badbit);
							Mindex.insert(streamid,ptr);
							it = Mindex.find(streamid);
						}
						assert ( it != Mindex.end() );

						return *(it->second.load());
					}

					std::string getDataFileName(std::size_t const streamid) const
					{
						std::ostringstream ostr;
						ostr << fn << "_" << streamid << ".data";
						return ostr.str();
					}

					std::string getIndexFileName(std::size_t const streamid) const
					{
						std::ostringstream ostr;
						ostr << fn << "_" << streamid << ".index";
						return ostr.str();
					}

					static libmaus2::util::shared_ptr<BlockAccum> allocateBlockAccum()
					{
						libmaus2::util::shared_ptr<BlockAccum> ptr(new BlockAccum);
						return ptr;
					}

					void addBlockAccum(ThreadPoolBamParseControl * control, std::size_t const n)
					{
						libmaus2::util::shared_ptr<
							libmaus2::parallel::AtomicPtrHeap<BlockAccum>
						> block_accum(
							new libmaus2::parallel::AtomicPtrHeap<BlockAccum>
						);
						for ( std::size_t i = 0; i < n; ++i ) {
							auto ptr(allocateBlockAccum());
							ptr->id.store(next_accum_id++);
							ptr->control.store(control);
							block_accum->push(ptr);
						}
						block_accum_free.insert(control, block_accum);
					}

					void putBackBlockAccum(libmaus2::util::shared_ptr<BlockAccum> ptr)
					{
						assert ( ptr );
						std::size_t const next_id = next_accum_id++;
						ptr->id.store(next_id);
						std::lock_guard<std::mutex> guard(accum_lock);
						assert ( ptr );
						assert ( ptr.get() );

						ThreadPoolBamParseControl * control = ptr->control.load();
						auto it = block_accum_free.find(control);

						assert ( it != block_accum_free.end() );
						libmaus2::parallel::AtomicPtrHeap<BlockAccum> & heap = *(it->second.load());

						heap.push(ptr);
					}

					bool getBlockAccum(ThreadPoolBamParseControl * control, libmaus2::util::shared_ptr<BlockAccum> & ptr)
					{
						std::lock_guard<std::mutex> guard(accum_lock);

						auto it = block_accum_free.find(control);

						if ( it == block_accum_free.end() )
						{
							addBlockAccum(control,num_buffers);
							it = block_accum_free.find(control);
						}

						assert ( it != block_accum_free.end() );

						libmaus2::parallel::AtomicPtrHeap<BlockAccum> & heap = *(it->second.load());

						bool const ok = heap.pop(ptr);

						if ( ! ok )
							return false;

						if ( ptr->id.load() != next_accum_process_id.load() )
						{
							heap.push(ptr);
							return false;
						}

						return true;
					}


					ThreadPoolBamParseAccumulateHandler(
						BamSortFunctionsInterface & r_BSFI,
						std::string const & r_fn,
						std::size_t const r_buffersize,
						std::size_t const r_num_buffers,
						std::size_t const r_encode_pack_size = 4*1024)
					: BSFI(r_BSFI), fn(r_fn), buffersize(r_buffersize), num_buffers(r_num_buffers), next_accum_id(0), next_accum_process_id(0), encode_pack_size(r_encode_pack_size),
					  encode_finished_heap_comp(
					  	[](std::atomic<std::size_t> const & a, std::atomic<std::size_t> const & b) { return a.load() < b.load(); }
					  ),
					  encode_finished_heap(encode_finished_heap_comp), encode_finished_heap_next(0),
					  encode_finished_heap_eof_set(false), encode_finished_heap_eof(0)
					{
					}

					libmaus2::util::shared_ptr<EncodePart> getEncodeBlock()
					{
						libmaus2::util::shared_ptr<EncodePart> ptr;
						bool const ok = part_stack.pop(ptr);

						if ( ok )
							return ptr;

						libmaus2::util::shared_ptr<EncodePart> tptr(EncodePart::allocate(this));

						return tptr;
					}

					void addEncodeFinishedHeap(std::size_t const i)
					{
						libmaus2::util::shared_ptr< std::atomic<std::size_t> > ptr(new std::atomic<std::size_t>(i));
						std::lock_guard<std::mutex> slock(encode_finished_heap_lock);
						encode_finished_heap.push(ptr);
					}

					bool getNextFinishedHeap(std::size_t & id, libmaus2::parallel::AtomicPtrHeap<EncodePart> & l_part_heap)
					{
						std::lock_guard<std::mutex> slock(encode_finished_heap_lock);

						libmaus2::util::shared_ptr<std::atomic<std::size_t> > ptr;
						bool const ok = encode_finished_heap.pop(ptr);

						if ( ! ok )
							return false;

						if ( ptr->load() != encode_finished_heap_next.load() )
						{
							encode_finished_heap.push(ptr);
							return false;
						}

						// get block id
						id = ptr->load();

						while ( true )
						{
							libmaus2::util::shared_ptr<EncodePart> part;
							bool const ok = part_heap.pop(part);

							if ( ! ok )
								break;
							else if ( part->super_block_id.load() != id )
							{
								part_heap.push(part);
								break;
							}
							else
							{
								l_part_heap.push(part);
							}
						}

						return true;
					}

					void returnBlockAccum(libmaus2::util::shared_ptr<BlockAccum> l_accum)
					{
						for ( auto block : l_accum->Vblock ) {
							std::size_t const streamid = block->streamid.load();
							bool const eof = block->eof.load();
							auto decompressHandler = block->decompressHandler.load();

							decompressHandler->returnBlock(streamid,block);

							if ( eof ) {
								std::cerr << "found parse eof on stream " << block->streamid.load() << std::endl;
								auto l_control = l_accum->control.load();
								l_control->notifyEOF(block->streamid.load());
							}
						}

						auto l_control = l_accum->control.load();

						assert ( l_accum );

						l_accum->Vblock.resize(0);
						l_accum->bytes.store(0);
						l_accum->alignments.store(0);

						putBackBlockAccum(l_accum);

						handle(l_control,0);
					}

					virtual void encodePartFinished(libmaus2::util::shared_ptr<EncodePart> r_ptr, bool const all_finished, libmaus2::util::shared_ptr<BlockAccum> l_accum)
					{
						auto l_control = l_accum->control.load();

						part_heap.push(r_ptr);

						if ( all_finished ) {
							std::cerr << "all finished for id " << l_accum->id.load() << std::endl;

							addEncodeFinishedHeap(l_accum->id.load());

							bool const eof = l_accum->getEOF();

							if ( eof )
							{
								std::lock_guard<std::mutex> slock(encode_finished_heap_eof_set_lock);
								encode_finished_heap_eof_set.store(true);
								encode_finished_heap_eof.store(l_accum->id.load());
							}

							returnBlockAccum(l_accum);
						}

						std::size_t block_id = 0;
						libmaus2::parallel::AtomicPtrHeap<EncodePart> l_part_heap;
						while ( getNextFinishedHeap(block_id,l_part_heap) )
						{
							libmaus2::util::shared_ptr<EncodePart> ptr;

							std::size_t const stream_id = 0;
							auto & data_str = getDataStream(stream_id);
							auto & index_str = getIndexStream(stream_id);

							// libmaus2::util::NumberSerialisation::serialiseNumber(index_str,);

							std::size_t l_f = 0;
							std::size_t l_c = 0;
							std::size_t n_pack = 0;
							std::size_t n_pack_expected = 0;

							for ( std::size_t pack_id = 0; l_part_heap.pop(ptr); ++pack_id )
							{
								assert ( ptr );
								assert ( ptr->i.load() == pack_id );

								// number of sub blocks
								if ( ptr->i.load() == 0 ) {
									libmaus2::util::NumberSerialisation::serialiseNumber(index_str,ptr->n.load());
									n_pack_expected = ptr->n.load();
								}

								assert ( ptr->super_block_id.load() == block_id );

								// position of compressed data
								libmaus2::util::NumberSerialisation::serialiseNumber(index_str,data_str.tellp());
								// compressed size
								libmaus2::util::NumberSerialisation::serialiseNumber(index_str,ptr->C_f.load());
								// uncompressed size
								libmaus2::util::NumberSerialisation::serialiseNumber(index_str,ptr->M_f.load());

								// get length of first record
								std::size_t const record_length = BSFI.getRecordLength(ptr->M.load().get());
								// write first record
								libmaus2::util::NumberSerialisation::serialiseNumber(index_str,record_length);
								index_str.write(ptr->M.load().get(),record_length);

								// store last record of last package
								if ( pack_id + 1 == ptr->n.load() )
								{
									char const * last_record = ptr->M_l.load();
									assert ( last_record );

									// get length of last record
									std::size_t const last_record_length = BSFI.getRecordLength(last_record);
									// write last record
									libmaus2::util::NumberSerialisation::serialiseNumber(index_str,last_record_length);
									index_str.write(last_record,last_record_length);
								}

								data_str.write(ptr->C.load().get(),ptr->C_f.load());

								l_f += ptr->M_f.load();
								l_c += ptr->C_f.load();
								n_pack += 1;
								part_stack.push(ptr);
							}

							assert ( n_pack == n_pack_expected );

							std::cerr << "l_f=" << l_f << " l_c=" << l_c << " block_id=" << block_id << " n_pack=" << n_pack << std::endl;

							std::lock_guard<std::mutex> slock(encode_finished_heap_eof_set_lock);
							if ( encode_finished_heap_eof_set.load() && encode_finished_heap_eof.load() == block_id )
							{
								std::cerr << "found encode eof" << std::endl;

								std::cerr << "calling terminate" << std::endl;

								l_control->TP.terminate();
							}

							{
							std::lock_guard<std::mutex> slock(encode_finished_heap_lock);
							encode_finished_heap_next++;
							}
						}
					}

					virtual void sortFinished(libmaus2::util::shared_ptr<BamSortInfo> ptr, libmaus2::util::shared_ptr<BlockAccum> l_accum)
					{
						std::cerr << "sort finished callback for id " << l_accum->id.load() << std::endl;

						libmaus2::util::AtomicArray<char const *> const & final = ptr->getFinal();
						std::size_t const n = ptr->n;

						std::size_t const encode_packs = (n + encode_pack_size - 1) / encode_pack_size;
						std::size_t const encode_pack_size = (n + encode_packs - 1) / encode_packs;
						std::size_t const encode_super_block = l_accum->id.load();

						libmaus2::util::shared_ptr< std::atomic<std::size_t> > f(new std::atomic< std::size_t>(0));

						auto l_control = l_accum->control.load();

						auto & TP = l_control->TP;

						for ( std::size_t i = 0; i < encode_packs; ++i ) {
							auto encode_block = getEncodeBlock();

							std::size_t const low = std::min(i * encode_pack_size,n);
							std::size_t const high = std::min(low + encode_pack_size,n);

							assert ( high > low );

							encode_block->setup(encode_super_block,i,encode_packs,f,ptr,l_accum,low,high);

							encode_block->enqueuePackage(TP);
						}

						#if 0
						auto const & comp = ptr->comp;

						// check sorting
						for ( std::size_t j = 1; j < n; ++j )
							assert ( ! comp(final[j-0],final[j-1]) );
						#endif

						#if 0
						std::size_t c_mergesort = 0;
						{
							std::size_t low = 0;

							while ( low < n )
							{
								std::size_t high = low;
								while ( high < n &&
									!comp(final[low],final[high]) )
									++high;

								c_mergesort += 1;

								low = high;
							}
						}

						std::cerr << "c_mergesort=" << c_mergesort << std::endl;
						#endif
					}

					std::vector < std::pair<std::size_t,std::size_t> > computeCopyStart(
						std::vector < std::pair<std::size_t,std::size_t> > const & V_baseblocksintv,
						libmaus2::util::shared_ptr<BlockAccum> const & l_accum
					) const
					{
						std::vector < std::pair<std::size_t,std::size_t> > V_copystart;

						std::size_t s = 0;
						std::size_t j = 0;
						for ( std::size_t i = 0; i < l_accum->Vblock.size(); ++i )
						{
							auto const & LP = l_accum->Vblock[i]->P;
							std::size_t const f = LP.f.load();


							while ( j < V_baseblocksintv.size() && V_baseblocksintv[j].first >= s && V_baseblocksintv[j].first < s+f )
							{
								V_copystart.push_back(
									std::make_pair(i,
										V_baseblocksintv[j].first-s
									)
								);
								++j;
							}

							s += f;
						}

						return V_copystart;
					}

					void handleBlocks(libmaus2::util::shared_ptr<BlockAccum> l_accum)
					{
						std::cerr << "handleBlocks " << l_accum->bytes.load() << " " << l_accum->alignments.load() << std::endl;

						auto l_control = l_accum->control.load();

						auto & TP = l_control->TP;
						std::size_t const threads = TP.threads;

						std::size_t const n = l_accum->alignments.load();

						libmaus2::util::AtomicArray<char const *> & P = l_accum->getP(n);
						libmaus2::util::AtomicArray<char const *> & Q = l_accum->getQ(n);

						std::vector < std::pair<std::size_t,std::size_t> > const V_baseblocksintv(BamSortInfo::getBaseBlockIntervals(n,threads));
						std::vector < std::pair<std::size_t,std::size_t> > const V_copystart(computeCopyStart(V_baseblocksintv,l_accum));

						auto comp(BSFI.getComparator());

						libmaus2::util::shared_ptr<BamSortInfo> BSI(BamSortInfo::allocate(TP,P,Q,n,threads,comp,this,l_accum,V_copystart));
						BSI->enquePackages();
					}

					static std::size_t getBlockDataSize(libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> block)
					{
						std::size_t const lnumel = block->P.f.load();

						if ( lnumel )
						{
							char const * first_p = block->P.p.load()[0];
							char const * last_p  = block->P.p.load()[lnumel-1];
							std::size_t const blocklength_last = libmaus2::parallel::threadpool::bgzf::BgzfHeaderDecoderBase::get32(last_p,0 /* offset */);
							std::size_t const data_size = (last_p-first_p)+blocklength_last+sizeof(uint32_t);
							return data_size;
						}
						else
						{
							return 0;
						}
					}


					virtual void handle(
						ThreadPoolBamParseControl * l_control,
						std::size_t const streamid
					)
					{
						if ( streamid > 0 ) {
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "ThreadPoolBamParseAccumulateHandler::handle: only one stream is supported" << std::endl;
							lme.finish();
							throw lme;
						}

						libmaus2::util::shared_ptr<BlockAccum> l_accum;

						while ( getBlockAccum(l_control,l_accum) )
						{
							bool l_need_process = false;

							assert ( l_accum.get() );

							auto & context = l_control->getContext(streamid);
							libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> block;

							while ( context.getOutputQueueElement(block) )
							{
								auto decompressHandler = block->decompressHandler.load();
								std::size_t const datablocksize = getBlockDataSize(block);
								std::size_t const decoderblocks = decompressHandler->getDecoderBlocks();

								if ( datablocksize > buffersize ) {
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "ThreadPoolBamParseAccumulateHandler::handle: cannot handle single block of size " << datablocksize << std::endl;
									lme.finish();
									throw lme;
								}

								if (
									l_accum->bytes.load() + datablocksize <= buffersize
								)
								{
									l_accum->bytes += datablocksize;
									l_accum->alignments += block->P.f.load();
									l_accum->Vblock.push_back(block);

									bool const eof = block->eof.load();

									if ( eof || l_accum->Vblock.size() >= ((decoderblocks + num_buffers - 1) / num_buffers) )
										l_need_process = true;

									context.bumpOutNext();
								}
								else
								{
									context.putBack(block);
									l_need_process = true;

									break;
								}
							}

							if ( ! l_need_process ) {
								block_accum_free.find(l_control)->second.load()->push(l_accum);
								break;
							}
							else
							{
								handleBlocks(l_accum);
								next_accum_process_id++;
							}
						}
					}
				};
			}
		}
	}
}
#endif
