/**
    bambam
    Copyright (C) 2009-2013 German Tischler
    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 <iostream>
#include <queue>

#include <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/util/ArgParser.hpp>
#include <libmaus2/timing/RealTimeClock.hpp>
#include <libmaus2/bambam/BamBlockWriterBaseFactory.hpp>
#include <libmaus2/bambam/BamMergeCoordinate.hpp>
#include <libmaus2/bambam/BamMergeQueryName.hpp>
#include <libmaus2/bambam/BamWriter.hpp>
#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>
#include <libmaus2/lz/BgzfDeflateOutputCallbackMD5.hpp>
#include <libmaus2/bambam/BgzfDeflateOutputCallbackBamIndex.hpp>

#include <biobambam2/Licensing.hpp>

static int getDefaultVerbose() { return 1; }
static int getDefaultMD5() { return 0; }
static int getDefaultIndex() { return 0; }

::libmaus2::bambam::BamHeader::unique_ptr_type updateHeader(
	::libmaus2::util::ArgParser const & arg,
	::libmaus2::bambam::BamHeader const & header
)
{
	std::string const headertext(header.text);

	// add PG line to header
	std::string const upheadtext = ::libmaus2::bambam::ProgramHeaderLineSet::addProgramLine(
		headertext,
		"bamaddne", // ID
		"bamaddne", // PN
		arg.commandline, // CL
		::libmaus2::bambam::ProgramHeaderLineSet(headertext).getLastIdInChain(), // PP
		std::string(PACKAGE_VERSION) // VN
	);
	// construct new header
	::libmaus2::bambam::BamHeader::unique_ptr_type uphead(new ::libmaus2::bambam::BamHeader(upheadtext));

	return uphead;
}

int bamaddne(libmaus2::util::ArgParser const & arg)
{
	arg.printArgs(std::cerr);

	libmaus2::timing::RealTimeClock rtc;
	rtc.start();

	if ( isatty(STDOUT_FILENO) && (!arg.uniqueArgPresent("O")) )
	{
		::libmaus2::exception::LibMausException se;
		se.getStream() << "Refusing write binary data to terminal, please redirect standard output to pipe or file." << std::endl;
		se.finish();
		throw se;
	}

	int const verbose = arg.getParsedArgOrDefault<int>("verbose",getDefaultVerbose());

	/*
	 * start index/md5 callbacks
	 */
	std::string const tmpfilenamebase = arg.getStringArgOrDefault("tmpfile",libmaus2::util::ArgInfo::getDefaultTmpFileName(arg.progname));
	std::string const tmpfileindex = tmpfilenamebase + "_index";
	::libmaus2::util::TempFileRemovalContainer::addTempFile(tmpfileindex);

	std::string md5filename;
	std::string indexfilename;

	std::vector< ::libmaus2::lz::BgzfDeflateOutputCallback * > cbs;
	::libmaus2::lz::BgzfDeflateOutputCallbackMD5::unique_ptr_type Pmd5cb;
	if ( arg.getParsedArgOrDefault<int>("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());
		}
	}
	libmaus2::bambam::BgzfDeflateOutputCallbackBamIndex::unique_ptr_type Pindex;
	if ( arg.getParsedArgOrDefault<int>("index",getDefaultIndex()) )
	{
		if ( libmaus2::bambam::BamBlockWriterBaseFactory::getIndexFileName(arg) != std::string() )
			indexfilename = libmaus2::bambam::BamBlockWriterBaseFactory::getIndexFileName(arg);
		else
			std::cerr << "[V] no filename for index given, not creating index" << std::endl;

		if ( indexfilename.size() )
		{
			libmaus2::bambam::BgzfDeflateOutputCallbackBamIndex::unique_ptr_type Tindex(new libmaus2::bambam::BgzfDeflateOutputCallbackBamIndex(tmpfileindex));
			Pindex = std::move(Tindex);
			cbs.push_back(Pindex.get());
		}
	}
	std::vector< ::libmaus2::lz::BgzfDeflateOutputCallback * > * Pcbs = 0;
	if ( cbs.size() )
		Pcbs = &cbs;
	/*
	 * end md5/index callbacks
	 */

	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper(
		libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arg)
	);
	libmaus2::bambam::BamAlignmentDecoder & decoder = decwrapper->getDecoder();
	libmaus2::bambam::BamAlignment & algn = decoder.getAlignment();
	::libmaus2::bambam::BamHeader::unique_ptr_type uphead(updateHeader(arg,decoder.getHeader()));
	std::string ne("1:N:0:AAAAAAAA");

	libmaus2::bambam::BamBlockWriterBase::unique_ptr_type Pwriter(
		libmaus2::bambam::BamBlockWriterBaseFactory::construct(*uphead,arg,Pcbs));

	std::size_t lineid = 0;
	for ( ; decoder.readAlignment(); )
	{
		int const isread1 = algn.isRead1();
		int const isread2 = algn.isRead2();

		if ( isread1 + isread2 != 1 )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] invalid flags " << algn.getFlags() << " for PE read with name " << algn.getName() << std::endl;
			lme.finish();
			throw lme;
		}

		if ( isread1 )
			ne[0] = '1';
		else
			ne[0] = '2';

		if ( ! algn.hasAux("ne") )
		{
			algn.putAuxString("ne",ne);
		}

		std::size_t const llineid = ++lineid;

		Pwriter->writeAlignment(algn);

		if ( verbose && llineid%(1024*1024) == 0 )
		{
			std::cerr << "[V] " << llineid << "\t" << rtc.getElapsedSeconds() << "\t" << (llineid/rtc.getElapsedSeconds()) << "/s" << std::endl;
		}
	}

	std::cerr << "[V] " << lineid << "\t" << rtc.getElapsedSeconds() << "\t" << lineid/rtc.getElapsedSeconds() << "/s" << std::endl;

	Pwriter.reset();

	if ( Pmd5cb )
	{
		Pmd5cb->saveDigestAsFile(md5filename);
	}
	if ( Pindex )
	{
		Pindex->flush(std::string(indexfilename));
	}

	return EXIT_SUCCESS;
}

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

		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.uniqueArgPresent("help") )
		{
			std::cerr << ::biobambam2::Licensing::license();
			std::cerr << std::endl;

			#if 0
			std::cerr << "Key=Value pairs:" << std::endl;
			std::cerr << std::endl;

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

			V.push_back ( std::pair<std::string,std::string> ( "I=<[filename]>", "input file, can be set multiple times" ) );
			V.push_back ( std::pair<std::string,std::string> ( "SO=<["+getDefaultSortOrder()+"]>]", "sort order (coordinate or queryname)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "level=<["+::biobambam2::Licensing::formatNumber(getDefaultLevel())+"]>", libmaus2::bambam::BamBlockWriterBaseFactory::getBamOutputLevelHelpText() ) );
			V.push_back ( std::pair<std::string,std::string> ( "verbose=<["+::biobambam2::Licensing::formatNumber(getDefaultVerbose())+"]>", "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 (no checksum is computed if md5filename is unset)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "index=<["+::biobambam2::Licensing::formatNumber(getDefaultIndex())+"]>", "create BAM index (default: 0)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "indexfilename=<filename>", "file name for BAM index file (no BAM index is produced if indexfilename is unset)" ) );
			V.push_back ( std::pair<std::string,std::string> ( "tmpfile=<filename>", "prefix for temporary files, default: create files in current directory" ) );
			V.push_back ( std::pair<std::string,std::string> ( "IL=<filename>", "name of file containing input file names" ) );
			#endif

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

			V.push_back ( std::pair<std::string,std::string> ( "-v/--verbose=0/1", "print progress report" ) );

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

			std::cerr << std::endl;
			return EXIT_SUCCESS;
		}

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