/*
    libmaus2
    Copyright (C) 2017 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/>.
*/
#if !defined(LIBMAUS2_DAZZLER_ALIGN_OVERLAPDATAINTERFACE_HPP)
#define LIBMAUS2_DAZZLER_ALIGN_OVERLAPDATAINTERFACE_HPP

#include <libmaus2/dazzler/align/OverlapData.hpp>
#include <libmaus2/dazzler/align/OverlapInfo.hpp>
#include <libmaus2/dazzler/align/Overlap.hpp>
#include <libmaus2/dazzler/align/SubIntervalResult.hpp>
#include <libmaus2/dazzler/align/AlignmentFileConstants.hpp>

namespace libmaus2
{
	namespace dazzler
	{
		namespace align
		{
			struct OverlapDataInterface
			{
				uint8_t const * p;

				OverlapDataInterface() : p(0) {}
				OverlapDataInterface(uint8_t const * rp) : p(rp) {}

				libmaus2::dazzler::align::OverlapInfo getInfo() const
				{
					return libmaus2::dazzler::align::OverlapInfo(
						(aread()<<1),
						(bread()<<1) | isInverse(),
						abpos(),aepos(),
						bbpos(),bepos()
					);
				}

				int64_t aread() const { return OverlapData::getARead(p); }
				int64_t bread() const { return OverlapData::getBRead(p); }
				int64_t abpos() const { return OverlapData::getABPos(p); }
				int64_t aepos() const { return OverlapData::getAEPos(p); }
				int64_t bbpos() const { return OverlapData::getBBPos(p); }
				int64_t bepos() const { return OverlapData::getBEPos(p); }
				int64_t tlen() const  { return OverlapData::getTLen(p); }
				int64_t diffs() const  { return OverlapData::getDiffs(p); }
				int64_t flags() const  { return OverlapData::getFlags(p); }
				int64_t isInverse() const  { return OverlapData::getInverseFlag(p); }
				bool getHaploFlag() const { return OverlapData::getHaploFlag(p); }
				bool getTrueFlag() const { return OverlapData::getTrueFlag(p); }
				uint64_t decodeTraceVector(libmaus2::autoarray::AutoArray<std::pair<uint16_t,uint16_t> > & A, int64_t const tspace) const
				{ return OverlapData::decodeTraceVector(p,A,libmaus2::dazzler::align::AlignmentFileConstants::tspaceToSmall(tspace)); }
				double getErrorRate() const
				{ return (aepos() > abpos()) ? (static_cast<double>(diffs()) / static_cast<double>(aepos()-abpos())) : 0.0; }

				void getOverlap(
					libmaus2::dazzler::align::Overlap & OVL,
					libmaus2::autoarray::AutoArray<std::pair<uint16_t,uint16_t> > & A,
					int64_t const tspace
				) const
				{
					uint64_t const tracelen = decodeTraceVector(A,tspace);
					OVL.flags = flags();
					OVL.aread = aread();
					OVL.bread = bread();
					OVL.path.abpos = abpos();
					OVL.path.aepos = aepos();
					OVL.path.bbpos = bbpos();
					OVL.path.bepos = bepos();
					OVL.path.diffs = diffs();
					OVL.path.tlen = tracelen * 2;
					OVL.path.path.resize(tracelen);
					std::copy(A.begin(),A.begin()+tracelen,OVL.path.path.begin());
				}

				libmaus2::dazzler::align::Overlap getOverlap(libmaus2::autoarray::AutoArray<std::pair<uint16_t,uint16_t> > & A, int64_t const tspace) const
				{
					libmaus2::dazzler::align::Overlap OVL;
					getOverlap(OVL,A,tspace);
					return OVL;
				}

				libmaus2::dazzler::align::Overlap getOverlap(int64_t const tspace) const
				{
					libmaus2::dazzler::align::Overlap OVL;
					libmaus2::autoarray::AutoArray<std::pair<uint16_t,uint16_t> > A;
					getOverlap(OVL,A,tspace);
					return OVL;
				}

				void computeTrace(
					libmaus2::autoarray::AutoArray<std::pair<uint16_t,uint16_t> > & A,
					int64_t const tspace,
					uint8_t const * aptr,
					uint8_t const * bptr,
					libmaus2::lcs::AlignmentTraceContainer & ATC,
					libmaus2::lcs::Aligner & aligner
				) const
				{
					OverlapData::computeTrace(p,A,tspace,aptr,bptr,ATC,aligner);
				}

				std::pair<uint8_t const *, uint8_t const *> subOverlap(
					int64_t const ab,
					int64_t const ae,
					libmaus2::autoarray::AutoArray<uint8_t> & B,
					int64_t const tspace
				) const
				{
					bool const small = libmaus2::dazzler::align::AlignmentFileConstants::tspaceToSmall(tspace);

					assert ( (ab == ae) || (ab % tspace == 0) );

					int64_t a = abpos();
					int64_t b = bbpos();

					int64_t bb = -1, be = -1;
					int64_t offb = -1, offe = -1;
					int64_t diffs = 0;

					if ( small )
					{
						uint8_t const * trace = libmaus2::dazzler::align::OverlapData::getTraceData(p);
						uint8_t const * tracea = trace;

						while ( a < ab )
						{
							// uint64_t const err = *(trace++);
							++trace;
							uint64_t const forw = *(trace++);

							if ( a % tspace == 0 )
								a += tspace;
							else
								a = ((a+tspace-1)/tspace)*tspace;
							b += forw;
						}

						assert ( a == ab );
						bb = b;
						offb = (trace - tracea)/2;

						while ( a < ae )
						{
							uint64_t const err = *(trace++);
							uint64_t const forw = *(trace++);

							if ( a % tspace == 0 )
								a += tspace;
							else
								a = ((a+tspace-1)/tspace)*tspace;

							diffs += err;
							b += forw;
						}

						be = b;
						offe = (trace - tracea)/2;
					}
					else
					{
						uint16_t const * trace = reinterpret_cast<uint16_t const *>(libmaus2::dazzler::align::OverlapData::getTraceData(p));
						uint16_t const * tracea = trace;

						while ( a < ab )
						{
							// uint64_t const err = *(trace++);
							++trace;
							uint64_t const forw = *(trace++);


							if ( a % tspace == 0 )
								a += tspace;
							else
								a = ((a+tspace-1)/tspace)*tspace;
							b += forw;
						}

						assert ( a == ab );
						bb = b;
						offb = (trace - tracea)/2;

						while ( a < ae )
						{
							uint64_t const err = *(trace++);
							uint64_t const forw = *(trace++);

							if ( a % tspace == 0 )
								a += tspace;
							else
								a = ((a+tspace-1)/tspace)*tspace;
							diffs += err;
							b += forw;
						}

						be = b;
						offe = (trace - tracea)/2;
					}

					// put ovl data structure
					uint64_t o = 0;
					o = OverlapData::putLE(o,B,2*(offe-offb),4); // tlen
					o = OverlapData::putLE(o,B,diffs,4); // diffs
					o = OverlapData::putLE(o,B,ab,4); // abpos
					o = OverlapData::putLE(o,B,bb,4); // bbpos
					o = OverlapData::putLE(o,B,ae,4); // aepos
					o = OverlapData::putLE(o,B,be,4); // bepos
					o = OverlapData::putLE(o,B,flags(),4); // flags
					o = OverlapData::putLE(o,B,aread(),4); // aread
					o = OverlapData::putLE(o,B,bread(),4); // bread
					o = OverlapData::putLE(o,B,0,4); // pad

					if ( small )
					{
						uint8_t const * trace = libmaus2::dazzler::align::OverlapData::getTraceData(p);

						for ( int64_t i = offb; i < offe; ++i )
						{
							B.push(o,trace[2*i+0]);
							B.push(o,trace[2*i+1]);
						}
					}
					else
					{
						uint16_t const * trace = reinterpret_cast<uint16_t const *>(libmaus2::dazzler::align::OverlapData::getTraceData(p));

						for ( int64_t i = offb; i < offe; ++i )
						{
							o = OverlapData::putLE(o,B,trace[2*i+0],2);
							o = OverlapData::putLE(o,B,trace[2*i+1],2);
						}
					}

					return std::pair<uint8_t const *,uint8_t const *>(
						B.begin(),
						B.begin()+o
					);
				}

				SubIntervalResult subInterval(
					int64_t ab,
					int64_t ae,
					libmaus2::autoarray::AutoArray<std::pair<uint16_t,uint16_t> > & A,
					libmaus2::autoarray::AutoArray<std::pair<int64_t,int64_t> > & P,
					int64_t const tspace
				) const
				{
					bool const small = libmaus2::dazzler::align::AlignmentFileConstants::tspaceToSmall(tspace);
					uint64_t const nt = OverlapData::decodeTraceVector(p, A, small);

					if ( ! nt )
					{
						assert ( abpos() == aepos() );
						assert ( bbpos() == bepos() );
						return SubIntervalResult(abpos(),aepos(),bbpos(),bepos());
					}

					int64_t a = (abpos() / tspace) * tspace;
					int64_t b = bbpos();

					uint64_t po = 0;
					for ( uint64_t i = 0; i < nt; ++i )
					{
						int64_t const ac = std::max(a,abpos());
						int64_t const bc = b;

						P.push(po,std::pair<int64_t,int64_t>(ac,bc));

						a += tspace;
						b += A[i].second;
					}

					int64_t const ac = std::min(a,aepos());
					int64_t const bc = b;

					P.push(po,std::pair<int64_t,int64_t>(ac,bc));

					ab = std::min(std::max(ab,abpos()),aepos());
					ae = std::min(std::max(ae,abpos()),aepos());

					assert ( ab >= abpos() && ab <= aepos() );
					assert ( ae >= abpos() && ae <= aepos() );

					int64_t minidxb = 0;
					int64_t minidxe = 0;
					int64_t difb = std::abs(ab-P[0].first);
					int64_t dife = std::abs(ae-P[0].first);

					for ( uint64_t i = 1; i < po; ++i )
					{
						int64_t const ldifb = std::abs(ab-P[i].first);
						int64_t const ldife = std::abs(ae-P[i].first);

						if ( ldifb < difb )
						{
							minidxb = i;
							difb = ldifb;
						}
						if ( ldife < dife )
						{
							minidxe = i;
							dife = ldife;
						}
					}

					int64_t bb, be;

					if ( ab >= P[minidxb].first )
					{
						int64_t const dif = ab - P[minidxb].first;
						bb = P[minidxb].second + dif;
					}
					else
					{
						int64_t const dif = P[minidxb].first - ab;
						bb = P[minidxb].second - dif;
					}
					if ( ae >= P[minidxe].first )
					{
						int64_t const dif = ae - P[minidxe].first;
						be = P[minidxe].second + dif;
					}
					else
					{
						int64_t const dif = P[minidxe].first - ae;
						be = P[minidxe].second - dif;
					}

					bb = std::min(std::max(bbpos(),bb),bepos());
					be = std::min(std::max(bbpos(),be),bepos());

					if ( be < bb )
						be = bb;

					assert ( bb >= bbpos() && bb <= bepos() );
					assert ( be >= bbpos() && be <= bepos() );

					assert ( be >= bb );

					return SubIntervalResult(ab,ae,bb,be);
				}
			};

			struct OverlapDataInterfaceFullComparator
			{
				uint8_t const * p;

				OverlapDataInterfaceFullComparator() : p(0) {}
				OverlapDataInterfaceFullComparator(uint8_t const * rp) : p(rp) {}

				static bool compare(
					uint8_t const * pa,
					uint8_t const * pb
				)
				{

					OverlapDataInterface const OA(pa);
					OverlapDataInterface const OB(pb);

					{
						int64_t const a = OA.aread();
						int64_t const b = OB.aread();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.bread();
						int64_t const b = OB.bread();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.isInverse();
						int64_t const b = OB.isInverse();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.abpos();
						int64_t const b = OB.abpos();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.aepos();
						int64_t const b = OB.aepos();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.bbpos();
						int64_t const b = OB.bbpos();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.bepos();
						int64_t const b = OB.bepos();

						if ( a != b )
							return a < b;
					}

					return false;
				}

				bool operator()(
					OverlapData::OverlapOffset const & A,
					OverlapData::OverlapOffset const & B
				) const
				{
					uint8_t const * pa = p + A.offset;
					uint8_t const * pb = p + B.offset;

					return compare(pa,pb);
				}

				bool operator()(
					OverlapData::DataIndex const & A,
					OverlapData::DataIndex const & B
				) const
				{
					uint8_t const * pa = A.data->getData(A.index).first;
					uint8_t const * pb = B.data->getData(B.index).first;

					return compare(pa,pb);
				}
			};

			struct OverlapDataInterfaceBAComparator
			{
				uint8_t const * p;

				OverlapDataInterfaceBAComparator() : p(0) {}
				OverlapDataInterfaceBAComparator(uint8_t const * rp) : p(rp) {}

				static bool compare(
					uint8_t const * pa,
					uint8_t const * pb
				)
				{
					OverlapDataInterface const OA(pa);
					OverlapDataInterface const OB(pb);

					{
						int64_t const a = OA.bread();
						int64_t const b = OB.bread();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.aread();
						int64_t const b = OB.aread();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.isInverse();
						int64_t const b = OB.isInverse();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.abpos();
						int64_t const b = OB.abpos();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.aepos();
						int64_t const b = OB.aepos();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.bbpos();
						int64_t const b = OB.bbpos();

						if ( a != b )
							return a < b;
					}

					{
						int64_t const a = OA.bepos();
						int64_t const b = OB.bepos();

						if ( a != b )
							return a < b;
					}

					return false;
				}

				bool operator()(
					OverlapData::OverlapOffset const & A,
					OverlapData::OverlapOffset const & B
				) const
				{
					uint8_t const * pa = p + A.offset;
					uint8_t const * pb = p + B.offset;

					return compare(pa,pb);
				}
			};

			struct OverlapDataArray : public OverlapDataInterface
			{
				uint64_t l;
				libmaus2::autoarray::AutoArray<uint8_t> A;

				OverlapDataArray() : OverlapDataInterface(0), l(0), A() {}

				std::ostream & serialise(std::ostream & out) const
				{
					libmaus2::util::NumberSerialisation::serialiseNumber(out,l);
					out.write(reinterpret_cast<char const *>(A.begin()),l);
					return out;
				}

				std::istream & deserialise(std::istream & in)
				{
					l = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
					A.ensureSize(l);
					in.read(reinterpret_cast<char *>(A.begin()),l);
					if ( !in || in.gcount() != static_cast<int64_t>(l) )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] input stream error in OverlapDataArray::deserialise()" << std::endl;
						lme.finish();
						throw lme;
					}
					OverlapDataInterface::p = A.begin();

					return in;
				}

				bool operator<(OverlapDataArray const & O) const
				{
					return OverlapDataInterfaceFullComparator::compare(A.begin(),O.A.begin());
				}
			};

			std::ostream & operator<<(std::ostream & out, OverlapDataInterface const & O);
		}
	}
}
#endif
