/**
    bambam
    Copyright (C) 2019 German Tischler

    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 <libmaus2/aio/GenericPeeker.hpp>
#include <libmaus2/aio/SerialisedPeeker.hpp>
#include <libmaus2/aio/OutputStreamInstanceArray.hpp>
#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>
#include <libmaus2/bambam/BamNumericalIndexDecoder.hpp>
#include <libmaus2/bambam/BamNumericalIndexGenerator.hpp>
#include <libmaus2/gtf/BAMGTFMap.hpp>
#include <libmaus2/gtf/ExonInfo.hpp>
#include <libmaus2/gtf/IntervalListSet.hpp>
#include <libmaus2/gtf/RangeSets.hpp>
#include <libmaus2/geometry/RangeSet.hpp>
#include <libmaus2/parallel/NumCpus.hpp>
#include <libmaus2/sorting/SortingBufferedOutputFile.hpp>
#include <libmaus2/util/ArgInfo.hpp>

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

static int getDefaultVerbose() { return 0; }
static int getDefaultExportCDNA() { return 0; }
static int getDefaultMapQMin() { return 255; }
static int getDefaultMapQMax() { return 255; }
static double getDefaultUncoveredThres() { return 0.1; }
static double getDefaultUniqueUncoveredThres() { return 0.1; }
static uint64_t getDefaultNumThreads() { return libmaus2::parallel::NumCpus::getNumLogicalProcessors(); }

static std::string formatDouble(double const v)
{
	std::ostringstream ostr;
	ostr << v;
	return ostr.str();
}

#if 0
static std::string print(Gene const & G, libmaus2::autoarray::AutoArray<char> const & Aid)
{
	std::ostringstream ostr;

	ostr << "Gene("
		<< Aid.begin() + G.chr_offset << ","
		<< G.chr_id << ","
		<< Aid.begin() + G.id_offset << ","
		<< Aid.begin() + G.name_offset << ","
		<< G.start << ","
		<< G.end << ","
		<< G.cstart << ","
		<< G.cend << ","
		<< G.freq << ")";

	return ostr.str();
}
#endif

static libmaus2::bambam::BamHeader::unique_ptr_type readBamHeader(libmaus2::util::ArgInfo const & arginfo, std::string const & bamfn)
{
	libmaus2::util::ArgInfo arginfoCopy = arginfo;
	arginfoCopy.replaceKey("I",bamfn);

	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type bamdecwrapper(
		libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arginfoCopy)
	);
	libmaus2::bambam::BamHeader::unique_ptr_type ubamheader(bamdecwrapper->getDecoder().getHeader().uclone());

	return ubamheader;
}

std::string printHelpMessage(libmaus2::util::ArgInfo const & arginfo)
{
	std::ostringstream ostr;

	ostr << ::biobambam2::Licensing::license();

	ostr << std::endl;

	ostr << "synopsis: " << arginfo.progname << " [threads=1] <annotation.gtf> <in.bam>" << std::endl;

	ostr << std::endl;
	ostr << "Key=Value pairs:" << std::endl;
	ostr << 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 report (default: 1)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "mapqmin=<["+::biobambam2::Licensing::formatNumber(getDefaultMapQMin())+"]>", "minimum mapping quality processed (default: 255)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "mapqmax=<["+::biobambam2::Licensing::formatNumber(getDefaultMapQMax())+"]>", "maximum mapping quality processed (default: 255)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "threads=<["+::biobambam2::Licensing::formatNumber(1)+"]>", "number of threads used (default: 1, use 0 for number cores on machine)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "uncoveredthres=<["+::formatDouble(getDefaultUncoveredThres())+"]>", "threshold for fraction of uncovered exon bases per transcript (default: 0.1)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "uniqueuncoveredthres=<["+::formatDouble(getDefaultUniqueUncoveredThres())+"]>", "threshold for fraction of uncovered unique exon bases per transcript (default: 0.1)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "T=<filename>", "prefix for temporary files, default: create files in current directory" ) );
	V.push_back ( std::pair<std::string,std::string> ( "exclude=<[SECONDARY]>", "exclude alignments matching any of the given flags" ) );
	V.push_back ( std::pair<std::string,std::string> ( "exportcdna=<["+::biobambam2::Licensing::formatNumber(getDefaultExportCDNA())+"]>", "export CDNA FastA file using annotation and reference, then quit (requires reference parameter to be set, default: 0)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "reference=<[]>", "name of reference FastA file" ) );

	::biobambam2::Licensing::printMap(ostr,V);

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

	return ostr.str();
}

int bamfeaturecount(libmaus2::util::ArgInfo const & arginfo)
{
	bool const strandspecific = false;
	unsigned int numthreads = arginfo.getValue<unsigned int>("threads",1);
	unsigned int verbose = arginfo.getValue<unsigned int>("verbose",getDefaultVerbose());
	unsigned int exportcdna = arginfo.getValue<unsigned int>("exportcdna",getDefaultExportCDNA());
	if ( numthreads == 0 )
		numthreads = getDefaultNumThreads();
	uint32_t const excludeflags = libmaus2::bambam::BamFlagBase::stringToFlags(arginfo.getUnparsedValue("exclude","SECONDARY"));
	std::string const tmpfilenamebase = arginfo.getValue<std::string>("T",arginfo.getDefaultTmpFileName());
	int const mapqmin = arginfo.getValue<int>("mapqmin",getDefaultMapQMin());
	int const mapqmax = arginfo.getValue<int>("mapqmax",getDefaultMapQMax());
	double const uncoveredthres = arginfo.getValue<double>("uncoveredthres",getDefaultUncoveredThres());
	double const uniqueuncoveredthres = arginfo.getValue<double>("uniqueuncoveredthres",getDefaultUniqueUncoveredThres());

	std::string const annofn = arginfo.getUnparsedRestArg(0);
	std::string const bamfn = arginfo.getUnparsedRestArg(1);

	libmaus2::gtf::GTFData::unique_ptr_type pGTF(libmaus2::gtf::GTFData::obtain(annofn,verbose));
	libmaus2::gtf::GTFData const & gtfdata = *pGTF;

	#if 0
	gtfdata.print(std::cerr);
	#endif

	if ( exportcdna )
	{
		if ( !arginfo.hasArg("reference") )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] required argument reference is missing" << std::endl;
			lme.finish();
			throw lme;
		}

		std::string const fafn = arginfo.getUnparsedValue("reference",std::string());

		Reference ref(fafn);

		for ( uint64_t ti = 0; ti < gtfdata.next_transcript_id; ++ti )
		{
			libmaus2::gtf::Transcript const & T = gtfdata.Atranscript.at(ti);
			libmaus2::gtf::Gene const & G = gtfdata.Agene.at(T.gene_id);
			char const * chr = gtfdata.Aid.begin() + G.chr_offset;
			uint64_t const refid = ref.getIdFor(chr);
			std::string const & s = ref.Vtext.at(refid);

			bool const strand = (T.end-T.start) ? gtfdata.Aexon.at(T.start).strand : true;

			std::ostringstream ostr;

			for ( uint64_t ei = 0; ei < T.end-T.start; ++ei )
			{
				uint64_t const rei =
					strand ? ei : ((T.end-T.start)-ei-1);

				libmaus2::gtf::Exon const & E = gtfdata.Aexon.at(T.start + rei);

				uint64_t const from = E.getFrom();
				uint64_t const to = E.getTo();

				ostr << s.substr(from,to-from);
			}

			std::string cdna = ostr.str();
			for ( uint64_t i = 0; i < cdna.size(); ++i )
				cdna[i] = libmaus2::fastx::remapChar(libmaus2::fastx::mapChar(cdna[i]));

			if ( !strand )
				cdna = libmaus2::fastx::reverseComplementUnmapped(cdna);

			char const * ctrans = gtfdata.Aid.begin() + T.id_offset;
			std::cout << ">" << ctrans << "\n" << cdna << "\n";
		}

		return EXIT_SUCCESS;
	}

	libmaus2::bambam::BamHeader::unique_ptr_type ubamheader(readBamHeader(arginfo,bamfn));
	libmaus2::bambam::BamHeader const & bamheader = *ubamheader;

	std::string const indexfn = libmaus2::bambam::BamNumericalIndexBase::getIndexName(bamfn);
	libmaus2::bambam::BamNumericalIndexGenerator::indexFileCheck(bamfn,indexfn,1024/*block size*/,numthreads/*threads*/);
	libmaus2::bambam::BamNumericalIndexDecoder indexdec(indexfn);
	uint64_t const numaln = indexdec.getAlignmentCount();

	uint64_t const packsperthread = 32;
	uint64_t const targetpacks = numthreads * packsperthread;

	uint64_t const alperthread = (numaln + targetpacks - 1)/targetpacks;
	uint64_t const alpacks = alperthread ? (numaln + alperthread - 1)/alperthread : 0;

	libmaus2::gtf::BAMGTFMap const bamgtfmap(bamheader,gtfdata);
	libmaus2::gtf::RangeSets const RS(gtfdata,bamgtfmap,bamheader);

	libmaus2::gtf::IntervalListSet ILS(gtfdata);

	libmaus2::aio::OutputStreamInstanceArray::unique_ptr_type pOSIA(
		new libmaus2::aio::OutputStreamInstanceArray(
			tmpfilenamebase + "_sorttmp",
			numthreads
		)
	);
	#if 0
	libmaus2::sorting::SerialisingSortingBufferedOutputFileArray<libmaus2::gtf::ExonInterval>::unique_ptr_type sortPtr;
	{
		std::string const sorttmp = tmpfilenamebase + "_sorttmp";
		typedef libmaus2::sorting::SerialisingSortingBufferedOutputFileArray<libmaus2::gtf::ExonInterval> sorter_type;
		sorter_type::unique_ptr_type tsortPtr(
			new sorter_type(
				sorttmp,numthreads,sorter_type::getDefaultBufSize(),true /* register files as temp */
			)
		);
		sortPtr = std::move(tsortPtr);
	}
	#endif

	struct ThreadContext
	{
		typedef ThreadContext this_type;
		typedef std::unique_ptr<this_type> unique_ptr_type;

		libmaus2::autoarray::AutoArray < libmaus2::gtf::Exon const * > query_R1;
		libmaus2::autoarray::AutoArray < libmaus2::gtf::Exon const * > query_R2;
		libmaus2::autoarray::AutoArray<libmaus2::geometry::RangeSet<libmaus2::gtf::Exon>::search_q_element> query_todo;
		libmaus2::autoarray::AutoArray<uint64_t> AGID1;
		libmaus2::autoarray::AutoArray<uint64_t> AGID2;
		libmaus2::autoarray::AutoArray<uint64_t> ATID1;
		libmaus2::autoarray::AutoArray<uint64_t> ATID2;
		libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> Aop;
		libmaus2::gtf::IntervalList::QueryContext ILQC_1;
		libmaus2::gtf::IntervalList::QueryContext ILQC_2;
	};

	libmaus2::autoarray::AutoArray<ThreadContext::unique_ptr_type> AT(numthreads);
	for ( uint64_t t = 0; t < numthreads; ++t )
	{
		ThreadContext::unique_ptr_type tptr(new ThreadContext);
		AT.at(t) = std::move(tptr);
	}

	std::atomic<uint64_t> algnid(0);
	std::atomic<uint64_t> numunique(0);
	std::atomic<uint64_t> numunmatched(0);
	std::atomic<uint64_t> numambig(0);
	std::atomic<uint64_t> numsplit(0);
	std::vector< std::atomic<uint64_t> > Vnumunmatched(gtfdata.Vchr.size());
	std::vector< std::atomic<uint64_t> > Vnumunique(gtfdata.Vchr.size());
	std::vector< std::atomic<uint64_t> > Vnumambig(gtfdata.Vchr.size());

	std::vector< std::atomic<uint64_t> > exonFreq(gtfdata.next_exon_id);
	std::vector< std::atomic<uint64_t> > geneFreq(gtfdata.next_gene_id);
	std::vector< std::atomic<uint64_t> > geneBases(gtfdata.next_gene_id);
	std::atomic<uint64_t> packComplete(0);

	std::atomic<uint64_t> parfailed(0);

	#if defined(_OPENMP)
	#pragma omp parallel for num_threads(numthreads) schedule(dynamic,1)
	#endif
	for ( uint64_t t = 0; t < alpacks; ++t )
	{
		try
		{
			#if defined(_OPENMP)
			uint64_t const tid = omp_get_thread_num();
			#else
			uint64_t const tid = 0;
			#endif

			uint64_t const tlow = t * alperthread;
			uint64_t const thigh = std::min(tlow + alperthread,numaln);
			uint64_t const tsize = thigh-tlow;

			libmaus2::lz::BgzfInflateFile::unique_ptr_type pdecoder(indexdec.getStreamAt(bamfn,tlow));
			libmaus2::bambam::BamAlignment algn;

			ThreadContext & TC = *(AT.at(tid));

			libmaus2::autoarray::AutoArray < libmaus2::gtf::Exon const * > & query_R1 = TC.query_R1;
			libmaus2::autoarray::AutoArray < libmaus2::gtf::Exon const * > & query_R2 = TC.query_R2;
			libmaus2::autoarray::AutoArray<libmaus2::geometry::RangeSet<libmaus2::gtf::Exon>::search_q_element> & query_todo = TC.query_todo;
			libmaus2::autoarray::AutoArray<uint64_t> & AGID1 = TC.AGID1;
			libmaus2::autoarray::AutoArray<uint64_t> & AGID2 = TC.AGID2;
			libmaus2::autoarray::AutoArray<uint64_t> & ATID1 = TC.ATID1;
			libmaus2::autoarray::AutoArray<uint64_t> & ATID2 = TC.ATID2;
			libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> & Aop = TC.Aop;

			//libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::gtf::ExonInterval> & SSBOF = (*sortPtr)[tid];
			libmaus2::aio::OutputStreamInstance & SSBOF = (*pOSIA)[tid];

			for ( uint64_t z = 0; z < tsize; ++z )
			{
				libmaus2::bambam::BamAlignmentDecoder::readAlignmentGz(*pdecoder,algn);

				try
				{
					int const mapq = algn.getMapQ();

					if (
						algn.isMapped() && !algn.isSecondary() && !algn.isSupplementary() &&
						((algn.getFlags() & excludeflags) == 0) &&
						mapq >= mapqmin &&
						mapq <= mapqmax
					)
					{
						int64_t const annoref1 = bamgtfmap.Vmap.at(algn.getRefID());

						if ( annoref1 >= 0 )
						{
							assert ( static_cast<unsigned int>(annoref1) < RS.VRS.size() );
							assert ( gtfdata.Vchr.at(annoref1) == bamheader.getRefIDName(algn.getRefID()) );

							if ( algn.isPaired() && algn.isRead1() && algn.isMateMapped() )
							{
								int64_t const annoref2 = bamgtfmap.Vmap.at(algn.getRefID());

								libmaus2::math::IntegerInterval<int64_t> const I1 = algn.getReferenceInterval();
								std::pair<uint64_t,uint64_t> P1(I1.from,I1.from+I1.diameter());
								libmaus2::math::IntegerInterval<int64_t> const I2 = algn.getNextReferenceInterval(Aop);
								std::pair<uint64_t,uint64_t> P2(I2.from,I2.from+I2.diameter());

								uint64_t const from_1 = I1.from;
								uint64_t const to_1 = from_1 + I1.diameter();
								uint64_t const from_2 = I2.from;
								uint64_t const to_2 = from_2 + I2.diameter();
								libmaus2::gtf::IntervalList const & IL_1 = ILS[annoref1];
								// std::pair<uint64_t,uint64_t> const IL_1_o = IL_1.query(std::pair<uint64_t,uint64_t>(from_1,to_1),TC.ILQC_1,AP64[annoref1]->begin(),annoref1);
								std::pair<uint64_t,uint64_t> const IL_1_o = ILS.query(std::pair<uint64_t,uint64_t>(from_1,to_1),TC.ILQC_1,annoref1);
								libmaus2::gtf::IntervalList const & IL_2 = ILS[annoref2];
								// std::pair<uint64_t,uint64_t> const IL_2_o = IL_2.query(std::pair<uint64_t,uint64_t>(from_2,to_2),TC.ILQC_2,AP64[annoref2]->begin(),annoref2);
								std::pair<uint64_t,uint64_t> const IL_2_o = ILS.query(std::pair<uint64_t,uint64_t>(from_2,to_2),TC.ILQC_2,annoref2);

								uint64_t const o1 =
									strandspecific
									?
										(
											algn.isReverse()
											?
											RS.VRS_reverse[annoref1]->search(libmaus2::gtf::Exon(P1.first,P1.second),query_R1,query_todo)
											:
											RS.VRS_forward[annoref1]->search(libmaus2::gtf::Exon(P1.first,P1.second),query_R1,query_todo)
										)
										:
										RS.VRS[annoref1]->search(libmaus2::gtf::Exon(P1.first,P1.second),query_R1,query_todo)
									;
								uint64_t const o2 =
									strandspecific
									?
										(
											algn.isReverse()
											?
											RS.VRS_forward[annoref2]->search(libmaus2::gtf::Exon(P2.first,P2.second),query_R2,query_todo)
											:
											RS.VRS_reverse[annoref2]->search(libmaus2::gtf::Exon(P2.first,P2.second),query_R2,query_todo)
										)
										:
										RS.VRS[annoref2]->search(libmaus2::gtf::Exon(P2.first,P2.second),query_R2,query_todo)
									;

								uint64_t AGID_o1 = 0;
								uint64_t ATID_o1 = 0;

								for ( uint64_t i = 0; i < o1; ++i )
								{
									libmaus2::gtf::Exon const & exon = *(query_R1[i]);
									assert ( exon == gtfdata.Aexon.at(exon.id) );
									libmaus2::gtf::Transcript const & transcript = gtfdata.Atranscript[exon.transcript_id];
									uint64_t const gene_id = transcript.gene_id;
									AGID1.push(AGID_o1,gene_id);
									ATID1.push(ATID_o1,exon.transcript_id);
								}

								std::sort(AGID1.begin(),AGID1.begin()+AGID_o1);
								AGID_o1 = std::unique(AGID1.begin(),AGID1.begin()+AGID_o1) - AGID1.begin();
								std::sort(ATID1.begin(),ATID1.begin()+ATID_o1);
								ATID_o1 = std::unique(ATID1.begin(),ATID1.begin()+ATID_o1) - ATID1.begin();

								uint64_t AGID_o2 = 0;
								uint64_t ATID_o2 = 0;

								for ( uint64_t i = 0; i < o2; ++i )
								{
									libmaus2::gtf::Exon const & exon = *(query_R2[i]);
									assert ( exon == gtfdata.Aexon.at(exon.id) );
									libmaus2::gtf::Transcript const & transcript = gtfdata.Atranscript.at(exon.transcript_id);
									uint64_t const gene_id = transcript.gene_id;
									AGID2.push(AGID_o2,gene_id);
									ATID2.push(ATID_o2,exon.transcript_id);
								}

								std::sort(AGID2.begin(),AGID2.begin()+AGID_o2);
								AGID_o2 = std::unique(AGID2.begin(),AGID2.begin()+AGID_o2) - AGID2.begin();
								std::sort(ATID2.begin(),ATID2.begin()+ATID_o2);
								ATID_o2 = std::unique(ATID2.begin(),ATID2.begin()+ATID_o2) - ATID2.begin();

								if ( annoref1 == annoref2 )
								{
									int64_t const annoref = annoref1;

									// only one gene
									if ( AGID_o1 == 1 && AGID_o2 == 1 && AGID1[0] == AGID2[0] )
									{
										// merge transcript id list
										uint64_t i_1 = 0;
										uint64_t i_2 = 0;
										uint64_t ATID_o = 0;
										while ( i_1 < ATID_o1 && i_2 < ATID_o2 )
											if (
												ATID1[i_1] < ATID2[i_2]
											)
												++i_1;
											else if (
												ATID2[i_2] < ATID1[i_1]
											)
												++i_2;
											else
											{
												assert ( ATID1[i_1] == ATID2[i_2] );

												uint64_t const transid = ATID1[i_1];

												++i_1;
												++i_2;

												ATID1[ATID_o++] = transid;
											}

										for ( uint64_t i = 1; i < ATID_o; ++i )
											assert ( ATID1[i-1] < ATID1[i] );

										// if there is a shared transcript
										if ( ATID_o > 0 )
										{
											for ( uint64_t i = 0; i < IL_1_o.second; ++i )
											{
												libmaus2::gtf::ExonInterval const & EI = TC.ILQC_1.EI[i];

												uint64_t const intid = IL_1.getId(EI);
												libmaus2::gtf::IntervalList::IntervalWithId const & IWID = IL_1.V[intid];

												// std::cerr << I1 << " " << EI << " " << IWID.toString() << std::endl;

												assert ( EI.from >= IWID.Q.first );
												assert ( EI.to   <= IWID.Q.second );
												assert ( IWID.end > IWID.start );

												bool found = false;

												for ( uint64_t j = IWID.start; j < IWID.end; ++j )
												{
													//libmaus2::gtf::Exon const & E = (*(AP64[annoref1]))[IL_1.Aid[j]];
													libmaus2::gtf::Exon const & E = ILS(annoref1,IL_1.Aid[j]);

													assert ( static_cast<int64_t>(EI.from) >= E.getFrom() );
													assert ( static_cast<int64_t>(EI.to  ) <= E.getTo()   );

													uint64_t const transid = E.transcript_id;
													uint64_t const * ATID_p = std::lower_bound(ATID1.begin(),ATID1.begin()+ATID_o,transid);
													if ( ATID_p != ATID1.begin()+ATID_o && *ATID_p == transid )
													{
														found = true;
													}
												}

												if ( found )
												{
													EI.serialise(SSBOF);
												}
											}
											for ( uint64_t i = 0; i < IL_2_o.second; ++i )
											{
												libmaus2::gtf::ExonInterval const & EI = TC.ILQC_2.EI[i];

												uint64_t const intid = IL_2.getId(EI);
												libmaus2::gtf::IntervalList::IntervalWithId const & IWID = IL_2.V[intid];

												// std::cerr << I2 << " " << EI << " " << IWID.toString() << std::endl;

												assert ( EI.from >= IWID.Q.first );
												assert ( EI.to   <= IWID.Q.second );
												assert ( IWID.end > IWID.start );

												bool found = false;

												for ( uint64_t j = IWID.start; j < IWID.end; ++j )
												{
													// libmaus2::gtf::Exon const & E = (*(AP64[annoref2]))[IL_2.Aid[j]];
													libmaus2::gtf::Exon const & E = ILS(annoref2,IL_2.Aid[j]);

													assert ( static_cast<int64_t>(EI.from) >= E.getFrom() );
													assert ( static_cast<int64_t>(EI.to  ) <= E.getTo()   );

													uint64_t const transid = E.transcript_id;
													uint64_t const * ATID_p = std::lower_bound(ATID2.begin(),ATID2.begin()+ATID_o,transid);
													if ( ATID_p != ATID2.begin()+ATID_o && *ATID_p == transid )
													{
														found = true;
													}
												}

												if ( found )
												{
													EI.serialise(SSBOF);
												}
											}
										}

										geneFreq [AGID1[0]] += 1;
										geneBases[AGID1[0]] += algn.getReferenceLength() + algn.getNextReferenceLength(Aop);

										for ( uint64_t i = 0; i < o1; ++i )
										{
											libmaus2::gtf::Exon const & exon = *(query_R1[i]);
											exonFreq[exon.id] += 1;
										}
										for ( uint64_t i = 0; i < o2; ++i )
										{
											libmaus2::gtf::Exon const & exon = *(query_R2[i]);
											exonFreq[exon.id] += 1;
										}

										numunique += 1;
										Vnumunique[annoref] += 1;
									}
									else if ( AGID_o1 == 0 || AGID_o2 == 0 )
									{
										numunmatched += 1;
										Vnumunmatched[annoref] += 1;
									}
									else
									{
										numambig += 1;
										Vnumambig[annoref] += 1;
									}
								}
								else
								{
									numsplit += 1;
								}
							}
							else if ( algn.isPaired() && !algn.isMateMapped() )
							{
								numunmatched += 1;
								Vnumunmatched[annoref1] += 1;
							}

							#if 0
							else if ( ! algn.isPaired() || ! algn.isMateMapped() )
							{

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

								uint64_t const o =
									strandspecific
									?
										(
										algn.isRead1()
											?
											(
												algn.isReverse()
												?
												VRS_reverse[annoref]->search(libmaus2::gtf::Exon(from,to),query_R,query_todo)
												:
												VRS_forward[annoref]->search(libmaus2::gtf::Exon(from,to),query_R,query_todo)
											)
											:
											(
												algn.isReverse()
												?
											VRS_forward[annoref]->search(libmaus2::gtf::Exon(from,to),query_R,query_todo)
												:
												VRS_reverse[annoref]->search(libmaus2::gtf::Exon(from,to),query_R,query_todo)
											)
										)
										:
										VRS[annoref]->search(libmaus2::gtf::Exon(from,to),query_R,query_todo);
										;

								libmaus2::gtf::IntervalList const & IL = *AIL[annoref];

								uint64_t const ILo = IL.query(std::pair<uint64_t,uint64_t>(from,to),ILQC,AP64[annoref]->begin());
								std::vector<libmaus2::gtf::Exon> VEA(ILQC.E.begin(),ILQC.E.begin()+ILo);
								std::sort(VEA.begin(),VEA.end(),ExonIdOffsetComparator());
								std::vector<libmaus2::gtf::Exon> VEB(o);
								for ( uint64_t i = 0; i < o; ++i )
									VEB[i] = *(query_R[i]);
								std::sort(VEB.begin(),VEB.end(),ExonIdOffsetComparator());

								if ( ILo != o )
								{
									std::cerr << "ILo=" << ILo << " o=" << o << std::endl;
									exit(1);
								}
								else
								{
									for ( uint64_t i = 0; i < o; ++i )
										if ( VEA[i].id_offset != VEB[i].id_offset )
										{
											std::cerr << VEA[i].id_offset << " != " <<  VEB[i].id_offset << std::endl;
											exit(1);
										}
								}

								#if defined(VERBOSE)
								std::cerr << IA << " " << algn.formatAlignment(bamheader) << std::endl;
								#endif

								uint64_t AGID_o = 0;

								for ( uint64_t i = 0; i < o; ++i )
								{
									libmaus2::gtf::Exon const & exon = *(query_R[i]);
									libmaus2::gtf::Transcript const & transcript = Atranscript[exon.transcript_id];
									uint64_t const gene_id = transcript.gene_id;
									AGID.push(AGID_o,gene_id);

									#if defined(VERBOSE)
									char const * exid = Aid.begin() + exon.id_offset;
									std::cerr << "\t[" << i <<"]\t" << exon.getFrom() << "\t" << exon.getTo() << "\t" << exid << std::endl;
									#endif
								}


								std::sort(AGID.begin(),AGID.begin()+AGID_o);
								AGID_o = std::unique(AGID.begin(),AGID.begin()+o) - AGID.begin();

								if ( AGID_o == 0 )
								{
									numunmatched += 1;
									Vnumunmatched[annoref] += 1;
								}
								else if ( AGID_o == 1 )
								{
									Agene[
										AGID[0]
									].freq += 1;

									numunique += 1;
									Vnumunique[annoref] += 1;
								}
								else
								{
									#if 0
									for ( uint64_t i = 0; i < AGID_o; ++i )
									{
										std::cerr << "[" << i << "]=" << print(Agene[AGID[i]],Aid) << std::endl;
									}
									#endif

									numambig += 1;
									Vnumambig[annoref] += 1;
								}
							}
							#endif
						}
						else
						{
							// std::cerr << "[W] no GTF data found for " << algn.formatAlignment(bamheader) << std::endl;
						}
					}

					#if 0
					if ( ((++algnid) % (1024*1024) == 0) )
					{
						std::cerr << "[V]\tprocessed\t" << algnid << "\t" << bamheader.getRefIDName(algn.getRefID()) << "\t" << algn.getPos() << "\t" << numunmatched << "\t" << numunique << "\t" << numambig << "\t" << numsplit << std::endl;
					}
					#endif
				}
				catch(std::exception const & ex)
				{
					libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
					std::cerr << "[E] encountered exception processing " << algn.formatAlignment(bamheader) << std::endl;
					throw;
				}
			}

			uint64_t const lpackcomplete = ++packComplete;

			if ( verbose )
			{
				libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
				std::cerr << "[V] complete\t" << lpackcomplete << "/" << alpacks << "\t" << static_cast<double>(lpackcomplete) / alpacks << std::endl;
			}
		}
		catch(std::exception const & ex)
		{
			parfailed = 1;
			libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
			std::cerr << ex.what() << std::endl;
		}
	}

	if ( parfailed )
	{
		libmaus2::exception::LibMausException lme;
		lme.getStream() << "[E] parallel loop failed" << std::endl;
		lme.finish();
		throw lme;
	}


	{
		libmaus2::autoarray::AutoArray<libmaus2::gtf::ExonInfo> AEI(gtfdata.next_exon_id);

		std::string const concfn = tmpfilenamebase + "_concat";
		libmaus2::util::TempFileRemovalContainer::addTempFile(concfn);
		pOSIA->concat(concfn);

		struct ExonIntervalOrder : public libmaus2::aio::MemorySerialisedOrder
		{
			bool operator()(libmaus2::gtf::ExonInterval const & A, libmaus2::gtf::ExonInterval const & B) const
			{
				return A < B;
			}

			bool operator()(char const * aa, char const * /* ae */, char const * ba, char const * /* be */) const
			{
				libmaus2::gtf::ExonInterval const * A = reinterpret_cast<libmaus2::gtf::ExonInterval const *>(aa);
				libmaus2::gtf::ExonInterval const * B = reinterpret_cast<libmaus2::gtf::ExonInterval const *>(ba);
				// std::cerr << *A << " comp " << *B << std::endl;
				return *A < *B;
			}
		};

		std::cerr << "[V] sorting exon interval...";
		typedef libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::gtf::ExonInterval,ExonIntervalOrder> sorter_type;
		libmaus2::sorting::SerialisingSortingBufferedOutputFile<libmaus2::gtf::ExonInterval,ExonIntervalOrder>::sort(
			concfn,
			8ull*1024ull*1024ull*1024ull,
			sorter_type::getDefaultBackBlockSize(),
			sorter_type::getDefaultMaxFan(),
			numthreads
		);
		std::cerr << "done." << std::endl;

		#if 0
		{
		libmaus2::aio::SerialisedPeeker<libmaus2::gtf::ExonInterval> GP(concfn);
		libmaus2::gtf::ExonInterval EI;
		while ( GP.peekNext(EI) )
		{
			uint64_t const chrid = EI.chr;
			uint64_t c = 0;
			std::string const & name = gtfdata.Vchr.at(chrid);
			std::string const pref = "Niels";
			bool const isniels = name.size() >= pref.size() && name.substr(0,pref.size()) == pref;

			while ( GP.peekNext(EI) && EI.chr == chrid )
			{
				GP.getNext(EI);
				if ( isniels )
					std::cerr << name << "\t" << EI << std::endl;
				++c;
			}

			std::cerr << gtfdata.Vchr.at(chrid) << "\t" << c << std::endl;
		}
		}
		#endif

		libmaus2::aio::SerialisedPeeker<libmaus2::gtf::ExonInterval> GP(concfn);
		libmaus2::util::FiniteSizeHeap<uint64_t> FSHD(0);

		libmaus2::gtf::ExonInterval EI;
		while ( GP.peekNext(EI) )
		{
			uint64_t const chrid = EI.chr;
			uint64_t nextintid = 0;
			libmaus2::gtf::IntervalList const & IL = ILS[chrid]; // *(AIL[chrid]);

			// std::cerr << "[V] handling chrid=" << chrid << " chr " << gtfdata.Vchr[chrid] << std::endl;

			struct DepthCallback
			{
				uint64_t uncovered;
				uint64_t basecover;
				uint64_t total;
				uint64_t maxdepth;

				DepthCallback()
				: uncovered(0), basecover(0), total(0), maxdepth(0) {}

				void operator()(uint64_t const from, uint64_t const to, uint64_t const depth)
				{
					// std::cerr << "[" << from << "," << to << ") " << depth << "\n";

					uint64_t const l = to-from;

					if ( depth > maxdepth )
						maxdepth = depth;

					if ( depth )
					{
						basecover += l * depth;
					}
					else
					{
						uncovered += l;
					}

					total += l;
				}

				void print(std::ostream & out) const
				{
					if ( total )
					{
						double const dtotal = total;
						out << "uncovered " << uncovered/dtotal << " avg " << basecover/dtotal << std::endl;
					}
					else
					{
						out << "uncovered " << 1 << " avg " << 0 << std::endl;
					}
				}
			};

			while ( GP.peekNext(EI) && EI.chr == chrid )
			{
				// interval id
				uint64_t const intid = IL.getId(EI);
				while ( nextintid < intid )
				{
					libmaus2::gtf::IntervalList::IntervalWithId const & IWI = IL.get(nextintid++);

					bool const unique = IWI.end-IWI.start == 1;
					for ( uint64_t z = IWI.start; z < IWI.end; ++z )
					{
						uint64_t const index = IL.Aid[z];
						// libmaus2::gtf::Exon const & PE = (*(AP64[chrid]))[index];
						libmaus2::gtf::Exon const & PE = ILS(chrid,index);
						uint64_t const exonid = PE.id;

						libmaus2::gtf::ExonInfo & info = AEI[exonid];
						if ( unique )
						{
							info.unique.uncovered += IWI.Q.second-IWI.Q.first;
							info.unique.total     += IWI.Q.second-IWI.Q.first;
						}
						else
						{
							info.ambig.uncovered += IWI.Q.second-IWI.Q.first;
							info.ambig.total     += IWI.Q.second-IWI.Q.first;

						}
					}
				}
				assert ( nextintid == intid );

				libmaus2::gtf::IntervalList::IntervalWithId const & IWI = IL.get(intid);

				// std::cerr << std::string(30,'-') << IL.V[intid].toString() << std::endl;

				DepthCallback DC;
				uint64_t prevstart = IWI.Q.first;
				uint64_t depth = 0;

				while ( GP.peekNext(EI) && EI.chr == chrid && IL.getId(EI) == intid )
				{
					while ( !FSHD.empty() && FSHD.top() <= EI.from )
					{
						uint64_t const e = FSHD.pop();

						if ( e > prevstart )
						{
							// std::cerr << "[" << prevstart << "," << e << ") " << depth << "\n";
							DC(prevstart,e,depth);
							// [prevstart,e)
							prevstart = e;
						}

						depth -= 1;
					}

					if ( EI.from > prevstart )
					{
						// std::cerr << "[" << prevstart << "," << EI.from << ") " << depth << "\n";
						DC(prevstart,EI.from,depth);
						// [prevstart,e)
						prevstart = EI.from;
					}

					depth += 1;
					FSHD.pushBump(EI.to);

					GP.getNext(EI);
				}

				while ( !FSHD.empty() )
				{
					uint64_t const e = FSHD.pop();

					if ( e > prevstart )
					{
						// std::cerr << "[" << prevstart << "," << e << ") " << depth << "\n";
						DC(prevstart,e,depth);
						// [prevstart,e)
						prevstart = e;
					}

					depth -= 1;
				}

				assert ( depth == 0 );

				if ( prevstart < IWI.Q.second )
				{
					DC(prevstart,IWI.Q.second,depth);
					// std::cerr << "[" << prevstart << "," << IWI.Q.second << ") " << depth << "\n";
				}

				// DC.print(std::cerr);

				bool const unique = ((IWI.end-IWI.start) == 1);
				for ( uint64_t z = IWI.start; z < IWI.end; ++z )
				{
					uint64_t const index = IL.Aid[z];
					// libmaus2::gtf::Exon const & PE = (*(AP64[chrid]))[index];
					libmaus2::gtf::Exon const & PE = ILS(chrid,index);
					uint64_t const exonid = PE.id;

					libmaus2::gtf::ExonInfo & info = AEI[exonid];
					if ( unique )
					{
						info.unique.uncovered += DC.uncovered;
						info.unique.basecount += DC.basecover;
						info.unique.total += DC.total;
						info.unique.maxdepth = std::max(info.unique.maxdepth,DC.maxdepth);
					}
					else
					{
						info.ambig.uncovered += DC.uncovered;
						info.ambig.basecount += DC.basecover;
						info.ambig.total += DC.total;
						info.ambig.maxdepth = std::max(info.ambig.maxdepth,DC.maxdepth);
					}
				}

				nextintid += 1;
			}

			while ( nextintid < IL.V_o )
			{
				libmaus2::gtf::IntervalList::IntervalWithId const & IWI = IL.get(nextintid++);

				bool const unique = IWI.end-IWI.start == 1;
				for ( uint64_t z = IWI.start; z < IWI.end; ++z )
				{
					uint64_t const index = IL.Aid[z];
					// libmaus2::gtf::Exon const & PE = (*(AP64[chrid]))[index];
					libmaus2::gtf::Exon const & PE = ILS(chrid,index);
					uint64_t const exonid = PE.id;

					libmaus2::gtf::ExonInfo & info = AEI[exonid];
					if ( unique )
					{
						info.unique.uncovered += IWI.Q.second-IWI.Q.first;
						info.unique.total     += IWI.Q.second-IWI.Q.first;
					}
					else
					{
						info.ambig.uncovered += IWI.Q.second-IWI.Q.first;
						info.ambig.total     += IWI.Q.second-IWI.Q.first;

					}
				}
			}
			assert ( nextintid == IL.V_o );
		}

		libmaus2::autoarray::AutoArray<libmaus2::gtf::ExonInfo> ATI(gtfdata.next_transcript_id);

		for ( uint64_t i = 0; i < gtfdata.next_exon_id; ++i )
		{
			libmaus2::gtf::Exon const & E = gtfdata.Aexon[i];
			libmaus2::gtf::ExonInfo const & I = AEI[i];
			uint64_t const transcript_id = E.transcript_id;
			ATI[transcript_id] += I;
		}

		libmaus2::autoarray::AutoArray<uint64_t> ATTI;

		for ( uint64_t g = 0; g < gtfdata.next_gene_id; ++g )
		{
			libmaus2::gtf::Gene const & G = gtfdata.Agene.at(g);
			char const * name = gtfdata.Aid.begin() + G.name_offset;
			uint64_t o = 0;

			std::string const refidname = gtfdata.Vchr.at(G.chr_id); //bamheader.getRefIDName(G.chr_id);

			for ( uint64_t ti = G.start; ti < G.end; ++ti )
			{
				if (
					ATI[ti].getUncoveredFraction() <= uncoveredthres
					&&
					ATI[ti].unique.getUncoveredFraction() <= uniqueuncoveredthres
				)
					ATTI.push(o,ti);
			}

			struct UncoveredComp
			{
				libmaus2::autoarray::AutoArray<libmaus2::gtf::ExonInfo> & ATI;
				UncoveredComp(libmaus2::autoarray::AutoArray<libmaus2::gtf::ExonInfo> & rATI)
				: ATI(rATI)
				{
				}

				bool operator()(uint64_t const i, uint64_t const j) const
				{
					return ATI[i].getUncoveredFraction() > ATI[j].getUncoveredFraction();
				}
			};

			if ( o )
			{
				std::sort(ATTI.begin(),ATTI.begin()+o,UncoveredComp(ATI));

				for ( uint64_t iti = 0; iti < o; ++iti )
				{
					uint64_t const ti = ATTI[iti];
					libmaus2::gtf::Transcript const & T = gtfdata.Atranscript[ti];
					char const * transid = gtfdata.Aid.begin() + T.id_offset;

					std::cout << "[transcript]\t" << refidname << ":" << name
						<< "\t" << transid << "\t"
						<< ATI[ti].unique
						<< "\t"
						<< ATI[ti].ambig
						<< "\t"
						<< ATI[ti];

					for ( uint64_t ei = T.start; ei < T.end; ++ei )
					{
						libmaus2::gtf::Exon const & E = gtfdata.Aexon[ei];
						std::cout << "\t";
						E.printCoord(std::cout);
					}

					std::cout << "\n";
				}
			}
		}
	}

	if ( verbose )
		std::cerr << "[V]\tprocessed\t" << algnid << "\t" << bamheader.getRefIDName(-1) << "\t" << -1 << "\t" << numunmatched << "\t" << numunique << "\t" << numambig << "\t" << numsplit << std::endl;

	if ( verbose )
		for ( uint64_t i = 0; i < gtfdata.Vchr.size(); ++i )
			std::cerr << "[C]\t" << gtfdata.Vchr[i] << "\t" << Vnumunmatched[i] << "\t" << Vnumunique[i] << "\t" << Vnumambig[i] << "\n";

	uint64_t sumfreq = 0;
	for ( uint64_t i = 0; i < gtfdata.next_gene_id; ++i )
		sumfreq += geneFreq[i];
	double const dsumfreq = sumfreq;

	for ( uint64_t i = 0; i < gtfdata.next_gene_id; ++i )
	{
		libmaus2::gtf::Gene const & gene = gtfdata.Agene[i];
		char const * gene_id = gtfdata.Aid.begin() + gene.id_offset;
		uint64_t const freq = geneFreq[i];
		double const tpm = 1e6 * (sumfreq ? freq/dsumfreq : 0);

		std::cout << "[gene]\t" << gene_id << "\t" << freq << "\t" << tpm << std::endl;
	}

	return EXIT_SUCCESS;
}

#include <libmaus2/fastx/FastaPeeker.hpp>
#include <libmaus2/lcs/SuffixArrayLCS.hpp>

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 << printHelpMessage(arginfo);
				return EXIT_SUCCESS;
			}

		if ( arginfo.getNumRestArgs() < 2 )
		{
			std::cerr << printHelpMessage(arginfo);
			return EXIT_FAILURE;
		}

		if ( arginfo.getValue<int>("comparefasta",0) )
		{
			std::string const fa0 = arginfo.getUnparsedRestArg(0);
			std::string const fa1 = arginfo.getUnparsedRestArg(1);

			libmaus2::fastx::FastaPeeker P0(fa0);
			libmaus2::fastx::FastaPeeker P1(fa1);

			libmaus2::fastx::FastAReader::pattern_type pat0;
			libmaus2::fastx::FastAReader::pattern_type pat1;

			while ( P0.peekNext(pat0) && P1.peekNext(pat1) )
			{
				std::string const short0 = pat0.getShortStringId();
				std::string const short1 = pat1.getShortStringId();

				int const r = libmaus2::bambam::StrCmpNum::strcmpnum(short0.c_str(),short1.c_str());

				if ( r < 0 )
				{
					std::cerr << "[V] only in first " << short0 << std::endl;

					P0.getNext(pat0);
				}
				else if ( r > 0 )
				{
					std::cerr << "[V] only in second " << short1 << std::endl;

					P1.getNext(pat1);
				}
				else
				{
					std::cerr << "[V] common " << short0 << std::endl;

					if ( pat0.spattern != pat1.spattern )
					{
						std::cerr << pat0.spattern << std::endl;
						std::cerr << pat1.spattern << std::endl;

						libmaus2::lcs::SuffixArrayLCS::LCSResult res = libmaus2::lcs::SuffixArrayLCS::lcsmin(pat0.spattern,pat1.spattern);

						std::cerr << res.maxlcp << " " << res.maxpos_a << " " << res.maxpos_b << std::endl;

						return EXIT_FAILURE;
					}

					P0.getNext(pat0);
					P1.getNext(pat1);
				}
			}

			return EXIT_SUCCESS;
		}

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