/*
    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_INPUT_THREADPOOLINPUTINFO_HPP)
#define LIBMAUS2_PARALLEL_THREADPOOL_INPUT_THREADPOOLINPUTINFO_HPP

#include <libmaus2/aio/InputStreamInstance.hpp>
#include <libmaus2/parallel/AtomicPtrStack.hpp>
#include <libmaus2/parallel/AtomicPtrQueue.hpp>
#include <libmaus2/timing/RealTimeClock.hpp>
#include <libmaus2/parallel/threadpool/input/ThreadPoolInputBlock.hpp>

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace input
			{
				struct InputThreadControl;

				/**
				 * input info for a single stream
				 **/
				struct ThreadPoolInputInfo
				{
					typedef ThreadPoolInputInfo this_type;
					typedef std::unique_ptr<this_type> unique_ptr_type;
					typedef std::shared_ptr<this_type> shared_ptr_type;

					InputThreadControl * const owner;
					// input stream pointer
					libmaus2::aio::InputStreamInstance::shared_ptr_type pISI;
					// input stream
					std::istream & in;
					libmaus2::parallel::AtomicPtrStack<ThreadPoolInputBlock> freeBlocks;
					libmaus2::parallel::AtomicPtrStack<ThreadPoolInputBlock> returnedBlocks;
					// stream id
					uint64_t const streamid;
					// total number of blocks
					uint64_t const numblocks;
					// minimum number of free blocks before reading starts
					uint64_t const blocksMinFree;
					std::atomic<uint64_t> blocksRead;
					std::atomic<uint64_t> blocksFree;
					std::atomic<bool> eof;
					std::mutex lock;
					libmaus2::parallel::AtomicPtrQueue<ThreadPoolInputBlock> outQueue;
					std::mutex outQueueReadLock;
					std::atomic<uint64_t> bytesRead;
					libmaus2::timing::RealTimeClock rtc;
					std::atomic<bool> rtcSet;

					void setup(uint64_t const numblocks, uint64_t const blocksize, uint64_t const putbacksize)
					{
						for ( uint64_t i = 0; i < numblocks; ++i )
						{
							libmaus2::util::shared_ptr<ThreadPoolInputBlock> ptr(new ThreadPoolInputBlock(blocksize,putbacksize));
							freeBlocks.push(ptr);
							++blocksFree;
						}
					}

					double getSpeed() const
					{
						double const sec = rtc.getElapsedSeconds();
						uint64_t const bytes = bytesRead.load();
						return bytes / sec;
					}

					static uint64_t getDiv()
					{
						return 8;
					}

					ThreadPoolInputInfo(
						InputThreadControl * const rowner,
						std::istream & rin, uint64_t const rstreamid, uint64_t const rnumblocks, uint64_t const blocksize, uint64_t const putbacksize)
					: owner(rowner), pISI(), in(rin), freeBlocks(), returnedBlocks(),  streamid(rstreamid), numblocks(rnumblocks), blocksMinFree(std::max(static_cast<uint64_t>(1),numblocks/getDiv())), blocksRead(0), blocksFree(0), eof(false), lock(), outQueue(), outQueueReadLock(), bytesRead(0), rtc(), rtcSet(false)
					{
						setup(numblocks,blocksize,putbacksize);
					}

					ThreadPoolInputInfo(
						InputThreadControl * const rowner,
						std::string const & rfn, uint64_t const rstreamid, uint64_t const rnumblocks, uint64_t const blocksize, uint64_t const putbacksize)
					: owner(rowner), pISI(new libmaus2::aio::InputStreamInstance(rfn)), in(*pISI), freeBlocks(), returnedBlocks(),  streamid(rstreamid), numblocks(rnumblocks), blocksMinFree(std::max(static_cast<uint64_t>(1),numblocks/getDiv())), blocksRead(0), blocksFree(0), eof(false), lock(), outQueue(), outQueueReadLock(), bytesRead(0), rtc(), rtcSet(false)
					{
						setup(numblocks,blocksize,putbacksize);
					}

					bool getBlocks(std::vector< libmaus2::util::shared_ptr<ThreadPoolInputBlock> > & V)
					{
						std::lock_guard<std::mutex> slock(outQueueReadLock);
						libmaus2::util::shared_ptr<ThreadPoolInputBlock> block;
						bool eof = false;
						while ( outQueue.pop(block) )
						{
							assert ( block );
							eof = eof || block->eof;
							V.push_back(block);
						}

						return eof;
					}

				};
			}
		}
	}
}
#endif
