/**
    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 <biobambam2/DepthInterval.hpp>

#include <iomanip>

#include <config.h>

#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>
#include <libmaus2/util/NumberSerialisation.hpp>

static void handle(libmaus2::autoarray::AutoArray<uint64_t> const & D, int64_t refid, int64_t & nextrefid, bool const binary, libmaus2::bambam::BamHeader const & header, uint64_t const mindepth, uint64_t const maxdepth)
{
	while ( nextrefid < refid )
	{
		uint64_t const l = header.getRefIDLength(nextrefid);

		biobambam2::DepthInterval const D(nextrefid,0,l,0);

		if ( D.depth >= mindepth && D.depth <= maxdepth )
		{
			if ( binary )
				D.serialise(std::cout);
			else
				std::cout << D << "\n";
		}

		nextrefid += 1;
	}

	assert ( nextrefid == refid );

	uint64_t low = 0;

	while ( low < D.size() )
	{
		uint64_t const d = D[low];
		uint64_t high = low+1;

		while ( high < D.size() && D[high] == d )
			++high;

		biobambam2::DepthInterval const D(refid,low,high,d);

		if ( D.depth >= mindepth && D.depth <= maxdepth )
		{
			if ( binary )
				D.serialise(std::cout);
			else
				std::cout << D << "\n";
		}

		low = high;
	}

	nextrefid = refid+1;
}

void bamsimpledepth(libmaus2::util::ArgParser const & arg)
{
	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper(
		libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arg,true /* put rank */));
	::libmaus2::bambam::BamAlignmentDecoder * ppdec = &(decwrapper->getDecoder());
	::libmaus2::bambam::BamAlignmentDecoder & dec = *ppdec;
	::libmaus2::bambam::BamHeader const & header = dec.getHeader();
	libmaus2::bambam::BamAlignment const & algn = dec.getAlignment();
	uint64_t const mindepth = arg.getParsedArgOrDefault<uint64_t>("mindepth",0);
	uint64_t const maxdepth = arg.getParsedArgOrDefault<uint64_t>("maxdepth",std::numeric_limits<uint64_t>::max());
	bool const binary = arg.getParsedArgOrDefault<int>("binary",0);
	int const verbose = arg.getParsedArgOrDefault<int>("verbose",1);

	int64_t prevrefid = -1;
	int64_t nextrefid = 0;
	libmaus2::autoarray::AutoArray<uint64_t> D;

	for ( uint64_t z = 0; dec.readAlignment(); )
	{
		if ( algn.isMapped() )
		{
			if ( algn.getRefID() != prevrefid )
			{
				if ( prevrefid >= 0 )
					handle(D,prevrefid,nextrefid,binary,header,mindepth,maxdepth);

				prevrefid = algn.getRefID();

				uint64_t const l = header.getRefIDLength(prevrefid);
				D.resize(l);

				std::fill(D.begin(),D.end(),0ull);
			}

			libmaus2::math::IntegerInterval<int64_t> const I = algn.getReferenceInterval();

			uint64_t const from = I.from;
			uint64_t const to = from + I.diameter();

			for ( uint64_t i = from; i < to; ++i )
				D.at(i) += 1;
		}

		if (verbose && (++z % (1024*1024) == 0) )
		{
			std::cerr << "[V] " << z << std::endl;
		}
	}

	if ( prevrefid >= 0)
		handle(D,prevrefid,nextrefid,binary,header,mindepth,maxdepth);

	while ( nextrefid < static_cast<int64_t>(header.getNumRef()) )
	{
		uint64_t const l = header.getRefIDLength(nextrefid);

		biobambam2::DepthInterval const D(nextrefid,0,l,0);

		if ( D.depth >= mindepth && D.depth <= maxdepth )
		{
			if ( binary )
				D.serialise(std::cout);
			else
				std::cout << D << "\n";
		}

		nextrefid += 1;
	}

}

int main(int argc, char * argv[])
{
	try
	{
		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> Vformat = libmaus2::bambam::BamAlignmentDecoderInfo::getArgumentDefinitions();
		Vformat.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","mindepth",true));
		Vformat.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","maxdepth",true));
		Vformat.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","binary",true));
		Vformat.push_back(libmaus2::util::ArgParser::ArgumentDefinition("v","verbose",true));
		Vformat.push_back(libmaus2::util::ArgParser::ArgumentDefinition("T","",true));

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

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