/**
    bambam
    Copyright (C) 2009-2020 German Tischler-Höhle
    Copyright (C) 2011-2013 Genome Research Limited

    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/bambam/BamBlockWriterBaseFactory.hpp>
#include <libmaus2/bambam/BamWriter.hpp>
#include <libmaus2/bambam/BamHeaderUpdate.hpp>
#include <libmaus2/util/ArgInfo.hpp>
#include <biobambam2/Licensing.hpp>
#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>
#include <libmaus2/bambam/BamPeeker.hpp>
#include <libmaus2/lz/BgzfDeflateOutputCallbackMD5.hpp>
#include <libmaus2/bambam/BgzfDeflateOutputCallbackBamIndex.hpp>

static int getDefaultMD5() { return 0; }

static void printVerbose(std::ostream & errstr, uint64_t const c0, uint64_t const c1, uint64_t const k, bool const verbose, uint64_t const mod)
{
	if ( verbose && ((c0+c1)%mod==0) )
	{
		errstr << "[V] " << c0 << "/" << c1 << "/" << c0+c1 << "/" << k << std::endl;
	}
}

/*
 * compute difference of two name sorted alignments files (SAM/BAM/CRAM)
 */
int bamintersect(libmaus2::util::ArgParser const & arg)
{
	std::ostream & verbstr = std::cerr;
	static uint64_t const mod = 1024*1024;

	libmaus2::util::ArgParser arg0 = arg;
	arg0.replaceArg("I",arg0[0]);
	libmaus2::util::ArgParser arg1 = arg;
	arg1.replaceArg("I",arg0[1]);

	bool const verbose = arg.argPresent("verbose");

	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper0(libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arg0));
	libmaus2::bambam::BamAlignmentDecoder & BD0 = decwrapper0->getDecoder();
	libmaus2::bambam::BamPeeker BP0(BD0);
	libmaus2::bambam::BamAlignment algn0;

	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper1(libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arg1));
	libmaus2::bambam::BamAlignmentDecoder & BD1 = decwrapper1->getDecoder();
	libmaus2::bambam::BamPeeker BP1(BD1);
	libmaus2::bambam::BamAlignment algn1;

	std::string md5filename;

	std::vector< ::libmaus2::lz::BgzfDeflateOutputCallback * > cbs;
	::libmaus2::lz::BgzfDeflateOutputCallbackMD5::unique_ptr_type Pmd5cb;
	if ( arg.getParsedArgOrDefault<uint64_t>("md5",getDefaultMD5()) )
	{
		if ( libmaus2::bambam::BamBlockWriterBaseFactory::getMD5FileName(arg) != std::string() )
			md5filename = libmaus2::bambam::BamBlockWriterBaseFactory::getMD5FileName(arg);
		else
			std::cerr << "[V] no filename for md5 given, not creating hash" << std::endl;

		if ( md5filename.size() )
		{
			::libmaus2::lz::BgzfDeflateOutputCallbackMD5::unique_ptr_type Tmd5cb(new ::libmaus2::lz::BgzfDeflateOutputCallbackMD5);
			Pmd5cb = std::move(Tmd5cb);
			cbs.push_back(Pmd5cb.get());
		}
	}
	std::vector< ::libmaus2::lz::BgzfDeflateOutputCallback * > * Pcbs = 0;
	if ( cbs.size() )
		Pcbs = &cbs;

	// construct writer
	libmaus2::bambam::BamBlockWriterBase::unique_ptr_type Pwriter(libmaus2::bambam::BamBlockWriterBaseFactory::construct(BD0.getHeader(),arg,Pcbs));
	libmaus2::bambam::BamBlockWriterBase & wr = *Pwriter;

	uint64_t c0 = 0, c1 = 0, k = 0;

	while ( BP0.peekNext(algn0) && BP1.peekNext(algn1) )
	{
		char const * name0 = algn0.getName();
		char const * name1 = algn1.getName();
		int const r = libmaus2::bambam::StrCmpNum::strcmpnum(name0,name1);

		// name is in file0 but no in file1
		if ( r < 0 )
		{
			std::string const name0 = algn0.getName();

			while ( BP0.peekNext(algn0) && algn0.getName() == name0 )
			{
				BP0.getNext(algn0);
				wr.writeAlignment(algn0);

				++c0;
				++k;

				printVerbose(verbstr, c0, c1, k, verbose, mod);
			}
		}
		// name is in both files, drop data
		else if ( r == 0 )
		{
			std::string const name = algn0.getName();

			while ( BP0.peekNext(algn0) && algn0.getName() == name )
			{
				BP0.getNext(algn0);

				++c0;
				printVerbose(verbstr, c0, c1, k, verbose, mod);
			}
			while ( BP1.peekNext(algn1) && algn1.getName() == name )
			{
				BP1.getNext(algn1);

				++c1;
				printVerbose(verbstr, c0, c1, k, verbose, mod);
			}
		}
		// name is only in file1, drop data
		else
		{
			std::string const name1 = algn1.getName();

			while ( BP1.peekNext(algn1) && algn1.getName() == name1 )
			{
				BP1.getNext(algn1);
				wr.writeAlignment(algn1);

				++c1;
				printVerbose(verbstr, c0, c1, k, verbose,mod);
			}
		}
	}

	// names only in file0 at end
	while ( BP0.getNext(algn0) )
	{
		wr.writeAlignment(algn0);

		++c0;
		++k;

		printVerbose(verbstr, c0, c1, k, verbose,mod);
	}

	Pwriter.reset();

	if ( Pmd5cb )
		Pmd5cb->saveDigestAsFile(md5filename);

	return EXIT_SUCCESS;
}

int main(int argc, char * argv[])
{
	try
	{
		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> Vformatcons;
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("h","help",false));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("v","version",false));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","verbose",false));

		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> const Vformatin = libmaus2::bambam::BamAlignmentDecoderInfo::getArgumentDefinitions();
		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> const Vformatout = libmaus2::bambam::BamBlockWriterBaseFactory::getArgumentDefinitions();

		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> Vformat =
			libmaus2::util::ArgParser::mergeFormat(libmaus2::util::ArgParser::mergeFormat(Vformatin,Vformatout),Vformatcons);

		libmaus2::util::ArgParser const arg(argc,argv,Vformat);

		if ( arg.argPresent("version") )
		{
			std::cerr << ::biobambam2::Licensing::license();
			return EXIT_SUCCESS;
		}
		else if ( arg.argPresent("help") || arg.size() < 2 )
		{
			std::cerr << ::biobambam2::Licensing::license();
			std::cerr << std::endl;
			std::cerr << "usage: " << arg.progname << " full.bam partial.bam" << std::endl;
			std::cerr << std::endl;
			std::cerr << "Argument:" << std::endl;
			std::cerr << std::endl;

			std::vector< std::pair<std::string,std::string> > V;

			V.push_back ( std::pair<std::string,std::string> ( "-v/--verbose", "print progress report" ) );
			V.push_back ( std::pair<std::string,std::string> ( "--md5 <["+::biobambam2::Licensing::formatNumber(getDefaultMD5())+"]>", "create md5 check sum (default: 0)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "--md5filename <filename>", "file name for md5 check sum (default: extend output file name)" ) );

			::biobambam2::Licensing::printMap(std::cerr,V);

			if ( arg.argPresent("help") )
				return EXIT_SUCCESS;
			else
				return EXIT_FAILURE;
		}

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