/*
    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_READINTERVALSVECTOR_HPP)
#define LIBMAUS2_DAZZLER_ALIGN_READINTERVALSVECTOR_HPP

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

namespace libmaus2
{
	namespace dazzler
	{
		namespace align
		{
			struct ReadIntervalsVector
			{
				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(
						std::pair<int64_t,int64_t> const VE,
						std::vector<uint64_t> const & V,
						libmaus2::dazzler::align::LasIntervals & LAI,
						libmaus2::dazzler::db::DatabaseFile const & DB,
						uint64_t const numthreads,
						int64_t const tspace
					)
					{
						LasIntervals::FileAndInfo::shared_ptr_type FAI;

						// 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;

						for ( int64_t z = ifrom; z < ito; ++z )
						{
							int64_t const aread = V[z];
							bool const ok = LAI.openSingle(aread, tspace, FAI);

							if ( ok )
							{
								LasIntervals::FileAndInfoGet FAIG(*FAI);

								// open file parser
								std::pair<uint8_t const *, uint8_t const *> P;
								while ( FAIG.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();

						assert ( rank < II.size() && II[rank] == i );

						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 LAI;
				std::vector<uint64_t> const & RL;
				std::vector<uint64_t> const & V;
				uint64_t const ilow;
				uint64_t const ihigh;
				int64_t const tspace;
				uint64_t const memlimit;
				uint64_t const numthreads;
				std::vector < std::pair<uint64_t,uint64_t> > const VE;

				ReadIntervalsVector(
					libmaus2::dazzler::db::DatabaseFile const & rDB,
					std::vector<std::string> const & Vlasfn,
					std::vector<uint64_t> const & rRL,
					std::vector<uint64_t> const & rV,
					uint64_t const rilow,
					uint64_t const rihigh,
					int64_t const rtspace,
					uint64_t const rmemlimit,
					uint64_t const rnumthreads
				) :
				    DB(rDB), indexostr(),
				    LAI(Vlasfn,DB.size(),indexostr),
				    RL(rRL),
				    V(rV),
				    ilow(rilow), ihigh(rihigh),
				    tspace(rtspace),
				    memlimit(rmemlimit),
				    numthreads(rnumthreads),
				    VE(getVE(LAI,RL,V,ilow,ihigh,tspace,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],
						V,
						LAI,
						DB,
						numthreads,
						tspace
					));

					return tptr;
				}

				static std::vector < std::pair<uint64_t,uint64_t> > getVE(
					libmaus2::dazzler::align::LasIntervals & LAI,
					std::vector<uint64_t> const & RL,
					std::vector<uint64_t> const & V,
					uint64_t const ilow,
					uint64_t const ihigh,
					int64_t const tspace,
					uint64_t const memlimit
				)
				{
					std::vector < std::pair<uint64_t,uint64_t> > VE;
					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(ilow,ilow);
					libmaus2::autoarray::AutoArray<uint64_t> I;
					LasIntervals::FileAndInfo::shared_ptr_type FAI;

					for ( uint64_t z = ilow; z < ihigh; ++z )
					{
						int64_t const aread = V[z];
						bool const ok = LAI.openSingle(aread, tspace, FAI);

						if ( ok )
						{
							LasIntervals::FileAndInfoGet FAIG(*FAI);

							while ( FAIG.peekNext(P) )
							{
								assert ( libmaus2::dazzler::align::OverlapData::getARead(P.first) == aread );

								uint64_t ls = 0;

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

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

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

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

								if ( s == 0 || s+ls <= memlimit )
								{
									s += ls;
									E.second = z+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 += RL[I[i]];
										S.insert(I[i]);
									}

									E = std::pair<uint64_t,uint64_t>(z,z+1);
								}
							}
						}
					}

					if ( E.second > E.first )
						VE.push_back(E);

					return VE;
				}
			};
		}
	}
}
#endif
