/*
    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_BGZFINPUTTHREADCONTROL_HPP)
#define LIBMAUS2_PARALLEL_THREADPOOL_BGZF_BGZFINPUTTHREADCONTROL_HPP

#include <libmaus2/parallel/threadpool/input/InputThreadControl.hpp>
#include <libmaus2/parallel/threadpool/bgzf/BgzfBlockInfo.hpp>
#include <libmaus2/parallel/threadpool/bgzf/BgzfBlockInfoHandlerInterface.hpp>
#include <libmaus2/parallel/threadpool/bgzf/ReadInfo.hpp>

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

					libmaus2::avl::AtomicAVLPtrValueMap<std::size_t,ReadInfo> Qmap;
					std::mutex Qmaplock;

					libmaus2::parallel::AtomicPtrStack<BgzfBlockSummary> summaryStack;
					libmaus2::parallel::AtomicPtrStack<BgzfBlockInfo> infoStack;

					BgzfBlockInfoHandlerInterface * blockHandler;

					libmaus2::avl::AtomicAVLPtrValueMap<std::size_t,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);
						libmaus2::util::shared_ptr<std::atomic<uint64_t> > sptr(numBlocksFinished.find(id)->second.load());
						return *sptr;
					}

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

					// get info object
					libmaus2::util::shared_ptr<BgzfBlockInfo> getInfo()
					{
						libmaus2::util::shared_ptr<BgzfBlockInfo> ptr;

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

						libmaus2::util::shared_ptr<BgzfBlockInfo> tptr(new BgzfBlockInfo);

						return tptr;
					}

					/*
					 * called by handler to put back a single bgzf block
					 */
					bool putInfo(uint64_t const streamid, libmaus2::util::shared_ptr<BgzfBlockInfo> info)
					{
						libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::BgzfBlockSummary> summary = info->owner;
						libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::input::ThreadPoolInputBlock> iblock = summary->ptr;
						libmaus2::parallel::threadpool::input::ThreadPoolInputInfo * inputinfo = summary->inputinfo;

						// return info object
						infoStack.push(info);

						// check whether complete input block/summary has been processed
						bool const finished = ++(summary->finished) == summary->numblocks.load();
						if ( finished )
							putSummary(summary);

						// if input block is finished
						if ( finished )
						{
							ITC.returnBlock(iblock);

							// number of blocks finished for streamid
							uint64_t const lnumblocksfinished = ++(getNumBlocksFinished(streamid));

							// if reading has finished on streamid and we have parsed all blocks
							if ( readFinished(streamid) && lnumblocksfinished == inputinfo->blocksRead.load() )
							{
								// mark parsing on stream as finished
								VbgzfParseFinished[streamid]->store(1);

								// increment number of finished streams
								uint64_t const lnumparsefinished = ++numBgzfParseFinished;

								// mark complete parsing as finished if all streams are done
								if ( lnumparsefinished == ITC.numstreams )
									bgzfParseFinished.store(1);
							}
						}

						return finished;
					}

					libmaus2::util::shared_ptr<BgzfBlockSummary> getSummary()
					{
						libmaus2::util::shared_ptr<BgzfBlockSummary> ptr;

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

						libmaus2::util::shared_ptr<BgzfBlockSummary> tptr(new BgzfBlockSummary);

						return tptr;
					}

					void putSummary(libmaus2::util::shared_ptr<BgzfBlockSummary> ptr)
					{
						summaryStack.push(ptr);
					}

					/*
					 * get queue for stream streamid
					 */
					ReadInfo & getQ(uint64_t const streamid)
					{
						std::lock_guard<std::mutex> slock(Qmaplock);

						auto it = Qmap.find(streamid);
						if ( it == Qmap.end() )
						{
							libmaus2::util::shared_ptr<ReadInfo> ptr(new ReadInfo);
							Qmap.insert(streamid,ptr);
							it = Qmap.find(streamid);
						}

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

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

					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(), blockHandler(nullptr), numBlocksFinished(), VbgzfParseFinished(rVstr.size()), numBgzfParseFinished(0), bgzfParseFinished(0)

					{
						for ( uint64_t i = 0; i < rVstr.size(); ++i )
						{
							numBlocksFinished.insert(i,libmaus2::util::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(), blockHandler(nullptr), numBlocksFinished(), VbgzfParseFinished(Vfn.size()), numBgzfParseFinished(0), bgzfParseFinished(0)
					{
						for ( uint64_t i = 0; i < Vfn.size(); ++i )
						{
							numBlocksFinished.insert(i,libmaus2::util::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();
					}
				};

			}
		}
	}
}
#endif
