/*
    libmaus2
    Copyright (C) 2019 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_VCF_VCFSORTER_HPP)
#define LIBMAUS2_VCF_VCFSORTER_HPP

#include <libmaus2/vcf/VCFParser.hpp>
#include <libmaus2/vcf/VCFSortEntry.hpp>
#include <libmaus2/lz/BgzfDeflate.hpp>
#include <libmaus2/sorting/SortingBufferedOutputFile.hpp>

namespace libmaus2
{
	namespace vcf
	{
		struct VCFSorter
		{
			static libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_ptr_type
				getMerger(
					std::istream & in,
					std::string const & tmpfilename,
					std::ostream * vout = nullptr
				)
			{
				libmaus2::util::TempFileRemovalContainer::addTempFile(tmpfilename);

				libmaus2::vcf::VCFParser vcf(in);

				if ( vout )
					vcf.printText(*vout);

				std::pair<
					std::vector<std::string>,
					::libmaus2::trie::LinearHashTrie<char,uint32_t>::shared_ptr_type
				> const vcfContigPair = vcf.getContigNamesAndTrie();
				::libmaus2::trie::LinearHashTrie<char,uint32_t> const & LHT = *(vcfContigPair.second);

				libmaus2::util::TabEntry<> T;
				libmaus2::vcf::VCFSortEntry VE;

				std::pair<bool, char const *> P;

				typedef libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry> sorter_type;
				libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::unique_ptr_type Psorter(
					new libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>(tmpfilename,sorter_type::getDefaultSortBlockSize())
				);

				while ( (P=vcf.readEntry(T)).first )
				{
					uint64_t const s = T.size();

					if ( s >= 5 )
					{
						char const * a = P.second;
						char const * e = T.get(s-1,a).second;

						std::pair<char const *,char const *> P0(T.get(0,a));
						std::pair<char const *,char const *> P1(T.get(1,a));
						std::pair<char const *,char const *> P3(T.get(3,a));
						std::pair<char const *,char const *> P4(T.get(4,a));

						int64_t const contid = LHT.searchCompleteNoFailure(P0.first,P0.second);
						int64_t const pos = libmaus2::vcf::VCFParser::parseUInt(P1.first,P1.second);

						if ( contid >= 0 && pos >= 0 )
						{
							VE.set(
								a,e,contid,pos,
								P3.first-a,
								P3.second-a,
								P4.first-a,
								P4.second-a
							);
							Psorter->put(VE);
						}
						else
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "[E] unable to parse " << std::string(a,e) << std::endl;
							lme.getStream() << "(contid,pos)=(" << contid << "," << pos << ")" << std::endl;
							lme.finish();
							throw lme;
						}
					}
				}

				libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_ptr_type Pmerger(Psorter->getMerger());

				Psorter.reset();

				return Pmerger;
			}

			static libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_ptr_type
				getMerger(
					std::string const & in,
					std::string const & tmpfilename,
					std::ostream * vout = nullptr
				)
			{
				libmaus2::aio::InputStreamInstance ISI(in);
				libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_ptr_type Pmerger(
					getMerger(ISI,tmpfilename,vout)
				);
				return Pmerger;
			}

			static void sort(std::istream & in, std::ostream & out, bool const gz, int const dedup, std::string const & tmpfilename)
			{
				libmaus2::lz::BgzfOutputStream::unique_ptr_type pBOS;

				if ( gz )
				{
					libmaus2::lz::BgzfOutputStream::unique_ptr_type tBOS(new libmaus2::lz::BgzfOutputStream(out));
					pBOS = std::move(tBOS);
				}

				std::ostream & vout = gz ? *pBOS : out;

				libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_ptr_type Pmerger(
					getMerger(in,tmpfilename,&vout)
				);

				libmaus2::vcf::VCFSortEntry VE;
				libmaus2::vcf::VCFSortEntry VEprev;
				bool VEprevvalid = false;

				while ( Pmerger->getNext(VE) )
				{
					if (
						(!dedup)
						||
						(!VEprevvalid)
						||
						(VEprev < VE)
					)
					{
						VE.print(vout);

						#if 0
						if ( VEprevvalid && VE.s_a == VEprev.s_a && VE.s_b == VEprev.s_b )
						{
							std::cerr << std::string(80,'=') << std::endl;
							VEprev.print(std::cerr);
							VE.print(std::cerr);
						}
						#endif
					}

					VEprev.swap(VE);
					VEprevvalid = true;
				}

				Pmerger.reset();

				if ( gz )
				{
					pBOS->flush();
					pBOS->addEOFBlock();
					pBOS.reset();
				}

				libmaus2::aio::FileRemoval::removeFile(tmpfilename);
			}
		};
	}
}
#endif
