/**
    libmaus2
    Copyright (C) 2009-2021 German Tischler-Höhle
    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_BWTMERGEISAPARALLEL_HPP)
#define LIBMAUS2_SUFFIXSORT_BWTB3M_BWTMERGEISAPARALLEL_HPP

#include <libmaus2/suffixsort/bwtb3m/GapArrayWrapper.hpp>
#include <libmaus2/suffixsort/bwtb3m/PreIsaAdapter.hpp>
#include <libmaus2/suffixsort/GapArrayByte.hpp>
#include <libmaus2/util/TempFileNameGenerator.hpp>
#include <libmaus2/aio/ConcatInputStream.hpp>

namespace libmaus2
{
	namespace suffixsort
	{
		namespace bwtb3m
		{
			// BwtMergeIsaParallelBase<GapArrayWrapper>
			// BwtMergeIsaParallelBase<GapArrayByte>
			template<typename gap_array>
			struct BwtMergeIsaParallelBase
			{
				struct PairFirstComparator
				{
					bool operator()(std::pair<uint64_t,uint64_t> const & A, std::pair<uint64_t,uint64_t> const & B) const
					{
						return A.first < B.first;
					}
				};

				static uint64_t getFileSize(std::vector<std::string> const & Vfn)
				{
					uint64_t s = 0;
					for ( uint64_t i = 0; i < Vfn.size(); ++i )
						s += ::libmaus2::util::GetFileSize::getFileSize(Vfn[i]);
					return s;
				}

				static std::pair < uint64_t, std::vector<std::string> >
					mergeIsaParallel(
						libmaus2::util::TempFileNameGenerator & tmpgen,
						// work packages
						std::vector < std::pair<uint64_t,uint64_t> > const & wpacks,
						// prefix sums over G for low marks in wpacks
						std::vector < uint64_t > const & P,
						// old (RHS block pre isa)
						std::vector<std::string> const & oldmergedisaname,
						// new (LHS block pre isa)
						std::vector<std::string> const & newmergedisaname,
						// start of new (LHS) block in text
						uint64_t const blockstart,
						// the gap array
						gap_array const & G,
						uint64_t const /* Gsize */,
						// number of threads
						uint64_t const
							#if defined(_OPENMP)
							numthreads
							#endif
						,
						// log stream
						std::ostream * logstr
				)
				{
					::libmaus2::timing::RealTimeClock rtc;

					// merge sampled inverse suffix arrays
					if ( logstr )
						(*logstr) << "[V] merging sampled inverse suffix arrays in parallel...";
					rtc.start();

					std::vector<std::string> Vout(wpacks.size());
					for ( uint64_t i = 0; i < wpacks.size(); ++i )
					{
						std::ostringstream fnostr;
						fnostr << tmpgen.getFileName() << "_" << std::setw(6) << std::setfill('0') << i << std::setw(0) << ".preisa";
						Vout[i] = fnostr.str();
					}

					std::atomic<uint64_t> blockp0rank(std::numeric_limits<uint64_t>::max());
					libmaus2::parallel::StdSpinLock blockp0ranklock;

					#if defined(_OPENMP)
					#pragma omp parallel for num_threads(numthreads)
					#endif
					for ( uint64_t t = 0; t < wpacks.size(); ++t )
					{
						uint64_t const blow = wpacks[t].first;
						uint64_t const bhigh = wpacks[t].second;
						uint64_t const slow = P[t];

						typename PreIsaAdapter::unique_ptr_type PIA_LHS(new PreIsaAdapter(newmergedisaname));
						typename PreIsaAdapter::unique_ptr_type PIA_RHS(new PreIsaAdapter(oldmergedisaname));

						typename PreIsaAdapter::const_iterator it_LHS = ::std::lower_bound(PIA_LHS->begin(),PIA_LHS->end(),std::pair<uint64_t,uint64_t>(blow,0),PairFirstComparator());
						assert ( it_LHS == PIA_LHS->end() || it_LHS[0].first >= blow );
						assert ( it_LHS == PIA_LHS->begin() || it_LHS[-1].first < blow );
						uint64_t const off_LHS = it_LHS - PIA_LHS->begin();
						PIA_LHS.reset();

						typename PreIsaAdapter::const_iterator it_RHS = ::std::lower_bound(PIA_RHS->begin(),PIA_RHS->end(),std::pair<uint64_t,uint64_t>(slow,0),PairFirstComparator());
						assert ( it_RHS == PIA_RHS->end() || it_RHS[0].first >= slow );
						assert ( it_RHS == PIA_RHS->begin() || it_RHS[-1].first < slow );
						uint64_t const off_RHS = it_RHS - PIA_RHS->begin();
						PIA_RHS.reset();

						// LHS
						libmaus2::aio::ConcatInputStream ISInew(newmergedisaname);
						ISInew.clear();
						ISInew.seekg(off_LHS * 2 * sizeof(uint64_t));

						// RHS
						libmaus2::aio::ConcatInputStream ISIold(oldmergedisaname);
						ISIold.clear();
						ISIold.seekg(off_RHS * 2 * sizeof(uint64_t));

						::libmaus2::aio::SynchronousGenericInput<uint64_t> SGIISAold(ISIold,16*1024);
						::libmaus2::aio::SynchronousGenericInput<uint64_t> SGIISAnew(ISInew,16*1024);
						::libmaus2::aio::SynchronousGenericOutput<uint64_t> SGOISA(Vout[t],16*1024);

						uint64_t lblockp0rank = std::numeric_limits<uint64_t>::max();

						typedef typename gap_array::sequence_type sequence_type;
						sequence_type gp = G.getOffsetSequence(blow);
						uint64_t s = slow;
						// scan the gap array
						for ( uint64_t i = blow; i < bhigh; ++i )
						{
							s += gp.get(); // *(Gc++);

							// while old suffixes are smaller than next new suffix
							int64_t re;
							while ( (re=SGIISAold.peek()) >= 0 && re < static_cast<int64_t>(s) )
							{
								// rank (add number of new suffixes processed before)
								uint64_t const r = SGIISAold.get() + i;
								// position (copy as is)
								uint64_t const p = SGIISAold.get();

								SGOISA . put ( r );
								SGOISA . put ( p );
							}

							// is next sample in next (LHS) block for rank i?
							if ( SGIISAnew.peek() == static_cast<int64_t>(i) )
							{
								// add number of old suffixes to rank
								uint64_t const r = SGIISAnew.get() + s;
								// keep absolute position as is
								uint64_t const p = SGIISAnew.get();

								SGOISA . put ( r );
								SGOISA . put ( p );

								// check whether this rank is for the leftmost position in the merged block
								if ( p == blockstart )
									blockp0rank = r;
							}
						}

						SGOISA.flush();

						if ( lblockp0rank != std::numeric_limits<uint64_t>::max() )
						{
							libmaus2::parallel::StdSpinLock::scope_lock_type slock(blockp0ranklock);
							blockp0rank = lblockp0rank;
						}
					}

					// sanity check, input length should equal output length
					assert (
						getFileSize(oldmergedisaname) + getFileSize(newmergedisaname) ==
						getFileSize(Vout)
					);

					if ( logstr )
						(*logstr) << "done, time " << rtc.getElapsedSeconds() << std::endl;

					return std::pair < uint64_t, std::vector<std::string> >(static_cast<uint64_t>(blockp0rank),Vout);
				}
			};

			// BwtMergeIsaParallelBase<GapArrayWrapper>
			// BwtMergeIsaParallelBase<GapArrayByte>
			extern template struct BwtMergeIsaParallelBase<GapArrayWrapper>;
			extern template struct BwtMergeIsaParallelBase<GapArrayByte>;
		}
	}
}
#endif
