/**
    libmaus2
    Copyright (C) 2009-2016 German Tischler
    Copyright (C) 2011-2014 Genome Research Limited

    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_SUFFIXSORT_BWTB3M_BASEBLOCKSORTTHREAD_HPP)
#define LIBMAUS2_SUFFIXSORT_BWTB3M_BASEBLOCKSORTTHREAD_HPP

#include <libmaus2/parallel/StdThread.hpp>
#include <libmaus2/parallel/StdSemaphore.hpp>
#include <libmaus2/suffixsort/bwtb3m/MergeStrategyBaseBlock.hpp>
#include <libmaus2/suffixsort/bwtb3m/BWTB3MBase.hpp>
#include <libmaus2/suffixsort/bwtb3m/MergeTree.hpp>

namespace libmaus2
{
	namespace suffixsort
	{
		namespace bwtb3m
		{
			/**
			 * sorting thread for base blocks
			 **/
			struct BaseBlockSortThreadCallable : public libmaus2::parallel::StdThreadCallable, public libmaus2::suffixsort::bwtb3m::BWTB3MBase
			{
				typedef BaseBlockSortThreadCallable this_type;
				typedef std::unique_ptr<this_type> unique_ptr_type;

				//! thread id
				uint64_t tid;

				/**
				 * semaphore. delivers a message whenever there is sufficient
				 * free space to process the next element
				 **/
				libmaus2::parallel::StdSemaphore & P;
				//
				libmaus2::suffixsort::bwtb3m::MergeTree & mergetree;
				//! vector of blocks to be processed
				std::vector < libmaus2::suffixsort::bwtb3m::MergeStrategyBaseBlock * > & V;

				//! next package to be processed
				std::atomic<uint64_t> & next;
				//! amount of free memory
				std::atomic<uint64_t> & freemem;
				//! number of finished threads
				std::atomic<uint64_t> & finished;
				//! failed flag
				std::atomic<int> & failed;
				//! lock for the above
				libmaus2::parallel::StdMutex & freememlock;
				//! inner node queue
				std::deque<uint64_t> & itodo;
				//! pending
				std::deque<uint64_t> & pending;
				//! log stream
				std::ostream * logstr;
				//! verbose level
				uint64_t verbose;

				BaseBlockSortThreadCallable(
					uint64_t rtid,
					libmaus2::parallel::StdSemaphore & rP,
					libmaus2::suffixsort::bwtb3m::MergeTree & r_mergetree,
					std::vector < libmaus2::suffixsort::bwtb3m::MergeStrategyBaseBlock * > & rV,
					std::atomic<uint64_t> & rnext,
					std::atomic<uint64_t> & rfreemem,
					std::atomic<uint64_t> & rfinished,
					std::atomic<int> & rfailed,
					libmaus2::parallel::StdMutex & rfreememlock,
					std::deque<uint64_t> & ritodo,
					std::deque<uint64_t> & rpending,
					std::ostream * rlogstr,
					uint64_t const rverbose
				) : tid(rtid), P(rP), mergetree(r_mergetree), V(rV), next(rnext), freemem(rfreemem), finished(rfinished), failed(rfailed), freememlock(rfreememlock),
				    itodo(ritodo), pending(rpending), logstr(rlogstr), verbose(rverbose)
				{

				}

				void run()
				{
					bool running = true;

					while ( running )
					{
						// wait until sufficient memory is free
						P.wait();

						// get package id
						uint64_t pack = 0;

						{
							// get lock
							libmaus2::parallel::ScopeStdMutex scopelock(freememlock);

							if ( pending.size() )
							{
								pack = pending.front();
								pending.pop_front();
							}
							else
							{
								running = false;
								P.post();
							}
						}

						if ( running )
						{
							try
							{
								// perform sorting
								libmaus2::suffixsort::bwtb3m::MergeStrategyBaseBlock * block = V[pack];
								block->sortresult = ::libmaus2::suffixsort::BwtMergeBlockSortResult::load(block->sortreq.dispatch<rl_encoder>(logstr));
							}
							catch(std::exception const & ex)
							{
								failed.store(1);
								libmaus2::parallel::ScopeStdMutex scopelock(freememlock);
								if ( logstr )
									*logstr << tid << " failed " << pack << " " << ex.what() << std::endl;
							}

							{
								// get lock
								libmaus2::parallel::ScopeStdMutex scopelock(freememlock);

								if ( logstr )
									*logstr << "[V] [" << tid << "] sorted block " << pack << " parent id " << V[pack]->parentid << std::endl;

								if ( V[pack]->hasParent() )
								{
									auto & parentnode = mergetree[V[pack]->parentid];
									bool const pfinished = parentnode.childFinished();

									if ( pfinished )
										itodo.push_back(parentnode.nodeid);
								}

								// "free" memory
								freemem += V[pack]->directSortSpace();

								// post if there is room for another active sorting thread
								while ( next < V.size() && freemem >= V[next]->directSortSpace() )
								{
									freemem -= V[next]->directSortSpace();
									pending.push_back(static_cast<uint64_t>(next));
									next += 1;
									P.post();
								}

								if ( next == V.size() )
									P.post();
							}
						}
					}

					{
					libmaus2::parallel::ScopeStdMutex scopelock(freememlock);
					finished++;
					}

					// quit
				}
			};
		}
	}
}
#endif
