/*
    libmaus2
    Copyright (C) 2021 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_BAMPARSEOUTPUTQUEUE_HPP)
#define LIBMAUS2_PARALLEL_THREADPOOL_BAM_BAMPARSEOUTPUTQUEUE_HPP

#include <libmaus2/parallel/threadpool/bgzf/GenericBlock.hpp>
#include <libmaus2/parallel/AtomicPtrHeap.hpp>

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bam
			{
				struct BamParseOutputQueue
				{
					libmaus2::parallel::AtomicPtrHeap<libmaus2::parallel::threadpool::bgzf::GenericBlock> outQ;
					std::mutex outQlock;
					std::atomic<std::size_t> outNext;
					std::atomic<bool> pushedEOF;

					BamParseOutputQueue()
					: outQ(), outQlock(), outNext(0), pushedEOF(false)
					{
					}

					void bumpOutNext(std::size_t const c = 1)
					{
						std::lock_guard<decltype(outQlock)> slock(outQlock);
						outNext += c;
					}

					std::size_t getOutputQueueElements(
						libmaus2::util::shared_ptr<libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock>[] > & Vblock,
						std::size_t const n
					)
					{
						std::lock_guard<decltype(outQlock)> slock(outQlock);

						if (
							(outQ.size() >= n)
							||
							pushedEOF
						)
						{
							std::size_t i = 0;

							Vblock = libmaus2::util::shared_ptr<libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock>[] >(
								new libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock>[n]
							);

							while ( i < n )
							{
								libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> ptr;

								bool const ok = outQ.pop(ptr);

								if ( ! ok )
									break;

								if ( ptr->absid.load() != outNext.load() + i )
								{
									outQ.push(ptr);
									break;
								}

								Vblock[i++] = ptr;
							}

							if ( ! i )
								return 0;

							if ( i == n || Vblock[i-1]->eof )
								return i;

							// push blocks back into queue as we do not have all blocks required
							// in order
							for ( std::size_t j = 0; j < i; ++j )
								outQ.push(Vblock[i-j-1]);
						}

						return 0;
					}

					void putBack(libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> block)
					{
						std::lock_guard<decltype(outQlock)> slock(outQlock);
						std::cerr << "put back " << block->absid.load() << " next " << outNext.load() << std::endl;
						outQ.push(block);
					}

					bool getOutputQueueElement(libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> & block)
					{
						std::lock_guard<decltype(outQlock)> slock(outQlock);

						libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> ptr;

						bool const ok = outQ.pop(ptr);

						if ( ! ok )
							return false;

						if ( ptr->absid.load() != outNext.load() )
						{
							outQ.push(ptr);
							return false;
						}

						block = ptr;
						return true;
					}

					void putOutputQueue(libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::bgzf::GenericBlock> block)
					{
						std::lock_guard<decltype(outQlock)> slock(outQlock);
						outQ.push(block);
						if ( block->eof )
							pushedEOF.store(true);
					}
				};
			}
		}
	}
}
#endif
