/*
    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_BGZFINPUTREADINTERFACE_HPP)
#define LIBMAUS2_PARALLEL_THREADPOOL_BGZF_BGZFINPUTREADINTERFACE_HPP

#include <libmaus2/parallel/threadpool/bgzf/BgzfInputThreadControl.hpp>
#include <libmaus2/parallel/threadpool/bgzf/BgzfHeaderDecoder.hpp>

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bgzf
			{
				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::util::shared_ptr<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock> > V;
						/* bool const eof = */ inputinfo->getBlocks(V);

						/* get queue from stream id */
						ReadInfo & RI = BITC.getQ(inputinfo->streamid);

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

						libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock> blockptr;

						while ( RI.getQNext(blockptr) )
						{
							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< libmaus2::util::shared_ptr<BgzfBlockInfo> > VblockInfo;
							// get block summary object
							libmaus2::util::shared_ptr<BgzfBlockSummary> psum(BITC.getSummary());
							psum->reset(blockptr,inputinfo);
							// block id inside data block
							uint64_t subblockid = 0;

							/*
							 * while there is enough data to get length of a bgzf header
							 */
							while ( ce-cc >= static_cast<std::ptrdiff_t>(BgzfHeaderDecoder::getCheckLength()) )
							{
								// check header
								bool const frontok = BgzfHeaderDecoder::checkFront(cc,0);

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

								// get length of header
								std::ptrdiff_t const headerlen = BgzfHeaderDecoder::getHeaderLength(cc,0);

								// if we have enough data to read the header
								if ( ce-cc >= headerlen )
								{
									// get block size
									std::ptrdiff_t const bs = BgzfHeaderDecoder::getBlockSize(cc,0);

									// if we have the complete block
									if ( ce-cc >= bs )
									{
										// get block info object
										libmaus2::util::shared_ptr<BgzfBlockInfo> pinfo = BITC.getInfo();
										// set data in block info object
										pinfo->reset(psum,subblockid++,((RI.absid))++,cc,cc+bs,blockptr->eof && (cc+bs == ce));

										VblockInfo.push_back(pinfo);

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

							// check whether we have a truncated block
							if ( blockptr->eof && cc != ce )
							{
								libmaus2::exception::LibMausException lme;
								lme.getStream() << "BgzfInputReadInterface: found EOF inside BGZF block" << std::endl;
								lme.finish();
								throw lme;
							}

							// store unused data for next processing loop
							RI.putbackdata = std::string(cc,ce);

							// pass to downstream processing
							for ( uint64_t i = 0; i < VblockInfo.size(); ++i )
								BITC.blockHandler->handleBgzfBlockInfo(VblockInfo[i]);

							++RI.next;
						}

						#if 0
						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;
						}
						#endif
					}
				};
			}
		}
	}
}
#endif
