/*
    libmaus2
    Copyright (C) 2018 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_DAZZLER_ALIGN_READINTERVALS_HPP)
#define LIBMAUS2_DAZZLER_ALIGN_READINTERVALS_HPP

#include <libmaus2/dazzler/align/LasIntervals.hpp>
#include <libmaus2/dazzler/db/DatabaseFile.hpp>

namespace libmaus2
{
	namespace dazzler
	{
		namespace align
		{
			struct ReadIntervals
			{
				struct ReadData
				{
					typedef ReadData this_type;
					typedef std::unique_ptr<this_type> unique_ptr_type;

					std::vector<uint64_t> II;
					libmaus2::dazzler::db::DatabaseFile::ReadDataRange::unique_ptr_type adata_forward;
					libmaus2::dazzler::db::DatabaseFile::ReadDataRange::unique_ptr_type adata_reverse;

					ReadData(uint8_t const * p, uint64_t const l)
					: II(1,0),
					  adata_forward(libmaus2::dazzler::db::DatabaseFile::ReadDataRange::getSingle(p,l,false /* inv */)),
					  adata_reverse(libmaus2::dazzler::db::DatabaseFile::ReadDataRange::getSingle(p,l,true  /* inv */))
					{

					}

					ReadData(
						std::pair<int64_t,int64_t> const VE,
						libmaus2::dazzler::align::LasIntervals & LAI,
						libmaus2::dazzler::db::DatabaseFile const & DB,
						uint64_t const numthreads
					)
					{
						// lower bound
						int64_t const ifrom = VE.first;
						// upper bound
						int64_t const ito = VE.second;

						{
							// set of read ids
							std::set<uint64_t> SR;

							// open file parser
							libmaus2::dazzler::align::SimpleOverlapParserConcat::unique_ptr_type tptr(LAI.getFileRangeParser(ifrom,ito,1024*1024/*bs*/));
							libmaus2::dazzler::align::SimpleOverlapParserConcatGet Sget(*tptr);
							std::pair<uint8_t const *, uint8_t const *> P;
							while ( Sget.getNext(P) )
							{
								SR.insert(libmaus2::dazzler::align::OverlapData::getARead(P.first));
								SR.insert(libmaus2::dazzler::align::OverlapData::getBRead(P.first));
							}

							II = std::vector<uint64_t>(SR.begin(),SR.end());
						}

						// decode forward
						libmaus2::dazzler::db::DatabaseFile::ReadDataRange::unique_ptr_type tadata_forward(
							DB.decodeReadDataByArrayParallelDecode(II.begin(),II.size(),numthreads,false /* rc */,4 /* term */));
						adata_forward = std::move(tadata_forward);
						// decode reverse
						libmaus2::dazzler::db::DatabaseFile::ReadDataRange::unique_ptr_type tadata_reverse(
							DB.decodeReadDataByArrayParallelDecode(II.begin(),II.size(),numthreads,true /* rc */ ,4 /* term */));
						adata_reverse = std::move(tadata_reverse);
					}

					std::pair<uint8_t const *,uint8_t const *> operator()(uint64_t const i, bool const inv) const
					{
						uint64_t const rank = std::lower_bound(II.begin(),II.end(),i) - II.begin();

						if ( inv )
							return adata_reverse->get(rank);
						else
							return adata_forward->get(rank);
					}

					uint64_t RL(uint64_t i) const
					{
						std::pair<uint8_t const *,uint8_t const *> const P = (*this)(i,false);
						return P.second-P.first;
					}

					std::string decodeRead(uint64_t const i, bool const inv) const
					{
						std::pair<uint8_t const *,uint8_t const *> const P = (*this)(i,inv);
						return std::string(P.first,P.second);
					}

					std::basic_string<uint8_t> getu(uint64_t const i, bool const inv) const
					{
						std::pair<uint8_t const *,uint8_t const *> const P = (*this)(i,inv);
						return std::basic_string<uint8_t>(P.first,P.second);
					}
				};

				libmaus2::dazzler::db::DatabaseFile const & DB;
				std::ostringstream indexostr;
				libmaus2::dazzler::align::LasIntervals::unique_ptr_type pLAI;
				libmaus2::dazzler::align::LasIntervals & LAI;
				std::vector<uint64_t> const & RL;
				int64_t const minaread;
				int64_t const toparead;
				uint64_t const memlimit;
				uint64_t const numthreads;
				std::vector < std::pair<uint64_t,uint64_t> > const VE;

				ReadIntervals(
					libmaus2::dazzler::db::DatabaseFile const & rDB,
					std::vector<std::string> const & Vlasfn,
					std::vector<uint64_t> const & rRL,
					int64_t const rminaread,
					int64_t const rtoparead,
					uint64_t const rmemlimit,
					uint64_t const rnumthreads
				) : DB(rDB), indexostr(), pLAI(new libmaus2::dazzler::align::LasIntervals(Vlasfn,DB.size(),indexostr)),
				    LAI(*pLAI), RL(rRL), minaread(rminaread), toparead(rtoparead), memlimit(rmemlimit), numthreads(rnumthreads),
				    VE(getVE(LAI,RL,minaread,toparead,memlimit))
				{
				}

				ReadIntervals(
					libmaus2::dazzler::db::DatabaseFile const & rDB,
					libmaus2::dazzler::align::LasIntervals & rLAI,
					std::vector<uint64_t> const & rRL,
					int64_t const rminaread,
					int64_t const rtoparead,
					uint64_t const rmemlimit,
					uint64_t const rnumthreads
				) : DB(rDB), indexostr(), pLAI(),
				    LAI(rLAI), RL(rRL), minaread(rminaread), toparead(rtoparead), memlimit(rmemlimit), numthreads(rnumthreads),
				    VE(getVE(LAI,RL,minaread,toparead,memlimit))
				{
				}

				uint64_t size() const
				{
					return VE.size();
				}

				ReadData::unique_ptr_type operator[](uint64_t const i)
				{
					ReadData::unique_ptr_type tptr(new ReadData(
						VE[i],
						LAI,
						DB,
						numthreads
					));

					return tptr;
				}

				static std::vector < std::pair<uint64_t,uint64_t> > getVE(
					libmaus2::dazzler::align::LasIntervals & LAI,
					std::vector<uint64_t> const & RL,
					int64_t const minaread,
					int64_t const toparead,
					uint64_t const memlimit
				)
				{
					std::vector < std::pair<uint64_t,uint64_t> > VE;

					libmaus2::dazzler::align::SimpleOverlapParserConcat::unique_ptr_type tptr(LAI.getFileRangeParser(minaread,toparead,1024*1024/*bs*/));
					libmaus2::dazzler::align::SimpleOverlapParserConcatGet Sget(*tptr);
					std::pair<uint8_t const *, uint8_t const *> P;

					std::set<uint64_t> S;
					uint64_t s = 0;

					std::pair<uint64_t,uint64_t> E(minaread,minaread);
					libmaus2::autoarray::AutoArray<uint64_t> I;

					while ( Sget.peekNext(P) )
					{
						int64_t const aread = libmaus2::dazzler::align::OverlapData::getARead(P.first);

						uint64_t ls = 0;

						if ( S.find(aread) == S.end() )
						{
							ls += 2*RL[aread];
							S.insert(aread);
						}

						uint64_t oI = 0;
						I.push(oI,aread);
						while ( Sget.peekNext(P) && libmaus2::dazzler::align::OverlapData::getARead(P.first) == aread )
						{
							Sget.getNext(P);

							int64_t const bread = libmaus2::dazzler::align::OverlapData::getBRead(P.first);
							I.push(oI,bread);

							if ( S.find(bread) == S.end() )
							{
								ls += 2*RL[bread];
								S.insert(bread);
							}
						}

						if ( s == 0 || s+ls <= memlimit )
						{
							s += ls;
							E.second = aread+1;
						}
						else
						{
							assert ( E.second > E.first );
							VE.push_back(E);

							std::sort(I.begin(),I.begin()+oI);
							oI = (std::unique(I.begin(),I.begin()+oI) - I.begin());
							S.clear();

							s = 0;
							for ( uint64_t i = 0; i < oI; ++i )
							{
								s += 2*RL[I[i]];
								S.insert(I[i]);
							}

							E = std::pair<uint64_t,uint64_t>(aread,aread+1);
						}
					}
					if ( E.second > E.first )
						VE.push_back(E);

					return VE;
				}
			};
		}
	}
}
#endif
