/*
    libmaus2
    Copyright (C) 2020 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/>.
*/
#if ! defined(LIBMAUS2_PARALLEL_THREADPOOL_BGZF_BGZFINPUTTHREAD_HPP)
#define LIBMAUS2_PARALLEL_THREADPOOL_BGZF_BGZFINPUTTHREAD_HPP

#include <libmaus2/parallel/threadpool/input/ThreadPoolInput.hpp>
#include <libmaus2/aio/StreamLock.hpp>
#include <set>

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bgzf
			{
				struct BgzfBlockSummary
				{
					typedef BgzfBlockSummary this_type;
					typedef std::shared_ptr<this_type> shared_ptr_type;

					libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type ptr;
					libmaus2::parallel::threadpool::input::ThreadPoolInputInfo * inputinfo;
					std::atomic<uint64_t> numblocks;
					std::atomic<uint64_t> finished;

					void reset(libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type rptr, libmaus2::parallel::threadpool::input::ThreadPoolInputInfo * rinputinfo)
					{
						ptr = rptr;
						inputinfo = rinputinfo;
						numblocks.store(0);
						finished.store(0);
					}

					bool operator<(BgzfBlockSummary const & O) const
					{
						return ptr->blockid < O.ptr->blockid;
					}
				};

				struct BgzfBlockInfo
				{
					typedef BgzfBlockInfo this_type;
					typedef std::shared_ptr<this_type> shared_ptr_type;

					BgzfBlockSummary::shared_ptr_type owner;
					std::atomic<uint64_t> subid;
					std::atomic<uint64_t> absid;
					char * from;
					char * to;

					void reset(BgzfBlockSummary::shared_ptr_type rowner, uint64_t const rsubid, uint64_t const rabsid, char * rfrom, char * rto)
					{
						owner = rowner;
						subid.store(rsubid);
						absid.store(rabsid);
						from = rfrom;
						to = rto;
					}

					bool operator<(BgzfBlockInfo const & O) const
					{
						assert ( owner );
						assert ( O.owner );

						if ( *owner < *(O.owner) )
							return true;
						else if ( *(O.owner) < *owner )
							return false;
						else
							return absid < O.absid;
					}
				};

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


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

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

				struct BgzfBlockInfoHandlerInterface
				{
					virtual ~BgzfBlockInfoHandlerInterface() {}
					virtual void registerBgzfBlock(uint64_t const streamid, uint64_t const blockid, uint64_t const numblocks) = 0;
					virtual void handleBgzfBlockInfo(BgzfBlockInfo::shared_ptr_type ptr) = 0;
				};

				struct BgzfInputThreadControl
				{
					libmaus2::parallel::threadpool::ThreadPool & TP;
					libmaus2::parallel::threadpool::input::InputThreadControl ITC;

					struct BlockIdComparator
					{
						bool operator()(libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type const & A, libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type const & B)
						{
							return A->blockid > B->blockid;
						}
					};

					std::map<
						uint64_t,
						ThreadPoolPriorityQueue<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type,BlockIdComparator>::shared_ptr_type
					>
						Qmap;
					std::mutex Qmaplock;

					std::map<uint64_t,std::shared_ptr<std::atomic<uint64_t> > > Amap;
					std::map<uint64_t,std::shared_ptr<std::atomic<uint64_t> > > ABSmap;
					std::map<uint64_t,std::shared_ptr<std::mutex> > Mmap;
					std::map<uint64_t,std::shared_ptr<std::string> > Smap;

					libmaus2::parallel::threadpool::ThreadPoolStack<BgzfBlockSummary::shared_ptr_type> summaryStack;
					libmaus2::parallel::threadpool::ThreadPoolStack<BgzfBlockInfo::shared_ptr_type> infoStack;

					struct SummarySet
					{
						typedef SummarySet this_type;
						typedef std::shared_ptr<this_type> shared_ptr_type;

						std::set<BgzfBlockSummary::shared_ptr_type,BgzfBlockSummaryComparator> S;
						std::mutex lock;

						SummarySet() : S(), lock() {}
					};

					struct InfoSet
					{
						typedef InfoSet this_type;
						typedef std::shared_ptr<this_type> shared_ptr_type;

						std::set<BgzfBlockInfo::shared_ptr_type,BgzfBlockInfoComparator> S;
						std::mutex lock;

						InfoSet() : S(), lock() {}
					};

					std::map<uint64_t,SummarySet::shared_ptr_type> summarySet;
					std::mutex summarySetLock;

					std::map<uint64_t,InfoSet::shared_ptr_type> infoSet;
					std::mutex infoSetLock;

					BgzfBlockInfoHandlerInterface * blockHandler;

					std::map < uint64_t, std::shared_ptr<std::atomic<uint64_t> > > numBlocksFinished;
					std::mutex numBlocksFinishedLock;

					std::vector < std::shared_ptr<std::atomic<int> > > VbgzfParseFinished;
					std::atomic<uint64_t> numBgzfParseFinished;
					std::atomic<int> bgzfParseFinished;

					bool parseFinished(uint64_t const streamid)
					{
						return VbgzfParseFinished[streamid]->load();
					}

					bool parseFinished()
					{
						return bgzfParseFinished.load();
					}

					std::atomic<uint64_t> & getNumBlocksFinished(uint64_t const id)
					{
						std::lock_guard<std::mutex> slock(numBlocksFinishedLock);
						std::shared_ptr<std::atomic<uint64_t> > sptr(numBlocksFinished.find(id)->second);
						return *sptr;
					}

					SummarySet & getSummarySet(uint64_t const streamid)
					{
						std::lock_guard<std::mutex> slock(summarySetLock);

						if ( summarySet.find(streamid) == summarySet.end() )
							summarySet[streamid] =
								SummarySet::shared_ptr_type(new SummarySet);

						return *(summarySet.find(streamid)->second);
					}

					InfoSet & getInfoSet(uint64_t const streamid)
					{
						std::lock_guard<std::mutex> slock(infoSetLock);

						if ( infoSet.find(streamid) == infoSet.end() )
							infoSet[streamid] =
								InfoSet::shared_ptr_type(new InfoSet);

						return *(infoSet.find(streamid)->second);
					}

					bool isIdle()
					{
						bool idle = true;

						{
						std::lock_guard<std::mutex> slock(summarySetLock);
						for ( std::map<uint64_t,SummarySet::shared_ptr_type>::const_iterator it =
							summarySet.begin(); it != summarySet.end(); ++it )
							{
								std::lock_guard<std::mutex> llock(it->second->lock);
								idle = idle && it->second->S.empty();
							}
						}

						{
						std::lock_guard<std::mutex> slock(infoSetLock);
						for ( std::map<uint64_t,InfoSet::shared_ptr_type>::const_iterator it =
							infoSet.begin(); it != infoSet.end(); ++it )
							{
								std::lock_guard<std::mutex> llock(it->second->lock);
								idle = idle && it->second->S.empty();
							}
						}

						return idle;
					}

					void setBlockHandler(BgzfBlockInfoHandlerInterface * rblockHandler)
					{
						blockHandler = rblockHandler;
					}

					BgzfBlockInfo::shared_ptr_type getInfo()
					{
						BgzfBlockInfo::shared_ptr_type ptr;

						if ( infoStack.pop(ptr) )
							return ptr;

						BgzfBlockInfo::shared_ptr_type tptr(new BgzfBlockInfo);

						return tptr;
					}

					bool putInfo(uint64_t const streamid, BgzfBlockInfo::shared_ptr_type ptr)
					{
						BgzfBlockSummary::shared_ptr_type summary = ptr->owner;
						libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type iblock = summary->ptr;
						libmaus2::parallel::threadpool::input::ThreadPoolInputInfo * inputinfo = summary->inputinfo;

						unregisterInfo(streamid,ptr);

						BgzfBlockSummary::shared_ptr_type info = ptr->owner;
						infoStack.push(ptr);
						bool const finished = ++(info->finished) == info->numblocks.load();
						if ( finished )
						{
							unregisterSummary(streamid,info);
							putSummary(info);
						}

						if ( finished )
						{
							ITC.returnBlock(inputinfo,iblock);

							uint64_t const lnumblocksfinished = ++(getNumBlocksFinished(streamid));

							if ( readFinished(streamid) && lnumblocksfinished == inputinfo->blocksRead.load() )
							{
								VbgzfParseFinished[streamid]->store(1);

								uint64_t const lnumparsefinished = ++numBgzfParseFinished;

								if ( lnumparsefinished == ITC.numstreams )
								{
									bgzfParseFinished.store(1);
								}
							}
						}

						return finished;
					}

					BgzfBlockSummary::shared_ptr_type getSummary()
					{
						BgzfBlockSummary::shared_ptr_type ptr;

						if ( summaryStack.pop(ptr) )
							return ptr;

						BgzfBlockSummary::shared_ptr_type tptr(new BgzfBlockSummary);

						return tptr;
					}

					void putSummary(BgzfBlockSummary::shared_ptr_type ptr)
					{
						summaryStack.push(ptr);
					}

					void registerSummary(uint64_t const streamid, BgzfBlockSummary::shared_ptr_type ptr)
					{
						SummarySet & summarySet = getSummarySet(streamid);
						std::lock_guard<std::mutex> slock(summarySet.lock);
						summarySet.S.insert(ptr);
					}

					void unregisterSummary(uint64_t const streamid, BgzfBlockSummary::shared_ptr_type ptr)
					{
						SummarySet & summarySet = getSummarySet(streamid);
						std::lock_guard<std::mutex> slock(summarySet.lock);
						summarySet.S.erase(ptr);
					}

					void registerInfo(uint64_t const streamid, BgzfBlockInfo::shared_ptr_type ptr)
					{
						InfoSet & infoSet = getInfoSet(streamid);
						std::lock_guard<std::mutex> slock(infoSet.lock);
						infoSet.S.insert(ptr);
					}

					void unregisterInfo(uint64_t const streamid, BgzfBlockInfo::shared_ptr_type ptr)
					{
						InfoSet & infoSet = getInfoSet(streamid);
						std::lock_guard<std::mutex> slock(infoSet.lock);
						infoSet.S.erase(ptr);
					}

					struct ReadInfo
					{
						ThreadPoolPriorityQueue<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type,BlockIdComparator>::shared_ptr_type Q;
						std::shared_ptr< std::atomic<uint64_t> > next;
						std::shared_ptr< std::atomic<uint64_t> > absid;
						std::shared_ptr<std::mutex > lock;
						std::shared_ptr<std::string > putbackdata;

						ReadInfo()
						{}
						ReadInfo(
							ThreadPoolPriorityQueue<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type,BlockIdComparator>::shared_ptr_type rQ,
							std::shared_ptr< std::atomic<uint64_t> > rnext,
							std::shared_ptr< std::atomic<uint64_t> > rabsid,
							std::shared_ptr<std::mutex> rlock,
							std::shared_ptr<std::string> rputbackdata
						) : Q(rQ), next(rnext), absid(rabsid), lock(rlock), putbackdata(rputbackdata)
						{

						}

						ReadInfo const & operator=(ReadInfo const & O)
						{
							Q = O.Q;
							next = O.next;
							absid = O.absid;
							lock = O.lock;
							putbackdata = O.putbackdata;
							return *this;
						}
					};

					ReadInfo getQ(uint64_t const streamid)
					{
						std::lock_guard<std::mutex> slock(Qmaplock);

						std::map<
							uint64_t,
							ThreadPoolPriorityQueue<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type,BlockIdComparator>::shared_ptr_type
						>::iterator it = Qmap.find(
							streamid
						);

						if ( it == Qmap.end() )
						{
							ThreadPoolPriorityQueue<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type,BlockIdComparator>::shared_ptr_type ptr(
								new ThreadPoolPriorityQueue<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type,BlockIdComparator>
							);
							Qmap[streamid] = ptr;
							Amap[streamid] = std::shared_ptr<std::atomic<uint64_t> >(new std::atomic<uint64_t>(0));
							ABSmap[streamid] = std::shared_ptr<std::atomic<uint64_t> >(new std::atomic<uint64_t>(0));
							Mmap[streamid] = std::shared_ptr<std::mutex>(new std::mutex);
							Smap[streamid] = std::shared_ptr<std::string>(new std::string);
						}

						it = Qmap.find(streamid);
						std::map<uint64_t,std::shared_ptr<std::atomic<uint64_t> > >::iterator absit =
							ABSmap.find(streamid);
						std::map<uint64_t,std::shared_ptr<std::atomic<uint64_t> > >::iterator ait =
							Amap.find(streamid);
						std::map<uint64_t,std::shared_ptr<std::mutex> >::iterator mit =
							Mmap.find(streamid);
						std::map<uint64_t,std::shared_ptr<std::string> >::iterator sit =
							Smap.find(streamid);

						assert ( it != Qmap.end() );
						assert ( ait != Amap.end() );
						assert ( absit != ABSmap.end() );
						assert ( mit != Mmap.end() );
						assert ( sit != Smap.end() );

						return ReadInfo(it->second,ait->second,absit->second,mit->second,sit->second);
					}

					bool getQNext(
						libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type & blockptr,
						ReadInfo & RI
					)
					{
						std::lock_guard<std::mutex> slock(*(RI.lock));

						if ( RI.Q->pop(blockptr) )
						{
							if ( blockptr->blockid == RI.next->load() )
								return true;
							else
								RI.Q->push(blockptr);
						}

						return false;
					}

					BgzfInputThreadControl(
						libmaus2::parallel::threadpool::ThreadPool & rTP,
						std::vector< std::shared_ptr<std::istream> > rVstr,
						uint64_t const numblocks,
						uint64_t const blocksize,
						uint64_t const putbacksize,
						uint64_t const baseprio,
						uint64_t const modprio
					)
					: TP(rTP), ITC(TP,rVstr,numblocks,blocksize,putbacksize,baseprio,modprio), Qmap(), Qmaplock(), summarySet(), blockHandler(nullptr), numBlocksFinished(), VbgzfParseFinished(rVstr.size()), numBgzfParseFinished(0), bgzfParseFinished(0)

					{
						for ( uint64_t i = 0; i < rVstr.size(); ++i )
						{
							numBlocksFinished[i] = std::shared_ptr<std::atomic<uint64_t> >(
								new std::atomic<uint64_t>(0)
							);
							VbgzfParseFinished[i] = std::shared_ptr<std::atomic<int> >(
								new std::atomic<int>(0)
							);
						}
					}

					BgzfInputThreadControl(
						libmaus2::parallel::threadpool::ThreadPool & rTP,
						std::vector<std::string> Vfn,
						uint64_t const numblocks,
						uint64_t const blocksize,
						uint64_t const putbacksize,
						uint64_t const baseprio,
						uint64_t const modprio
					)
					: TP(rTP), ITC(TP,Vfn,numblocks,blocksize,putbacksize,baseprio,modprio), Qmap(), Qmaplock(), summarySet(), blockHandler(nullptr), numBlocksFinished(), VbgzfParseFinished(Vfn.size()), numBgzfParseFinished(0), bgzfParseFinished(0)
					{
						for ( uint64_t i = 0; i < Vfn.size(); ++i )
						{
							numBlocksFinished[i] = std::shared_ptr<std::atomic<uint64_t> >(
								new std::atomic<uint64_t>(0)
							);
							VbgzfParseFinished[i] = std::shared_ptr<std::atomic<int> >(
								new std::atomic<int>(0)
							);
						}
					}

					bool readFinished()
					{
						return ITC.readFinished();
					}

					bool readFinished(uint64_t const streamid)
					{
						return ITC.readFinished(streamid);
					}

					void setReadCallback(
						std::vector<libmaus2::parallel::threadpool::input::InputThreadCallbackInterface *> rreadCallback)
					{
						ITC.setCallback(rreadCallback);
					}

					void start()
					{
						ITC.start();
					}
				};

			}
		}
	}
}

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bgzf
			{
				struct BgzfHeaderDecoderBase
				{

					static uint32_t get8(char const * p, std::size_t const offset)
					{
						unsigned char const * u = reinterpret_cast<unsigned char const *>(p);
						return static_cast<uint8_t>(u[offset]);
					}

					static uint32_t get16(char const * p, std::size_t const offset)
					{
						return get8(p,offset) | (get8(p,offset+1)<<8);
					}

					static uint32_t get32(char const * p, std::size_t const offset)
					{
						return get16(p,offset) | (get16(p,offset+2)<<16);
					}

					// decode 32 bit 2s complement encoded signed number
					static int32_t geti32(char const * p, std::size_t const offset)
					{
						uint32_t const u = get32(p,offset);

						if ( u & (0x8000000ul) )
						{
							return (-(1l << 31)) + static_cast<int32_t>(u & 0x7FFFFFFFl);
						}
						else
						{
							return static_cast<int32_t>(u);
						}
					}

					static ::std::size_t getMaxBlockSize()
					{
						return 64*1024;
					}

					static void put8(char * p, std::size_t const offset, uint8_t const v)
					{
						unsigned char * u = reinterpret_cast<unsigned char *>(p);
						u[offset] = static_cast<unsigned char>(v);
					}

					static void put16(char * p, std::size_t const offset, uint16_t const v)
					{
						put8(p,offset+0,((v>>0)&0xFFu));
						put8(p,offset+1,((v>>8)&0xFFu));
					}

					static void put32(char * p, std::size_t const offset, uint32_t const v)
					{
						put8(p,offset+0,((v>> 0)&0xFFFFu));
						put8(p,offset+2,((v>>16)&0xFFFFu));
					}
				};
				struct BgzfHeaderDecoder : public BgzfHeaderDecoderBase
				{
					static bool checkId(char const * p, std::size_t const offset)
					{
						return get8(p,offset) == 31 && get8(p,offset+1) == 139;
					}

					static bool checkCM(char const * p, std::size_t const offset)
					{
						return get8(p,offset+2) == 8;
					}

					static bool checkFlag(char const * p, std::size_t const offset)
					{
						return get8(p,offset+3) == 4;
					}

					static bool checkFront(char const * p, std::size_t const offset)
					{
						return
							checkId(p,offset)
							&&
							checkCM(p,offset)
							&&
							checkFlag(p,offset);
					}

					static uint32_t getMTIME(char const * p, std::size_t const offset)
					{
						return get32(p,offset+4);
					}

					static uint32_t getXFL(char const * p, std::size_t const offset)
					{
						return get8(p,offset+8);
					}

					static uint32_t getOS(char const * p, std::size_t const offset)
					{
						return get8(p,offset+9);
					}

					static uint32_t getXLEN(char const * p, std::size_t const offset)
					{
						return get16(p,offset+10);
					}

					static void printFront(char const * p, std::size_t const offset, std::ostream & out)
					{
						out << "[";
						for ( uint64_t i = 0; i < getCheckLength(); ++i )
							out << static_cast<int>(get8(p,offset+i)) << ";";
						out << "]";
					}

					static uint32_t getCheckLength()
					{
						return 12;
					}

					static uint32_t getHeaderLength(char const * p, std::size_t const offset)
					{
						return 12 + getXLEN(p,offset);
					}

					static uint32_t getBlockSize(char const * p, std::size_t const offset)
					{
						std::size_t xlen = getXLEN(p,offset);

						p += offset;
						p += 12;

						while ( xlen )
						{
							if ( xlen <= 4 )
							{
								throw libmaus2::exception::LibMausException("BgzfHeaderDecoder::getBlockSize: malformed gzip header");
							}
							else
							{
								uint8_t const SI1 = get8(p,0);
								uint8_t const SI2 = get8(p,1);
								std::size_t const slen = get16(p,2);

								xlen -= 4;
								p += 4;

								if ( slen > xlen )
								{
									throw libmaus2::exception::LibMausException("BgzfHeaderDecoder::getBlockSize: malformed gzip header");
								}

								if ( SI1 == 66 && SI2 == 67 )
								{
									if ( slen == 2 )
									{
										return get16(p,0)+1;
									}
									else
									{
										throw libmaus2::exception::LibMausException("BgzfHeaderDecoder::getBlockSize: malformed gzip header");
									}
								}
								else
								{
									p += slen;
									xlen -= slen;
								}
							}
						}

						throw libmaus2::exception::LibMausException("BgzfHeaderDecoder::getBlockSize: malformed gzip header");
					}
				};

				struct BgzfInputReadInterface : public libmaus2::parallel::threadpool::input::InputThreadCallbackInterface
				{
					libmaus2::parallel::threadpool::ThreadPool & TP;
					BgzfInputThreadControl & BITC;

					BgzfInputReadInterface(
						libmaus2::parallel::threadpool::ThreadPool & rTP,
						BgzfInputThreadControl & rBITC
					)
					: TP(rTP), BITC(rBITC)
					{
					}

					virtual void inputThreadCallbackInterfaceBlockRead(libmaus2::parallel::threadpool::input::ThreadPoolInputInfo * inputinfo)
					{
						std::vector<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type> V;
						/* bool const eof = */ BITC.ITC.getBlocks(V,inputinfo);

						BgzfInputThreadControl::ReadInfo RI = BITC.getQ(inputinfo->streamid);

						for ( uint64_t i = 0; i < V.size(); ++i )
							RI.Q->push(V[i]);

						libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type blockptr;

						while ( BITC.getQNext(blockptr,RI) )
						{
							while ( blockptr->putback < RI.putbackdata->size() )
								blockptr->bumpPutBack();
							assert ( blockptr->putback >= RI.putbackdata->size() );

							blockptr->ca -= RI.putbackdata->size();
							std::copy(RI.putbackdata->begin(),RI.putbackdata->end(),blockptr->ca.load());

							char * cc = blockptr->ca;
							char * ce = blockptr->ce;

							std::vector<BgzfBlockInfo::shared_ptr_type> VblockInfo;
							BgzfBlockSummary::shared_ptr_type psum(BITC.getSummary());
							psum->reset(blockptr,inputinfo);
							uint64_t subblockid = 0;

							while ( ce-cc >= BgzfHeaderDecoder::getCheckLength() )
							{
								bool const frontok = BgzfHeaderDecoder::checkFront(cc,0);

								if ( ! frontok )
									throw libmaus2::exception::LibMausException("[E] BgzfInputReadInterface: invalid bgzf header");

								std::ptrdiff_t const headerlen = BgzfHeaderDecoder::getHeaderLength(cc,0);

								if ( ce-cc >= headerlen )
								{
									std::ptrdiff_t const bs = BgzfHeaderDecoder::getBlockSize(cc,0);

									if ( ce-cc >= bs )
									{
										//std::cerr << "here " << frontok << " " << headerlen << " bs " << bs << std::endl;

										BgzfBlockInfo::shared_ptr_type pinfo = BITC.getInfo();
										pinfo->reset(psum,subblockid++,(*(RI.absid))++,cc,cc+bs);
										BITC.registerInfo(inputinfo->streamid,pinfo);
										VblockInfo.push_back(pinfo);

										psum->numblocks++;
										cc += bs;
									}
									else
									{
										break;
									}
								}
								else
								{
									break;
								}
							}

							*(RI.putbackdata) = std::string(cc,ce);

							BITC.registerSummary(inputinfo->streamid,psum);

							BITC.blockHandler->registerBgzfBlock(inputinfo->streamid,blockptr->blockid,VblockInfo.size());
							for ( uint64_t i = 0; i < VblockInfo.size(); ++i )
								BITC.blockHandler->handleBgzfBlockInfo(VblockInfo[i]);

							// std::cerr << "here" << std::endl;

							++(*(RI.next));
						}

						uint64_t batchsize = V.size();

						{
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(
								libmaus2::aio::StreamLock::cerrlock
							);
							std::cerr << "[V] got batch of size " << batchsize << " for stream " << inputinfo->streamid
								<< " bytes read " << inputinfo->bytesRead.load()
								<< " speed " << inputinfo->getSpeed()/(1024.0*1024.0)
								<< std::endl;
						}
					}
				};
			}
		}
	}
}

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bgzf
			{
				struct ThreadPoolBGZFReadControl
				{
					libmaus2::parallel::threadpool::ThreadPool & TP;
					libmaus2::parallel::threadpool::bgzf::BgzfInputThreadControl BITC;
					libmaus2::parallel::threadpool::bgzf::BgzfInputReadInterface ITTRI;

					ThreadPoolBGZFReadControl(
						libmaus2::parallel::threadpool::ThreadPool & rTP,
						std::vector<std::string> const & rVfn,
						uint64_t const numblocks = 256,
						uint64_t const blocksize = 256*1024,
						uint64_t const putbacksize = 1024,
						uint64_t const baseprio = 64*1024,
						uint64_t const modprio = 256
					) : TP(rTP), BITC(TP,rVfn,numblocks,blocksize,putbacksize,baseprio,modprio), ITTRI(TP,BITC)
					{
						BITC.setReadCallback(std::vector<libmaus2::parallel::threadpool::input::InputThreadCallbackInterface *>(rVfn.size(),&ITTRI));
					}

					ThreadPoolBGZFReadControl(
						libmaus2::parallel::threadpool::ThreadPool & rTP,
						std::vector< std::shared_ptr<std::istream> > rVstr,
						uint64_t const numblocks = 64,
						uint64_t const blocksize = 256*1024,
						uint64_t const putbacksize = 1024,
						uint64_t const baseprio = 64*1024,
						uint64_t const modprio = 256
					) : TP(rTP), BITC(TP,rVstr,numblocks,blocksize,putbacksize,baseprio,modprio), ITTRI(TP,BITC)
					{
						BITC.setReadCallback(std::vector<libmaus2::parallel::threadpool::input::InputThreadCallbackInterface *>(rVstr.size(),&ITTRI));
					}

					void setBlockHandler(BgzfBlockInfoHandlerInterface * handler)
					{
						BITC.setBlockHandler(handler);
					}

					void start()
					{
						BITC.start();
					}

					bool isIdle()
					{
						return BITC.isIdle();
					}
				};
			}
		}
	}
}


namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bgzf
			{
				struct BgzfBlockInfoTrivialReturnHandler : BgzfBlockInfoHandlerInterface
				{
					ThreadPool & TP;
					BgzfInputThreadControl & BITC;

					BgzfBlockInfoTrivialReturnHandler(
						ThreadPool & rTP,
						BgzfInputThreadControl & rBITC
					)
					: TP(rTP), BITC(rBITC)
					{

					}

					virtual void registerBgzfBlock(uint64_t const, uint64_t const, uint64_t const)
					{

					}

					virtual void handleBgzfBlockInfo(BgzfBlockInfo::shared_ptr_type ptr)
					{
						BgzfBlockSummary::shared_ptr_type summary = ptr->owner;
						libmaus2::parallel::threadpool::input::ThreadPoolInputBlock::shared_ptr_type iblock = summary->ptr;
						libmaus2::parallel::threadpool::input::ThreadPoolInputInfo * inputinfo = summary->inputinfo;

						uint64_t const streamid = inputinfo->streamid;
						/* bool const blockfinished = */
							BITC.putInfo(streamid,ptr);

						if ( BITC.parseFinished() )
						{
							std::cerr << "[V] terminating" << std::endl;
							TP.terminate();
						}
					}
				};

			}
		}
	}
}
#endif
