/**
    biobambam
    Copyright (C) 2009-2014 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 <biobambam2/BamBamConfig.hpp>
#include <biobambam2/Licensing.hpp>

#include <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/util/MemUsage.hpp>
#include <libmaus2/timing/RealTimeClock.hpp>
#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>
#include <libmaus2/bambam/BamPeeker.hpp>
#include <libmaus2/bambam/BamAlignmentNameComparator.hpp>
#include <regex>

#if defined(BIOBAMBAM_LIBMAUS2_HAVE_IO_LIB)
static std::string getDefaultInputFormat()
{
	return "bam";
}
#endif

void bamalignfrac(::libmaus2::util::ArgInfo const & arginfo)
{
	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper(
		libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arginfo));
	::libmaus2::bambam::BamAlignmentDecoder * ppdec = &(decwrapper->getDecoder());
	::libmaus2::bambam::BamAlignmentDecoder & dec = *ppdec;
	::libmaus2::bambam::BamPeeker peeker(dec);
	::libmaus2::bambam::BamAlignment algn;
	::libmaus2::bambam::BamAlignment prevalgn;
	bool prevalgnvalid = false;
	::libmaus2::bambam::BamHeader const & header = dec.getHeader();
	libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> cigop;
	int const verbose = arginfo.getValue<int>("verbose",0);

	std::string const SO = header.getSortOrder();
	if ( SO != "queryname" )
	{
		libmaus2::exception::LibMausException lme;
		lme.getStream() << "[E] input file not sorted by query name" << std::endl;
		lme.finish();
		throw lme;
	}

	uint64_t basealgn = 0;
	uint64_t clip = 0;
	uint64_t totalbases = 0;
	uint64_t primary = 0;
	uint64_t nameskept = 0;
	uint64_t maps = 0;
	uint64_t unmapped = 0;

        std::string const regexs = arginfo.getUnparsedValue("name","");
        std::unique_ptr<std::regex> regex_ptr;
        if ( regexs.size() )
	{
		std::unique_ptr<std::regex> tregex_ptr(new std::regex(regexs,std::regex_constants::extended));
	        regex_ptr = std::move(tregex_ptr);
	}

	libmaus2::autoarray::AutoArray< libmaus2::math::IntegerInterval<int64_t> > VA[3];
	uint64_t VAo[3];
	uint64_t L[3];
	uint64_t c = 0;

	std::cout << "[C]\tnames passing regex filter" << std::endl;
	std::cout << "[C]\tprimary alignments" << std::endl;
	std::cout << "[C]\ttotal alignments" << std::endl;
	std::cout << "[C]\ttotal number of bases" << std::endl;
	std::cout << "[C]\ttotal number of aligned bases" << std::endl;
	std::cout << "[C]\tfraction of aligned bases" << std::endl;
	std::cout << "[C]\ttotal number of clipped/unaligned bases" << std::endl;
	std::cout << "[C]\tunmapped reads" << std::endl;

	while ( peeker.peekNext(algn) )
	{
		bool const orderok =
			(!prevalgnvalid)
			||
			(!libmaus2::bambam::BamAlignmentNameComparator::compare(algn,prevalgn));

		if ( ! orderok )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] file is not in query name sorted order" << std::endl;
			lme.finish();
			throw lme;
		}

		std::string const sname = algn.getName();
		bool const keep = (!regex_ptr) || ::std::regex_match(algn.getName(),*regex_ptr);

	        std::fill(&VAo[0],&VAo[sizeof(VAo)/sizeof(VAo[0])],0);
	        std::fill(&L[0],&L[sizeof(L)/sizeof(L[0])],0);

	        if ( keep )
			nameskept += 1;

		while (
			peeker.peekNext(algn)
			&&
			(algn.getName() == sname)
		)
		{
			peeker.getNext(algn);

			if ( keep )
			{
			        // uint32_t const numcig = algn.getCigarOperations(cigop);

				if ( !algn.isSecondary() && !algn.isSupplementary() )
				{
					totalbases += algn.getLseq();

					if ( ! algn.isMapped() )
						unmapped += 1;
				}

				if ( algn.isMapped() )
				{
					if ( !algn.isSecondary() && !algn.isSupplementary() )
						primary += 1;

					uint64_t const frontclip = algn.getFrontHardClipping() + algn.getFrontSoftClipping();
					uint64_t const backclip = algn.getBackHardClipping() + algn.getBackSoftClipping();

					libmaus2::math::IntegerInterval<int64_t> const IA(frontclip,static_cast<int64_t>(algn.getLseq()-backclip)-1);

					uint64_t type = 0;

					if ( algn.isRead1() )
						type = 1;
					else if ( algn.isRead2() )
						type = 2;

					VA[type].push(VAo[type],IA);
					L[type] = algn.getLseq();

					maps += 1;
				}
			}
		}

		if ( keep )
		{
			for ( uint64_t type = 0; type < sizeof(VAo)/sizeof(VAo[0]); ++type )
				if ( L[type] )
				{
					libmaus2::math::IntegerInterval<int64_t> * p = VA[type].begin();
					uint64_t const n = VAo[type];

					if ( n )
					{
						::std::sort(p,p+n);
						int64_t minstart = p[0].from;
						int64_t maxend   = p[0].from + p[0].diameter();

						uint64_t low = 0;
						while ( low < n )
						{
							int64_t start = p[low].from;
							int64_t end = p[low].from + p[low].diameter();

							uint64_t high = low+1;
							while ( high < n && end >= p[high].from )
							{
								int64_t const nend = p[high].from + p[high].diameter();

								if ( nend > end )
									end = nend;

								++high;
							}

							assert ( start >= minstart );
							assert ( end >= maxend );
							maxend = end;

							basealgn += end-start;

							low = high;
						}

						clip += minstart,
						clip += L[type]-maxend;
					}
				}
		}

		c += 1;

		if ( verbose && (c % (1024*1024) == 0) )
		{
			std::cerr << "[V]\t" << nameskept << "\t" << primary << "\t" << maps << "\t" << totalbases << "\t" << basealgn << "\t" << static_cast<double>(basealgn)/totalbases << "\t" << clip << "\t" << unmapped << std::endl;
		}

		prevalgnvalid = true;
		prevalgn.copyFrom(algn);
	}

	std::cout << nameskept << "\t" << primary << "\t" << maps << "\t" << totalbases << "\t" << basealgn << "\t" << static_cast<double>(basealgn)/totalbases << "\t" << clip << "\t" << unmapped << std::endl;
}

int main(int argc, char *argv[])
{
	try
	{
		libmaus2::timing::RealTimeClock rtc; rtc.start();

		::libmaus2::util::ArgInfo 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::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> ( "I=<[stdin]>", "input filename (default: read file from standard input)" ) );
				#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> ( "name=<[]>", "consider only reads with names matching the given regualr expression (default: use all reads)" ) );

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

				std::cerr << std::endl;

				return EXIT_SUCCESS;
			}

		bamalignfrac(arginfo);

		std::cerr << "[V] " << libmaus2::util::MemUsage() << " wall clock time " << rtc.formatTime(rtc.getElapsedSeconds()) << std::endl;
	}
	catch(std::exception const & ex)
	{
		std::cerr << ex.what() << std::endl;
		return EXIT_FAILURE;
	}

}
