/**
    biobambam
    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 <biobambam2/BamBamConfig.hpp>
#include <biobambam2/Licensing.hpp>

#include <iomanip>

#include <config.h>

#include <libmaus2/bambam/CircularHashCollatingBamDecoder.hpp>
#include <libmaus2/bambam/BamToFastqOutputFileSet.hpp>
#include <libmaus2/util/TempFileRemovalContainer.hpp>
#include <libmaus2/util/Histogram.hpp>
#include <libmaus2/bambam/CollatingBamDecoderAlignmentInputCallback.hpp>
#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>

struct DepthHist : public libmaus2::bambam::CollatingBamDecoderAlignmentInputCallback
{
	std::pair<int64_t,int64_t> prev;
	std::vector < uint32_t > D;
	libmaus2::bambam::BamHeader const * bamheader;
	libmaus2::util::Histogram hist;

	DepthHist()
	: prev(-1,-1), D(), bamheader(0)
	{

	}

	void handleD()
	{
		for ( uint64_t i = 0; i < D.size(); ++i )
			if ( D[i] )
				hist(D[i]);
	}

	void flush()
	{
		if ( prev.first != -1 )
			handleD();
	}

	void operator()(::libmaus2::bambam::BamAlignment const & A)
	{
		// consider only mapped pairs
		if (  (!A.isUnmap()) && (!A.isMateUnmap()) )
		{
			// get coordinates
			int64_t const chr = A.getRefID();
			int64_t const pos = A.getPos();

			std::pair<int64_t,int64_t> const next(chr,pos);

			// check for order
			if ( next < prev )
			{
				libmaus2::exception::LibMausException se;
				se.getStream() << "File is not sorted: "
					<< " prev=(" << prev.first << "," << prev.second << ")"
					<< " next=(" << chr << "," << pos << ")"
					<< std::endl;
				se.finish();
				throw se;
			}

			if ( chr != prev.first )
			{
				handleD();
				D.resize(bamheader->getRefIDLength(chr));
				std::fill(D.begin(),D.end(),0);

				std::cerr << "[V] start of " << bamheader->getRefIDName(chr) << std::endl;
			}

			int64_t const start = A.getPos();
			int64_t const end = A.getAlignmentEnd()+1;

			for ( int64_t i = start; i < end; ++i )
				if ( i >= 0 && i < static_cast<int64_t>(D.size()) )
					D[i]++;

			prev.first = chr;
			prev.second = pos;
		}
	}
};

void bamdisthist(libmaus2::bambam::CircularHashCollatingBamDecoder & CHCBD, std::string const & prefix)
{
	libmaus2::bambam::CircularHashCollatingBamDecoder::OutputBufferEntry const * ob = 0;

	// number of alignments written to files
	uint64_t cnt = 0;
	unsigned int const verbshift = 20;
	libmaus2::timing::RealTimeClock rtc; rtc.start();
	::libmaus2::autoarray::AutoArray<uint8_t> T;
	libmaus2::util::Histogram hist;
	libmaus2::util::Histogram tlenhist;
	libmaus2::util::Histogram tlenproperhist;

	while ( (ob = CHCBD.process()) )
	{
		uint64_t const precnt = cnt;

		if (
			ob->fpair &&
			(!libmaus2::bambam::BamAlignmentDecoderBase::isUnmap(libmaus2::bambam::BamAlignmentDecoderBase::getFlags(ob->Da))) &&
			(!libmaus2::bambam::BamAlignmentDecoderBase::isUnmap(libmaus2::bambam::BamAlignmentDecoderBase::getFlags(ob->Db)))
		)
		{
			uint64_t const ranka = libmaus2::bambam::BamAlignmentDecoderBase::getAuxRank(ob->Da, ob->blocksizea);
			uint64_t const rankb = libmaus2::bambam::BamAlignmentDecoderBase::getAuxRank(ob->Db, ob->blocksizeb);

			if ( ranka > rankb )
				hist(ranka-rankb);
			else
				hist(rankb-ranka);

			int32_t const tlen = std::abs(libmaus2::bambam::BamAlignmentDecoderBase::getTlen(ob->Da));

			if ( libmaus2::bambam::BamAlignmentDecoderBase::isProper(libmaus2::bambam::BamAlignmentDecoderBase::getFlags(ob->Da)))
				tlenproperhist(tlen);
			tlenhist(tlen);

			cnt += 2;
		}

		if ( precnt >> verbshift != cnt >> verbshift )
		{
			std::cerr
				<< "[V] " << (cnt >> 20)
				<< "\t" << static_cast<double>(cnt)/rtc.getElapsedSeconds()
				<< std::endl;
		}
	}
	std::cerr
		<< "[V] " << (cnt >> 20)
		<< "\t" << static_cast<double>(cnt)/rtc.getElapsedSeconds()
		<< std::endl;

	{
	libmaus2::aio::OutputStreamInstance disthiststr(prefix+"disthist.gpl");
	hist.print(disthiststr);
	disthiststr.flush();
	}

	std::cerr << "[D] median of dist hist " << hist.median() << std::endl;

	{
	libmaus2::aio::OutputStreamInstance tlenhiststr(prefix+"tlenhist.gpl");
	tlenhist.print(tlenhiststr);
	tlenhiststr.flush();
	}

	std::cerr << "[D] median of tlen hist " << tlenhist.median() << std::endl;

	{
	libmaus2::aio::OutputStreamInstance tlenhistproperstr(prefix+"tlenhistproper.gpl");
	tlenproperhist.print(tlenhistproperstr);
	tlenhistproperstr.flush();
	}

	std::cerr << "[D] median of tlen hist proper " << tlenproperhist.median() << std::endl;
}

void bamdisthist(libmaus2::util::ArgInfo const & arginfo)
{
	uint32_t const excludeflags = libmaus2::bambam::BamFlagBase::stringToFlags(arginfo.getValue<std::string>("exclude","SECONDARY,SUPPLEMENTARY"));
	libmaus2::util::TempFileRemovalContainer::setup();
	std::string const tmpfilename = arginfo.getValue<std::string>("T",arginfo.getDefaultTmpFileName());
	libmaus2::util::TempFileRemovalContainer::addTempFile(tmpfilename);
	std::string const prefix = arginfo.getUnparsedValue("prefix",std::string());

	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper(
		libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arginfo,true /* put rank */));
	::libmaus2::bambam::BamAlignmentDecoder * ppdec = &(decwrapper->getDecoder());
	::libmaus2::bambam::BamAlignmentDecoder & dec = *ppdec;

	::libmaus2::bambam::CircularHashCollatingBamDecoder CHCBD(dec,tmpfilename,excludeflags);
	DepthHist dh;

	dh.bamheader = &(dec.getHeader());
	CHCBD.setInputCallback(&dh);
	bamdisthist(CHCBD,prefix);
	dh.flush();

	std::cerr << "[D] median of depth hist " << dh.hist.median() << std::endl;
	std::cerr << "[D] avg of depth hist " << dh.hist.avg() << std::endl;
}

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::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> ( "exclude=<[SECONDARY,SUPPLEMENTARY]>", "exclude alignments matching any of the given flags" ) );
				V.push_back ( std::pair<std::string,std::string> ( std::string("T=<[") + arginfo.getDefaultTmpFileName() + "]>" , "temporary file name" ) );
				V.push_back ( std::pair<std::string,std::string> ( std::string("prefix=<[]>"), "output file name prefix" ) );

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

				std::cerr << std::endl;
				std::cerr << "Alignment flags: PAIRED,PROPER_PAIR,UNMAP,MUNMAP,REVERSE,MREVERSE,READ1,READ2,SECONDARY,QCFAIL,DUP,SUPPLEMENTARY" << std::endl;

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

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