/*
    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_SORTING_RANKSEARCH_HPP)
#define LIBMAUS2_SORTING_RANKSEARCH_HPP

#include <libmaus2/parallel/threadpool/ThreadPool.hpp>

namespace libmaus2
{
	namespace sorting
	{
		struct RankSearch
		{
			struct SortOp
			{
				virtual ~SortOp() {}
				virtual void dispatch() = 0;
			};

			template<
				typename iterator,
				typename order_type
			>
			struct SortRequest : public SortOp
			{
				iterator a;
				iterator e;
				order_type const * order;

				SortRequest() : a(), e(), order(nullptr)
				{
				}
				SortRequest(
					iterator ra,
					iterator re,
					order_type const * const rorder
				) : a(ra), e(re), order(rorder) {}

				void dispatch()
				{
					::std::sort(a,e,*order);
				}
			};

			template<
				typename iterator,
				typename order_type
			>
			struct StableSortRequest : public SortOp
			{
				iterator a;
				iterator e;
				order_type const * order;

				StableSortRequest() : a(), e(), order(nullptr)
				{
				}
				StableSortRequest(
					iterator ra,
					iterator re,
					order_type const * const rorder
				) : a(ra), e(re), order(rorder) {}

				void dispatch()
				{
					::std::stable_sort(a,e,*order);
				}
			};

			template<
				typename from_iterator,
				typename to_iterator
			>
			struct CopyRequest : public SortOp
			{
				from_iterator from_b;
				from_iterator from_e;
				to_iterator to_b;

				CopyRequest()
				{

				}
				CopyRequest(
					from_iterator rfrom_b,
					from_iterator rfrom_e,
					to_iterator rto_b
				) : from_b(rfrom_b), from_e(rfrom_e), to_b(rto_b)
				{

				}

				void dispatch()
				{
					std::copy(from_b,from_e,to_b);
				}
			};

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type
			>
			struct MergeRequest : public SortOp
			{
				const_iterator ab;
				const_iterator ae;
				const_iterator bb;
				const_iterator be;
				temp_iterator itt;
				order_type const * order;

				MergeRequest()
				{}
				MergeRequest(
					const_iterator rab,
					const_iterator rae,
					const_iterator rbb,
					const_iterator rbe,
					temp_iterator ritt,
					order_type const * rorder
				) : ab(rab), ae(rae), bb(rbb), be(rbe), itt(ritt), order(rorder)
				{}

				void dispatch()
				{
					std::merge(
						ab,ae,
						bb,be,
						itt,
						*order
					);
				}
			};

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type
			>
			struct MergeRequestDelayedSearch : public SortOp
			{
				const_iterator ab;
				const_iterator ae;
				const_iterator bb;
				const_iterator be;
				temp_iterator itt;
				std::size_t from;
				std::size_t to;
				order_type const * order;

				MergeRequestDelayedSearch()
				{}
				MergeRequestDelayedSearch(
					const_iterator rab,
					const_iterator rae,
					const_iterator rbb,
					const_iterator rbe,
					temp_iterator ritt,
					std::size_t const rfrom,
					std::size_t const rto,
					order_type const * rorder
				) : ab(rab), ae(rae), bb(rbb), be(rbe), itt(ritt), from(rfrom), to(rto), order(rorder)
				{}

				void dispatch()
				{
					std::vector< std::pair<const_iterator,const_iterator> > const Vsplitin(
						{
							std::make_pair(ab,ae),
							std::make_pair(bb,be)
						}
					);
					auto const Vlow = rankSearchMulti(Vsplitin,from);
					auto const Vhigh = rankSearchMulti(Vsplitin,to);
					auto const off = std::distance(ab,Vlow.front()) + std::distance(bb,Vlow.back());
					assert ( static_cast<std::size_t>(off) == from );

					std::merge(
						Vlow.front(),Vhigh.front(),
						Vlow.back(),Vhigh.back(),
						itt + off,
						*order
					);
				}

				std::string toString(const_iterator ref) const
				{
					std::ostringstream ostr;

					ostr << "MergeRequestDelayedSearch("
						<< ab-ref << ","
						<< ae-ref << ","
						<< bb-ref << ","
						<< be-ref << ","
						<< from << ","
						<< to
						<< ")";

					return ostr.str();
				}
			};

			template<
				typename const_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static std::pair<const_iterator,const_iterator> rankSearch(
				const_iterator ab, const_iterator ae,
				const_iterator bb, const_iterator be,
				typename std::iterator_traits<const_iterator>::difference_type const p,
				order_type const & order = order_type()
			)
			{
				const_iterator low_a = ab;
				const_iterator high_a = ae;
				const_iterator low_b = bb;
				const_iterator high_b = be;

				bool diff_a = (high_a != low_a) && order(*low_a,high_a[-1]);
				bool diff_b = (high_b != low_b) && order(*low_b,high_b[-1]);

				assert ( (low_a-ab) + (low_b-bb) <= p );

				while ( diff_a || diff_b )
				{
					if ( diff_a )
					{
						auto const ln = std::distance(low_a,high_a);
						auto P = std::equal_range(low_a,high_a,low_a[ln/2],order);

						const_iterator ac;

						if ( P.first == low_a )
							ac = P.second;
						else
							ac = P.first;

						assert ( ac != ae );

						auto const & e = *ac;
						auto const it = std::lower_bound(low_b,high_b,e,order);
						typename std::iterator_traits<const_iterator>::difference_type const z = (it - bb) + (ac-ab);

						if ( p >= z )
							low_a = ac;
						else
							high_a = ac;

						assert (
							(low_a-ab) + (low_b-bb) <= p
						);


						diff_a = order(*low_a,high_a[-1]);
					}
					if ( diff_b )
					{
						auto const ln = std::distance(low_b,high_b);
						auto P = std::equal_range(low_b,high_b,low_b[ln/2],order);

						const_iterator bc;

						if ( P.first == low_b )
							bc = P.second;
						else
							bc = P.first;

						assert ( bc != be );

						auto const & e = *bc;
						auto const it = std::lower_bound(low_a,high_a,e,order);
						typename std::iterator_traits<const_iterator>::difference_type const z = (it - ab) + (bc-bb);

						if ( p >= z )
							low_b = bc;
						else
							high_b = bc;

						assert ( (low_a-ab) + (low_b-bb) <= p );

						diff_b = order(*low_b,high_b[-1]);
					}
				}

				auto av_a = high_a - low_a;
				auto av_b = high_b - low_b;

				while (
					(low_a-ab) + (low_b-bb) < p
					&&
					(av_a || av_b)
				)
				{
					auto mis = p - ((low_a-ab) + (low_b-bb));

					if ( ! av_a )
					{
						auto const av = av_b;
						auto const use = std::min(av,mis);
						low_b += use;
					}
					else if ( ! av_b )
					{
						auto const av = av_a;
						auto const use = std::min(av,mis);
						low_a += use;
					}
					else if ( order(*low_a,*low_b) )
					{
						auto const av = av_a;
						auto const use = std::min(av,mis);
						low_a += use;
						av_a -= use;
					}
					else
					{
						auto const av = av_b;
						auto const use = std::min(av,mis);
						low_b += use;
						av_b -= use;
					}

					assert ( (low_a-ab) + (low_b-bb) <= p );
				}

				return std::make_pair(low_a,low_b);
			}

			template<
				typename const_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static std::vector<const_iterator> rankSearchMulti(
				std::vector< std::pair<const_iterator,const_iterator> > const & V,
				typename std::iterator_traits<const_iterator>::difference_type const p,
				order_type const & order = order_type()
			)
			{
				std::vector< std::pair<const_iterator,const_iterator> > VLH(V);
				std::vector< bool > Vdiff(V.size());
				bool anydiff = false;

				for ( std::size_t i = 0; i < VLH.size(); ++i )
				{
					const_iterator const & low  = VLH[i].first;
					const_iterator const & high = VLH[i].second;
					Vdiff[i] = (high != low) && order(*low,high[-1]);
					anydiff = anydiff || Vdiff[i];
				}

				typename std::iterator_traits<const_iterator>::difference_type s = 0;

				assert ( s <= p );

				while ( anydiff )
				{
					anydiff = false;

					for ( std::size_t i = 0; i < Vdiff.size(); ++i )
						if ( Vdiff[i] )
						{
							const_iterator const & low  = VLH[i].first;
							const_iterator const & high = VLH[i].second;

							auto const ln = std::distance(low,high);
							auto P = std::equal_range(low,high,low[ln/2],order);

							const_iterator ac;

							if ( P.first == low )
								ac = P.second;
							else
								ac = P.first;

							assert ( ac != V[i].second );

							auto const & e = *ac;

							typename std::iterator_traits<const_iterator>::difference_type z = ac-V[i].first;

							for ( std::size_t j = 0; j < Vdiff.size(); ++j )
								if ( j != i )
								{
									auto const it = std::lower_bound(VLH[j].first,VLH[j].second,e,order);
									z += it - V[j].first;
								}

							if ( p >= z )
								VLH[i].first = ac;
							else
								VLH[i].second = ac;

							Vdiff[i] = order(*(VLH[i].first),VLH[i].second[-1]);

							anydiff = anydiff || Vdiff[i];
						}

					s = 0;
					for ( std::size_t i = 0; i < VLH.size(); ++i )
						s += VLH[i].first - V[i].first;

					assert ( s <= p );
				}

				std::vector < std::size_t> VI(V.size());
				for ( std::size_t i = 0; i < V.size(); ++i )
					VI[i] = i;

				// sort remaining intervals (by index) in ascending order
				std::sort(
					VI.begin(),
					VI.end(),
					[&VLH,&order](auto const i, auto const j)
					{
						bool const empty_i = VLH[i].second == VLH[i].first;
						bool const empty_j = VLH[j].second == VLH[j].first;

						if ( empty_j )
							return true;
						else if ( empty_i )
							return false;
						else if ( order(*(VLH[i].first),*(VLH[j].first)) )
							return true;
						else if ( order(*(VLH[j].first),*(VLH[i].first)) )
							return false;
						else
							return i < j;
					}
				);

				for ( std::size_t j = 0; s < p && j < VI.size(); ++j )
				{
					std::size_t const i = VI[j];
					auto const av = VLH[i].second - VLH[i].first;
					auto const mis = p - s;
					auto const use = std::min(av,mis);

					VLH[i].first += use;
					s += use;
				}

				std::vector<const_iterator> R(VLH.size());

				for ( std::size_t i = 0; i < VLH.size(); ++i )
					R[i] = VLH[i].first;

				return R;
			}

			template<typename iterator>
			struct SwapReverse
			{
				iterator ab;
				iterator ae;
				iterator be;

				SwapReverse(){}
				SwapReverse(
					iterator rab,
					iterator rae,
					iterator rbe
				) : ab(rab), ae(rae), be(rbe)
				{
					assert ( ae >= ab );
					assert ( be >= ae );
				}

				void dispatch()
				{
					while ( ab != ae )
						std::swap(*ab++,*--be);
				}

				std::string toString(iterator ref) const
				{
					auto const an = std::distance(ab,ae);
					std::ostringstream ostr;
					ostr << "SwapReverse([" << ab-ref << "," << ae-ref << "),[" << ((be-an)-ref) << "," << be-ref << "))";

					return ostr.str();
				}

				void pushIntervals(std::vector< std::pair<iterator,iterator> > & V) const
				{
					auto const d = std::distance(ab,ae);
					V.push_back(std::make_pair(ab,ae));
					V.push_back(std::make_pair(be-d,be));
				}
			};

			template<typename iterator>
			static void swap(
				iterator itab,
				iterator itae,
				iterator itbb,
				iterator itbe
			)
			{
				auto const an = std::distance(itab,itae);
				auto const bn = std::distance(itbb,itbe);

				SwapReverse(itab,itab+an/2,itae).dispatch();
				SwapReverse(itbb,itbb+bn/2,itbe).dispatch();

				if ( an <= bn )
				{
					SwapReverse(itab,itab+an,itbe).dispatch();
					itab += an;
					itbe -= an;

					auto const rest = bn - an;
					auto const rest2 = rest / 2;

					SwapReverse(itbb,itbb+rest2,itbe).dispatch();
				}
				else
				{
					SwapReverse(itab,itab+bn,itbe).dispatch();
					itab += bn;
					itbe -= bn;

					auto const rest = an - bn;
					auto const rest2 = rest / 2;

					SwapReverse(itab,itab+rest2,itae).dispatch();
				}
			}

			template<typename iterator>
			static void planReverse(
				iterator itab,
				iterator itae,
				std::size_t const threads,
				std::vector< SwapReverse<iterator> > & V
			)
			{
				auto const an = std::distance(itab,itae);

				if ( an > 1 )
				{
					std::size_t const san = an/2;
					std::size_t const packsize = (san + threads - 1)/threads;
					std::size_t const numpacks = (san + packsize - 1)/packsize;

					// std::cerr << "an=" << an << " san=" << san << " packsize=" << packsize << std::endl;

					std::size_t rest = san;

					for ( std::size_t i = 0; i < numpacks; ++i )
					{
						std::size_t const use = std::min(rest,packsize);

						// std::cerr << "use=" << use << std::endl;

						V.push_back(SwapReverse<iterator>(itab,itab+use,itae));

						itab += use;
						itae -= use;
						rest -= use;
					}

					assert (
						itab == itae
						||
						itab+1 == itae
					);
				}
			}

			template<typename iterator>
			static void planSwapReverse(
				iterator itab,
				iterator itae,
				iterator itbe,
				std::size_t const threads,
				std::vector< SwapReverse<iterator> > & V
			)
			{
				auto const an = std::distance(itab,itae);

				if ( ! an )
					return;

				assert ( std::distance(itae,itbe) >= an );

				std::size_t const san = an;
				std::size_t const packsize = (san + threads - 1)/threads;
				std::size_t const numpacks = (san + packsize - 1)/packsize;

				std::size_t rest = san;

				for ( std::size_t i = 0; i < numpacks; ++i )
				{
					std::size_t const use = std::min(rest,packsize);
					V.push_back(
						SwapReverse<iterator>(
							itab,
							itab+use,
							itbe
						)
					);

					rest -= use;
					itab += use;
					itbe -= use;
				}

				assert ( rest == 0 );
			}

			template<typename iterator>
			static std::pair< std::vector < SwapReverse<iterator> >,std::vector < SwapReverse<iterator> > > planSwap(
				iterator itab,
				iterator itae,
				iterator itbb,
				iterator itbe,
				std::size_t const threads
			)
			{
				assert ( itae == itbb );

				auto const an = std::distance(itab,itae);
				auto const bn = std::distance(itbb,itbe);
				// decltype(an) dthreads = threads;

				// reverse A and B
				std::vector < SwapReverse<iterator> > Vfirst;
				// reverse RARB to obtain BA
				std::vector < SwapReverse<iterator> > Vsecond;

				planReverse(itab,itae,threads,Vfirst);
				planReverse(itbb,itbe,threads,Vfirst);

				if ( an <= bn )
				{
					planSwapReverse(itab,itab+an,itbe,threads,Vsecond);

					itab += an;
					itbe -= an;

					planReverse(itbb,itbe,threads,Vsecond);
				}
				else
				{
					planSwapReverse(itab,itab+bn,itbe,threads,Vsecond);

					itab += bn;
					itbe -= bn;

					planReverse(itab,itae,threads,Vsecond);
				}

				std::vector < std::pair<iterator,iterator> > VIfirst;
				std::vector < std::pair<iterator,iterator> > VIsecond;

				for ( auto R : Vfirst )
					R.pushIntervals(VIfirst);
				for ( auto R : Vsecond )
					R.pushIntervals(VIsecond);

				std::sort(VIfirst.begin(),VIfirst.end());
				std::sort(VIsecond.begin(),VIsecond.end());

				for ( std::size_t i = 1; i < VIfirst.size(); ++i )
					assert ( VIfirst[i-1].second <= VIfirst[i].first );
				for ( std::size_t i = 1; i < VIsecond.size(); ++i )
					assert ( VIsecond[i-1].second <= VIsecond[i].first );

				return std::make_pair(Vfirst,Vsecond);
			}

			template<typename iterator>
			static void swapParallel(
				iterator itab,
				iterator itae,
				iterator itbb,
				iterator itbe,
				std::size_t const threads
			)
			{
				assert ( itae == itbb );

				// #define SWAP_PARALLEL_DEBUG

				#if defined(SWAP_PARALLEL_DEBUG)
				typedef typename std::iterator_traits<iterator>::value_type value_type;
				std::vector<value_type> VA(itab,itae);
				std::vector<value_type> VB(itbb,itbe);
				std::vector<value_type> V;
				for ( auto i : VB )
					V.push_back(i);
				for ( auto i : VA )
					V.push_back(i);
				#endif

				auto P = planSwap(itab,itae,itbb,itbe,threads);

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t i = 0; i < P.first.size(); ++i )
					P.first[i].dispatch();

				#if defined(SWAP_PARALLEL_DEBUG)
				std::vector<value_type> VAR = VA;
				std::vector<value_type> VBR = VB;
				std::reverse(VAR.begin(),VAR.end());
				std::reverse(VBR.begin(),VBR.end());
				std::vector<value_type> VR;
				for ( auto i : VAR )
					VR.push_back(i);
				for ( auto i : VBR )
					VR.push_back(i);

				for ( std::size_t i = 0; i < VR.size(); ++i )
					assert ( VR[i] == itab[i] );

				for ( std::size_t i = 0; i < P.second.size(); ++i )
					std::cerr << P.second[i].toString(itab) << std::endl;
				#endif

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t i = 0; i < P.second.size(); ++i )
					P.second[i].dispatch();

				#if defined(SWAP_PARALLEL_DEBUG)
				std::reverse(VR.begin(),VR.end());
				for ( std::size_t i = 0; i < V.size(); ++i )
					assert ( VR[i] == V[i] );

				bool ok = true;
				for ( std::size_t i = 0; i < V.size(); ++i )
					ok = ok && ( itab[i] == V[i] );

				if ( !ok )
				{
					for ( std::size_t i = 0; i < V.size(); ++i )
						std::cerr << "V[" << i << "]=" << V[i] << " itab[" << i << "]=" << itab[i] << std::endl;

					assert ( ok );
				}
				#endif
			}

			template<
				typename iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void mergeInPlace(iterator ita, iterator itm, iterator ite, order_type const & order = order_type())
			{
				typedef std::tuple<iterator,iterator,iterator> element;
				std::stack<element> S;
				auto const d = std::distance(ita,ite);

				if ( d > 1 )
					S.push(element(ita,itm,ite));

				while ( ! S.empty() )
				{
					element E = S.top();
					S.pop();

					std::vector<std::pair<iterator,iterator> > V(
						{
							std::make_pair(std::get<0>(E),std::get<1>(E)),
							std::make_pair(std::get<1>(E),std::get<2>(E))
						}
					);

					auto const d = std::distance(std::get<0>(E),std::get<2>(E));

					assert ( d > 1 );

					std::vector<iterator> const R = rankSearchMulti(V,d/2,order);

					auto const I0 = std::make_pair(V.front().first,R.front());
					auto const I1 = std::make_pair(R.front(),V.front().second);
					auto const I2 = std::make_pair(V.back().first,R.back());
					auto const I3 = std::make_pair(R.back(),V.back().second);

					assert ( I0.second == I1.first );
					assert ( I1.second == I2.first );
					assert ( I2.second == I3.first );

					swap(I1.first,I1.second,I1.second,I2.second);

					// I0,I2,I1,I3
					auto const n_0 = std::distance(I0.first,I0.second);
					auto const n_1 = std::distance(I1.first,I1.second);
					auto const n_2 = std::distance(I2.first,I2.second);
					auto const n_3 = std::distance(I3.first,I3.second);
					auto const n_left  = n_0 + n_2;
					auto const n_right = n_1 + n_3;

					if ( n_right > 1 )
					{
						S.push(element(I0.first + n_0 + n_2,I0.first + n_0 + n_2 + n_1, I0.first + n_0 + n_2 + n_1 + n_3));
					}
					if ( n_left > 1 )
					{
						S.push(element(I0.first,I0.first + n_0,I0.first + n_0 + n_2));
					}
				}
			}

			template<typename iterator>
			struct MergeInfo
			{
				iterator a;
				iterator m;
				iterator e;
				std::size_t parts;

				MergeInfo()
				{

				}

				MergeInfo(
					iterator ra,
					iterator rm,
					iterator re,
					std::size_t const rparts
				) : a(ra), m(rm), e(re), parts(rparts)
				{

				}

				template<typename order_type>
				void dispatch(order_type const & order)
				{
					mergeInPlace(a,m,e,order);
				}

				template<typename temp_iterator, typename order_type>
				void dispatch(
					temp_iterator itt,
					std::size_t const limit,
					order_type const & order
				)
				{
					mergeLimited(a,m,e,itt,limit,order);
				}
			};


			template<
				typename iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void mergeInPlaceParallel(
				iterator ita,
				iterator itm,
				iterator ite,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				std::stack < MergeInfo<iterator> > S;
				std::vector < MergeInfo<iterator> > V;
				S.push(MergeInfo<iterator>(ita,itm,ite,threads));

				while ( !S.empty() )
				{
					MergeInfo<iterator> M = S.top();
					S.pop();

					if ( M.parts > 1 )
					{
						std::size_t const left = (M.parts + 1) / 2;
						std::size_t const right = M.parts - left;
						std::size_t const ln = M.e-M.a;
						std::size_t const split = (left * ln + M.parts- 1) / M.parts;
						assert ( split <= ln );

						#if 0
						std::cerr << "parts=" << M.parts
							<< " left=" << left
							<< " right=" << right
							<< " ln=" << ln
							<< " split=" << split
							<< std::endl;
						#endif

						auto const Vsplit =
							rankSearchMulti(
								std::vector< std::pair<iterator,iterator> >(
									{
										std::make_pair(M.a,M.m),
										std::make_pair(M.m,M.e)
									}
								),
								split,
								order
							);

						iterator const ab = M.a;
						iterator const am = Vsplit.front();
						iterator const ae = M.m;
						iterator const bb = M.m;
						iterator const bm = Vsplit.back();
						iterator const be = M.e;

						std::size_t const r0 = am-ab;
						std::size_t const r1 = ae-am;
						std::size_t const r2 = bm-bb;
						std::size_t const r3 = be-bm;

						#if 0
						std::cerr << "r0=" << r0 << std::endl;
						std::cerr << "r1=" << r1 << std::endl;
						std::cerr << "r2=" << r2 << std::endl;
						std::cerr << "r3=" << r3 << std::endl;
						#endif

						swapParallel(am,ae,bb,bm,threads);

						S.push(MergeInfo<iterator>(ab+r0+r2,ab+r0+r2+r1,ab+r0+r2+r1+r3,right));
						S.push(MergeInfo<iterator>(ab,ab+r0,ab+r0+r2,left));
					}
					else
					{
						V.push_back(M);
					}
				}

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t i = 0; i < V.size(); ++i )
					V[i].dispatch(order);
			}

			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void mergeLimitedParallel(
				iterator ita,
				iterator itm,
				iterator ite,
				temp_iterator itt,
				std::size_t const tmpsize,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				std::stack < MergeInfo<iterator> > S;
				std::vector < MergeInfo<iterator> > V;
				S.push(MergeInfo<iterator>(ita,itm,ite,threads));

				while ( !S.empty() )
				{
					MergeInfo<iterator> M = S.top();
					S.pop();

					if ( M.parts > 1 )
					{
						std::size_t const left = (M.parts + 1) / 2;
						std::size_t const right = M.parts - left;
						std::size_t const ln = M.e-M.a;
						std::size_t const split = (left * ln + M.parts- 1) / M.parts;
						assert ( split <= ln );

						#if 0
						std::cerr << "parts=" << M.parts
							<< " left=" << left
							<< " right=" << right
							<< " ln=" << ln
							<< " split=" << split
							<< std::endl;
						#endif

						auto const Vsplit =
							rankSearchMulti(
								std::vector< std::pair<iterator,iterator> >(
									{
										std::make_pair(M.a,M.m),
										std::make_pair(M.m,M.e)
									}
								),
								split,
								order
							);

						iterator const ab = M.a;
						iterator const am = Vsplit.front();
						iterator const ae = M.m;
						iterator const bb = M.m;
						iterator const bm = Vsplit.back();
						iterator const be = M.e;

						std::size_t const r0 = am-ab;
						std::size_t const r1 = ae-am;
						std::size_t const r2 = bm-bb;
						std::size_t const r3 = be-bm;

						#if 0
						std::cerr << "r0=" << r0 << std::endl;
						std::cerr << "r1=" << r1 << std::endl;
						std::cerr << "r2=" << r2 << std::endl;
						std::cerr << "r3=" << r3 << std::endl;
						#endif

						swapParallel(am,ae,bb,bm,threads);

						S.push(MergeInfo<iterator>(ab+r0+r2,ab+r0+r2+r1,ab+r0+r2+r1+r3,right));
						S.push(MergeInfo<iterator>(ab,ab+r0,ab+r0+r2,left));
					}
					else
					{
						V.push_back(M);
					}
				}

				std::size_t const tmpperthread = (tmpsize + threads - 1)/threads;

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t i = 0; i < V.size(); ++i )
				{
					std::size_t const tmplow = i * tmpperthread;
					std::size_t const tmphigh = std::min(tmplow + tmpperthread, tmpsize);

					V[i].dispatch(itt+tmplow,tmphigh-tmplow,order);
				}
			}


			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void mergeLimited(
				iterator ita,
				iterator itm,
				iterator ite,
				temp_iterator itt,
				std::size_t const limit,
				order_type const & order = order_type()
			)
			{
				typedef std::tuple<iterator,iterator,iterator> element;
				std::stack<element> S;
				auto const d = std::distance(ita,ite);

				if ( d > 1 )
					S.push(element(ita,itm,ite));

				while ( ! S.empty() )
				{
					element E = S.top();
					S.pop();

					auto const d = std::distance(std::get<0>(E),std::get<2>(E));

					if ( d <= static_cast<decltype(d)>(limit) )
					{
						std::merge(std::get<0>(E),std::get<1>(E),std::get<1>(E),std::get<2>(E),itt);
						std::copy(itt,itt+d,std::get<0>(E));
					}
					else
					{
						std::vector<std::pair<iterator,iterator> > V(
							{
								std::make_pair(std::get<0>(E),std::get<1>(E)),
								std::make_pair(std::get<1>(E),std::get<2>(E))
							}
						);


						assert ( d > 1 );

						std::vector<iterator> const R = rankSearchMulti(V,d/2,order);

						auto const I0 = std::make_pair(V.front().first,R.front());
						auto const I1 = std::make_pair(R.front(),V.front().second);
						auto const I2 = std::make_pair(V.back().first,R.back());
						auto const I3 = std::make_pair(R.back(),V.back().second);

						assert ( I0.second == I1.first );
						assert ( I1.second == I2.first );
						assert ( I2.second == I3.first );

						swap(I1.first,I1.second,I1.second,I2.second);

						// I0,I2,I1,I3
						auto const n_0 = std::distance(I0.first,I0.second);
						auto const n_1 = std::distance(I1.first,I1.second);
						auto const n_2 = std::distance(I2.first,I2.second);
						auto const n_3 = std::distance(I3.first,I3.second);
						auto const n_left  = n_0 + n_2;
						auto const n_right = n_1 + n_3;

						if ( n_right > 1 )
						{
							S.push(element(I0.first + n_0 + n_2,I0.first + n_0 + n_2 + n_1, I0.first + n_0 + n_2 + n_1 + n_3));
						}
						if ( n_left > 1 )
						{
							S.push(element(I0.first,I0.first + n_0,I0.first + n_0 + n_2));
						}
					}
				}
			}


			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static std::vector<
				MergeRequest<
					const_iterator,
					temp_iterator,
					order_type
				>
			> planMerge(
				const_iterator ab,
				const_iterator ae,
				const_iterator bb,
				const_iterator be,
				temp_iterator itt,
				std::size_t const parts,
				bool const parallelSearch,
				order_type const & order = order_type()
			)
			{
				auto const n = std::distance(ab,ae);
				auto const m = std::distance(bb,be);
				auto const s = n + m;

				if ( ! s )
					return std::vector< MergeRequest<const_iterator,temp_iterator,order_type> >();

				auto const perthread = (s + parts - 1 ) / parts;
				auto const numpack = (s + perthread - 1) / perthread;

				std::vector< std::pair<const_iterator,const_iterator> > const Vsplitin(
					{
						std::make_pair(ab,ae),
						std::make_pair(bb,be)
					}
				);

				std::vector < std::vector<const_iterator> > VV(numpack+1);

				if ( parallelSearch )
				{
					#if defined(_OPENMP)
					#pragma omp parallel for num_threads(parallelSearch ? parts : 1) schedule(dynamic,1)
					#endif
					for ( std::size_t t = 0; t < numpack; ++t )
						VV[t] = rankSearchMulti(Vsplitin,t * perthread);
				}
				else
				{
					for ( std::size_t t = 0; t < numpack; ++t )
						VV[t] = rankSearchMulti(Vsplitin,t * perthread);
				}
				VV[numpack] = rankSearchMulti(Vsplitin,s,order);

				std::vector < MergeRequest<const_iterator,temp_iterator,order_type> > VREQ;
				for ( std::size_t t = 0; t < numpack; ++t )
				{
					auto const lab = VV.at(t+0).front();
					auto const lae = VV.at(t+1).front();
					auto const lbb = VV.at(t+0).back();
					auto const lbe = VV.at(t+1).back();
					auto const o = t * perthread;

					assert ( (lab-ab) + (lbb-bb) == static_cast<decltype(n)>(o) );

					VREQ.push_back(MergeRequest<const_iterator,temp_iterator,order_type>(lab,lae,lbb,lbe,itt + o,&order));
				}

				return VREQ;
			}

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static std::vector<
				MergeRequestDelayedSearch<
					const_iterator,
					temp_iterator,
					order_type
				>
			> planMergeDelayedSearch(
				const_iterator ab,
				const_iterator ae,
				const_iterator bb,
				const_iterator be,
				temp_iterator itt,
				std::size_t const parts,
				order_type const & order = order_type()
			)
			{
				auto const n = std::distance(ab,ae);
				auto const m = std::distance(bb,be);
				auto const s = n + m;

				if ( ! s )
					return std::vector< MergeRequestDelayedSearch<const_iterator,temp_iterator,order_type> >();

				auto const perthread = (s + parts - 1 ) / parts;
				auto const numpack = (s + perthread - 1) / perthread;

				std::vector < MergeRequestDelayedSearch<const_iterator,temp_iterator,order_type> > VREQ;
				for ( std::size_t t = 0; t < numpack; ++t )
				{
					auto const from = t * perthread;
					auto const to = (t+1 == numpack) ? s : (from+perthread);
					VREQ.push_back(MergeRequestDelayedSearch<const_iterator,temp_iterator,order_type>(ab,ae,bb,be,itt,from,to,&order));
				}

				return VREQ;
			}

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static std::size_t planMergeDelayedSearchP(
				std::vector < std::shared_ptr<SortOp> > & Vop,
				const_iterator ab,
				const_iterator ae,
				const_iterator bb,
				const_iterator be,
				temp_iterator itt,
				std::size_t const parts,
				order_type const & order = order_type()
			)
			{
				auto const n = std::distance(ab,ae);
				auto const m = std::distance(bb,be);
				auto const s = n + m;

				if ( ! s )
					return 0;

				auto const perthread = (s + parts - 1 ) / parts;
				auto const numpack = (s + perthread - 1) / perthread;

				for ( std::size_t t = 0; t < numpack; ++t )
				{
					auto const from = t * perthread;
					auto const to = (t+1 == numpack) ? s : (from+perthread);
					std::shared_ptr< SortOp > ptr(
						new MergeRequestDelayedSearch<const_iterator,temp_iterator,order_type>(ab,ae,bb,be,itt,from,to,&order)
					);
					Vop.emplace_back(ptr);
				}

				return numpack;
			}

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static std::vector<
				MergeRequest<
					const_iterator,
					temp_iterator,
					order_type
				>
			> planMerge(
				const_iterator ab,
				const_iterator ac,
				const_iterator ae,
				temp_iterator itt,
				std::size_t const parts,
				bool const parallelSearch,
				order_type const & order = order_type()
			)
			{
				return planMerge(ab,ac,ac,ae,itt,parts,parallelSearch,order);
			}

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static std::vector<
				MergeRequestDelayedSearch<
					const_iterator,
					temp_iterator,
					order_type
				>
			> planMergeDelayedSearch(
				const_iterator ab,
				const_iterator ac,
				const_iterator ae,
				temp_iterator itt,
				std::size_t const parts,
				order_type const & order = order_type()
			)
			{
				return planMergeDelayedSearch(ab,ac,ac,ae,itt,parts,order);
			}

			template<
				typename iterator,
				typename temp_iterator
			>
			static std::size_t
				planCopyBack(
				std::vector < std::shared_ptr<SortOp> > & Vop,
				iterator ab,
				iterator ae,
				temp_iterator itt,
				std::size_t const parts
			)
			{
				auto const n = std::distance(ab,ae);

				if ( ! n )
					return 0;

				std::size_t const perthread = (n + parts - 1)/parts;
				std::size_t const numpacks = (n + perthread - 1)/perthread;

				for ( std::size_t i = 0; i < numpacks; ++i )
				{
					auto const low = i * perthread;
					auto const high = (i+1 == numpacks) ? n : low+perthread;

					std::shared_ptr<SortOp> ptr(
						new CopyRequest<temp_iterator,iterator>(
							itt + low,
							itt + high,
							ab + low
						)
					);

					Vop.emplace_back(ptr);
				}

				return numpacks;
			}

			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static
				std::pair
				<
					std::vector
					<
						MergeRequest<
							iterator,
							temp_iterator,
							order_type
						>
					>,
					std::vector
					<
						CopyRequest<
							temp_iterator,
							iterator
						>
					>
				>
				planMergeCopyBack(
				iterator ab,
				iterator ac,
				iterator ae,
				temp_iterator itt,
				std::size_t const parts,
				bool const parallelSearch,
				order_type const & order = order_type()
			)
			{
				auto const Vmerge = planMerge(ab,ac,ae,itt,parts,parallelSearch,order);
				auto const gn = std::distance(ab,ae);
				typename std::remove_const<decltype(gn)>::type o = 0;
				std::vector< CopyRequest<temp_iterator,iterator> > Vcopy(Vmerge.size());

				for ( std::size_t i = 0; i < Vmerge.size(); ++i )
				{
					auto const & req = Vmerge[i];
					auto const n = (req.ae-req.ab) + (req.be-req.bb);

					auto const from = o;
					auto const to   = from + n;

					Vcopy[i] = CopyRequest<temp_iterator,iterator>(itt+from,itt+to,ab+from);

					o = to;
				}

				assert ( o == gn );

				return std::make_pair(Vmerge,Vcopy);
			}

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static void mergeParallel(
				const_iterator ab,
				const_iterator ae,
				const_iterator bb,
				const_iterator be,
				temp_iterator itt,
				std::size_t const threads,
				bool const parallelSearch,
				order_type const & order = order_type()
			)
			{
				auto VREQ = planMerge(ab,ae,bb,be,itt,threads,parallelSearch,order);

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t t = 0; t < VREQ.size(); ++t )
					VREQ[t].dispatch();
			}

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static void mergeParallelDelayedSearch(
				const_iterator ab,
				const_iterator ae,
				const_iterator bb,
				const_iterator be,
				temp_iterator itt,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				auto VREQ = planMergeDelayedSearch(ab,ae,bb,be,itt,threads,order);

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t t = 0; t < VREQ.size(); ++t )
					VREQ[t].dispatch();
			}

			template<
				typename const_iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							const_iterator
						>::value_type
					>
			>
			static void mergeParallelCopyBack(
				const_iterator ab,
				const_iterator ac,
				const_iterator ae,
				temp_iterator itt,
				std::size_t const threads,
				bool const parallelSearch,
				order_type const & order = order_type()
			)
			{
				auto PREQCOPY = planMergeCopyBack(ab,ac,ae,itt,threads,parallelSearch,order);

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t t = 0; t < PREQCOPY.first.size(); ++t )
					PREQCOPY.first[t].dispatch();

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( std::size_t t = 0; t < PREQCOPY.second.size(); ++t )
					PREQCOPY.second[t].dispatch();
			}

			struct SortFinishedInfoInterface
			{
				virtual ~SortFinishedInfoInterface() {}
			};

			struct SortFinishedInterface
			{
				virtual ~SortFinishedInterface() {}
				virtual void sortFinished(std::shared_ptr<SortFinishedInfoInterface>) = 0;
			};

			struct SortInfo
			{
				std::vector < std::vector < std::shared_ptr<SortOp> > > Vop;

				void dispatchOMP(
					std::size_t const
						#if defined(_OPENMP)
						threads
						#endif
				)
				{
					std::atomic<int> parfailed(0);

					for ( std::size_t i = 0; (! parfailed.load()) && i < Vop.size(); ++i )
					{
						std::vector < std::shared_ptr<SortOp> > & Lop = Vop.at(i);

						#if defined(_OPENMP)
						#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
						#endif
						for ( std::size_t t = 0; t < Lop.size(); ++t )
						{
							try
							{
								Lop[t]->dispatch();
							}
							catch(...)
							{
								parfailed.store(1);
							}
						}
					}
				}
			};

			struct SortControl
			{
				std::shared_ptr<SortInfo> SI;
				std::vector < std::shared_ptr < std::atomic < std::size_t > > > VA;

				SortControl(std::shared_ptr<SortInfo> rSI)
				: SI(rSI), VA(SI->Vop.size())
				{
					for ( std::size_t i = 0; i < SI->Vop.size(); ++i )
					{
						std::shared_ptr< std::atomic<std::size_t> > ptr(
							new std::atomic<std::size_t>(SI->Vop[i].size())
						);
						VA[i] = ptr;
					}
				}

				bool dec(std::size_t const j)
				{
					std::atomic<std::size_t> & A = *(VA[j]);
					std::size_t const r = --A;
					return r == 0;
				}
			};

			struct SortPackageData : public libmaus2::parallel::threadpool::ThreadWorkPackageData
			{
				libmaus2::util::atomic_shared_ptr<SortControl> SC;
				std::pair < std::atomic<std::size_t>, std::atomic<std::size_t> > P;
				std::shared_ptr<SortFinishedInfoInterface> finishedInfo;
				std::atomic<SortFinishedInterface *> finishedInterface;

				SortPackageData() : SC(), P(std::make_pair(0,0)), finishedInfo(), finishedInterface(nullptr) {}
			};

			struct SortPackageDispatcher : public libmaus2::parallel::threadpool::ThreadPoolDispatcher
			{
				virtual void dispatch(
					libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::ThreadWorkPackage> package,
					libmaus2::parallel::threadpool::ThreadPool * pool
				)
				{
					SortPackageData & SPD = dynamic_cast<SortPackageData &>(*(package->data.load()));

					std::size_t const first = SPD.P.first.load();
					std::size_t const second = SPD.P.second.load();

					SPD.SC.load()->SI->Vop.at(first).at(second)->dispatch();

					bool const leveldone = SPD.SC.load()->dec(first);

					if ( leveldone )
					{
						std::size_t const nextlevel = first+1;

						if ( nextlevel < SPD.SC.load()->SI->Vop.size() )
						{
							std::size_t const levelsize = SPD.SC.load()->SI->Vop.at(nextlevel).size();

							for ( size_t i = 0; i < levelsize; ++i )
							{
								libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::ThreadWorkPackage> newpack(pool->getPackage<SortPackageData>());
								SortPackageData & newSPD = dynamic_cast<SortPackageData &>(*(newpack->data.load()));
								newSPD.SC.store(SPD.SC.load());
								newSPD.P.first.store(nextlevel);
								newSPD.P.second.store(i);
								newSPD.finishedInfo = SPD.finishedInfo;
								newSPD.finishedInterface.store(SPD.finishedInterface.load());
								pool->enqueue(newpack);
							}
						}
						else
						{
							SPD.finishedInterface.load()->sortFinished(SPD.finishedInfo);
						}
					}
				}
			};

			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void sortWithTempSpace(
				iterator ab,
				iterator ae,
				temp_iterator itt,
				libmaus2::parallel::threadpool::ThreadPool & TP,
				std::shared_ptr<SortFinishedInfoInterface> finishedInfo,
				SortFinishedInterface * finishedInterface,
				order_type const & order = order_type()
			)
			{
				auto const n = std::distance(ab,ae);

				TP.ensureDispatcherRegistered<SortPackageData>(libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::ThreadPoolDispatcher>(new SortPackageDispatcher));

				if ( ! n )
					finishedInterface->sortFinished(finishedInfo);

				std::shared_ptr<SortInfo> SI = planSortWithTempSpace(ab,ae,itt,TP.threads,order);
				libmaus2::util::shared_ptr<SortControl> SC(new SortControl(SI));

				assert ( SI->Vop.size() );

				std::size_t const nextlevel = 0;
				std::size_t const levelsize = SI->Vop.at(nextlevel).size();

				for ( size_t i = 0; i < levelsize; ++i )
				{
					libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::ThreadWorkPackage> newpack(TP.getPackage<SortPackageData>());
					SortPackageData & newSPD = dynamic_cast<SortPackageData &>(*(newpack->data.load()));
					newSPD.SC.store(SC);
					newSPD.P.first.store(nextlevel);
					newSPD.P.second.store(i);
					newSPD.finishedInfo = finishedInfo;
					newSPD.finishedInterface.store(finishedInterface);
					TP.enqueue(newpack);
				}
			}

			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void sortWithTempSpaceViaThreadPool(
				iterator ab,
				iterator ae,
				temp_iterator itt,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				libmaus2::parallel::threadpool::ThreadPool TP(threads);
				TP.registerDispatcher<SortPackageData>(libmaus2::util::shared_ptr<libmaus2::parallel::threadpool::ThreadPoolDispatcher>(new SortPackageDispatcher));

				struct SortFinishedInfo : public SortFinishedInfoInterface
				{
					libmaus2::parallel::threadpool::ThreadPool * TP;

					SortFinishedInfo(libmaus2::parallel::threadpool::ThreadPool * rTP)
					: TP(rTP)
					{
					}
				};

				std::shared_ptr<SortFinishedInfoInterface> info(new SortFinishedInfo(&TP));

				struct SortFinished : public SortFinishedInterface
				{
					virtual void sortFinished(std::shared_ptr<SortFinishedInfoInterface> ptr)
					{
						SortFinishedInfo & info = dynamic_cast<SortFinishedInfo &>(*ptr);
						info.TP->terminate();
					}
				};

				SortFinished SF;

				sortWithTempSpace(ab,ae,itt,TP,info,&SF,order);

				TP.join();
			}


			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static std::shared_ptr<SortInfo> planSortWithTempSpace(
				iterator ab,
				iterator ae,
				temp_iterator itt,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				std::shared_ptr<SortInfo> SI(new SortInfo);

				auto const n = std::distance(ab,ae);

				if ( ! n )
					return SI;

				typedef typename std::remove_const<decltype(n)>::type index_type;

				index_type const packsize = (n + threads - 1) / threads;
				index_type const numpacks = (n + packsize - 1) / packsize;

				std::vector < std::pair<index_type,index_type> > Vblock(numpacks);

				std::vector < std::vector < std::shared_ptr<SortOp> > > & Vop = SI->Vop;

				{
					std::vector < std::shared_ptr<SortOp> > Lop(numpacks);

					for ( index_type t = 0; t < numpacks; ++t )
					{
						index_type const low  = t * packsize;
						index_type const high = std::min(static_cast<index_type>(low+packsize),n);

						Vblock[t] = std::make_pair(low,high);

						std::shared_ptr<SortOp> ptr(
							new SortRequest<iterator,order_type>(ab+low,ab+high,&order)
						);

						Lop[t] = ptr;
					}

					Vop.emplace_back(Lop);
				}


				while ( Vblock.size() > 1 )
				{
					std::vector < std::pair<index_type,index_type> > Nblock;

					std::vector < std::shared_ptr<SortOp> > Lopmerge;
					std::vector < std::shared_ptr<SortOp> > Lopcopy;

					std::size_t i = 0;

					for ( ; i + 1 < Vblock.size(); i += 2 )
					{
						index_type const low = Vblock[i].first;
						index_type const middle = Vblock[i].second;
						assert ( middle == Vblock[i+1].first );
						index_type const high = Vblock[i+1].second;

						planMergeDelayedSearchP(
							Lopmerge,
							ab+low,
							ab+middle,
							ab+middle,
							ab+high,
							itt+low,
							threads,
							order
						);

						planCopyBack(
							Lopcopy,
							ab+low,
							ab+high,
							itt+low,
							threads
						);

						Nblock.push_back(std::make_pair(low,high));
					}

					Vop.emplace_back(Lopmerge);
					Vop.emplace_back(Lopcopy);

					if ( i < Vblock.size() )
						Nblock.push_back(Vblock[i++]);

					assert ( i == Vblock.size() );

					Nblock.swap(Vblock);
				}

				return SI;
			}

			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void sortWithTempSpace(
				iterator ab,
				iterator ae,
				temp_iterator itt,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				std::shared_ptr<SortInfo> SI = planSortWithTempSpace(ab,ae,itt,threads,order);
				SI->dispatchOMP(threads);
			}

			template<
				typename iterator,
				typename temp_iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void sortLimited(
				iterator ab,
				iterator ae,
				temp_iterator itt,
				std::size_t const limit,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				auto const n = std::distance(ab,ae);

				typedef typename std::remove_const<decltype(n)>::type index_type;

				if ( ! n )
					return;

				index_type const packsize = (n + threads - 1) / threads;
				index_type const numpacks = (n + packsize - 1) / packsize;

				std::vector < std::pair<index_type,index_type> > Vblock(numpacks);

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( index_type t = 0; t < numpacks; ++t )
				{
					index_type const low  = t * packsize;
					index_type const high = std::min(static_cast<index_type>(low+packsize),n);

					Vblock[t] = std::make_pair(low,high);

					std::sort(ab + low, ab + high, order);
				}

				while ( Vblock.size() > 1 )
				{
					std::vector < std::pair<index_type,index_type> > Nblock;

					std::size_t i = 0;

					for ( ; i + 1 < Vblock.size(); i += 2 )
					{
						index_type const low = Vblock[i].first;
						index_type const middle = Vblock[i].second;
						assert ( middle == Vblock[i+1].first );
						index_type const high = Vblock[i+1].second;

						mergeLimitedParallel(
							ab + low,ab + middle,ab + high,
							itt,
							limit,
							threads,
							order);

						Nblock.push_back(std::make_pair(low,high));
					}

					if ( i < Vblock.size() )
						Nblock.push_back(Vblock[i++]);

					assert ( i == Vblock.size() );

					Nblock.swap(Vblock);
				}
			}

			template<
				typename iterator,
				typename order_type =
					std::less<
						typename std::iterator_traits<
							iterator
						>::value_type
					>
			>
			static void sortInPlace(
				iterator ab,
				iterator ae,
				std::size_t const threads,
				order_type const & order = order_type()
			)
			{
				auto const n = std::distance(ab,ae);

				typedef typename std::remove_const<decltype(n)>::type index_type;

				if ( ! n )
					return;

				index_type const packsize = (n + threads - 1) / threads;
				index_type const numpacks = (n + packsize - 1) / packsize;

				std::vector < std::pair<index_type,index_type> > Vblock(numpacks);

				#if defined(_OPENMP)
				#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
				#endif
				for ( index_type t = 0; t < numpacks; ++t )
				{
					index_type const low  = t * packsize;
					index_type const high = std::min(static_cast<index_type>(low+packsize),n);

					Vblock[t] = std::make_pair(low,high);

					std::sort(ab + low, ab + high, order);
				}

				while ( Vblock.size() > 1 )
				{
					std::vector < std::pair<index_type,index_type> > Nblock;

					std::size_t i = 0;

					for ( ; i + 1 < Vblock.size(); i += 2 )
					{
						index_type const low = Vblock[i].first;
						index_type const middle = Vblock[i].second;
						assert ( middle == Vblock[i+1].first );
						index_type const high = Vblock[i+1].second;

						mergeInPlaceParallel(
							ab + low,ab + middle,ab + high,
							threads,
							order);

						Nblock.push_back(std::make_pair(low,high));
					}

					if ( i < Vblock.size() )
						Nblock.push_back(Vblock[i++]);

					assert ( i == Vblock.size() );

					Nblock.swap(Vblock);
				}
			}
		};
	}
}
#endif
