/**
    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 <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/bambam/BamMultiAlignmentDecoderFactory.hpp>
#include <libmaus2/bambam/BamAlignmentEncoderBase.hpp>
#include <libmaus2/util/GrowingFreeList.hpp>
#include <libmaus2/util/FiniteSizeHeap.hpp>
#include <libmaus2/util/MemUsage.hpp>
#include <libmaus2/lcs/PosMapping.hpp>
#include <libmaus2/lcs/NP.hpp>
#include <libmaus2/lcs/AlignmentPrint.hpp>
#include <libmaus2/lcs/NP.hpp>
#include <libmaus2/util/TabEntry.hpp>
#include <libmaus2/clustering/KmerBase.hpp>
#include <libmaus2/graph/TopologicalSorting.hpp>
#include <libmaus2/graph/IdentityTargetProjector.hpp>
#include <libmaus2/graph/StronglyConnectedComponents.hpp>
#include <libmaus2/fastx/FastAIndex.hpp>
#include <libmaus2/fastx/FastAIndexGenerator.hpp>
#include <libmaus2/geometry/RangeSet.hpp>
#include <libmaus2/bambam/BamBlockWriterBaseFactory.hpp>

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

#include <config.h>

struct BamAlignmentTypeInfo
{
	typedef libmaus2::bambam::BamAlignment element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct BamAlignmentAllocator
{
	BamAlignmentAllocator() {}

	BamAlignmentTypeInfo::pointer_type operator()() const
	{
		BamAlignmentTypeInfo::pointer_type ptr(new libmaus2::bambam::BamAlignment);
		return ptr;
	}
};

struct AlignmentTraceContainerTypeInfo
{
	typedef libmaus2::lcs::AlignmentTraceContainer element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct AlignmentTraceContainerAllocator
{
	AlignmentTraceContainerAllocator() {}

	AlignmentTraceContainerTypeInfo::pointer_type operator()() const
	{
		AlignmentTraceContainerTypeInfo::pointer_type ptr(new libmaus2::lcs::AlignmentTraceContainer);
		return ptr;
	}
};

struct PosMappingTypeInfo
{
	typedef libmaus2::lcs::PosMapping element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct PosMappingAllocator
{
	PosMappingAllocator() {}

	PosMappingTypeInfo::pointer_type operator()() const
	{
		PosMappingTypeInfo::pointer_type ptr(new libmaus2::lcs::PosMapping);
		return ptr;
	}
};

struct BamAlignmentHeapElement
{
	libmaus2::bambam::BamAlignment::shared_ptr_type ptr;
	libmaus2::lcs::AlignmentTraceContainer::shared_ptr_type atc;
	libmaus2::lcs::PosMapping::shared_ptr_type pm;
	int64_t start;
	int64_t end;
	uint64_t readid;

	BamAlignmentHeapElement() {}
	BamAlignmentHeapElement(
		libmaus2::bambam::BamAlignment::shared_ptr_type rptr,
		libmaus2::lcs::AlignmentTraceContainer::shared_ptr_type ratc,
		libmaus2::lcs::PosMapping::shared_ptr_type rpm,
		int64_t const rstart,
		int64_t const rend,
		uint64_t const rreadid
	) : ptr(rptr), atc(ratc), pm(rpm), start(rstart), end(rend), readid(rreadid) {}

	bool operator<(BamAlignmentHeapElement const & O) const
	{
		return end < O.end;
	}
};

uint64_t getDefaultDepth()
{
	return 30;
}

struct KmerCoordinate
{
	std::pair<int64_t,int64_t> refStart;
	std::pair<int64_t,int64_t> refEnd;

	KmerCoordinate()
	{

	}
	KmerCoordinate(
		std::pair<int64_t,int64_t> const & rrefStart,
		std::pair<int64_t,int64_t> const & rrefEnd
	) : refStart(rrefStart), refEnd(rrefEnd)
	{
	}

	bool operator<(KmerCoordinate const & RHS) const
	{
		if ( refStart.first != RHS.refStart.first )
			return refStart.first < RHS.refStart.first;
		else if ( refStart.second != RHS.refStart.second )
			return refStart.second < RHS.refStart.second;
		else if ( refEnd.first != RHS.refEnd.first )
			return refEnd.first < RHS.refEnd.first;
		else if ( refEnd.second != RHS.refEnd.second )
			return refEnd.second < RHS.refEnd.second;
		else
			return false;
	}

	bool endStartCompare(KmerCoordinate const & RHS) const
	{
		if ( refEnd.first != RHS.refEnd.first )
			return refEnd.first < RHS.refEnd.first;
		else if ( refEnd.second != RHS.refEnd.second )
			return refEnd.second < RHS.refEnd.second;
		else if ( refStart.first != RHS.refStart.first )
			return refStart.first < RHS.refStart.first;
		else if ( refStart.second != RHS.refStart.second )
			return refStart.second < RHS.refStart.second;
		else
			return false;
	}

	bool operator==(KmerCoordinate const & RHS) const
	{
		if ( *this < RHS )
			return false;
		else if ( RHS < *this )
			return false;
		else
			return true;
	}

	bool operator!=(KmerCoordinate const & RHS) const
	{
		if ( *this < RHS )
			return true;
		else if ( RHS < *this )
			return true;
		else
			return false;
	}
};

struct KmerCoordinateStartComparator
{
	static bool compare(KmerCoordinate const & A, KmerCoordinate const & B)
	{
		if ( A.refStart.first != B.refStart.first )
			return A.refStart.first < B.refStart.first;
		else
			return A.refStart.second < B.refStart.second;
	}

	bool operator()(KmerCoordinate const & A, KmerCoordinate const & B) const
	{
		return compare(A,B);
	}
};

std::ostream & operator<<(std::ostream & out, KmerCoordinate const & K)
{
	out << "["
		<< "(" << K.refStart.first << "," << K.refStart.second << ")"
		<< ","
		<< "(" << K.refEnd.first << "," << K.refEnd.second << ")"
		<< ")";
	return out;
}

struct KmerPos
{
	KmerCoordinate ref;
	uint64_t readpos;
	uint64_t readid;

	KmerPos()
	{

	}

	KmerPos(
		KmerCoordinate const & rref,
		uint64_t const rreadpos,
		uint64_t const rreadid
	) : ref(rref), readpos(rreadpos), readid(rreadid)
	{
	}

	#if 0
	KmerPos(
		uint64_t const rrefpos,
		int64_t const rrefshift,
		uint64_t const rreadpos,
		uint64_t const rreadid
	) : refpos(rrefpos), refshift(rrefshift), readpos(rreadpos), readid(rreadid)
	{

	}
	#endif

	bool operator<(KmerPos const & RHS) const
	{
		if ( ref < RHS.ref )
			return true;
		else if ( RHS.ref < ref )
			return false;
		else if ( readpos != RHS.readpos )
			return readpos < RHS.readpos;
		else
			return readid < RHS.readid;
	}

	static bool isSameRefPos(KmerPos const * A, KmerPos const * B)
	{
		if ( A == B )
			return true;

		KmerCoordinate const ref = A->ref;

		for ( KmerPos const * C = A; C < B; ++C )
			if ( C->ref != ref )
				return false;

		return true;
	}
};

std::ostream & operator<<(std::ostream & out, KmerPos const & KP)
{
	out << "KmerPos(" << KP.ref << "," << "," << KP.readpos << "," << KP.readid << ")";
	return out;
}

struct KmerPosKillHeapComparator
{
	bool operator()(KmerPos const & KA, KmerPos const & KB) const
	{
		// return KA.ref < KB.ref;
		return KA.ref.endStartCompare(KB.ref);
	}
};

struct KmerPosKillHeapReadComparator
{
	bool operator()(KmerPos const & KA, KmerPos const & KB) const
	{
		if ( KA.readid != KB.readid )
			return KA.readid < KB.readid;
		else
			return KA.readpos < KB.readpos;
	}
};

struct KmerPosStartReadComparator
{
	bool operator()(KmerPos const & A, KmerPos const & B) const
	{
		if ( KmerCoordinateStartComparator::compare(A.ref,B.ref) )
			return true;
		else if ( KmerCoordinateStartComparator::compare(B.ref,A.ref) )
			return false;
		else if ( A.readid != B.readid )
			return A.readid < B.readid;
		else
			return A.readpos < B.readpos;
	}
};

struct KmerFreq
{
	unsigned int k;
	uint64_t kcode;
	KmerPos KP;
	uint64_t freq;

	KmerFreq() {}
	KmerFreq(
		unsigned int const rk,
		uint64_t rkcode,
		KmerPos const & rKP,
		uint64_t rfreq
	) : k(rk), kcode(rkcode), KP(rKP), freq(rfreq) {}

	bool operator<(KmerFreq const & K) const
	{
		if ( freq != K.freq )
			return freq > K.freq;
		else if ( kcode != K.kcode )
			return kcode < K.kcode;
		else
			return KP.ref < K.KP.ref;
	}
};

static std::string printKmer(uint64_t const kmer, unsigned int const k)
{
	libmaus2::fastx::SingleWordDNABitBuffer swordbuffer(k);
	swordbuffer.buffer = kmer;
	return swordbuffer.toStringDNA();
}

std::ostream & operator<<(std::ostream & out, KmerFreq const & K)
{
	out << "KmerFreq(" << printKmer(K.kcode,K.k) << "," << K.KP << "," << K.freq << ")";

	return out;
}


struct KmerInfo
{
	typedef KmerInfo this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	unsigned int const k;
	uint64_t kcode;

	libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapComparator> killHeap;

	KmerInfo(unsigned int const rk) : k(rk), kcode(0), killHeap(0)
	{

	}

	uint64_t getPrevKmer(uint64_t const i) const
	{
		libmaus2::fastx::SingleWordDNABitBuffer swordbuffer(k);
		swordbuffer.buffer = kcode;
		swordbuffer.pushFront(i);
		return swordbuffer.buffer;
	}

	uint64_t getNextKmer(uint64_t const i) const
	{
		libmaus2::fastx::SingleWordDNABitBuffer swordbuffer(k);
		swordbuffer.buffer = kcode;
		swordbuffer.pushBackMasked(i);
		return swordbuffer.buffer;
	}

	uint64_t getRefMatches(
		libmaus2::autoarray::AutoArray<KmerPos> & ASFV,
		KmerPos const & KPref,
		libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapComparator> & H,
		uint64_t o = 0
	) const
	{
		H.clear();
		H.copyFrom(killHeap);

		while ( ! H.empty() )
		{
			KmerPos const K = H.pop();

			if ( K.ref == KPref.ref )
				ASFV.push(o,K);
		}

		return o;
	}

	static uint64_t mergeReadPos(
		KmerPos const * Aa,
		KmerPos const * Ae,
		KmerInfo const & B,
		libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapComparator> & H,
		libmaus2::autoarray::AutoArray<KmerPos> & ASFV,
		int64_t const shift,
		uint64_t ostart
	)
	{
		libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapReadComparator> HA(0);
		libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapReadComparator> HB(0);
		libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapComparator> HC(0);

		libmaus2::autoarray::AutoArray<KmerPos> KPA;
		libmaus2::autoarray::AutoArray<KmerPos> KPB;

		#if 0
		for ( std::ptrdiff_t i = 0; i < Ae-Aa; ++i )
			std::cerr << "mergeReadPos[A]=" << Aa[i] << std::endl;
		#endif

		HA.clear();
		while ( Aa < Ae )
			HA.pushBump(*(Aa++));

		HB.clear();
		H.clear();
		H.copyFrom(B.killHeap);
		while ( !H.empty() )
			HB.pushBump(H.pop());

		uint64_t o = ostart;
		H.clear();
		while ( ! HA.empty() && ! HB.empty() )
		{
			KmerPos const & PA = HA.top();
			KmerPos const & PB = HB.top();

			if ( PA.readid < PB.readid )
				HA.pop();
			else if ( PB.readid < PA.readid )
				HB.pop();
			else
			{
				assert ( PA.readid == PB.readid );

				if ( static_cast<int64_t>(PA.readpos) + shift < static_cast<int64_t>(PB.readpos) )
					HA.pop();
				else if ( static_cast<int64_t>(PB.readpos) < static_cast<int64_t>(PA.readpos) + shift )
					HB.pop();
				else
				{
					assert ( static_cast<int64_t>(PA.readpos) + shift == static_cast<int64_t>(PB.readpos) );

					// std::cerr << "[V] adding " << PB << std::endl;

					ASFV.push(o,PB);
					H.pushBump(PB);
					HA.pop();
					HB.pop();
				}
			}
		}

		HC.clear();
		HC.copyFrom(B.killHeap);

		while ( ! H.empty() && ! HC.empty() )
		{
			KmerPos const PA = H.top();
			KmerPos const PB = HC.top();

			if ( PA.ref < PB.ref )
				H.pop();
			else if ( PB.ref < PA.ref )
				HC.pop();
			else
			{
				assert ( PA.ref == PB.ref );

				KmerCoordinate const ref = PA.ref;

				uint64_t KPAo = 0;
				uint64_t KPBo = 0;

				while ( ! H.empty()  && H.top().ref  == ref )
					KPA.push(KPAo,H.pop());
				while ( ! HC.empty() && HC.top().ref == ref )
					KPB.push(KPBo,HC.pop());

				std::sort(KPA.begin(),KPA.begin()+KPAo,KmerPosKillHeapReadComparator());
				std::sort(KPB.begin(),KPB.begin()+KPBo,KmerPosKillHeapReadComparator());

				#if 0
				std::cerr << "KPAo=" << KPAo << std::endl;
				std::cerr << "KPBo=" << KPBo << std::endl;

				for ( uint64_t i = 0; i < KPAo; ++i )
					std::cerr << "H[]=" << KPA[i] << std::endl;
				for ( uint64_t i = 0; i < KPBo; ++i )
					std::cerr << "HC[]=" << KPB[i] << std::endl;
				#endif

				uint64_t pa = 0;
				uint64_t pb = 0;

				while ( pa < KPAo && pb < KPBo )
					if ( KPA[pa].readid < KPB[pb].readid )
					{
						// in H but not in HC
						pa++;
					}
					else if ( KPB[pb].readid < KPA[pa].readid )
					{
						// in HC but no in H
						// std::cerr << "*1adding " << KPB[pb] << std::endl;
						ASFV.push(o,KPB[pb++]);
					}
					else
					{
						// in both
						++pa;
						++pb;
					}

				while ( pb < KPBo )
				{
					//std::cerr << "*2adding " << KPB[pb] << std::endl;
					ASFV.push(o,KPB[pb++]);
				}
			}
		}

		return o;
	}

	uint64_t countFreq(
		libmaus2::autoarray::AutoArray< KmerFreq > & A,
		libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapComparator> & H,
		uint64_t o = 0
	) const
	{
		H.clear();
		H.copyFrom(killHeap);

		while ( ! H.empty() )
		{
			KmerPos const K = H.top();
			uint64_t c = 0;

			while ( ! H.empty() && H.top().ref == K.ref )
			{
				H.pop();
				c += 1;
			}

			A.push(
				o,KmerFreq(k,kcode,K,c)
			);
		}

		return o;
	}

	void reset()
	{
		killHeap.clear();
	}
};

std::ostream & operator<<(std::ostream & out, KmerInfo const & K)
{
	libmaus2::fastx::SingleWordDNABitBuffer wordbuffer(K.k);
	wordbuffer.buffer = K.kcode;

	assert ( K.killHeap.f );

	out << "KmerInfo(" << wordbuffer << "," << K.killHeap.f << "," << K.killHeap.top()  << ")";

	return out;
}

struct KmerInfoTypeInfo
{
	typedef KmerInfo element_type;
	typedef element_type::shared_ptr_type pointer_type;

	static pointer_type getNullPointer()
	{
		pointer_type p;
		return p;
	}

	static pointer_type deallocate(pointer_type /* p */)
	{
		return getNullPointer();
	}
};

struct KmerInfoAllocator
{
	unsigned int const k;

	KmerInfoAllocator(unsigned int const rk) : k(rk) {}

	KmerInfoTypeInfo::pointer_type operator()() const
	{
		KmerInfoTypeInfo::pointer_type ptr(new KmerInfo(k));
		return ptr;
	}
};

static void handleKMEnd(
	unsigned int const
		#if 0
		k
		#endif
		,
	libmaus2::util::FiniteSizeHeap < std::pair<uint64_t,uint64_t> > & KMend,
	std::map<uint64_t,KmerInfo::shared_ptr_type> & KM1,
	std::map<uint64_t,KmerInfo::shared_ptr_type> & KM,
	int64_t const KMendv,
	libmaus2::util::GrowingFreeList<
		KmerInfo,
		KmerInfoAllocator,
		KmerInfoTypeInfo
	> & kmerInfoFreeList
)
{
	while ( !KMend.empty() && static_cast<int64_t>(KMend.top().first) <= KMendv )
	{
		std::pair<uint64_t,uint64_t> const Pend = KMend.pop();
		uint64_t const kmer = Pend.second;

		// find entry in KM
		std::map<uint64_t,KmerInfo::shared_ptr_type>::iterator it = KM.find(kmer);

		// if we have any
		if ( it != KM.end() )
		{
			// get it
			KmerInfo::shared_ptr_type info = it->second;

			assert ( it != KM.end() );
			assert ( !info->killHeap.empty() );
			assert ( info->killHeap.f > 1 );

			#if 0
			KMend.pushBump(
				std::pair<uint64_t,uint64_t>(
					KP.ref.refEnd.first,
					word
				)
			);
			#endif

			bool const khok = static_cast<int64_t>(info->killHeap.top().ref.refEnd.first) <= KMendv;

			if ( ! khok )
			{
				std::cerr << "info->killHeap.top().ref.refEnd.first=" << info->killHeap.top().ref.refEnd.first << std::endl;
				std::cerr << "KMendv=" << KMendv << std::endl;
				assert ( khok );
			}

			info->killHeap.pop();

			if ( info->killHeap.f == 1 )
			{
				KM.erase(it);
				KM1[kmer] = info;
			}
		}
		else
		{
			it = KM1.find(kmer);

			assert ( it != KM1.end() );

			KmerInfo::shared_ptr_type info = it->second;

			info->killHeap.pop();

			assert ( info->killHeap.empty() );

			info->reset();
			kmerInfoFreeList.put(info);
			KM1.erase(it);
		}
	}
}

struct BranchPoint
{
	unsigned int k;
	uint64_t kmer;
	KmerCoordinate ref;

	BranchPoint()
	{
	}
	BranchPoint(
		unsigned const rk,
		uint64_t const rkmer,
		KmerCoordinate const & rref
	) : k(rk), kmer(rkmer), ref(rref)
	{

	}

	bool operator<(BranchPoint const & BP) const
	{
		if ( kmer != BP.kmer )
			return kmer < BP.kmer;
		else
			return ref < BP.ref;
	}

	bool operator==(BranchPoint const & BP) const
	{
		if ( *this < BP )
			return false;
		else if ( BP < *this )
			return false;
		else
			return true;
	}
};

std::ostream & operator<<(std::ostream & out, BranchPoint const & BP)
{
	#if 0
	out << "(" << printKmer(BP.kmer,BP.k) << "," << BP.refpos << "," << BP.refshift << ")";
	#else
	out << "(" << BP.ref << ")";
	#endif
	return out;
}

struct KmerLink
{
	unsigned int k;

	uint64_t kfrom;
	uint64_t kto;

	KmerCoordinate reffrom;
	#if 0
	uint64_t refposfrom;
	int64_t refshiftfrom;
	#endif

	KmerCoordinate refto;
	#if 0
	uint64_t refposto;
	int64_t refshiftto;
	#endif

	uint64_t fromfreq;
	uint64_t tofreq;

	uint64_t rid_start;
	uint64_t rid_end;

	BranchPoint getFrom() const
	{
		return BranchPoint(k,kfrom,reffrom);
	}

	BranchPoint getTo() const
	{
		return BranchPoint(k,kto,refto);
	}

	bool operator<(KmerLink const & KL) const
	{
		if ( kfrom != KL.kfrom )
			return kfrom < KL.kfrom;
		else if ( kto != KL.kto )
			return kto < KL.kto;
		else if ( reffrom != KL.reffrom )
			return reffrom < KL.reffrom;
		else if ( refto != KL.refto )
			return refto < KL.refto;
		else if ( fromfreq != KL.fromfreq )
			return fromfreq < KL.fromfreq;
		else
			return tofreq < KL.tofreq;
	}

	KmerLink()
	{

	}

	KmerLink(
		unsigned int const rk,
		uint64_t const rkfrom,
		uint64_t const rkto,
		KmerCoordinate const & rreffrom,
		KmerCoordinate const & rrefto,
		uint64_t const rfromfreq,
		uint64_t const rtofreq,
		uint64_t const r_rid_start,
		uint64_t const r_rid_end
	) : k(rk), kfrom(rkfrom), kto(rkto),
	    reffrom(rreffrom),
	    refto(rrefto),
	    fromfreq(rfromfreq), tofreq(rtofreq),
	    rid_start(r_rid_start), rid_end(r_rid_end)
	{

	}
};

struct KmerLinkFromComparator
{
	bool operator()(KmerLink const & A, KmerLink const & B)
	{
		return A.kfrom < B.kfrom;
	}
};

struct KmerLinkToComparator
{
	bool operator()(KmerLink const & A, KmerLink const & B)
	{
		return A.kto < B.kto;
	}
};

struct KmerLinkToPosComparator
{
	bool operator()(KmerLink const & A, KmerLink const & B)
	{
		if ( A.kto != B.kto )
			return A.kto < B.kto;
		else // if ( A.refto != B.refto )
			return A.refto < B.refto;
	}
};

struct KmerLinkFromPosComparator
{
	bool operator()(KmerLink const & A, KmerLink const & B)
	{
		if ( A.kfrom != B.kfrom )
			return A.kfrom < B.kfrom;
		else // if ( A.reffrom != B.reffrom )
			return A.reffrom < B.reffrom;
	}
};


std::ostream & operator<<(std::ostream & out, KmerLink const & KL)
{
	out << "KmerLink("
		<< printKmer(KL.kfrom,KL.k) << "(" << KL.reffrom << "," << KL.fromfreq << ")"
		<< "->"
		<< printKmer(KL.kto,KL.k) << "(" << KL.refto << "," << KL.tofreq << ")"
		<< ")"
		;
	return out;
}

struct BranchLink
{
	BranchPoint from;
	BranchPoint to;
	uint64_t weight;
	uint64_t fromweight;
	uint64_t toweight;
	uint64_t length;
	uint64_t link_start;
	uint64_t link_end;

	BranchLink()
	{

	}

	BranchLink(BranchPoint const & rfrom)
	: from(rfrom), to(), weight(0), fromweight(0), toweight(0), length(0), link_start(0), link_end(0) {}

	BranchLink(
		BranchPoint const & rfrom,
		BranchPoint const & rto
	) : from(rfrom), to(rto), weight(0), fromweight(0), toweight(0), length(0), link_start(0), link_end(0)
	{}

	BranchLink(
		BranchPoint const & rfrom,
		BranchPoint const & rto,
		uint64_t const rweight,
		uint64_t const rfromweight,
		uint64_t const rtoweight,
		uint64_t const rlength,
		uint64_t const r_link_start,
		uint64_t const r_link_end
	) : from(rfrom), to(rto), weight(rweight), fromweight(rfromweight), toweight(rtoweight), length(rlength), link_start(r_link_start), link_end(r_link_end)
	{

	}

	double getWeight() const
	{
		return static_cast<double>(weight)/(length+1);
	}

	double getFromWeight() const
	{
		return static_cast<double>(fromweight)/(length);
	}

	double getToWeight() const
	{
		return static_cast<double>(toweight)/(length);
	}

	bool operator<(BranchLink const & B) const
	{
		if ( from < B.from )
			return true;
		else if ( B.from < from )
			return false;
		else if ( getWeight() < B.getWeight() )
			return true;
		else if ( B.getWeight() < getWeight() )
			return false;
		else
			return false;
	}
};

std::ostream & operator<<(std::ostream & out, BranchLink const & BL)
{
	out << "BranchLink(" << BL.from << "," << BL.to << "," << BL.weight << "," << BL.length << "," << BL.getWeight() << ")";
	return out;
}

struct BranchLinkFromComparator
{
	bool operator()(BranchLink const & A, BranchLink const & B)
	{
		return A.from < B.from;
	}
};

struct BranchLinkToComparator
{
	bool operator()(BranchLink const & A, BranchLink const & B)
	{
		return A.to < B.to;
	}
};

struct BranchLinkFromToComparator
{
	bool operator()(BranchLink const & A, BranchLink const & B)
	{
		if ( A.from < B.from )
			return true;
		else if ( B.from < A.from )
			return false;
		else if ( A.to < B.to )
			return true;
		else if ( B.to < A.to )
			return false;
		else
			return false;
	}
};

struct Ident
{
	static char mapfun(char const c)
	{
		return c;
	}
};

struct ConsensusPart
{
	uint64_t from;
	uint64_t to;
	std::string path;

	ConsensusPart() {}
	ConsensusPart(
		uint64_t const rfrom,
		uint64_t const rto,
		std::string const & rpath
	) : from(rfrom), to(rto), path(rpath) {}

	bool operator<(ConsensusPart const & C) const
	{
		return from < C.from;
	}

	uint64_t diameter() const
	{
		return to-from;
	}

	libmaus2::math::IntegerInterval<int64_t> getInterval() const
	{
		return libmaus2::math::IntegerInterval<int64_t>(from,static_cast<int64_t>(to)-1);
	}

	std::string print(std::string const & ref) const
	{
		std::string const sub = ref.substr(from,to-from);
		libmaus2::lcs::NP np;
		np.np(sub.begin(),sub.end(),path.begin(),path.end());

		std::ostringstream ostr;

		ostr << np.getAlignmentStatistics() << std::endl;

		libmaus2::lcs::AlignmentPrint::printAlignmentLines(
			ostr,
			sub.begin(),
			sub.size(),
			path.begin(),
			path.size(),
			80,
			np.ta,
			np.te,
			Ident::mapfun,
			from,
			0,
			"ref",
			"cons"
		);

		return ostr.str();
	}
};

struct ConsensusPartDiameterComparator
{
	bool operator()(ConsensusPart const & LHS, ConsensusPart const & RHS) const
	{
		return LHS.diameter() < RHS.diameter();
	}
};

std::ostream & operator<<(std::ostream & out, ConsensusPart const & C)
{
	out << "ConsensusPart(" << C.from << "," << C.to << "," << C.path << ")";
	return out;
}


struct Extract
{
	static std::string extract(ConsensusPart const & CP, std::string const & ref, libmaus2::math::IntegerInterval<int64_t> const & IC)
	{
		std::string const & fullpath = CP.path;
		uint64_t const reffrom = CP.from;
		uint64_t const refto = CP.to;

		assert ( refto >= reffrom );
		assert ( refto <= ref.size() );

		std::string const sub = ref.substr(reffrom,refto-reffrom);
		libmaus2::lcs::NP np;

		np.np(
			sub.begin(),sub.end(),
			fullpath.begin(),fullpath.end()
		);

		std::pair<uint64_t,uint64_t> const advA = libmaus2::lcs::AlignmentTraceContainer::advanceMaxA(np.ta,np.te,IC.from - reffrom);
		std::pair<uint64_t,uint64_t> const slA = libmaus2::lcs::AlignmentTraceContainer::getStringLengthUsed(np.ta,np.ta + advA.second);
		std::pair<uint64_t,uint64_t> const advB = libmaus2::lcs::AlignmentTraceContainer::advanceMaxA(np.ta + advA.second,np.te,IC.diameter());
		std::pair<uint64_t,uint64_t> const slB = libmaus2::lcs::AlignmentTraceContainer::getStringLengthUsed(np.ta + advA.second,np.ta + advA.second + advB.second);

		return fullpath.substr(slA.second,slB.second);
	}
};


struct Range
{
	uint64_t from;
	uint64_t to;

	Range()
	{

	}

	Range(uint64_t const rfrom, uint64_t const rto)
	: from(rfrom), to(rto)
	{

	}

	uint64_t getFrom() const
	{
		return from;
	}

	uint64_t getTo() const
	{
		return to;
	}

	libmaus2::math::IntegerInterval<int64_t> getInterval() const
	{
		return libmaus2::math::IntegerInterval<int64_t>(from,static_cast<int64_t>(to)-1);
	}
};

struct ConsensusMerge
{
	static std::vector<ConsensusPart> merge(
		std::vector<ConsensusPart> const & Vcons,
		uint64_t const
			#if 0
			conswindowstart
			#endif
			,
		uint64_t const
			#if 0
			conswindowend
			#endif
			,
		int64_t const
			#if 0
			prevrefid
			#endif
			,
		std::string const & ref
	)
	{
		#if 0
		for ( uint64_t i = 0; i < Vcons.size(); ++i )
			std::cerr << "[" << i << "]=" << Vcons[i].getInterval() << std::endl;
		#endif

		std::vector<ConsensusPart> Vconsdiam(Vcons);
		std::sort(Vconsdiam.begin(),Vconsdiam.end(),ConsensusPartDiameterComparator());
		std::reverse(Vconsdiam.begin(),Vconsdiam.end());

		libmaus2::geometry::RangeSet<Range> R(ref.size());
		#if 0
		libmaus2::math::IntegerInterval<int64_t> const IF(conswindowstart,static_cast<int64_t>(conswindowend) - 1);
		std::vector < libmaus2::math::IntegerInterval<int64_t> > VIU;
		#endif
		std::vector<ConsensusPart> Vconsout;
		for ( uint64_t i = 0; i < Vconsdiam.size(); ++i )
		{
			libmaus2::math::IntegerInterval<int64_t> const IA = Vconsdiam[i].getInterval();
			Range const RA(IA.from,IA.from+IA.diameter());

			std::vector<Range const *> const VR = R.search(RA);
			std::vector< libmaus2::math::IntegerInterval<int64_t> > VC;
			for ( uint64_t j = 0; j < VR.size(); ++j )
				VC.push_back(VR[j]->getInterval());

			std::vector< libmaus2::math::IntegerInterval<int64_t> > const VD = libmaus2::math::IntegerInterval<int64_t>::difference(IA,VC);

			for ( uint64_t j = 0; j < VD.size(); ++j )
			{
				libmaus2::math::IntegerInterval<int64_t> const IC = VD[j];
				assert ( IC.intersection(IA) == IC );
				// std::cerr << "can use " << IC << " from " << Vconsdiam[i] << std::endl;

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

				R.insert(Range(from,to));

				std::string const E = Extract::extract(Vconsdiam[i],ref,IC);

				Vconsout.push_back(ConsensusPart(from,to,E));
			}
		}

		std::sort(Vconsout.begin(),Vconsout.end());

		bool changed = true;

		while ( changed )
		{
			changed = false;

			uint64_t ilow = 0;
			uint64_t o = 0;
			while ( ilow < Vconsout.size() )
			{
				uint64_t ihigh = ilow+1;

				while ( ihigh < Vconsout.size() && Vconsout[ihigh-1].to == Vconsout[ihigh].from )
					++ihigh;

				std::ostringstream datastr;
				for ( uint64_t i = ilow; i < ihigh; ++i )
					datastr << Vconsout[i].path;

				ConsensusPart CP(
					Vconsout[ilow].from,
					Vconsout[ihigh-1].to,
					datastr.str()
				);

				Vconsout[o++] = CP;

				if ( ihigh-ilow > 1 )
					changed = true;

				ilow = ihigh;
			}

			Vconsout.resize(o);
		}

		return Vconsout;
	}
};

// #define HANDLE_DEBUG
#define BAMCONSENSUS_RUN_DOT

std::vector<ConsensusPart> handleConsensus(
	uint64_t const k,
	uint64_t const thres,
	uint64_t &
		#if defined(BAMCONSENSUS_RUN_DOT)
		dotid
		#endif
		,
	int64_t const prevrefid,
	int64_t const
		conswindowstart
		,
	int64_t const
		conswindowend
		,
	libmaus2::autoarray::AutoArray< KmerFreq > & countFreqA,
	libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapComparator> & countFreqH,
	libmaus2::autoarray::AutoArray<KmerLink> & AKL,
	libmaus2::util::FiniteSizeHeap < std::pair<uint64_t,uint64_t> > const & KMend,
	std::map<uint64_t,KmerInfo::shared_ptr_type> const & KM,
	libmaus2::autoarray::AutoArray<KmerPos> & LASFV,
	libmaus2::autoarray::AutoArray<KmerPos> & RASFV,
	libmaus2::autoarray::AutoArray<uint64_t> & ARID,
	libmaus2::autoarray::AutoArray<KmerLink> & AKLBL,
	std::string const & ref,
	std::string const &
		#if defined(HANDLE_DEBUG)
		fullname
		#endif
		,
	libmaus2::bambam::BamHeader const & /* header */
)
{
	std::vector<ConsensusPart> Vcons;

	// if leftmost k-mer entry is inside the consensus window
	if ( ! KMend.empty() && static_cast<int64_t>(KMend.top().first) <= conswindowend )
	{
		#if defined(HANDLE_DEBUG)
		std::cerr << "window " << prevrefid << "(";

		std::cerr << fullname;

		std::cerr << ")" << "[" << conswindowstart << "," << conswindowend << ")";

		std::cerr << "/[" << 0 << "," << ref.size() << ")";

		std::cerr << std::endl;
		#endif

		uint64_t o = 0;
		uint64_t KLo = 0;
		uint64_t ARIDo = 0;
		uint64_t AKLBLo = 0;

		// iterate over current k-mer info objects
		for ( std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator it = KM.begin();
			it != KM.end(); ++it )
		{
			#if 0
			std::string const kfull = "CGGGGCCAGGTTCTCACACCATGCA";
			std::string const kk = kfull.substr(0,k);

			bool const verbose = printKmer(it->first,k) == kk;
			if ( verbose )
			{

				countFreqH.copyFrom(it->second->killHeap);

				while ( !countFreqH.empty() )
				{
					KmerPos KP = countFreqH.pop();

					std::cerr << KP << std::endl;
				}
			}
			#endif

			// code (k-mer)
			uint64_t const kcode = it->first;
			// info object
			KmerInfo const & info = *(it->second);
			// we should have more than one (otherwise it should be in KM1)
			assert ( info.killHeap.f > 1 );

			// std::cerr << "\t" << *(it->second) << std::endl;

			// count/enumerate same refpos/shift
			o = info.countFreq(countFreqA,countFreqH,0);

			// std::cerr << "kcode=" << kcode << " info.kcode=" << info.kcode << " o=" << o << std::endl;

			// iterate over (refpos,shift)
			for ( uint64_t i = 0; i < o; ++i )
			{
				// get frequency object
				KmerFreq const & KF = countFreqA[i];

				// std::cerr << "KF.kcode=" << KF.kcode << std::endl;

				assert ( KF.k == k );
				assert ( KF.kcode == kcode );
				KmerPos const & KP = KF.KP;

				// get matches for position
				uint64_t const LASFVo =
					info.getRefMatches(LASFV,KP,countFreqH,0);

				bool const samerefin = ( KmerPos::isSameRefPos(LASFV.begin(),LASFV.begin()  + LASFVo) );

				assert ( samerefin );

				if ( !LASFVo )
					continue;

				// get (pos,shift) pair
				KmerCoordinate const refFrom = LASFV[0].ref;

				// check for extensions
				for ( unsigned int j = 0; j < 4; ++j )
				{
					uint64_t const nextk = info.getNextKmer(j);
					std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator oit = KM.find(nextk);

					if ( oit != KM.end() )
					{
						// merge/filter by read position
						uint64_t const lASFVo = KmerInfo::mergeReadPos(
							// input KmerPos
							LASFV.begin(),
							LASFV.begin()  + LASFVo,
							// KmerInfo on the right
							*(oit->second),
							// temp heap
							countFreqH,
							// output KmerPos
							RASFV,
							1 /* shift */,
							0
						);

						// sort
						std::sort(
							RASFV.begin(),RASFV.begin()  + lASFVo,
							KmerPosKillHeapComparator()
						);

						// if any
						if ( lASFVo )
						{
							// split by (refpos,shift)
							uint64_t rlow = 0;
							while ( rlow < lASFVo )
							{
								KmerCoordinate const ref = RASFV[rlow].ref;
								uint64_t rhigh = rlow+1;

								while (
									rhigh < lASFVo
									&&
									RASFV[rhigh].ref == ref
								)
								{
									++rhigh;
								}

								if (
									static_cast<int64_t>(refFrom.refStart.first) >= conswindowstart
									&&
									static_cast<int64_t>(ref.refStart.first)      < conswindowend
								)
								{
									// store read id vector
									uint64_t const ARIDo_start = ARIDo;
									for ( uint64_t z = rlow; z < rhigh; ++z )
										ARID.push(ARIDo,RASFV[z].readid);
									uint64_t const ARIDo_end = ARIDo;

									KmerLink KL(
										k,
										kcode,nextk,
										refFrom,
										ref,
										LASFVo,
										rhigh-rlow,
										ARIDo_start,
										ARIDo_end
									);

									#if 0
									if ( verbose )
										std::cerr << KL << std::endl;
									#endif

									if ( rhigh-rlow >= thres )
										AKL.push(KLo,KL);
								}

								rlow = rhigh;
							}

							#if 0
							bool const samerefout = ( KmerPos::isSameRefPos(RASFV.begin(),RASFV.begin()  + lASFVo) );
							std::cerr << (samerefin?'+':'-') << kcode << " " << KP << " " << KF.freq << " " << LASFVo << std::endl;
							std::cerr << "\t" << (samerefout?'+':'-') << j << " " << o << " " << lASFVo << std::endl;

							if ( ! samerefout )
							{
								std::cerr << (samerefin?'+':'-') << kcode << " " << KP << " " << KF.freq << " " << LASFVo << std::endl;
								std::cerr << "\t" << (samerefout?'+':'-') << j << " " << o << " " << lASFVo << std::endl;

								for ( uint64_t z = 0; z < lASFVo; ++z )
								{
									//uint64_t const readid = RASFV[z].readid;
									std::cerr << "\t\t" << RASFV[z] << std::endl;
								}
							}
							#endif
						}
					}
				}
			}
		}

		// sort links
		std::sort(
			AKL.begin(),
			AKL.begin() + KLo,
			KmerLinkFromPosComparator()
		);

		// branch point from list (unique)
		libmaus2::autoarray::AutoArray<BranchPoint> ABPF;
		uint64_t ABPFo = 0;
		// branch point to list (unique)
		libmaus2::autoarray::AutoArray<BranchPoint> ABPT;
		uint64_t ABPTo = 0;
		// branch point all list (unique)
		libmaus2::autoarray::AutoArray<BranchPoint> ABPN;
		uint64_t ABPNo = 0;
		// branch points (from or to) appearing at least twice (unique)
		libmaus2::autoarray::AutoArray<BranchPoint> ABPA;
		uint64_t ABPAo = 0;

		// fill lists
		for ( uint64_t i = 0; i < KLo; ++i )
		{
			ABPF.push(ABPFo,AKL[i].getFrom());
			ABPT.push(ABPTo,AKL[i].getTo());

			ABPN.push(ABPNo,AKL[i].getFrom());
			ABPN.push(ABPNo,AKL[i].getTo());
		}

		// sort lists
		std::sort(ABPF.begin(),ABPF.begin()+ABPFo);
		std::sort(ABPT.begin(),ABPT.begin()+ABPTo);
		std::sort(ABPN.begin(),ABPN.begin()+ABPNo);


		{
			uint64_t low = 0;
			while ( low < ABPFo )
			{
				uint64_t high = low+1;

				while ( high < ABPFo && !(ABPF[low] < ABPF[high]) )
					++high;

				if ( high - low > 1 )
				{
					// std::cerr << "ABPF " << ABPF[low] << " " << high-low << std::endl;
					ABPA.push(ABPAo,ABPF[low]);
				}

				low = high;
			}
		}
		{
			uint64_t low = 0;
			while ( low < ABPTo )
			{
				uint64_t high = low+1;

				while ( high < ABPTo && !(ABPT[low] < ABPT[high]) )
					++high;

				if ( high - low > 1 )
				{
					// std::cerr << "ABPT " << ABPT[low] << " " << high-low << std::endl;
					ABPA.push(ABPAo,ABPT[low]);
				}

				low = high;
			}
		}

		std::sort(ABPA.begin(),ABPA.begin()+ABPAo);

		if ( ABPAo )
		{
			uint64_t o = 1;
			for ( uint64_t i = 1; i < ABPAo; ++i )
				if ( ABPA[i-1] < ABPA[i] )
					ABPA[o++] = ABPA[i];
			ABPAo = o;
		}

		if ( ABPFo )
		{
			uint64_t o = 1;
			for ( uint64_t i = 1; i < ABPFo; ++i )
				if ( ABPF[i-1] < ABPF[i] )
					ABPF[o++] = ABPF[i];
			ABPFo = o;
		}

		if ( ABPTo )
		{
			uint64_t o = 1;
			for ( uint64_t i = 1; i < ABPTo; ++i )
				if ( ABPT[i-1] < ABPT[i] )
					ABPT[o++] = ABPT[i];
			ABPTo = o;
		}

		if ( ABPNo )
		{
			uint64_t o = 1;
			for ( uint64_t i = 1; i < ABPNo; ++i )
				if ( ABPN[i-1] < ABPN[i] )
					ABPN[o++] = ABPN[i];
			ABPNo = o;
		}

		std::sort(
			AKL.begin(),
			AKL.begin() + KLo,
			KmerLinkFromPosComparator()
		);

		// construct vector of branch links (left end either non-extending or branching)
		std::vector<BranchLink> VBL;

		// iterate over k-mer link
		for ( uint64_t i = 0; i < KLo; ++i )
		{
			KmerLink cur = AKL[i];

			std::pair<BranchPoint const *, BranchPoint const *> const BPP =
				std::equal_range(
					ABPA.begin(),
					ABPA.begin()+ABPAo,
					cur.getFrom()
				);
			std::pair<BranchPoint const *, BranchPoint const *> const BPPT =
				std::equal_range(
					ABPT.begin(),
					ABPT.begin()+ABPTo,
					cur.getFrom()
				);

			if (
				// left end branching
				(BPP.second != BPP.first)
				||
				// left end is a source
				(BPPT.second == BPPT.first)
			)
			{
				std::vector<KmerLink> Vpath;
				Vpath.push_back(cur);

				// follow branch until we find a branching node (on the left or on the right) or a sink
				while ( true )
				{
					std::pair<BranchPoint const *, BranchPoint const *> const BPP =
						std::equal_range(
							ABPA.begin(),
							ABPA.begin()+ABPAo,
							cur.getTo()
						);
					std::pair<BranchPoint const *, BranchPoint const *> const BPPF =
						std::equal_range(
							ABPF.begin(),
							ABPF.begin()+ABPFo,
							cur.getTo()
						);

					if (
						// right end is not branching
						BPP.first == BPP.second
						&&
						// right end is not a sink
						BPPF.first != BPPF.second
					)
					{
						std::pair<KmerLink const *,KmerLink const *> const KP =
							std::equal_range(
								AKL.begin(),
								AKL.begin()+KLo,
								KmerLink(
									k,cur.kto,0,
									cur.refto,
									KmerCoordinate(),
									0,0,
									0,0
								),
								KmerLinkFromPosComparator()
							);

						std::ptrdiff_t const t = KP.second-KP.first;
						assert ( t == 1 );

						cur = *(KP.first);
						Vpath.push_back(cur);
					}
					else
					{
						break;
					}
				}

				// sum up k-mer weight to obtain path weight
				uint64_t weight = Vpath[0].fromfreq;
				for ( uint64_t j = 0; j < Vpath.size(); ++j )
					weight += Vpath[j].tofreq;

				uint64_t toweight = 0;
				for ( uint64_t j = 0; j < Vpath.size(); ++j )
					toweight += Vpath[j].tofreq;

				uint64_t fromweight = Vpath[0].fromfreq;
				for ( uint64_t j = 0; j+1 < Vpath.size(); ++j )
					fromweight += Vpath[j].tofreq;

				// std::cerr << Vpath.front().getFrom() << "," << Vpath.back().getTo() << " " << Vpath.size() << " weight=" << weight << " aweight=" << static_cast<double>(weight)/(Vpath.size()+1) << std::endl;

				uint64_t const AKLBLo_start = AKLBLo;
				for ( uint64_t j = 0; j < Vpath.size(); ++j )
					AKLBL.push(AKLBLo,Vpath[j]);
				uint64_t const AKLBLo_end = AKLBLo;

				BranchLink BL(Vpath.front().getFrom(),Vpath.back().getTo(),weight,fromweight,toweight,Vpath.size(),AKLBLo_start,AKLBLo_end);

				// std::cerr << BL << std::endl;

				VBL.push_back(BL);

				#if 0
				for ( uint64_t j = 0; j < Vpath.size(); ++j )
				{
					std::cerr << "\tVpath[" << j << "]=" << Vpath[j] << std::endl;
				}
				#endif
			}
		}

		// sort branch links
		std::sort(VBL.begin(),VBL.end());

		std::vector<BranchLink> VBLFT = VBL;
		std::sort(VBLFT.begin(),VBLFT.end(),BranchLinkFromToComparator());

		#if defined(BAMCONSENSUS_RUN_DOT)
		if ( VBL.size() )
		{
			// construct dot file name
			uint64_t const ldotid = dotid++;
			std::ostringstream fnostr;
			fnostr << "dot"
				<< "_"
				<< std::setw(6) << std::setfill('0') << prevrefid << std::setw(0)
				<< "_"
				<< std::setw(6) << std::setfill('0') << conswindowstart << std::setw(0)
				<< "_"
				<< std::setw(6) << std::setfill('0') << conswindowend << std::setw(0)
				<< "_"
				<< std::setw(6) << std::setfill('0') << ldotid    << std::setw(0)
				;
			std::string const fn = fnostr.str();

			// consturct dot command line
			std::ostringstream comstr;
			comstr << "dot -Tsvg <" << (fn+".dot") << " >" << (fn+".svg");
			std::string const com = comstr.str();

			// produce dot file
			libmaus2::aio::OutputStreamInstance::unique_ptr_type OSI(new libmaus2::aio::OutputStreamInstance(fn + ".dot"));
			(*OSI) << "digraph dot_" << ldotid << "\n";
			(*OSI) << "{\n";

			// iterate over branch links
			for ( uint64_t i = 0; i < VBL.size(); ++i )
			{
				BranchLink const & source = VBL.at(i);

				(*OSI) << "\t"
					<< "\"" << source.from << "\""
					<< " -> "
					<< "\"" << source.to << "\""
					<< "["
					<< "label="
					<< "\""
					<< source.length
					<< ","
					<< source.getWeight()
					<< "\""
					<< "]"
					<< ";\n";
			}

			(*OSI) << "}\n";

			// run dot on the file
			OSI->flush();
			OSI.reset();
			int const r = system(com.c_str());
			if ( r != 0 )
			{
				std::cerr << "[E] failed to run " << com << std::endl;
			}
		}
		#endif

		#if 0
		// iterate over branch links
		for ( uint64_t i = 0; i < VBL.size(); ++i )
		{
			BranchLink const & source = VBL.at(i);

			#if 0
			std::cerr << VBL[i] << std::endl;

			typedef std::vector<BranchLink>::const_iterator it;

			std::pair<it,it> const P = std::equal_range(
				VBL.begin(),
				VBL.end(),
				BranchLink(VBL[i].to),
				BranchLinkFromComparator()
			);

			for ( uint64_t j = 0; j < P.second-P.first; ++j )
				std::cerr << "\t" << P.first[j] << std::endl;
			#endif

			#if 0
			std::pair<BranchPoint const *, BranchPoint const *> const BPPT =
				std::equal_range(
					ABPT.begin(),
					ABPT.begin()+ABPTo,
					source.from
				);

			if ( BPPT.second == BPPT.first )
			{
				std::cerr << "source " << source << std::endl;

				libmaus2::autoarray::AutoArray<BranchLink> ABL;

				struct StackNode
				{
					int64_t visit;
					int64_t parent;
					uint64_t depth;
					BranchLink link;

					uint64_t ABLstart;
					uint64_t ABLend;

					StackNode() {}
					StackNode(int64_t const rvisit, int64_t const rparent, uint64_t const rdepth, BranchLink const & rlink)
					: visit(rvisit), parent(rparent), depth(rdepth), link(rlink), ABLstart(0), ABLend(0) {}
				};

				std::vector<StackNode> S;
				uint64_t oABL = 0;

				S.push_back(StackNode(-1,-1,0,source));

				while ( !S.empty() )
				{
					StackNode SN = S.back();
					S.pop_back();

					uint64_t const id = S.size();

					if ( SN.visit < 0 )
					{
						typedef std::vector<BranchLink>::const_iterator it;

						std::pair<it,it> const P = std::equal_range(
							VBL.begin(),
							VBL.end(),
							BranchLink(SN.link.to),
							BranchLinkFromComparator()
						);

						SN.visit += 1;
						SN.ABLstart = oABL;

						for ( std::ptrdiff_t j = 0; j < P.second-P.first; ++j )
							ABL.push(oABL,P.first[j]);

						SN.ABLend = oABL;

						S.push_back(SN);
					}
					else if ( SN.visit < static_cast<int64_t>(SN.ABLend-SN.ABLstart) )
					{
						BranchLink const sublink = ABL[SN.ABLstart + SN.visit];

						SN.visit += 1;
						S.push_back(SN);

						StackNode subnode(-1,id,SN.depth+1,sublink);
						S.push_back(subnode);
					}
					else
					{
						if ( SN.ABLend-SN.ABLstart == 0 )
						{
							int64_t cur = id;

							while ( cur >= 0 )
							{
								std::cerr << std::string(S[cur].depth,' ') << S[cur].link << std::endl;
								cur = S[cur].parent;
							}
						}

						oABL = SN.ABLstart;
					}
				}
			}
			#endif
		}
		#endif

		// get set of branch points
		std::set<BranchPoint> SBP;
		for ( uint64_t i = 0; i < VBL.size(); ++i )
		{
			SBP.insert(VBL[i].from);
			SBP.insert(VBL[i].to);
		}
		// get vector of unique branch points
		std::vector<BranchPoint> VBP(SBP.begin(),SBP.end());

		// construct edge map by integer id (1 based)
		std::map<uint64_t,std::vector<uint64_t> > edgeMap;
		for ( uint64_t i = 0; i < VBL.size(); ++i )
		{
			BranchLink const & cur = VBL[i];
			BranchPoint const & from = cur.from;
			BranchPoint const & to = cur.to;

			// get from id in branch point vector
			uint64_t const fromid = std::lower_bound(VBP.begin(),VBP.begin()+VBP.size(),from) - VBP.begin();
			assert ( fromid < VBP.size() && VBP[fromid] == from );
			// get to id in branch point vector
			uint64_t const toid = std::lower_bound(VBP.begin(),VBP.begin()+VBP.size(),to) - VBP.begin();
			//
			assert ( toid < VBP.size() && VBP[toid] == to );

			// store link (1 based)
			edgeMap[fromid+1].push_back(toid+1);
		}

		std::vector< std::vector<uint64_t> > Vcomp;
		std::map<uint64_t,uint64_t> Vcompmap;

		// compute connected components
		{
			std::set<uint64_t> unseen;
			std::map<uint64_t,std::vector<uint64_t> > redgeMap;

			for ( std::map<uint64_t,std::vector<uint64_t> >::const_iterator it = edgeMap.begin(); it != edgeMap.end(); ++it )
			{
				unseen.insert(it->first);
				std::vector<uint64_t> const & V = it->second;
				for ( uint64_t i = 0; i < V.size(); ++i )
				{
					unseen.insert(V[i]);
					// std::cerr << "edge (" << it->first << "," << V[i] << ")" << std::endl;
					redgeMap[V[i]].push_back(it->first);
				}
			}

			while ( unseen.size() )
			{
				#if 0
				std::cerr << "unseen.size()=" << unseen.size() << std::endl;
				#endif

				uint64_t const rv = *(unseen.begin());

				std::deque<uint64_t> todo;
				todo.push_back(rv);

				std::set<uint64_t> component;

				while ( todo.size() )
				{
					uint64_t const v = todo.front();
					todo.pop_front();

					if ( component.find(v) == component.end() )
					{
						component.insert(v);

						if ( edgeMap.find(v) != edgeMap.end() )
						{
							std::vector<uint64_t> const & V = edgeMap.find(v)->second;
							for ( uint64_t i = 0; i < V.size(); ++i )
								if ( component.find(V[i]) == component.end() )
									todo.push_back(V[i]);
						}
						if ( redgeMap.find(v) != redgeMap.end() )
						{
							std::vector<uint64_t> const & V = redgeMap.find(v)->second;
							for ( uint64_t i = 0; i < V.size(); ++i )
								if ( component.find(V[i]) == component.end() )
									todo.push_back(V[i]);
						}
					}
				}

				for ( std::set<uint64_t>::const_iterator it = component.begin(); it != component.end(); ++it )
					unseen.erase(*it);

				std::vector<uint64_t> const V(component.begin(),component.end());

				// set branch point id to component map
				for ( uint64_t i = 0; i < V.size(); ++i )
					Vcompmap[V[i]] = Vcomp.size();

				// push connected component
				Vcomp.push_back(V);

				#if 0
				std::cerr << "component ";
				for ( uint64_t i = 0; i < V.size(); ++i )
					std::cerr << V[i] << ";";
				std::cerr << std::endl;
				#endif
			}
		}

		// consider connected components
		for ( uint64_t z = 0; z < Vcomp.size(); ++z )
		{
			std::vector<uint64_t> const & V = Vcomp[z];

			int64_t maxfreq = 0;
			int64_t maxy = -1;
			int64_t maxx = -1;
			#if 0
			int64_t maxu = -1;
			#endif

			// iterate over branch points in component
			for ( uint64_t y = 0; y < V.size(); ++y )
			{
				assert ( V[y] > 0 );
				// get branch point
				BranchPoint const & BP = VBP[V[y]-1];

				typedef std::vector<BranchLink>::const_iterator it;

				// get branch links originating from BP
				std::pair<it,it> const P = std::equal_range(
					VBL.begin(),
					VBL.end(),
					BranchLink(BP),
					BranchLinkFromComparator()
				);

				// iterate over branch links
				for ( it x = P.first; x != P.second; ++x )
				{
					// get link
					BranchLink const & BL = *x;
					// get kmer links
					KmerLink const * KL_A = AKLBL.begin() + BL.link_start;
					KmerLink const * KL_E = AKLBL.begin() + BL.link_end;

					assert ( KL_A[ 0].getFrom() == BL.from );
					assert ( KL_E[-1].getTo()   == BL.to   );

					// std::cerr << BL << std::endl;

					// iterate over kmer links on branch link
					for ( KmerLink const * KL_C = KL_A; KL_C != KL_E; ++KL_C )
					{
						// get frequency of from and to
						int64_t const fromfreq = KL_C->fromfreq;
						int64_t const tofreq = KL_C->tofreq;

						if ( fromfreq > maxfreq )
						{
							assert ( KL_C - KL_A == 0 );
							maxy = y;
							maxx = x - P.first;
							#if 0
							maxu = KL_C - KL_A;
							#endif
							maxfreq = fromfreq;
						}
						if ( tofreq > maxfreq )
						{
							maxy = y;
							maxx = x - P.first;
							#if 0
							maxu = (KL_C - KL_A)+1;
							#endif
							maxfreq = tofreq;
						}

						// std::cerr << "\t" << *KL_C << std::endl;
					}
				}
			}

			assert ( maxfreq > 0 );

			// get branch point carying maximum frequency link
			BranchPoint const & BP = VBP[V[maxy]-1];

			typedef std::vector<BranchLink>::const_iterator it;

			// get vector of links originating from BP
			std::pair<it,it> const P = std::equal_range(
				VBL.begin(),
				VBL.end(),
				BranchLink(BP),
				BranchLinkFromComparator()
			);

			assert ( maxx < P.second-P.first );

			#if 0
			BranchLink const & centerBL = P.first[maxx];
			KmerLink   const & centerKL = maxu ? AKLBL[centerBL.link_start+maxu-1] : AKLBL[centerBL.link_start + maxu];
			// std::cerr << "maxlink " << maxfreq << " " << centerBL << " " << centerKL << std::endl;
			#endif

			std::vector<BranchLink> VBLto(VBL);
			std::sort(VBLto.begin(),VBLto.end(),BranchLinkToComparator());

			std::vector<BranchLink> VBLmax;

			BranchLink curBL = P.first[maxx];
			VBLmax.push_back(curBL);

			// follow backward links from curBL
			while ( true )
			{
				// look for links
				std::pair<it,it> const LP =
					std::equal_range(
						VBLto.begin(),
						VBLto.end(),
						BranchLink(BranchPoint(),curBL.from),
						BranchLinkToComparator()
					);

				// if any
				if ( LP.second-LP.first )
				{
					// look for maximum weight
					double weight = -std::numeric_limits<double>::max();
					int64_t maxi = -1;
					for ( int64_t i = 0; i < LP.second-LP.first; ++i )
						if ( LP.first[i].getWeight() > weight )
						{
							weight = LP.first[i].getWeight();
							maxi = i;
						}
					assert ( maxi >= 0 );

					curBL = LP.first[maxi];
					VBLmax.push_back(curBL);
				}
				else
				{
					break;
				}
			}

			// reverse path (we were going backward)
			std::reverse(VBLmax.begin(),VBLmax.end());

			curBL = P.first[maxx];

			std::vector<BranchLink> VBLfrom(VBL);
			std::sort(VBLfrom.begin(),VBLfrom.end(),BranchLinkFromComparator());

			// follow forward
			while ( true )
			{
				// look for extensions
				std::pair<it,it> const LP =
					std::equal_range(
						VBLfrom.begin(),
						VBLfrom.end(),
						BranchLink(curBL.to,BranchPoint()),
						BranchLinkFromComparator()
					);

				// if any
				if ( LP.second-LP.first )
				{
					// look for maximum weight
					double weight = -std::numeric_limits<double>::max();
					int64_t maxi = -1;
					for ( int64_t i = 0; i < LP.second-LP.first; ++i )
						if ( LP.first[i].getWeight() > weight )
						{
							weight = LP.first[i].getWeight();
							maxi = i;
						}
					assert ( maxi >= 0 );

					curBL = LP.first[maxi];
					VBLmax.push_back(curBL);

					// std::cerr << "extending by " << curBL << std::endl;
				}
				else
				{
					break;
				}
			}

			#if 0
			std::cerr << "full path" << std::endl;
			#endif

			// decode kmer path
			std::ostringstream fullpathstr;
			if ( VBLmax.size() )
			{
				BranchLink const & BL = VBLmax[0];
				KmerLink const * KL_A = AKLBL.begin() + BL.link_start;
				fullpathstr << printKmer(KL_A->kfrom,KL_A->k);
			}

			for ( uint64_t i = 0; i < VBLmax.size(); ++i )
			{
				BranchLink const & BL = VBLmax[i];
				KmerLink const * KL_A = AKLBL.begin() + BL.link_start;
				KmerLink const * KL_E = AKLBL.begin() + BL.link_end;

				for ( KmerLink const * KL_C = KL_A; KL_C != KL_E; ++KL_C )
				{
					fullpathstr << libmaus2::fastx::remapChar(KL_C->kto & 3);
					// std::cerr << *KL_C << std::endl;
				}

				#if 0
				std::cerr << VBLmax[i] << std::endl;
				#endif
			}

			std::string const fullpath = fullpathstr.str();
			#if 0
			std::cerr << "path " << fullpath << std::endl;
			#endif

			uint64_t const reffrom = VBLmax.front().from.ref.refStart.first; // refpos;
			uint64_t const refto   = VBLmax.back().to.ref.refEnd.first;

			ConsensusPart part(
				reffrom,refto,fullpath
			);
			Vcons.push_back(part);

			// std::cerr << "constructed " << part.getInterval() << " reffrom=" << reffrom << " refto=" << refto << " ref.size()=" << ref.size() << std::endl;

			#if 0
			std::string const sub = ref.substr(reffrom,refto-reffrom);
			libmaus2::lcs::NP np;

			np.np(
				sub.begin(),sub.end(),
				fullpath.begin(),fullpath.end()
			);


			libmaus2::lcs::AlignmentPrint::printAlignmentLines(
				std::cerr,
				sub.begin(),
				sub.size(),
				fullpath.begin(),
				fullpath.size(),
				80,
				np.ta,
				np.te,
				Ident::mapfun,
				reffrom,
				0,
				"ref",
				"cons"
			);
			#endif
		}

		#if 0
		#endif

		// add edge from node 0 (graph source) to k-mer graph sources
		for ( uint64_t i = 0; i < VBP.size(); ++i )
		{
			std::pair<BranchPoint const *, BranchPoint const *> const BPPT =
				std::equal_range(
					ABPT.begin(),
					ABPT.begin()+ABPTo,
					VBP[i]
				);

			if ( BPPT.second == BPPT.first )
			{
				uint64_t const id = std::lower_bound(VBP.begin(),VBP.begin()+VBP.size(),VBP[i]) - VBP.begin();
				assert ( id < VBP.size() && VBP[id] == VBP[i] );

				edgeMap[0].push_back(id+1);
			}
		}

		for ( std::map<uint64_t,std::vector<uint64_t> >::iterator it = edgeMap.begin(); it != edgeMap.end(); ++it )
		{
			std::vector<uint64_t> & V = it->second;
			std::sort(V.begin(),V.end());
		}

		// get topological sorting of graph
		std::pair<bool,std::map<uint64_t,uint64_t> > const topSort =
			libmaus2::graph::TopologicalSorting::topologicalSorting<uint64_t,libmaus2::graph::IdentityTargetProjector>(edgeMap,0);

		std::map<uint64_t,std::vector<uint64_t> > DOM;
		std::map<uint64_t,uint64_t> RTOP;

		if ( topSort.first )
		{
			// forward edges according to top sorting ids
			std::map< uint64_t,std::vector<uint64_t> > edgeMapOut;
			// reverse edges according to top sorting ids
			std::map< uint64_t,std::vector<uint64_t> > redgeMapOut;

			// minimum and maximum id appearing in links on top sort ids
			uint64_t maxid = 0;
			uint64_t minid = std::numeric_limits<uint64_t>::max();

			// reverse mapping for topological sorting
			for ( std::map<uint64_t,uint64_t>::const_iterator it = topSort.second.begin(); it != topSort.second.end(); ++it )
				RTOP[it->second] = it->first;

			// construct edgeMapOut and redgeMapOut
			for ( std::map< uint64_t,std::vector<uint64_t> >::const_iterator it = edgeMap.begin(); it != edgeMap.end(); ++it )
			{
				uint64_t const from = it->first;
				uint64_t const fromid = topSort.second.find(from)->second;

				maxid = std::max(maxid,fromid);
				minid = std::min(minid,fromid);

				std::vector<uint64_t> const & V = it->second;

				for ( uint64_t j = 0; j < V.size(); ++j )
				{
					uint64_t const to = V[j];
					uint64_t const toid = topSort.second.find(to)->second;

					maxid = std::max(maxid,toid);
					minid = std::min(minid,toid);

					edgeMapOut[fromid].push_back(toid);
					redgeMapOut[toid].push_back(fromid);
				}
			}

			// calculate dominating node sets
			DOM[0].push_back(0);

			for ( uint64_t j = 1; j <= maxid; ++j )
			{
				if ( redgeMapOut.find(j) != redgeMapOut.end() )
				{
					std::vector<uint64_t> const & V = redgeMapOut.find(j)->second;
					assert ( V.size() );

					assert ( DOM.find(V[0]) != DOM.end() );
					std::vector<uint64_t> CDOM = DOM.find(V[0])->second;

					for ( uint64_t z = 1; z < V.size(); ++z )
					{
						assert ( DOM.find(V[z]) != DOM.end() );
						std::vector<uint64_t> LDOM = DOM.find(V[z])->second;
						std::vector<uint64_t> ODOM;

						std::set_intersection(
							CDOM.begin(),CDOM.end(),
							LDOM.begin(),LDOM.end(),
							std::back_inserter(ODOM)
						);

						CDOM = ODOM;
					}

					for ( uint64_t z = 0; z < CDOM.size(); ++z )
						DOM[j].push_back(CDOM[z]);
				}

				DOM[j].push_back(j);
			}

			edgeMap = edgeMapOut;
		}
		else
		{
			std::cerr << "[W] topological sorting failed" << std::endl;
		}

		// output graph with dominating node info
		#if defined(BAMCONSENSUS_RUN_DOT)
		if ( VBL.size() )
		{
			uint64_t const ldotid = dotid++;
			std::ostringstream fnostr;
			fnostr << "topdot"
				<< "_"
				<< std::setw(6) << std::setfill('0') << prevrefid << std::setw(0)
				<< "_"
				<< std::setw(6) << std::setfill('0') << conswindowstart << std::setw(0)
				<< "_"
				<< std::setw(6) << std::setfill('0') << conswindowend << std::setw(0)
				<< "_"
				<< std::setw(6) << std::setfill('0') << ldotid    << std::setw(0)
				;
			std::string const fn = fnostr.str();

			std::ostringstream comstr;
			comstr << "dot -Tsvg <" << (fn+".dot") << " >" << (fn+".svg");
			std::string const com = comstr.str();

			libmaus2::aio::OutputStreamInstance::unique_ptr_type OSI(new libmaus2::aio::OutputStreamInstance(fn + ".dot"));
			(*OSI) << "digraph dot_" << ldotid << "\n";
			(*OSI) << "{\n";

			OSI->setf(std::ios::fixed,std::ios::floatfield);
			OSI->precision(1);

			// output node names
			for ( std::map< uint64_t,std::vector<uint64_t> >::const_iterator it = DOM.begin(); it != DOM.end(); ++it )
			{
				std::vector<uint64_t> const & V = it->second;

				uint64_t const from   = it->first;
				uint64_t const dom    = (V.size() > 1) ? V[V.size()-2] : V[V.size()-1];
				uint64_t const fromid = (RTOP.find(from) != RTOP.end()) ? RTOP.find(from)->second : from;
				uint64_t const domid  = (RTOP.find(dom)  != RTOP.end()) ? RTOP.find(dom)->second  : dom;

				if ( fromid > 0 )
				{
					(*OSI) << fromid << "[ label=\"";

					if ( fromid )
						(*OSI) << VBP[fromid-1] << "(" << fromid << ")";
					else
						(*OSI) << fromid;

					if ( domid != fromid )
						(*OSI) << "(" << domid << ")";

					(*OSI) << "\"];\n";
				}
			}

			// output edges
			for ( std::map< uint64_t,std::vector<uint64_t> >::const_iterator it = edgeMap.begin(); it != edgeMap.end(); ++it )
			{
				// id on top sorting
				uint64_t const from = it->first;
				// orignal id
				uint64_t const fromid = (RTOP.find(from) != RTOP.end()) ? RTOP.find(from)->second : from;

				// get edge target vector
				std::vector<uint64_t> const & V = it->second;

				uint64_t ilow = 0;
				while ( ilow < V.size() )
				{
					// to id on top sorting
					uint64_t const to = V[ilow];
					// to original id
					uint64_t const toid = (RTOP.find(to) != RTOP.end()) ? RTOP.find(to)->second : to;

					uint64_t ihigh = ilow+1;

					while ( ihigh < V.size() && V[ihigh] == to )
						++ihigh;

					if ( fromid > 0 && toid > 0 )
					{
						BranchPoint const & fromBP = VBP[fromid-1];
						BranchPoint const & toBP   = VBP[toid-1];

						typedef std::vector<BranchLink>::const_iterator it;

						std::pair<it,it> const P = std::equal_range(VBLFT.begin(),VBLFT.end(),BranchLink(fromBP,toBP),BranchLinkFromToComparator());

						assert ( P.second - P.first == static_cast< ::std::ptrdiff_t >(ihigh-ilow) );
						assert ( P.second - P.first > 0 );

						for ( it it_c = P.first; it_c != P.second; ++it_c )
						{
							(*OSI) << fromid << " -> " << toid;
							(*OSI) << "[label=\"" << it_c->getWeight() << "," << it_c->getFromWeight() << "," << it_c->getToWeight() << "\"]";
							(*OSI) << ";\n";
						}
					}


					ilow = ihigh;
				}

				#if 0
				for ( uint64_t j = 0; j < V.size(); ++j )
				{
					// to id on top sorting
					uint64_t const to = V[j];
					// to original id
					uint64_t const toid = (RTOP.find(to) != RTOP.end()) ? RTOP.find(to)->second : to;

					if ( fromid > 0 && toid > 0 )
					{
						// output edge ids
						(*OSI) << fromid << " -> " << toid;

						BranchPoint const & fromBP = VBP[fromid-1];
						BranchPoint const & toBP   = VBP[toid-1];

						typedef std::vector<BranchLink>::const_iterator it;

						std::pair<it,it> const P = std::equal_range(VBLFT.begin(),VBLFT.end(),BranchLink(fromBP,toBP),BranchLinkFromToComparator());

						if ( P.second != P.first )
						{
							(*OSI) << "[label=\"" << P.first->getWeight() << "\"]";
							// (*OSI) << "[label=\"" << *(P.first) << "\"]";
						}
						else
						{
							std::cerr << "unable to find " << fromBP << " -> " << toBP << std::endl;
							for ( uint64_t i = 0; i < VBL.size(); ++i )
								std::cerr << "VBL[" << i << "]=" << VBL[i] << std::endl;
						}

						(*OSI) << ";\n";
					}
				}
				#endif
			}

			(*OSI) << "}\n";

			OSI->flush();
			OSI.reset();

			int const r = system(com.c_str());
			if ( r != 0 )
			{
				std::cerr << "[E] failed to run " << com << std::endl;
			}
			else
			{
				std::cerr << "[V] produced " << fn << std::endl;
			}
		}
		#endif

		#if 0
		std::sort(
			AKL.begin(),
			AKL.begin() + KLo,
			KmerLinkToPosComparator()
		);

		std::vector<KmerLink> kbranchleft;
		std::vector<KmerLink> kbranchright;
		std::vector<KmerLink> kbranch;


		for ( uint64_t i = 0; i < KLo; ++i )
		{
			uint64_t const kfrom = AKL[i].kfrom;

			std::pair<KmerLink const *,KmerLink const *> const KP =
				std::equal_range(
					AKL.begin(),
					AKL.begin()+KLo,
					KmerLink(
						k,0,kfrom,
						0,
						0,
						AKL[i].refposfrom,
						AKL[i].refshiftfrom,
						0,0
					),
					KmerLinkToPosComparator()
				);
			std::ptrdiff_t const t = KP.second-KP.first;

			if ( t != 1 )
			{
				BranchPoint const BP(
					k,
					kfrom,
					AKL[i].refposfrom,
					AKL[i].refshiftfrom
				);
				// std::cerr << AKL[i] << " " << t << std::endl;
				kbranchleft.push_back(AKL[i]);
				kbranch.push_back(AKL[i]);
			}

			#if 0
			for ( std::ptrdiff_t j = 0; j < t; ++j )
			{
				std::cerr << "[" << j << "]=" << KP.first[j] << std::endl;
			}
			#endif
		}

		std::sort(
			AKL.begin(),
			AKL.begin() + KLo,
			KmerLinkFromPosComparator()
		);

		{
			uint64_t ilow = 0;

			while ( ilow < KLo )
			{
				uint64_t ihigh = ilow+1;
				while ( ihigh < KLo && AKL[ihigh].kfrom == AKL[ilow].kfrom &&
					AKL[ihigh].refposfrom == AKL[ilow].refposfrom &&
					AKL[ihigh].refshiftfrom == AKL[ilow].refshiftfrom )
				{
					++ihigh;
				}

				if ( ihigh - ilow > 1 )
				{
					for ( uint64_t i = ilow; i < ihigh; ++i )
					{
						kbranchright.push_back(AKL[i]);
						kbranch.push_back(AKL[i]);
					}
				}

				ilow = ihigh;
			}
		}

		std::sort(kbranch.begin(),kbranch.end());
		std::sort(kbranchleft.begin(),kbranchleft.end());
		std::sort(kbranchright.begin(),kbranchright.end());

		if ( kbranch.size() )
		{
			uint64_t io = 1;
			for ( uint64_t i = 1; i < kbranch.size(); ++i )
				if ( kbranch[i-1] < kbranch[i] )
					kbranch[io++] = kbranch[i];
			kbranch.resize(io);
		}


		for ( uint64_t i = 0; i < kbranch.size(); ++i )
		{
			KmerLink cur = kbranch[i];

			std::vector<KmerLink> Vpath;
			Vpath.push_back(cur);

			while ( true )
			{
				std::pair<std::vector<KmerLink>::const_iterator,std::vector<KmerLink>::const_iterator> const KB =
					std::equal_range(
						kbranch.begin(),
						kbranch.end(),
						cur
					);

				if ( KB.first == KB.second )
				{
					std::pair<KmerLink const *,KmerLink const *> const KP =
						std::equal_range(
							AKL.begin(),
							AKL.begin()+KLo,
							KmerLink(
								k,cur.kto,0,
								cur.refposto,
								cur.refshiftto,
								0,0,
								0,0
							),
							KmerLinkFromPosComparator()
						);

					std::ptrdiff_t const t = KP.second-KP.first;
					assert ( t == 1 );

					cur = *(KP.first);

					Vpath.push_back(cur);
				}
				else
				{
					#if 0
					std::pair<KmerLink const *,KmerLink const *> const KP =
						std::equal_range(
							AKL.begin(),
							AKL.begin()+KLo,
							KmerLink(
								k,cur.kto,0,
								cur.refposto,
								cur.refshiftto,
								0,0,
								0,0
							),
							KmerLinkFromPosComparator()
						);

					std::ptrdiff_t const t = KP.second-KP.first;

					assert ( t != 1 );
					#endif

					break;
				}
			}

			// std::cerr << std::string(80,'-') << std::endl;
			std::cerr << Vpath.front() << " -> " << Vpath.back() << " " << Vpath.size() << " " << std::endl;

			#if 0
			for ( uint64_t j = 0; j < Vpath.size(); ++j )
				std::cerr << Vpath[j] << std::endl;
			#endif
		}
		#endif

		#if 0
		std::sort(
			countFreqA.begin(),
			countFreqA.begin() + o
		);

		libmaus2::autoarray::AutoArray<KmerPos> ASFV;
		libmaus2::autoarray::AutoArray<KmerPos> AASFV[4];
		struct StackElement
		{
			int64_t parent;
			uint64_t depth;
			uint64_t kmer;
			uint64_t ASFV_start;
			uint64_t ASFV_end;
			int64_t visit;

			StackElement()
			{

			}
			StackElement(
				int64_t const rparent,
				uint64_t const rdepth,
				uint64_t const rkmer,
				uint64_t const rASFV_start,
				uint64_t const rASFV_end,
				int64_t const rvisit
			) : parent(rparent), depth(rdepth), kmer(rkmer), ASFV_start(rASFV_start), ASFV_end(rASFV_end), visit(rvisit)
			{
			}

			StackElement nextVisit() const
			{
				StackElement S = *this;
				S.visit += 1;
				return S;
			}
		};
		uint64_t ASFVo = 0;

		std::vector<StackElement> S;

		for ( uint64_t i = 0; i < o; ++i )
		{
			std::cerr << "\t" << countFreqA[i] << std::endl;
			KmerPos const KPref = countFreqA[i].KP;

			ASFVo = KM.find(countFreqA[i].kcode)->second->getRefMatches(ASFV,KPref,countFreqH,0);

			S.push_back(
				StackElement(-1 /* parent */,0 /* depth */,countFreqA[i].kcode,0,ASFVo,0)
			);

			while ( !S.empty() )
			{
				StackElement const SE = S.back();
				S.pop_back();

				uint64_t const nodeid = S.size();

				if ( SE.visit == 0 )
					std::cerr << std::string(SE.depth,' ') << printKmer(SE.kmer,k) << " [" << SE.ASFV_start << "," << SE.ASFV_end << ")" << std::endl;

				if ( SE.visit == 0 )
				{
					S.push_back(SE.nextVisit());

					std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator it = KM.find(SE.kmer);
					assert ( it != KM.end() );
					KmerInfo const & info = *(it->second);

					std::vector<std::pair<uint64_t,uint64_t> > VP;

					for ( unsigned int j = 0; j < 4; ++j )
					{
						uint64_t const nextk = info.getNextKmer(j);
						std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator oit = KM.find(nextk);

						if ( oit != KM.end() )
						{
							uint64_t const lASFVo = KmerInfo::mergeReadPos(
								ASFV.begin() + SE.ASFV_start,
								ASFV.begin() + SE.ASFV_end,
								*(oit->second),
								countFreqH,
								AASFV[j],
								1 /* shift */,
								0
							);

							if ( lASFVo )
								VP.push_back(std::make_pair(lASFVo,j));

							#if 0
							if ( ASFVo > ASFVostart )
							{
								StackElement SEn(
									nodeid,
									SE.depth+1,
									nextk,
									ASFVostart,ASFVo,0
								);
								S.push_back(SEn);
							}
							#endif
						}
					}

					std::sort(VP.begin(),VP.end());

					for ( uint64_t jj = 0; jj < VP.size(); ++jj )
					{
						uint64_t const j = VP[jj].second;
						uint64_t const oo = VP[jj].first;
						uint64_t const nextk = info.getNextKmer(j);

						uint64_t const ASFVostart = ASFVo;
						for ( uint64_t i = 0; i < oo; ++i )
							ASFV.push(ASFVo,AASFV[j][i]);

						StackElement SEn(
							nodeid,
							SE.depth+1,
							nextk,
							ASFVostart,ASFVo,0
						);
						S.push_back(SEn);
					}

					if ( ! VP.size() )
					{
						int64_t id = nodeid;
						uint64_t c = 0;

						while ( id >= 0 )
						{
							id = S.at(id).parent;
							c += 1;
						}

						std::cerr << "c=" << c << std::endl;
					}
				}
				else
				{
					ASFVo = SE.ASFV_start;
				}
			}

			assert ( ASFVo == 0 );

			#if 0
			std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator it = KM.find(countFreqA[i].kcode);
			assert ( it != KM.end() );
			KmerInfo const & info = *(it->second);

			for ( unsigned int j = 0; j < 4; ++j )
			{
				uint64_t const prevk = info.getPrevKmer(j);
				std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator oit = KM.find(prevk);

				if ( oit != KM.end() )
					std::cerr << "\t\tprev " << printKmer(prevk,k) << " " << oit->second->killHeap.f << std::endl;
			}
			for ( unsigned int j = 0; j < 4; ++j )
			{
				uint64_t const nextk = info.getNextKmer(j);
				std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator oit = KM.find(nextk);

				if ( oit != KM.end() )
					std::cerr << "\t\tnext " << printKmer(nextk,k) << " " << oit->second->killHeap.f << std::endl;
			}
			#endif
		}
		#endif

		#if 0
		std::sort(Vcons.begin(),Vcons.end());
		for ( uint64_t i = 0; i < Vcons.size(); ++i )
			std::cerr << Vcons[i] << std::endl;
		#endif

		Vcons = ConsensusMerge::merge(
			Vcons,conswindowstart,conswindowend+k,prevrefid,ref
		);

		std::sort(Vcons.begin(),Vcons.end());

		#if defined(HANDLE_DEBUG)
		for ( uint64_t i = 0; i < Vcons.size(); ++i )
			std::cerr << Vcons[i] << std::endl;
		#endif

		#if defined(HANDLE_DEBUG)
		for ( uint64_t i = 0; i < Vcons.size(); ++i )
		{
			ConsensusPart const & CP = Vcons[i];
			std::string const & fullpath = CP.path;
			uint64_t const reffrom = CP.from;
			uint64_t const refto = CP.to;

			std::string const sub = ref.substr(reffrom,refto-reffrom);
			libmaus2::lcs::NP np;

			np.np(
				sub.begin(),sub.end(),
				fullpath.begin(),fullpath.end()
			);

			struct Ident
			{
				static char mapfun(char const c)
				{
					return c;
				}
			};

			libmaus2::lcs::AlignmentPrint::printAlignmentLines(
				std::cerr,
				sub.begin(),
				sub.size(),
				fullpath.begin(),
				fullpath.size(),
				80,
				np.ta,
				np.te,
				Ident::mapfun,
				reffrom,
				0,
				"ref",
				"cons"
			);
		}

		#if 0

		for ( uint64_t i = 1; i < Vcons.size(); ++i )
		{
			ConsensusPart const & CP0 = Vcons[i-1];
			ConsensusPart const & CP1 = Vcons[i-0];

			if ( CP0.to > CP1.from )
			{
				libmaus2::math::IntegerInterval<int64_t> const IA(CP0.from,CP0.to-1);
				libmaus2::math::IntegerInterval<int64_t> const IB(CP1.from,CP1.to-1);
				libmaus2::math::IntegerInterval<int64_t> const IC = IA.intersection(IB);
				std::cerr << CP0 << std::endl;
				std::cerr << CP1 << std::endl;
				std::cerr << IA << " " << IB << " " << IC << std::endl;

				std::string const EA = Extract::extract(CP0,ref,IC);
				std::string const EB = Extract::extract(CP1,ref,IC);

				std::cerr << "EA=" << EA << std::endl;
				std::cerr << "EB=" << EB << std::endl;

				libmaus2::lcs::NP np;
				np.np(EA.begin(),EA.end(),EB.begin(),EB.end());

				libmaus2::lcs::AlignmentPrint::printAlignmentLines(
					std::cerr,
					EA.begin(),
					EA.size(),
					EB.begin(),
					EB.size(),
					80,
					np.ta,
					np.te,
					Ident::mapfun,
					0,
					0,
					"EA",
					"EB"
				);
			}
		}
		#endif

		for ( uint64_t i = 0; i < o; ++i )
			std::cerr << "\t\t" << countFreqA[i] << std::endl;
		#endif
	}

	return Vcons;
}

void mergeConsensus(
	std::vector<ConsensusPart> & Vcons,
	int64_t const prevrefid,
	std::string const & ref,
	std::string const & fullname,
	libmaus2::bambam::BamHeader const & header,
	int const verbose,
	libmaus2::bambam::BamBlockWriterBase & BBWB
)
{
	if ( verbose > 1 )
		std::cerr << "[mergeConsensus] " << prevrefid << " " << Vcons.size() << std::endl;

	if ( Vcons.size() )
	{
		Vcons = ConsensusMerge::merge(Vcons,0,ref.size(),prevrefid,ref);

		libmaus2::lcs::NP np;
		libmaus2::autoarray::AutoArray< std::pair<libmaus2::lcs::AlignmentTraceContainer::step_type,uint64_t> > Aopblocks;
		libmaus2::autoarray::AutoArray< libmaus2::bambam::cigar_operation> Aop;
		libmaus2::bambam::BamSeqEncodeTable const seqenc;
		::libmaus2::fastx::EntityBuffer<uint8_t,libmaus2::bambam::BamAlignment::D_array_alloc_type> buffer;
		libmaus2::autoarray::AutoArray<uint8_t> Q;
		::libmaus2::bambam::BamFormatAuxiliary auxdata;
		::libmaus2::bambam::MdStringComputationContext mdcontext;

		for ( uint64_t i = 0; i < Vcons.size(); ++i )
		{
			if ( verbose > 1 )
			{
				std::cerr << prevrefid << "\t" << ref.size() << "\t" << fullname << "\t" << Vcons[i] << std::endl;
				std::cerr << Vcons[i].print(ref);
			}

			std::string const   sub  = ref.substr(Vcons[i].from,Vcons[i].to-Vcons[i].from);
			std::string const & path = Vcons[i].path;
			np.np(
				sub.begin(),
				sub.end(),
				path.begin(),
				path.end()
			);


			uint64_t const ncig = libmaus2::bambam::CigarStringParser::traceToCigar(
				np,
				Aopblocks,
				Aop,
				0,0,0,0 /* clipping */
			);

			std::ostringstream shortnamestr;
			shortnamestr << "contig_" << prevrefid << "_" << header.getRefIDName(prevrefid) << "_" << i << "_" << Vcons[i].from << "_" << Vcons[i].to;
			std::string const shortname = shortnamestr.str();

			std::ostringstream longnamestr;
			longnamestr << shortname << " " << fullname;
			std::string const longname = longnamestr.str();

			Q.ensureSize(path.size());
			std::fill(Q.begin(),Q.begin() + path.size(), 255);

			libmaus2::bambam::BamAlignmentEncoderBase::encodeAlignment(
				buffer,
				seqenc,
				shortname.c_str(),
				shortname.size(),
				prevrefid,
				Vcons[i].from,
				255 /* mapping quality */,
				0 /* flags */,
				Aop.begin(),
				ncig,
				-1 /* next ref */,
				-1 /* next pos */,
				path.size() /* tlen */,
				path.c_str(),
				path.size(),
				Q.begin(),
				0 /* quality offset */,
				true /* reset buffer */
			);

			libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(buffer,"fn",longname.c_str());

			#if 0
			std::cerr << "sub =" << sub  << std::endl;
			std::cerr << "path=" << path << std::endl;
			#endif

			libmaus2::bambam::BamAlignmentDecoderBase::calculateMd(
				buffer.buffer,
				buffer.length,
				mdcontext,
				sub.c_str(),
				libmaus2::bambam::BamAlignmentDecoderBase::getSeq(buffer.buffer),
				path.size(),
				false,
				NULL
			);

			libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
				buffer,
				"MD",
				mdcontext.md.begin()
			);
			libmaus2::bambam::BamAlignmentEncoderBase::putAuxNumber(
				buffer,
				"NM",
				'i',
				mdcontext.nm
			);
			libmaus2::bambam::BamAlignmentEncoderBase::putAuxString(
				buffer,
				"RG",
				"bamconsensus_RG"
			);

			#if 0
			std::cerr << "NM=" << mdcontext.nm << " distance " << np.getAlignmentStatistics() << std::endl;
			#endif

			BBWB.writeBamBlock(buffer.buffer,buffer.length);

			#if 0
			std::string const samline = libmaus2::bambam::BamAlignmentDecoderBase::formatAlignment(
				buffer.buffer,
				buffer.length,
				header,
				auxdata
			);

			std::cerr << samline << std::endl;

			std::cout << ">" << longname << "\n";
			libmaus2::fastx::Pattern::printMultiLine(std::cout,Vcons[i].path.c_str(),Vcons[i].path.size(),80);
			#endif
		}
	}

	Vcons.resize(0);
}

struct RefEntry
{
	typedef RefEntry this_type;
	typedef std::unique_ptr<this_type> unique_ptr_type;
	typedef std::shared_ptr<this_type> shared_ptr_type;

	uint64_t id;
	std::string ref;
	std::string name;

	RefEntry()
	{}

	RefEntry(
		uint64_t const rid,
		std::string const & rref,
		std::string const & rname
	) : id(rid), ref(rref), name(rname)
	{

	}

	bool operator<(RefEntry const & RHS) const
	{
		return id < RHS.id;
	}
};

struct ReferenceCache
{
	std::string const & fafn;
	std::string const faifn;
	libmaus2::fastx::FastAIndex::unique_ptr_type Pindex;

	std::vector<std::string> Vfullnames;
	std::vector< std::atomic<unsigned int> > VuseCnt;
	std::vector< RefEntry::shared_ptr_type > Vref;
	libmaus2::parallel::StdMutex lock;

	libmaus2::bambam::BamHeader const & header;

	static libmaus2::fastx::FastAIndex::unique_ptr_type loadIndex(std::string const & fafn, std::string const & tmpprefix)
	{
		std::string const faifn = fafn + ".fai";
		std::string const longfn = fafn + ".fai.longnames";
		libmaus2::fastx::FastAIndexGenerator::generate(fafn,faifn,0 /* verbose */,&longfn,&tmpprefix);
		libmaus2::fastx::FastAIndex::unique_ptr_type Tindex(libmaus2::fastx::FastAIndex::load(faifn));
		return Tindex;
	}

	ReferenceCache(
		std::string const & rfafn,
		std::string const & tmpprefix,
		libmaus2::bambam::BamHeader const & rheader
	)
	: fafn(rfafn), faifn(fafn + ".fai"), Pindex(loadIndex(fafn,tmpprefix)),
	  VuseCnt(Pindex->size()),
	  Vref(Pindex->size()),
	  lock(),
	  header(rheader)
	{
		if ( header.getNumRef() != Pindex->size() )
		{
			libmaus2::exception::LibMausException lme;
			lme.getStream() << "[E] number of sequences in FastA " << Pindex->size() << " does not match number of sequences in BAM file " << header.getNumRef() << std::endl;
			lme.finish();
			throw lme;
		}

		for ( uint64_t i = 0; i < Pindex->size(); ++i )
		{
			char const * bamref = header.getRefIDName(i);
			libmaus2::fastx::FastAIndexEntry const & entry = (*Pindex)[i];

			if ( entry.name != std::string(bamref) )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream() << "[E] name mismatch " << entry.name << " != " << bamref << std::endl;
				lme.finish();
				throw lme;
			}

			if ( static_cast<int64_t>(entry.length) != static_cast<int64_t>(header.getRefIDLength(i)) )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream() << "[E] length mismatch " << entry.name << " != " << bamref << std::endl;
				lme.finish();
				throw lme;
			}
		}

		std::string const longfn = fafn + ".fai.longnames";

		std::cerr << "[V] loading names...";
		libmaus2::aio::InputStreamInstance ISI(longfn);
		std::string line;

		while ( std::getline(ISI,line) )
			if ( line.size() )
				Vfullnames.push_back(line);
		std::cerr << "done." << std::endl;

		assert ( Vfullnames.size() == Pindex->size() );
	}

	std::string loadString(uint64_t const id)
	{
		libmaus2::aio::InputStreamInstance ISI(fafn);
		libmaus2::autoarray::AutoArray<char> A = Pindex->readSequence(ISI,id);
		for ( uint64_t i = 0; i < A.size(); ++i )
			A[i] = libmaus2::fastx::remapChar(libmaus2::fastx::mapChar(A[i]));
		std::string const ref(A.begin(),A.size());

		return ref;
	}

	RefEntry::shared_ptr_type loadSeq(uint64_t const id)
	{
		RefEntry::shared_ptr_type ptr(
			new RefEntry(
				id,
				loadString(id),
				Vfullnames.at(id)
			)
		);

		return ptr;
	}

	RefEntry::shared_ptr_type get(uint64_t const id)
	{
		libmaus2::parallel::ScopeStdMutex slock(lock);

		if ( VuseCnt.at(id) == 0 )
		{
			Vref.at(id) = loadSeq(id);
			VuseCnt.at(id) += 1;

			std::cerr << "[V] reference cache loading " << Vref.at(id)->name << std::endl;
		}

		assert ( VuseCnt.at(id) );
		assert ( Vref.at(id) );

		return Vref.at(id);
	}

	RefEntry::shared_ptr_type put(RefEntry::shared_ptr_type ptr)
	{
		uint64_t const id = ptr->id;

		libmaus2::parallel::ScopeStdMutex slock(lock);

		if ( ! --VuseCnt.at(id) )
		{
			Vref.at(id) = RefEntry::shared_ptr_type();
			std::cerr << "[V] reference cache deallocating " << ptr->name << std::endl;
		}

		RefEntry::shared_ptr_type rptr;

		return rptr;
	}
};

static int getDefaultVerbose()
{
	return 1;
}

static unsigned int getDefaultK()
{
	return 32;
}

static unsigned int getDefaultMinLen()
{
	return 50;
}

int bamconsensus(libmaus2::util::ArgParser const & arg)
{
	// reference,T,k,verbose,minlen
	if ( !arg.uniqueArgPresent("reference") )
	{
		libmaus2::exception::LibMausException lme;
		lme.getStream() << "[E] required argument reference is missing" << std::endl;
		lme.finish();
		throw lme;
	}

	// k-mer size used
	std::string const fafn = arg["reference"];
	std::string const tmpprefix = arg.uniqueArgPresent("T") ? arg["T"] : libmaus2::util::ArgInfo::getDefaultTmpFileName(arg.progname);
	unsigned int const k = arg.getParsedArgOrDefault<uint64_t>("k",getDefaultK());
	unsigned int const verbose = arg.getParsedArgOrDefault<uint64_t>("verbose",getDefaultVerbose());
	unsigned int const minlen = arg.getParsedArgOrDefault<uint64_t>("minlen",getDefaultMinLen());
	uint64_t depththres = arg.getParsedArgOrDefault<int>("depththres",getDefaultDepth());
	uint32_t const excludeflags = libmaus2::bambam::BamFlagBase::stringToFlags(arg.uniqueArgPresent("exclude") ? arg["exclude"] : std::string());

	if ( k > 32 )
	{
		libmaus2::exception::LibMausException lme;
		lme.getStream() << "[E] k values larger than 32 are not supported" << std::endl;
		lme.finish();
		throw lme;
	}

	libmaus2::autoarray::AutoArray<char> AR_SEQ;
	uint64_t o_AR_OFF = 0;
	libmaus2::autoarray::AutoArray<uint64_t> AR_OFF;

	if ( arg.uniqueArgPresent("readsbam") )
	{
		libmaus2::bambam::BamDecoder decoder(arg["readsbam"]);
		libmaus2::bambam::BamAlignment const & algn = decoder.getAlignment();
		libmaus2::util::TabEntry<'_'> TE;
		libmaus2::autoarray::AutoArray<char> ASEQ;
		uint64_t o = 0;

		AR_OFF.push(o_AR_OFF,o);
		for ( uint64_t readid = 0; decoder.readAlignment(); ++readid )
		{
			bool const typeok =
				(((readid % 2 == 0) && algn.isRead1())
				||
				((readid % 2 == 1) && algn.isRead2()))
				&&
				(algn.getLseq() != 0)
				;

			if ( ! typeok )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream() << "[E] readsbam read type for " << algn.getName() << " is invalid: readid=" << readid << " isRead1=" << algn.isRead1() << " isRead2=" << algn.isRead2() << " lseq=" << algn.getLseq() << std::endl;
				lme.finish();
				throw lme;
			}

			char const * n_a = algn.getName();
			char const * n_e = n_a + strlen(n_a);

			TE.parse(n_a,n_a,n_e);

			uint64_t const i0 = TE.getParsed<uint64_t>(0,n_a);
			uint64_t const i1 = TE.getParsed<uint64_t>(1,n_a);
			uint64_t const pairid = readid/2;

			bool nameok = (i0==2*pairid+0) && (i1==2*pairid+1);

			if ( ! nameok )
			{
				libmaus2::exception::LibMausException lme;
				lme.getStream() << "[E] readsbam read type for " << algn.getName() << " is invalid (numbering wrong)" << std::endl;
				lme.finish();
				throw lme;
			}

			uint64_t const aseqlen = algn.decodeRead(ASEQ);

			AR_SEQ.push(o,ASEQ.begin(),ASEQ.begin()+aseqlen);
			AR_OFF.push(o_AR_OFF,o);
		}
	}

	// open decoder
	libmaus2::bambam::BamAlignmentDecoderWrapper::unique_ptr_type decwrapper(
		libmaus2::bambam::BamMultiAlignmentDecoderFactory::construct(arg)
	);
	libmaus2::bambam::BamAlignmentDecoder & decoder = decwrapper->getDecoder();
	// get alignment
	libmaus2::bambam::BamAlignment & algn = decoder.getAlignment();
	// get header
	libmaus2::bambam::BamHeader const & header = decoder.getHeader();

	// get lines
	std::vector<libmaus2::bambam::HeaderLine> allheaderlines = libmaus2::bambam::HeaderLine::extractLines(header.text);

	std::ostringstream upheadstr;
	upheadstr << "@HD\tVN:1.4\tSO:unknown\n";
	for ( uint64_t i = 0; i < allheaderlines.size(); ++i )
		if ( allheaderlines[i].type == "SQ" )
			upheadstr << allheaderlines[i].line << std::endl;
	RgInfo rginfo;
	rginfo.ID = "bamconsensus_RG";
	rginfo.LB = "bamconsensus_LG";
	rginfo.PU = "bamconsensus_PU";
	rginfo.PL = "bamconsensus_PL";
	rginfo.SM = "bamconsensus_SM";
	upheadstr << rginfo.toString();

	std::string upheadtext = upheadstr.str();
	upheadtext = libmaus2::bambam::ProgramHeaderLineSet::addProgramLine(upheadtext,"bamconsensus","bamconsensus",arg.commandline,std::string(),std::string(PACKAGE_VERSION));
	libmaus2::bambam::BamHeader const uphead(upheadtext);


	ReferenceCache RC(fafn,tmpprefix,header);
	RefEntry::shared_ptr_type RCcur;


	std::cerr << "[V] using k=" << k << std::endl;

	// alignment free list
	libmaus2::util::GrowingFreeList<libmaus2::bambam::BamAlignment,BamAlignmentAllocator,BamAlignmentTypeInfo> alFreeList;
	// alignment trace container free list
	libmaus2::util::GrowingFreeList<libmaus2::lcs::AlignmentTraceContainer,AlignmentTraceContainerAllocator,AlignmentTraceContainerTypeInfo> atcFreeList;
	// pos mapping free list
	libmaus2::util::GrowingFreeList<libmaus2::lcs::PosMapping,PosMappingAllocator,PosMappingTypeInfo> pmFreeList;
	// alignment heap
	libmaus2::util::FiniteSizeHeap<BamAlignmentHeapElement> alHeap(0);

	// allocator for kmer info objects
	KmerInfoAllocator kmerInfoAlloc(k);
	// free list for kmer info objects
	libmaus2::util::GrowingFreeList<
		KmerInfo,
		KmerInfoAllocator,
		KmerInfoTypeInfo
	> kmerInfoFreeList(kmerInfoAlloc);

	std::map<uint64_t,KmerInfo::shared_ptr_type> KM;
	std::map<uint64_t,KmerInfo::shared_ptr_type> KM1;

	// kmer end heap (END,k-mer)
	libmaus2::util::FiniteSizeHeap < std::pair<uint64_t,uint64_t> > KMend(0);

	// current depth
	uint64_t depth = 0;
	// end of previous interval
	int64_t prevend = 0;

	int64_t dstart = -1;
	int64_t dend = -1;
	uint64_t numint = 0;
	uint64_t const thres = 1;

	std::map<uint64_t,uint64_t> L;
	libmaus2::autoarray::AutoArray<libmaus2::bambam::cigar_operation> cigop;
	libmaus2::autoarray::AutoArray<char> ASEQ;

	libmaus2::clustering::KmerBase kbase;
	libmaus2::fastx::SingleWordDNABitBuffer wordbuffer(k);

	int64_t prevpos = 0;
	int64_t prevrefid = std::numeric_limits<int64_t>::max();

	// start of current consensus window
	int64_t       conswindowstart = 0;
	// size of consensus window
	int64_t const conswindowsize = 1000;

	libmaus2::autoarray::AutoArray< KmerFreq > countFreqA;
	libmaus2::util::FiniteSizeHeap<KmerPos,KmerPosKillHeapComparator> countFreqH(0);

	libmaus2::autoarray::AutoArray<KmerPos> LASFV;
	libmaus2::autoarray::AutoArray<KmerPos> RASFV;
	libmaus2::autoarray::AutoArray<KmerLink> AKL;
	libmaus2::autoarray::AutoArray<uint64_t> ARID;
	libmaus2::autoarray::AutoArray<KmerLink> AKLBL;
	libmaus2::autoarray::AutoArray < libmaus2::lcs::AlignmentTraceContainer::KMatch > R;
	libmaus2::autoarray::AutoArray<KmerPos> ARKP;
	libmaus2::autoarray::AutoArray<KmerPos> LARKP;
	libmaus2::util::TabEntry<'_'> TE;

	uint64_t dotid = 0;

	std::vector<ConsensusPart> Vcons;

	std::vector< ::libmaus2::lz::BgzfDeflateOutputCallback * > * Pcbs = 0;

	// construct writer
	libmaus2::bambam::BamBlockWriterBase::unique_ptr_type Pwriter(libmaus2::bambam::BamBlockWriterBaseFactory::construct(uphead,arg,Pcbs));
	libmaus2::bambam::BamBlockWriterBase & wr = *Pwriter;

	uint32_t u_prevrefid = 0;
	uint32_t u_prevpos = 0;


	libmaus2::autoarray::AutoArray<libmaus2::bambam::BamAlignmentDecoderBase::MdFieldToken> ENC_AMD;
	libmaus2::autoarray::AutoArray<char> ENC_Aref;
	libmaus2::autoarray::AutoArray<char> ENC_Aread;
	libmaus2::autoarray::AutoArray<uint32_t> ENC_Acigar;
	libmaus2::autoarray::AutoArray< std::pair<uint32_t,uint32_t> > ENC_O;
	libmaus2::bambam::BamAlignment::D_array_type ENC_T;
	libmaus2::autoarray::AutoArray<char> TA;
	libmaus2::autoarray::AutoArray<char> TQ;
	libmaus2::bambam::BamSeqEncodeTable const seqenc;
	libmaus2::autoarray::AutoArray<uint8_t,libmaus2::bambam::BamAlignment::D_array_alloc_type> TT;

	// iterate over alignments
	for ( uint64_t readid = 0; decoder.readAlignment(); ++readid )
	{
		// skip read if it contains non ACGT symbols
		if ( ! algn.checkReadACGT() )
			continue;

		// make sure we have no M operations in the CIGAR string of the alignment
		if ( algn.isMapped() )
		{
			if ( algn.getLseq() == 0 )
			{
				char const * n_a = algn.getName();
				char const * n_e = n_a + strlen(n_a);

				TE.parse(n_a,n_a,n_e);

				uint64_t const i0 = TE.getParsed<uint64_t>(0,n_a);
				uint64_t const i1 = TE.getParsed<uint64_t>(1,n_a);

				uint64_t o_0, o_1;

				if ( algn.isRead1() )
				{
					if ( ! (i0+1 < o_AR_OFF) )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] data not in readsbam " << algn.getName() << std::endl;
						lme.finish();
						throw lme;
					}

					o_0 = AR_OFF[i0+0];
					o_1 = AR_OFF[i0+1];
				}
				else if ( algn.isRead2() )
				{
					if ( ! (i1+1 < o_AR_OFF) )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] data not in readsbam " << algn.getName() << std::endl;
						lme.finish();
						throw lme;
					}

					o_0 = AR_OFF[i1+0];
					o_1 = AR_OFF[i1+1];
				}
				else
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] alignment has neither read1 nor read2 set " << algn.getName() << std::endl;
					lme.finish();
					throw lme;
				}

				TA.ensureSize(o_1-o_0);
				std::copy(AR_SEQ.begin()+o_0,AR_SEQ.begin()+o_1,TA.begin());

				if ( algn.isReverse() )
				{
					std::reverse(TA.begin(),TA.begin()+(o_1-o_0));
					for ( uint64_t i = 0; i < (o_1-o_0); ++i )
						TA[i] = libmaus2::fastx::invertUnmapped(TA[i]);
				}

				uint64_t l_0 = algn.getFrontHardClipping();
				uint64_t l_1 = (o_1-o_0) - algn.getBackHardClipping();

				TQ.ensureSize(l_1-l_0);
				std::fill(TQ.begin(),TQ.begin() + (l_1-l_0),'H');

				algn.replaceSequence(seqenc,TA.begin()+l_0,TQ.begin(),l_1-l_0,TT);

				// std::cerr << "produced " << algn.formatAlignment(header) << std::endl;
			}
			algn.ensureNormalizedCigar(ENC_AMD,ENC_Aref,ENC_Aread,ENC_Acigar,ENC_O,ENC_T);
		}

		// std::cerr << algn.formatAlignment(header) << std::endl;

		// check sorting
		if ( algn.isMapped() )
		{
			if (
				static_cast<uint32_t>(algn.getRefID()) < static_cast<uint32_t>(u_prevrefid)
				||
				(
					static_cast<uint32_t>(algn.getRefID()) == static_cast<uint32_t>(u_prevrefid)
					&&
					static_cast<uint32_t>(algn.getPos())   < static_cast<uint32_t>(u_prevpos)
				)
			)
			{
				libmaus2::exception::LibMausException se;
				se.getStream() << "[E] Input file is not sorted by coordinate." << std::endl;
				se.finish();
				throw se;
			}

			u_prevrefid = algn.getRefID();
			u_prevpos = algn.getPos();
		}

		// skip read if it contains non ACGT symbols
		if ( ! algn.checkReadACGT() )
			continue;

		// if line is mapped
		if ( algn.isMapped() && ((algn.getFlags() & excludeflags) == 0) && algn.getReferenceInterval().diameter() >= minlen )
		{
			// std::cerr << readid << "\t" << algn.formatAlignment(header) << std::endl;
			// std::cerr << "refid=" << algn.getRefID() << " " << algn.getReferenceInterval() << " " << header.getChromosomes()[algn.getRefID()].createLine() << " " << algn.getCigarString() << std::endl;

			// sanity check
			assert ( algn.getRefID() >= 0 );

			// ref id of alignment
			bool const newrefid = (algn.getRefID() != prevrefid);
			// size of previous ref id (or -1 if none)
			int64_t const prevrefidsize = header.getRefIDLength(prevrefid);

			if ( verbose && ((algn.getPos() / (1024*1024)) != (prevpos / (1024*1024))) )
			{
				std::cerr << "[V] " << algn.getRefID() << ":" << header.getRefIDName(algn.getRefID()) << ":" << algn.getPos()
					<< " " << KMend.f << " " << KM1.size() << " " << KM.size() << " " << " " << libmaus2::util::MemUsage() << std::endl;

				#if 0
				for ( std::map<uint64_t,KmerInfo::shared_ptr_type>::const_iterator it = KM.begin();
					it != KM.end(); ++it )
				{
					std::cerr << *(it->second) << std::endl;
				}
				#endif
			}
			// update prev position
			prevpos = algn.getPos();

			// get interval on reference
			libmaus2::math::IntegerInterval<int64_t> intv = algn.getReferenceInterval();
			// start and end point
			int64_t const start = intv.from;
			int64_t const end = intv.from + intv.diameter();

			// if the new alignment is past the current consensus window
			// then process the consensus window
			while (
				((!newrefid) && (start >= conswindowstart + conswindowsize))
				||
				(newrefid && (conswindowstart <= prevrefidsize))
			)
			{
				int64_t const nextwindowstart = conswindowstart + (conswindowsize+1)/2;
				int64_t const conswindowend = conswindowstart + conswindowsize;

				if ( ! RCcur )
					RCcur = RC.get(prevrefid);

				assert ( RCcur );
				std::vector<ConsensusPart> const Lcons =
					handleConsensus(
						k,
						thres,
						dotid,
						prevrefid,
						conswindowstart,
						conswindowend,
						countFreqA,
						countFreqH,
						AKL,
						KMend,
						KM,
						LASFV,
						RASFV,
						ARID,
						AKLBL,
						RCcur->ref,
						RCcur->name,
						header
					);

				// std::cerr << "[" << conswindowstart << "," << conswindowend << ")" << Lcons.size() << std::endl;

				for ( uint64_t i = 0; i < Lcons.size(); ++i )
					Vcons.push_back(Lcons[i]);

				handleKMEnd(k,KMend,KM1,KM,nextwindowstart,kmerInfoFreeList);

				conswindowstart = nextwindowstart;
			}

			if ( newrefid )
			{
				if ( Vcons.size() )
				{
					handleKMEnd(k,KMend,KM1,KM,std::numeric_limits<int64_t>::max(),kmerInfoFreeList);
					assert ( RCcur );
					mergeConsensus(Vcons,prevrefid,RCcur->ref,RCcur->name,header,verbose,wr);
				}

				if ( RCcur )
					RCcur = RC.put(RCcur);
			}

			while (
				! alHeap.empty()
				&&
				(
					alHeap.top().ptr->getRefID() != algn.getRefID()
					||
					alHeap.top().end <= intv.from
				)
			)
			{
				BamAlignmentHeapElement E = alHeap.pop();

				if ( depth == depththres )
				{
					dend = E.end;

					// std::cerr << E.ptr->getRefID() << ":" << "[" << dstart << "," << dend << ")" << std::endl;
					L[dend-dstart] += 1;
					numint += 1;
				}

				depth -= 1;

				if ( E.end > prevend )
				{
					// std::cerr << E.ptr->getRefID() << ":" << "[" << prevend << "," << E.end << "]" << depth << std::endl;

					prevend = E.end;
				}

				if ( E.ptr->getRefID() != algn.getRefID() )
				{
					prevend = 0;
				}

				alFreeList.put(E.ptr);
				atcFreeList.put(E.atc);
				pmFreeList.put(E.pm);
			}

			if ( intv.from > prevend )
			{
				// std::cerr << algn.getRefID() << ":" << "[" << prevend << "," << intv.from << "]" << depth << std::endl;
				prevend = intv.from;
			}

			// static void cigarToTrace(iterator ita, iterator ite, libmaus2::lcs::AlignmentTraceContainer & ATC, bool ignoreUnknown = true)
			uint32_t const ncig = algn.getCigarOperations(cigop);

			libmaus2::lcs::AlignmentTraceContainer::shared_ptr_type patc = atcFreeList.get();
			libmaus2::bambam::CigarStringParser::cigarToTrace(cigop.begin(),cigop.begin()+ncig,*patc);

			std::pair<int64_t,int64_t> const SL =
				patc->getStringLengthUsed();

			#if 0
			std::cerr << "SL.first=" << SL.first << " diam=" << intv.diameter()
				<< " " << algn.getCigarString() << std::endl;
			#endif

			assert ( SL.first == intv.diameter() );

			libmaus2::lcs::PosMapping::shared_ptr_type pm = pmFreeList.get();
			*pm = libmaus2::lcs::PosMapping(
				*patc,
				start,
				end,
				algn.getFrontSoftClipping(),
				algn.getLseq()-algn.getBackSoftClipping()
			);

			// std::cerr << pm->toString();

			#if 0
			for ( uint64_t i = 0; i < 100; ++i )
			{
				std::pair<int64_t,int64_t> P = pm->mapBA(i);

				std::cerr << "PM " << i << " " << P.first << "," << P.second << std::endl;
			}
			#endif

			uint64_t const nk = libmaus2::lcs::AlignmentTraceContainer::getKMatchOffsets(
				patc->ta,patc->te,k,R,
				intv.from,algn.getFrontSoftClipping()
			);

			uint64_t ARKP_o = 0;
			for ( uint64_t i = 0; i < nk; ++i )
			{
				std::pair<int64_t,int64_t> const fromA(R[i].a  ,0);
				std::pair<int64_t,int64_t> const toA  (R[i].a+k,0);
				KmerCoordinate const C(fromA,toA);

				ARKP.push(
					ARKP_o,
					KmerPos(C,R[i].b,readid)
				);
			}

			struct KmerCallback
			{
				unsigned int const k;
				uint64_t const readid;
				uint64_t const frontclip;
				libmaus2::lcs::PosMapping::shared_ptr_type pm;

				libmaus2::util::GrowingFreeList<
					KmerInfo,
					KmerInfoAllocator,
					KmerInfoTypeInfo
				> & kmerInfoFreeList;

				std::map<uint64_t,KmerInfo::shared_ptr_type> & KM1;
				std::map<uint64_t,KmerInfo::shared_ptr_type> & KM;
				libmaus2::util::FiniteSizeHeap < std::pair<uint64_t,uint64_t> > & KMend;

				libmaus2::fastx::SingleWordDNABitBuffer lwordbuffer;

				libmaus2::autoarray::AutoArray<KmerPos> & ARKP;
				libmaus2::autoarray::AutoArray<KmerPos> ARKP_start;
				uint64_t ARKP_o;

				libmaus2::math::IntegerInterval<int64_t> IV;

				bool debug;

				KmerCallback(
					unsigned int const rk,
					uint64_t const rreadid,
					uint64_t const rfrontclip,
					libmaus2::lcs::PosMapping::shared_ptr_type rpm,
					libmaus2::util::GrowingFreeList<
						KmerInfo,
						KmerInfoAllocator,
						KmerInfoTypeInfo
					> & rkmerInfoFreeList,
					std::map<uint64_t,KmerInfo::shared_ptr_type> & rKM1,
					std::map<uint64_t,KmerInfo::shared_ptr_type> & rKM,
					libmaus2::util::FiniteSizeHeap < std::pair<uint64_t,uint64_t> > & rKMend,
					libmaus2::autoarray::AutoArray<KmerPos> & rARKP,
					libmaus2::math::IntegerInterval<int64_t> const & rIV,
					bool const rdebug
				)
				: k(rk), readid(rreadid), frontclip(rfrontclip), pm(rpm),
				  kmerInfoFreeList(rkmerInfoFreeList), KM1(rKM1), KM(rKM), KMend(rKMend),
				  lwordbuffer(k), ARKP(rARKP), ARKP_o(0),
				  IV(rIV), debug(rdebug)
				{

				}

				void print(std::ostream & out, std::string const prefix = std::string()) const
				{
					for ( uint64_t i = 0; i < ARKP_o; ++i )
						out << prefix << ARKP[i] << "\n";
				}

				bool havePos(KmerPos const & KP) const
				{
					std::pair<KmerPos const *,KmerPos const *> const P =
						std::equal_range(
							ARKP_start.begin(),
							ARKP_start.begin()+ARKP_o,
							KP,
							KmerPosStartReadComparator()
						);
					return P.second != P.first;
				}

				void sort()
				{
					std::sort(ARKP.begin(),ARKP.begin()+ARKP_o);

					ARKP_start.resize(ARKP_o);
					std::copy(ARKP.begin(),ARKP.begin()+ARKP_o,ARKP_start.begin());
					std::sort(ARKP_start.begin(),ARKP_start.begin()+ARKP_o,KmerPosStartReadComparator());
				}

				void operator()(uint64_t const word, uint64_t const z)
				{
					std::pair<int64_t,int64_t> const refPStart = pm->mapBA(frontclip + z);
					std::pair<int64_t,int64_t> const refPEnd   = pm->mapBA(frontclip + z + k);
					KmerCoordinate const refC(refPStart,refPEnd);

					KmerPos KP(refC,frontclip + z,readid);

					#if 0
					if ( debug )
					{
						lwordbuffer.buffer = word;
						std::cerr
							<< lwordbuffer
							<< "\t" << KP << "\t" << readid << "\t"
							<< "\t" << "(" << refPEnd.first << "," << refPEnd.second << ")"
							<< "\t" << IV << std::endl;
					}
					#endif

					#if 0
					lwordbuffer.buffer = word;
					std::string sk = "AGGTTCTCACACCATGCAGGTGATGTAT";
					sk = sk.substr(0,k);

					if ( lwordbuffer.toStringDNA() == sk )
						std::cerr << sk << "\t" << KP << "\t" << readid << std::endl;
					#endif

					ARKP.push(ARKP_o,KP);

					// std::cerr << KP << std::endl;

					KMend.pushBump(
						std::pair<uint64_t,uint64_t>(
							KP.ref.refEnd.first,
							word
						)
					);

					// do we have a KM entry for word?
					std::map<uint64_t,KmerInfo::shared_ptr_type>::iterator it =
						KM.find(word);

					if ( it != KM.end() )
					{
						// push KmerPos
						it->second->killHeap.pushBump(KP);
						return;
					}

					// do we have a KM1 entry for word?
					it = KM1.find(word);

					if ( it != KM1.end() )
					{
						// push KmerPos and move to KM
						KmerInfo::shared_ptr_type kinfo = it->second;
						kinfo->killHeap.pushBump(KP);
						KM1.erase(it);
						KM[word] = kinfo;
						return;
					}

					// no entry yet for word, insert into KM1
					KmerInfo::shared_ptr_type kinfo = kmerInfoFreeList.get();
					kinfo->kcode = word;
					kinfo->killHeap.pushBump(KP);
					KM1[word] = kinfo;

					#if 0
					lwordbuffer.buffer = word;
					std::cerr << lwordbuffer << " " << KP << std::endl;
					#endif


				}
			};

			uint64_t const aseqlen = algn.decodeRead(ASEQ);
			uint64_t const frontclip = algn.getFrontSoftClipping();
			uint64_t const backclip = algn.getBackSoftClipping();
			uint64_t const kuse = aseqlen - (frontclip + backclip);

			// bool const debug = (algn.getRefID() == 3776) && algn.getPos() >= 1000;
			bool const debug = false;

			KmerCallback KC(
				k,
				readid,
				frontclip,
				pm,
				kmerInfoFreeList,
				KM1,
				KM,
				KMend,
				LARKP,
				algn.getReferenceInterval(),
				debug
			);
			kbase.kmerCallbackPosForwardOnly(
				ASEQ.begin()+frontclip,kuse,
				KC,
				wordbuffer,
				k
			);

			KC.sort();

			std::sort(ARKP.begin(),ARKP.begin()+ARKP_o);
			for ( uint64_t i = 0; i < ARKP_o; ++i )
			{
				bool const ok = KC.havePos(ARKP[i]);

				if ( ! ok )
				{
					std::cerr << algn.getPos() << " " << algn.getCigarString() << " " << algn.getRead() << std::endl;
					std::cerr << ARKP[i] << "\t" << (ok?"+":"-") << std::endl;
					std::cerr << patc->traceToString() << std::endl;
					std::cerr << std::string(ASEQ.begin()+frontclip,kuse) << std::endl;

					KC.print(std::cerr);

					assert ( false );
				}
			}

			libmaus2::bambam::BamAlignment::shared_ptr_type alptr = alFreeList.get();
			alptr->copyFrom(algn);

			alHeap.pushBump(BamAlignmentHeapElement(alptr,patc,pm,start,end,readid));


			depth += 1;

			if ( depth == depththres )
				dstart = intv.from;

			prevrefid = algn.getRefID();

			if ( newrefid )
				conswindowstart = 0;
		}
	}

	// handle consensus windows at end
	while (
		prevrefid >= 0
		&&
		conswindowstart <= header.getRefIDLength(prevrefid)
	)
	{
		int64_t const nextwindowstart = conswindowstart + (conswindowsize+1)/2;
		int64_t const conswindowend = conswindowstart + conswindowsize;

		if ( ! RCcur )
			RCcur = RC.get(prevrefid);

		std::vector<ConsensusPart> const Lcons =
			handleConsensus(
				k,
				thres,
				dotid,
				prevrefid,
				conswindowstart,
				conswindowend,
				countFreqA,
				countFreqH,
				AKL,
				KMend,
				KM,
				LASFV,
				RASFV,
				ARID,
				AKLBL,
				RCcur->ref,
				RCcur->name,
				header
			);

		for ( uint64_t i = 0; i < Lcons.size(); ++i )
			Vcons.push_back(Lcons[i]);

		handleKMEnd(k,KMend,KM1,KM,nextwindowstart,kmerInfoFreeList);

		conswindowstart = nextwindowstart;
	}

	handleKMEnd(k,KMend,KM1,KM,std::numeric_limits<int64_t>::max(),kmerInfoFreeList);
	assert ( KM.empty() );

	if ( Vcons.size() )
	{
		assert ( RCcur );
		mergeConsensus(Vcons,prevrefid,RCcur->ref,RCcur->name,header,verbose,wr);
	}

	if ( RCcur )
		RC.put(RCcur);

	while (
		! alHeap.empty()
	)
	{
		BamAlignmentHeapElement E = alHeap.pop();

		if ( depth == depththres )
		{
			dend = E.end;

			L[dend-dstart] += 1;
			numint += 1;
		}

		depth -= 1;

		if ( E.end > prevend )
		{
			prevend = E.end;
		}

		alFreeList.put(E.ptr);
		atcFreeList.put(E.atc);
		pmFreeList.put(E.pm);
	}

	#if 0
	uint64_t wsum = 0;
	for ( std::map<uint64_t,uint64_t>::const_iterator it = L.begin(); it != L.end(); ++it )
		wsum += it->first * it->second;
	double const dwsum = wsum;

	std::cerr << "wsum=" << wsum << std::endl;

	uint64_t sum = 0;
	for ( std::map<uint64_t,uint64_t>::const_iterator it = L.begin(); it != L.end(); ++it )
	{
		sum += it->first * it->second;

		std::cerr << it->first << "\t" << static_cast<double>(sum) / dwsum << std::endl;
	}
	#endif

	Pwriter.reset();

	return EXIT_SUCCESS;
}

void printHelpMessage(libmaus2::util::ArgInfo const & arginfo)
{
	std::cerr << ::biobambam2::Licensing::license();
	std::cerr << std::endl;
	std::cerr << "synopsis " << arginfo.progname << " [k=32] [minlen=50] [verbose=1] [reference=ref.fasta] < in.bam >out.bam" << std::endl;
	std::cerr << std::endl;
	std::cerr << "Key=Value pairs:" << std::endl;
	std::cerr << std::endl;

	std::vector< std::pair<std::string,std::string> > V;

	// reference
	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> ( "verbose=<["+::biobambam2::Licensing::formatNumber(getDefaultVerbose())+"]>", "print progress report (default: 1)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "minlen=<["+::biobambam2::Licensing::formatNumber(getDefaultMinLen())+"]>", "minimum length of alignments (on reference) to be considered (default: 50)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "k=<["+::biobambam2::Licensing::formatNumber(getDefaultK())+"]>", "k-mer size used for consensus computation (default: 32)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "reference=<filename.fasta>", "file name of reference FastA file (no default)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "exclude=<[]>", "exclude alignments matching any of the given flags" ) );

	#if 0
	V.push_back ( std::pair<std::string,std::string> ( "level=<["+::biobambam2::Licensing::formatNumber(getDefaultLevel())+"]>", libmaus2::bambam::BamBlockWriterBaseFactory::getBamOutputLevelHelpText() ) );
	V.push_back ( std::pair<std::string,std::string> ( "SO=<["+getDefaultSortOrder()+"]>", "sorting order (coordinate, queryname, hash, tag, queryname_HI or queryname_lexicographic)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "blockmb=<["+::biobambam2::Licensing::formatNumber(getDefaultBlockSize())+"]>", "size of internal memory buffer used for sorting in MiB" ) );
	V.push_back ( std::pair<std::string,std::string> ( "disablevalidation=<["+::biobambam2::Licensing::formatNumber(getDefaultDisableValidation())+"]>", "disable input validation (default is 0)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "md5=<["+::biobambam2::Licensing::formatNumber(getDefaultMD5())+"]>", "create md5 check sum (default: 0)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "md5filename=<filename>", "file name for md5 check sum" ) );
	V.push_back ( std::pair<std::string,std::string> ( "index=<["+::biobambam2::Licensing::formatNumber(getDefaultIndex())+"]>", "create BAM index (default: 0)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "indexfilename=<filename>", "file name for BAM index file" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("inputformat=<[")+getDefaultInputFormat()+"]>", std::string("input format (") + libmaus2::bambam::BamMultiAlignmentDecoderFactory::getValidInputFormats() + ")" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("outputformat=<[")+libmaus2::bambam::BamBlockWriterBaseFactory::getDefaultOutputFormat()+"]>", std::string("output format (") + libmaus2::bambam::BamBlockWriterBaseFactory::getValidOutputFormats() + ")" ) );
	V.push_back ( std::pair<std::string,std::string> ( "I=<[stdin]>", "input filename (standard input if unset)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "inputthreads=<[1]>", "input helper threads (for inputformat=bam only, default: 1)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "reference=<>", "reference FastA (.fai file required, for cram i/o only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "range=<>", "coordinate range to be processed (for coordinate sorted indexed BAM input only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "outputthreads=<[1]>", "output helper threads (for outputformat=bam only, default: 1)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "O=<[stdout]>", "output filename (standard output if unset)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("fixmates=<[")+::biobambam2::Licensing::formatNumber(getDefaultFixMates())+"]>", "fix mate information (for name collated input only, disabled by default)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("calmdnm=<[")+::biobambam2::Licensing::formatNumber(getDefaultCalMdNm())+"]>", "calculate MD and NM aux fields (for coordinate sorted output only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("calmdnmreference=<[]>"), "reference for calculating MD and NM aux fields (calmdnm=1 only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("calmdnmrecompindetonly=<[")+::biobambam2::Licensing::formatNumber(getDefaultCalMdNm())+"]>", "only recalculate MD and NM in the presence of indeterminate bases (calmdnm=1 only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("calmdnmwarnchange=<[")+::biobambam2::Licensing::formatNumber(getDefaultCalMdNmWarnChange())+"]>", "warn when changing existing MD/NM fields (calmdnm=1 only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("adddupmarksupport=<[")+::biobambam2::Licensing::formatNumber(getDefaultAddDupMarkSupport())+"]>", "add info for streaming duplicate marking (for name collated input only, ignored for fixmate=0, disabled by default)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "tag=<[a-zA-Z][a-zA-Z0-9]>", "aux field id for tag string extraction (adddupmarksupport=1 only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "nucltag=<[a-zA-Z][a-zA-Z0-9]>", "aux field id for nucleotide tag extraction (adddupmarksupport=1 only)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("markduplicates=<[")+::biobambam2::Licensing::formatNumber(getDefaultMarkDuplicates())+"]>", "mark duplicates (only when input name collated and output coordinate sorted, disabled by default)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("rmdup=<[")+::biobambam2::Licensing::formatNumber(getDefaultRmDup())+"]>", "remove duplicates (only when input name collated and output coordinate sorted, disabled by default)" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("streaming=<[")+::biobambam2::Licensing::formatNumber(getDefaultStreaming())+"]>", "do not open input files multiple times when set" ) );
	V.push_back ( std::pair<std::string,std::string> ( std::string("sorttag=<[]>"), std::string("tag used by SO=tag (no default)") ) );
	V.push_back ( std::pair<std::string,std::string> ( "sortthreads=<[1]>", "threads used for sorting (default: 1)" ) );
	V.push_back ( std::pair<std::string,std::string> ( "hash=<[crc32prod]>", "hash used for producing bamseqchksum type checksums (default: crc32prod)" ) );
	#endif

	::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;
}


int main(int argc, char * argv[])
{
	try
	{
		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> Vformatcons;
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("r","reference",true));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("T","tmpprefix",true));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("k","",true));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("v","verbose",true));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","minlen",true));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("d","depththres",true));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","exclude",true));
		Vformatcons.push_back(libmaus2::util::ArgParser::ArgumentDefinition("","readsbam",true));

		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> const Vformatin = libmaus2::bambam::BamAlignmentDecoderInfo::getArgumentDefinitions();
		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> const Vformatout = libmaus2::bambam::BamBlockWriterBaseFactory::getArgumentDefinitions();

		std::vector<libmaus2::util::ArgParser::ArgumentDefinition> Vformat =
			libmaus2::util::ArgParser::mergeFormat(libmaus2::util::ArgParser::mergeFormat(Vformatin,Vformatout),Vformatcons);

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

		int const r = bamconsensus(arg);

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