/**
    bambam
    Copyright (C) 2019 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/>.
**/
#include <config.h>

#include <libmaus2/vcf/VCFParser.hpp>
#include <libmaus2/vcf/VCFSortEntry.hpp>
#include <libmaus2/vcf/VCFSorter.hpp>
#include <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/util/ArgParser.hpp>
#include <libmaus2/lz/BgzfDeflate.hpp>

#include <biobambam2/Licensing.hpp>

#include <libmaus2/util/AutoArrayCharBaseAllocator.hpp>
#include <libmaus2/util/AutoArrayCharBaseTypeInfo.hpp>
#include <libmaus2/util/GrowingFreeList.hpp>
#include <libmaus2/sorting/SortingBufferedOutputFile.hpp>
#include <libmaus2/aio/GenericPeeker.hpp>
#include <libmaus2/lz/BgzfDeflate.hpp>

// static int getDefaultVerbose() { return 0; }
static int getDefaultGZ() { return 0; }

struct OutputFile
{
	typedef OutputFile this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;

	libmaus2::aio::OutputStreamInstance OSI;
	libmaus2::lz::BgzfOutputStream::unique_ptr_type pBOS;
	std::ostream & out;

	static libmaus2::lz::BgzfOutputStream::unique_ptr_type getBOS(std::ostream & outstr, bool const gz)
	{
		libmaus2::lz::BgzfOutputStream::unique_ptr_type ptr;

		if ( gz )
		{
			libmaus2::lz::BgzfOutputStream::unique_ptr_type tptr(
				new libmaus2::lz::BgzfOutputStream(outstr)
			);
			ptr = std::move(tptr);
		}

		return ptr;
	}

	static std::ostream & selectStream(std::ostream & out, libmaus2::lz::BgzfOutputStream::unique_ptr_type & ptr, bool const gz)
	{
		if ( gz )
			return *ptr;
		else
			return out;
	}

	OutputFile(std::string const & fn, bool const gz)
	: OSI(fn), pBOS(getBOS(OSI,gz)), out(selectStream(OSI,pBOS,gz))
	{

	}

	void flush()
	{
		if ( pBOS )
			pBOS->flush();
		OSI.flush();
	}
};

static void putEntry(
	libmaus2::vcf::VCFSortEntry const & VSE,
	OutputFile::unique_ptr_type & Pout,
	std::string const & fn,
	bool const gz,
	std::string const & header
)
{
	if ( ! Pout )
	{
		OutputFile::unique_ptr_type Tout(
			new OutputFile(fn,gz)
		);
		Pout = std::move(Tout);
		(Pout->out) << header;
	}

	VSE.print(Pout->out);
}

int vcfdiff(libmaus2::util::ArgParser const & arg)
{
	bool const gz = arg.getParsedArgOrDefault<int>("gz",getDefaultGZ());
	std::string const tmpfilenamebase = arg.uniqueArgPresent("T") ? arg["T"] : libmaus2::util::ArgInfo::getDefaultTmpFileName(arg.progname);

	std::string const outprefix = arg[0];
	std::string const out_only_1_fn = outprefix + "_1.vcf" + (gz ? ".gz" : "");
	std::string const out_only_2_fn = outprefix + "_2.vcf" + (gz ? ".gz" : "");
	std::string const out_12_fn = outprefix + "_12.vcf" + (gz ? ".gz" : "");

	std::ostringstream ostr_header_1;
	std::ostringstream ostr_header_2;

	libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_ptr_type merger1(
		libmaus2::vcf::VCFSorter::getMerger(arg[1],tmpfilenamebase+"_1",&ostr_header_1)
	);
	libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_ptr_type merger2(
		libmaus2::vcf::VCFSorter::getMerger(arg[2],tmpfilenamebase+"_2",&ostr_header_2)
	);

	std::string const header_1 = ostr_header_1.str();
	std::string const header_2 = ostr_header_2.str();

	typedef libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::vcf::VCFSortEntry>::merger_type merger_type;

	libmaus2::aio::GenericPeeker<merger_type> GP1(*merger1);
	libmaus2::aio::GenericPeeker<merger_type> GP2(*merger2);

	libmaus2::vcf::VCFSortEntry VSE1;
	libmaus2::vcf::VCFSortEntry VSE2;

	OutputFile::unique_ptr_type PoutOnly1;
	OutputFile::unique_ptr_type PoutOnly2;
	OutputFile::unique_ptr_type Pout12;

	while ( GP1.peekNext(VSE1) && GP2.peekNext(VSE2) )
	{
		if ( VSE1 < VSE2 )
		{
			GP1.getNext(VSE1);
			putEntry(VSE1,PoutOnly1,out_only_1_fn,gz,header_1);
		}
		else if ( VSE2 < VSE1 )
		{
			GP2.getNext(VSE2);
			putEntry(VSE2,PoutOnly2,out_only_2_fn,gz,header_2);
		}
		else
		{
			GP1.getNext(VSE1);
			GP2.getNext(VSE2);
			putEntry(VSE1,Pout12,out_12_fn,gz,header_1);
		}
	}

	while ( GP1.getNext(VSE1) )
		putEntry(VSE1,PoutOnly1,out_only_1_fn,gz,header_1);
	while ( GP2.getNext(VSE2) )
		putEntry(VSE2,PoutOnly2,out_only_2_fn,gz,header_2);

	return EXIT_SUCCESS;
}

int main(int argc, char * argv[])
{
	try
	{
		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> Vformat;
		Vformat.push_back(libmaus2::util::ArgParser::ArgumentDefinition("T","",true));
		Vformat.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","gz",true));
		::libmaus2::util::ArgParser const arg(argc,argv,Vformat);

		if ( arg.size() < 3 )
		{
			std::cerr << "[E] usage: " << arg.progname << " <out_prefix> <in_1.vcf> <in_2.vcf>" << std::endl;
			return EXIT_FAILURE;
		}

		return vcfdiff(arg);
	}
	catch(std::exception const & ex)
	{
		std::cerr << ex.what() << std::endl;
		return EXIT_FAILURE;
	}
}
