/**
    bambam
    Copyright (C) 2014-2014 David K. Jackson
    Copyright (C) 2009-2016 German Tischler
    Copyright (C) 2011-2014 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 <libmaus2/bambam/ChecksumsFactory.hpp>
#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>
#include <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/bambam/BamNumericalIndexDecoder.hpp>
#include <libmaus2/bambam/BamNumericalIndexGenerator.hpp>

#include <biobambam2/Licensing.hpp>
#include <biobambam2/BamBamConfig.hpp>

static int getDefaultHeaderChecksums() { return 0 ; }
static int getDefaultTryOQ() { return 0 ; }
static int getDefaultVerbose() { return 0; }
static int getDefaultThreads() { return 1; }
#if defined(BIOBAMBAM_LIBMAUS2_HAVE_IO_LIB)
static std::string getDefaultInputFormat() { return "bam"; }
#endif
static std::string getDefaultHash() { return "crc32prod"; }

int bamseqchksum(::libmaus2::util::ArgInfo const & arginfo)
{
	libmaus2::timing::RealTimeClock rtc;
	rtc.start();
	double prevtime = 0;

	int const verbose = arginfo.getValue<int>("verbose",getDefaultVerbose());
	int const tryoq = arginfo.getValue<int>("tryoq",getDefaultTryOQ());
	unsigned int const threads = arginfo.getValue<unsigned int>("threads",getDefaultThreads());
	std::string const hash = arginfo.getValue<std::string>("hash",getDefaultHash());
	int const headerchecksums = arginfo.getValue<int>("headerchecksums",getDefaultHeaderChecksums());

	libmaus2::bambam::ChecksumsInterface::unique_ptr_type Pchksums;
	::libmaus2::bambam::BamHeader::unique_ptr_type pheader;

	if ( threads <= 1 )
	{
		// input decoder wrapper
		libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper(
			libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arginfo));
		::libmaus2::bambam::BamAlignmentDecoder & dec = decwrapper->getDecoder();
		::libmaus2::bambam::BamHeader const & header = dec.getHeader();
		::libmaus2::bambam::BamHeader::unique_ptr_type theader(header.uclone());
		pheader = std::move(theader);

		::libmaus2::bambam::BamAlignment & algn = dec.getAlignment();

		libmaus2::bambam::ChecksumsInterface::unique_ptr_type Tchksums(libmaus2::bambam::ChecksumsFactory::construct(hash,*pheader));
		Pchksums = std::move(Tchksums);
		libmaus2::bambam::ChecksumsInterface & chksums = *Pchksums;

		uint64_t c = 0;
		while ( dec.readAlignment() )
		{
			chksums.update(algn,tryoq);

			if ( verbose && (++c & (1024*1024-1)) == 0 )
			{
				double const elapsed = rtc.getElapsedSeconds();
				chksums.printVerbose(std::cerr,c,algn,elapsed-prevtime);
				prevtime = elapsed;
			}
		}
	}
	else
	{
		if ( arginfo.hasArg("inputformat") && arginfo.getUnparsedValue("inputformat",std::string()) != "bam" )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] multi threaded mode not supported for inputformat != bam" << std::endl;
			lme.finish();
			throw lme;
		}

		if ( arginfo.getNumRestArgs() )
		{
			std::string const fn = arginfo.getUnparsedRestArg(0);
			libmaus2::util::ArgInfo arginfofn = arginfo;
			arginfofn.replaceKey("I",fn);
			libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper(
				libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(
					arginfofn
				)
			);
			libmaus2::bambam::BamAlignmentDecoder & dec = decwrapper->getDecoder();

			::libmaus2::bambam::BamHeader const & header = dec.getHeader();
			::libmaus2::bambam::BamHeader::unique_ptr_type theader(header.uclone());
			pheader = std::move(theader);

			libmaus2::bambam::ChecksumsInterface::unique_ptr_type Tchksums(libmaus2::bambam::ChecksumsFactory::construct(hash,*pheader));
			Pchksums = std::move(Tchksums);
		}

		for ( uint64_t z = 0; z < arginfo.getNumRestArgs(); ++z )
		{
			std::string const fn = arginfo.getUnparsedRestArg(z);
			libmaus2::bambam::BamNumericalIndexGenerator::indexFileCheck(fn,1024,threads);
			libmaus2::bambam::BamNumericalIndexDecoder BNID(
				libmaus2::bambam::BamNumericalIndexBase::getIndexName(fn)
			);
			uint64_t const alcnt = BNID.getAlignmentCount();
			uint64_t const alperthread = (alcnt + threads - 1)/threads;
			uint64_t const numpacks = alcnt ? (alcnt + alperthread - 1)/alperthread : 0;
			libmaus2::parallel::StdMutex lock;

			#if defined(_OPENMP)
			#pragma omp parallel for num_threads(threads) schedule(dynamic,1)
			#endif
			for ( uint64_t t = 0; t < numpacks; ++t )
			{
				uint64_t const low = t * alperthread;
				uint64_t const high = std::min(low+alperthread,alcnt);
				uint64_t const lcnt = high-low;
				libmaus2::bambam::ChecksumsInterface::unique_ptr_type Lchksums(libmaus2::bambam::ChecksumsFactory::construct(hash,*pheader));

				libmaus2::bambam::BamRawDecoder::unique_ptr_type prawdec(BNID.getRawDecoderAt(fn, low));

				for ( uint64_t i = 0; i < lcnt; ++i )
				{
					std::pair<uint8_t const *,uint64_t> const P = prawdec->get();
					assert ( P.first && P.second );
					Lchksums->update(P.first,P.second,tryoq);
				}

				libmaus2::parallel::ScopeStdMutex llock(lock);
				Pchksums->update(*Lchksums);
			}
		}
	}

	if ( Pchksums )
	{
		if ( headerchecksums )
			Pchksums->printChecksumsForBamHeader(std::cout);
		else
			Pchksums->printChecksums(std::cout);
	}

	if ( verbose )
	{
		std::cerr << "[V] run time " << rtc.getElapsedSeconds() << " (" << rtc.formatTime(rtc.getElapsedSeconds()) << ")" << std::endl;
	}

	return EXIT_SUCCESS;
}

int main(int argc, char * argv[])
{
	try
	{
		::libmaus2::util::ArgInfo const arginfo(argc,argv);

		for ( uint64_t i = 0; i < arginfo.restargs.size(); ++i )
			if (
				arginfo.restargs[i] == "-v"
				||
				arginfo.restargs[i] == "--version"
			)
			{
				std::cerr << ::biobambam2::Licensing::license();
				return EXIT_SUCCESS;
			}
			else if (
				arginfo.restargs[i] == "-h"
				||
				arginfo.restargs[i] == "--help"
			)
			{
				std::cerr << ::biobambam2::Licensing::license();
				std::cerr << std::endl;
				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> ( "verbose=<["+::biobambam2::Licensing::formatNumber(getDefaultVerbose())+"]>", "print progress information" ) );
				V.push_back ( std::pair<std::string,std::string> ( "tryoq=<["+::biobambam2::Licensing::formatNumber(getDefaultTryOQ())+"]>", "use OQ aux field instead of quality field if present" ) );
				V.push_back ( std::pair<std::string,std::string> ( "headerchecksums=<["+::biobambam2::Licensing::formatNumber(getDefaultHeaderChecksums())+"]>", "print checksum lines in form of comments for SAM/BAM/CRAM header" ) );
				#if defined(BIOBAMBAM_LIBMAUS2_HAVE_IO_LIB)
				V.push_back ( std::pair<std::string,std::string> ( std::string("inputformat=<[")+getDefaultInputFormat()+"]>", "input format: cram, bam or sam" ) );
				V.push_back ( std::pair<std::string,std::string> ( "reference=<[]>", "name of reference FastA in case of inputformat=cram" ) );
				#else
				V.push_back ( std::pair<std::string,std::string> ( "inputformat=<[bam]>", "input format: bam" ) );
				#endif

				V.push_back ( std::pair<std::string,std::string> ( std::string("hash=<[")+getDefaultHash()+"]>", "hash digest function: " + libmaus2::bambam::ChecksumsFactory::getSupportedHashVariantsList()) );

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

				std::cerr << std::endl;

				return EXIT_SUCCESS;
			}

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