/**
    biobambam2
    Copyright (C) 2020 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(DEPTHINTERVALGETTER_HPP)
#define DEPTHINTERVALGETTER_HPP

#include <biobambam2/DepthInterval.hpp>
#include <libmaus2/aio/SerialisedPeeker.hpp>

namespace biobambam2
{
	struct DepthIntervalGetter
	{
		int64_t refid;
		libmaus2::aio::SerialisedPeeker<biobambam2::DepthInterval> & SP;
		biobambam2::DepthInterval DI;

		DepthIntervalGetter(libmaus2::aio::SerialisedPeeker<biobambam2::DepthInterval> & rSP) : SP(rSP)
		{
			bool const ok = SP.peekNext(DI);
			assert ( ok );
			refid = DI.refid;
		}

		bool peekNext(uint64_t & v, uint64_t & p)
		{
			while ( true )
			{
				if ( DI.from < DI.to )
				{
					v = DI.depth;
					p = DI.from;
					return true;
				}
				else
				{
					bool const ok = SP.peekNext(DI);

					if ( ! ok )
						return false;
					if ( static_cast<int64_t>(DI.refid) != refid )
						return false;

					SP.getNext(DI);
				}
			}
		}

		bool getNext(uint64_t & v, uint64_t & p)
		{
			while ( true )
			{
				if ( DI.from < DI.to )
				{
					v = DI.depth;
					p = DI.from++;
					return true;
				}
				else
				{
					bool const ok = SP.peekNext(DI);

					if ( ! ok )
						return false;
					if ( static_cast<int64_t>(DI.refid) != refid )
						return false;

					SP.getNext(DI);
				}
			}
		}
	};

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

		libmaus2::aio::SerialisedPeeker<biobambam2::DepthInterval> & SP;
		biobambam2::DepthInterval DI;

		DepthIntervalRefGetter(libmaus2::aio::SerialisedPeeker<biobambam2::DepthInterval> & rSP) : SP(rSP), DI()
		{
		}

		bool peekNext(uint64_t & v, uint64_t & p, uint64_t & refid)
		{
			while ( true )
			{
				if ( DI.from < DI.to )
				{
					v = DI.depth;
					p = DI.from;
					refid = DI.refid;
					return true;
				}
				else
				{
					bool const ok = SP.peekNext(DI);

					if ( ! ok )
						return false;

					SP.getNext(DI);
				}
			}
		}

		bool getNext(uint64_t & v, uint64_t & p, uint64_t & refid)
		{
			while ( true )
			{
				if ( DI.from < DI.to )
				{
					v = DI.depth;
					p = DI.from++;
					refid = DI.refid;
					return true;
				}
				else
				{
					bool const ok = SP.peekNext(DI);

					if ( ! ok )
						return false;

					SP.getNext(DI);
				}
			}
		}
	};

	struct DepthIntervalGetterMerge
	{
		struct HeapElement
		{
			uint64_t id;
			uint64_t refid;
			uint64_t p;
			uint64_t v;

			HeapElement() {}
			HeapElement(uint64_t const rid, uint64_t const rrefid, uint64_t const rp, uint64_t const rv)
			: id(rid), refid(rrefid), p(rp), v(rv) {}

			bool operator<(HeapElement const & H) const
			{
				if ( refid != H.refid )
					return refid < H.refid;
				else if ( p != H.p )
					return p < H.p;
				else
					return id < H.id;
			}
		};

		std::vector<libmaus2::aio::SerialisedPeeker<DepthInterval>::shared_ptr_type> Vin;
		std::vector<DepthIntervalRefGetter::shared_ptr_type> VDIRG;
		libmaus2::util::FiniteSizeHeap<HeapElement> H;

		DepthIntervalGetterMerge(
			std::vector<std::string> const & Vfn
		) : H(0)
		{
			for ( uint64_t i = 0; i < Vfn.size(); ++i )
			{
				libmaus2::aio::SerialisedPeeker<DepthInterval>::shared_ptr_type SP(
					new libmaus2::aio::SerialisedPeeker<DepthInterval>(Vfn[i])
				);
				Vin.emplace_back(SP);

				DepthIntervalRefGetter::shared_ptr_type tptr(new DepthIntervalRefGetter(*SP));
				VDIRG.emplace_back(tptr);

				uint64_t v;
				uint64_t p;
				uint64_t refid;

				bool const ok = tptr->peekNext(v,p,refid);

				if ( ok )
					H.pushBump(HeapElement(i,refid,p,v));
			}
		}

		uint64_t getNext(
			uint64_t & refid, uint64_t & p,
			// (id,v)
			libmaus2::autoarray::AutoArray< std::pair<uint64_t,uint64_t> > & A
		)
		{
			uint64_t o = 0;
			if ( !H.empty() )
			{
				HeapElement const & HE = H.top();

				refid = H.top().refid;
				p = H.top().p;

				while ( ! H.empty() && H.top().refid == refid && H.top().p == p )
				{
					A.push(
						o,std::make_pair(H.top().id,H.top().v)
					);
					H.pop();
				}

				for ( uint64_t i = 0; i < o; ++i )
				{
					uint64_t const id = A[i].first;

					uint64_t nv;
					uint64_t np;
					uint64_t nrefid;

					bool const ok = VDIRG[i]->peekNext(nv,np,nrefid);

					if ( ok )
					{
						VDIRG[i]->getNext(nv,np,nrefid);
						H.pushBump(HeapElement(id,nrefid,np,nv));
					}
				}
			}

			return o;
		}
	};
}
#endif
