/*
    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_LASSORT2CASCADE_HPP)
#define LIBMAUS2_DAZZLER_ALIGN_LASSORT2CASCADE_HPP

#include <libmaus2/aio/InputStreamInstance.hpp>
#include <libmaus2/dazzler/align/OverlapIndexer.hpp>
#include <libmaus2/parallel/NumCpus.hpp>
#include <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/util/ArgParser.hpp>
#include <libmaus2/util/DirectoryStructure.hpp>

namespace libmaus2
{
	namespace dazzler
	{
		namespace align
		{
			struct LasSort2Cascade
			{
				static uint64_t getDefaultFanIn()
				{
					return 16;
				}

				static std::string getTmpFileBase(libmaus2::util::ArgParser const & arg)
				{
					std::string const tmpfilebase = arg.uniqueArgPresent("T") ? arg["T"] : libmaus2::util::ArgInfo::getDefaultTmpFileName(arg.progname);
					return tmpfilebase;
				}

				static void removeLAS(std::ostream & OSI, std::string const & fn)
				{
					OSI << "\trm -f " << fn << "\n";
					OSI << "\trm -f " << libmaus2::dazzler::align::OverlapIndexer::getIndexName(fn) << "\n";
					OSI << "\trm -f " << libmaus2::dazzler::align::DalignerIndexDecoder::getDalignerIndexName(fn) << "\n";
				}

				static void lassort2cascadeImpl(
					std::ostream & mkout,
					std::string const & outfn,
					uint64_t const numthreads,
					uint64_t const fanin,
					bool deletein,
					bool setdep,
					bool const Ddep,
					bool const index,
					std::vector < std::string > Vin,
					std::vector < std::string > VDdep,
					std::string const & tmpbaseglob,
					std::string const & tmpfn,
					uint64_t const blocksize = (1024ull * 1024ull * 1024ull),
					std::string const & sortorder = "canonical"
				)
				{
					if ( Vin.size() == 0 )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] input is empty" << std::endl;
						lme.finish();
						throw lme;
					}

					uint64_t numtmp = 0;
					uint64_t ncur = Vin.size();

					// sorting
					numtmp += ncur;

					while ( ncur > fanin )
					{
						ncur = (ncur + fanin - 1)/fanin;
						// merging
						numtmp += ncur;
					}

					numtmp += 1;

					libmaus2::util::DirectoryStructure DS(
						tmpfn,
						/* tmpbaseglob + "_dirstruct.ftmp", */
						8,numtmp,tmpbaseglob + "_dirstruct"
					);
					std::ostringstream depstr;
					if ( Ddep )
						for ( uint64_t i = 0; i < VDdep.size(); ++i )
							depstr << VDdep[i] << " ";
					if ( setdep )
						for ( uint64_t i = 0; i < Vin.size(); ++i )
							depstr << Vin[i] << " ";
					std::string dep = depstr.str();
					while ( dep.size() && isspace(dep[dep.size()-1]) )
						dep = dep.substr(0,dep.size()-1);

					std::string const dirdep = DS.printGenerate(mkout,dep);

					uint64_t tmpid = 0;
					std::vector < std::string > Vsortsync;
					for ( uint64_t i = 0; i < Vin.size(); ++i )
					{
						std::string const tmpbase = DS[tmpid++];

						std::string const outfn = tmpbase + "_sortfile";

						mkout << outfn << ": " << dirdep << "\n";
						mkout << "\tlassort2";

						mkout << " -t" << numthreads;
						mkout << " -s" << sortorder;
						mkout << " -f" << fanin;
						mkout << " -M" << blocksize;
						if ( index )
							mkout << " --index";

						mkout << " -T" << tmpbase << "_sorttmp";


						mkout << " " << outfn;
						mkout << " " << Vin[i];
						mkout << "\n";

						if ( index )
							mkout << "\tlasindex " << outfn << "\n";

						mkout << outfn << ".cleanup: " << outfn << "\n";

						if ( deletein )
							removeLAS(mkout,Vin[i]);
						else
							mkout << "\techo\n";

						Vsortsync.push_back(outfn);
						Vin[i] = outfn;
					}

					deletein = true;

					while ( Vin.size() > fanin )
					{
						uint64_t const n = Vin.size();
						uint64_t const tnumpacks = (n + fanin - 1)/fanin;
						uint64_t const packsize = (n + tnumpacks - 1)/tnumpacks;
						uint64_t const numpacks = (n + packsize - 1)/packsize;

						std::vector<std::string> Vout;

						for ( uint64_t p = 0; p < numpacks; ++p )
						{
							uint64_t const low  = std::min(p * packsize,n);
							uint64_t const high = std::min(low+packsize,n);

							std::string const tmpbase = DS[tmpid++];

							std::string const outfn = tmpbase + "_sortfile";

							mkout << outfn << ":";
							for ( uint64_t i = low; i < high; ++i )
								mkout << " " << Vin[i] << ".cleanup";
							mkout << "\n";

							mkout << "\tlasmerge2"
								<< " -T" << tmpbase << "_tmpprefix";

							if ( index )
								mkout << " --index";

							mkout << " " << outfn;

							for ( uint64_t i = low; i < high; ++i )
								mkout << " " << Vin[i];

							mkout << "\n";

							if ( index )
								mkout << "\tlasindex " << outfn << "\n";

							mkout << outfn << ".cleanup" << ": " << outfn << "\n";
							if ( deletein )
								for ( uint64_t i = low; i < high; ++i )
									removeLAS(mkout,Vin[i]);
							else
								mkout << "\techo\n";

							Vout.push_back(outfn);
						}

						deletein = true;
						setdep = true;
						Vin = Vout;
					}

					std::string const tmpbase = DS[tmpid++];

					assert ( tmpid == numtmp );

					mkout << outfn << ":";

					for ( uint64_t i = 0; i < Vin.size(); ++i )
						mkout << " " << Vin[i] << ".cleanup";

					mkout << "\n";

					mkout << "\tlasmerge2"
						<< " -T" << tmpbase << "_tmpprefix";

					if ( index )
						mkout << " --index";

					mkout << " " << outfn;

					for ( uint64_t i = 0; i < Vin.size(); ++i )
						mkout << " " << Vin[i];

					mkout << "\n";

					if ( index )
						mkout << "\tlasindex " << outfn << "\n";

					mkout << outfn << ".cleanup: " << outfn << "\n";

					if ( deletein )
					{
						for ( uint64_t i = 0; i < Vin.size(); ++i )
							removeLAS(mkout,Vin[i]);
					}
					else
					{
						mkout << "\techo\n";
					}

					std::string const remdep = DS.printRemove(mkout,outfn + ".cleanup");

					mkout << outfn << ".all: " << remdep << "\n";
					mkout << "\techo\n";

				}

				static void lassort2cascade(libmaus2::util::ArgParser const & arg)
				{
					std::string const outfn = arg[0];
					std::string const sortorder = arg.uniqueArgPresent("s") ? arg["s"] : std::string("canonical");

					bool const listmode = arg.uniqueArgPresent("l");

					uint64_t const numthreads = arg.uniqueArgPresent("t") ? arg.getUnsignedNumericArg<uint64_t>("t") : libmaus2::parallel::NumCpus::getNumLogicalProcessors();
					uint64_t const fanin = arg.uniqueArgPresent("f") ? arg.getUnsignedNumericArg<uint64_t>("f") : getDefaultFanIn();
					uint64_t const blocksize = arg.uniqueArgPresent("M") ? arg.getUnsignedNumericArg<uint64_t>("M") : (1024ull * 1024ull * 1024ull);
					assert ( fanin );
					std::string const tmpbaseglob = getTmpFileBase(arg);
					bool deletein = arg.uniqueArgPresent("deletein");
					bool setdep = arg.uniqueArgPresent("setdep");
					bool Ddep = arg.uniqueArgPresent("D");
					bool const bindex = arg.uniqueArgPresent("index");

					std::vector < std::string > Vin;
					std::vector < std::string > VDdep;

					if ( Ddep )
					{
						libmaus2::aio::InputStreamInstance ISI(arg["D"]);

						while ( ISI )
						{
							std::string s;
							ISI >> s;

							if ( s.size() )
								VDdep.push_back(s);
						}
					}

					if ( listmode )
					{
						for ( uint64_t z = 1; z < arg.size(); ++z )
						{
							libmaus2::aio::InputStreamInstance ISI(arg[z]);

							while ( ISI )
							{
								std::string s;
								ISI >> s;

								if ( s.size() )
									Vin.push_back(s);
							}
						}

						for ( uint64_t i = 0; i < Vin.size(); ++i )
							std::cerr << "[L]\t" << Vin[i] << std::endl;
					}
					else
					{
						for ( uint64_t z = 1; z < arg.size(); ++z )
							Vin.push_back(arg[z]);
					}

					lassort2cascadeImpl(std::cout,outfn,numthreads,fanin,deletein,setdep,Ddep,bindex,Vin,VDdep,
						tmpbaseglob,tmpbaseglob + "_dirstruct.ftmp",blocksize,sortorder);
				}
			};
		}
	}
}
#endif
