/*
    libmaus2
    Copyright (C) 2016 German Tischler

    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_ALIGNMENTWRITERARRAY_HPP)
#define LIBMAUS2_DAZZLER_ALIGN_ALIGNMENTWRITERARRAY_HPP

#include <libmaus2/dazzler/align/AlignmentWriter.hpp>
#include <libmaus2/dazzler/align/SortingOverlapOutputBuffer.hpp>
#include <libmaus2/dazzler/align/LasSort2.hpp>
#include <libmaus2/dazzler/align/LasMerge2.hpp>

namespace libmaus2
{
	namespace dazzler
	{
		namespace align
		{
			struct AlignmentWriterArray
			{
				typedef AlignmentWriterArray this_type;
				typedef std::unique_ptr<this_type> unique_ptr_type;
				typedef std::shared_ptr<this_type> shared_ptr_type;

				std::vector<std::string> Vfn;
				libmaus2::autoarray::AutoArray<libmaus2::dazzler::align::AlignmentWriter::unique_ptr_type> A;

				AlignmentWriterArray(std::string const basefn, uint64_t const n, int64_t const tspace)
				: Vfn(n), A(n)
				{
					for ( uint64_t i = 0; i < n; ++i )
					{
						std::ostringstream ostr;
						ostr << basefn << "_" << std::setw(6) << std::setfill('0') << i;
						Vfn[i] = ostr.str();
						libmaus2::util::TempFileRemovalContainer::addTempFile(Vfn[i]);
						libmaus2::util::TempFileRemovalContainer::addTempFile(libmaus2::dazzler::align::OverlapIndexer::getIndexName(Vfn[i]));
						libmaus2::dazzler::align::AlignmentWriter::unique_ptr_type tptr(
							new libmaus2::dazzler::align::AlignmentWriter(Vfn[i],tspace)
						);
						A[i] = std::move(tptr);
					}
				}

				libmaus2::dazzler::align::AlignmentWriter & operator[](uint64_t const i)
				{
					return *A[i];
				}

				template<typename comparator_type = libmaus2::dazzler::align::OverlapFullComparator>
				void merge(
					std::string const & fn, std::string const & tmp,
					uint64_t const fanin = libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::getDefaultMergeFanIn(),
					uint64_t const numsortthreads = 1,
					uint64_t const nummergethreads = 1
				)
				{
					for ( uint64_t i = 0; i < A.size(); ++i )
					{
						A[i]->flush();
						A[i].reset();
					}

					// std::cerr << "Calling sort and merge..." << std::endl;

					libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::sortAndMergeThread(Vfn,fn,tmp,fanin,numsortthreads,nummergethreads);

					for ( uint64_t i = 0; i < Vfn.size(); ++i )
						libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::removeFileAndIndex(Vfn[i]);
				}

				template<typename comparator_type = libmaus2::dazzler::align::OverlapDataInterfaceFullComparator>
				void merge2(
					std::string const & fn, std::string const & tmp,
					uint64_t const blocksize = 1024*1024,
					bool const index = false,
					uint64_t const fanin = libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::getDefaultMergeFanIn(),
					std::ostream * err = nullptr
				)
				{
					for ( uint64_t i = 0; i < A.size(); ++i )
					{
						A[i]->flush();
						A[i].reset();
					}

					libmaus2::dazzler::align::LasSort2<comparator_type>::lassort2(
						fn,
						Vfn,
						blocksize,
						fanin,
						tmp,
						index,
						err
					);

					for ( uint64_t i = 0; i < Vfn.size(); ++i )
						libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::removeFileAndIndex(Vfn[i]);
				}

				template<typename comparator_type = libmaus2::dazzler::align::OverlapDataInterfaceFullComparator>
				void merge2Parallel(
					uint64_t const numthreads,
					std::string const & fn, std::string const & tmp,
					uint64_t const blocksize = 1024*1024,
					bool const index = false,
					uint64_t const fanin = libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::getDefaultMergeFanIn(),
					std::ostream * err = nullptr
				)
				{
					for ( uint64_t i = 0; i < A.size(); ++i )
					{
						A[i]->flush();
						A[i].reset();
					}

					#if defined(_OPENMP)
					#pragma omp parallel for num_threads(numthreads) schedule(dynamic,1)
					#endif
					for ( uint64_t i = 0; i < Vfn.size(); ++i )
					{
						std::string const tmpfn = Vfn[i] + ".tmp";

						#if 0
						{
							libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
							std::cerr << "[V] sorting " << Vfn[i] << " to " << tmpfn << std::endl;
						}
						#endif

						libmaus2::dazzler::align::LasSort2<comparator_type>::lassort2(
							tmpfn,
							std::vector<std::string>(1,Vfn[i]),
							blocksize,
							fanin,
							tmpfn + "_subtmp",
							false,
							err
						);

						libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::removeFileAndIndex(Vfn[i]);
						libmaus2::aio::OutputStreamFactoryContainer::rename(tmpfn,Vfn[i]);
					}

					libmaus2::dazzler::align::LasMerge2::lasmerge2(
						fn,
						Vfn,
						tmp,
						fanin,
						index
					);

					for ( uint64_t i = 0; i < Vfn.size(); ++i )
						libmaus2::dazzler::align::SortingOverlapOutputBuffer<comparator_type>::removeFileAndIndex(Vfn[i]);
				}
			};
		}
	}
}
#endif
