/*
    libmaus2
    Copyright (C) 2020 German Tischler-Höhle

    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_LCS_TWOSIDEDNPL_HPP)
#define LIBMAUS2_LCS_TWOSIDEDNPL_HPP

#include <libmaus2/lcs/NPL.hpp>

namespace libmaus2
{
	namespace lcs
	{
		struct TwoSidedNPL : public libmaus2::lcs::NPL
		{
			struct TwoSidedNPLResult
			{
				uint64_t abpos;
				uint64_t aepos;
				uint64_t bbpos;
				uint64_t bepos;

				TwoSidedNPLResult() {}
				TwoSidedNPLResult(
					uint64_t const rabpos,
					uint64_t const raepos,
					uint64_t const rbbpos,
					uint64_t const rbepos
				) : abpos(rabpos), aepos(raepos), bbpos(rbbpos), bepos(rbepos) {}

				std::tuple<uint64_t,uint64_t,uint64_t,uint64_t> getTuple() const {
					return std::tie(abpos,aepos,bbpos,bepos);
				}

				bool operator==(TwoSidedNPLResult const & T) const {
					return getTuple() == T.getTuple();
				}

				bool operator!=(TwoSidedNPLResult const & T) const {
					return getTuple() != T.getTuple();
				}
			};

			TwoSidedNPL() {}

			template<typename iter_a, typename iter_b>
			TwoSidedNPLResult np(
				iter_a const ca,
				int64_t const rla,
				uint64_t const seed_a,
				iter_b const cb,
				int64_t const rlb,
				uint64_t const seed_b,
				uint64_t const seedlen
			)
			{
				if ( !(static_cast<int64_t>(seed_a) < rla) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNPL::np: seed_a=" << seed_a << " is not smaller than rla=" << rla << std::endl;
					lme.finish();
					throw lme;
				}
				if ( !(static_cast<int64_t>(seed_b) < rlb) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNPL::np: seed_b=" << seed_b << " is not smaller than rlb=" << rlb << std::endl;
					lme.finish();
					throw lme;
				}

				// end of seed
				uint64_t const seed_end_a = seed_a + seedlen;
				uint64_t const seed_end_b = seed_b + seedlen;

				if ( ! ( static_cast<int64_t>(seed_end_a) <= rla ) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNPL::np: seed_end_a=" << seed_end_a << " is not smaller or equal than rla=" << rla << std::endl;
					lme.finish();
					throw lme;
				}
				if ( ! ( static_cast<int64_t>(seed_end_b) <= rlb ) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNPL::np: seed_end_b=" << seed_end_b << " is not smaller or equal than rlb=" << rlb << std::endl;
					lme.finish();
					throw lme;
				}

				// backward alignment from end of seed
				libmaus2::lcs::NPL::np(
					std::reverse_iterator<iter_a>(ca + seed_end_a),
					std::reverse_iterator<iter_a>(ca),
					std::reverse_iterator<iter_b>(cb + seed_end_b),
					std::reverse_iterator<iter_b>(cb)
				);

				std::pair<uint64_t,uint64_t> const SLR =
					libmaus2::lcs::NPL::getStringLengthUsed();

				if ( ! ( SLR.first  <= seed_end_a ) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNP::np: SLR.first=" << SLR.first << " > seed_end_a=" << seed_end_a << std::endl;
					lme.finish();
					throw lme;
				}
				if ( ! ( SLR.second  <= seed_end_b ) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNP::np: SLR.second=" << SLR.second << " > seed_end_b=" << seed_end_b << std::endl;
					lme.finish();
					throw lme;
				}

				uint64_t abpos = seed_end_a - SLR.first;
				uint64_t bbpos = seed_end_b - SLR.second;

				if ( ! ( static_cast<int64_t>(abpos) <= rla ) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNP::np: abpos=" << abpos << " > rla=" << rla << std::endl;
					lme.finish();
					throw lme;
				}
				if ( ! ( static_cast<int64_t>(bbpos) <= rlb ) ) {
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "TwoSidedNP::np: bbpos=" << bbpos << " > rlb=" << rla << std::endl;
					lme.finish();
					throw lme;
				}

				libmaus2::lcs::NPL::np(ca+abpos,ca+rla,cb+bbpos,cb+rlb);

				std::pair<uint64_t,uint64_t> const SLF =
					libmaus2::lcs::NPL::getStringLengthUsed();

				uint64_t aepos = abpos + SLF.first;
				uint64_t bepos = bbpos + SLF.second;

				if (
					NPL::ta != NPL::te && NPL::ta[0] == libmaus2::lcs::AlignmentTraceContainer::STEP_DEL && bbpos > 0
				)
				{
					assert ( bbpos );
					bbpos -= 1;

					NPL::ta[0] =
						ca[abpos] == cb[bbpos]
						?
						libmaus2::lcs::AlignmentTraceContainer::STEP_MATCH
						:
						libmaus2::lcs::AlignmentTraceContainer::STEP_MISMATCH
						;

					assert ( NPL::checkAlignment(
						NPL::ta,NPL::te,
						ca+abpos,
						cb+bbpos
					) );

				}
				if (
					NPL::ta != NPL::te && NPL::te[-1] == libmaus2::lcs::AlignmentTraceContainer::STEP_DEL && static_cast< ::std::ptrdiff_t >(bepos) < (rlb)
				)
				{
					bepos += 1;

					NPL::te[-1] =
						ca[aepos-1] == cb[bepos-1]
						?
						libmaus2::lcs::AlignmentTraceContainer::STEP_MATCH
						:
						libmaus2::lcs::AlignmentTraceContainer::STEP_MISMATCH;


					assert ( NPL::checkAlignment(
						NPL::ta,NPL::te,
						ca+abpos,
						cb+bbpos
					) );
				}

				// qq
				if (
					NPL::ta != NPL::te && NPL::ta[0] == libmaus2::lcs::AlignmentTraceContainer::STEP_INS && abpos > 0
				)
				{
					assert ( abpos );
					abpos -= 1;

					NPL::ta[0] =
						ca[abpos] == cb[bbpos]
						?
						libmaus2::lcs::AlignmentTraceContainer::STEP_MATCH
						:
						libmaus2::lcs::AlignmentTraceContainer::STEP_MISMATCH
						;

					assert ( NPL::checkAlignment(
						NPL::ta,NPL::te,
						ca+abpos,
						cb+bbpos
					) );

				}
				if (
					NPL::ta != NPL::te && NPL::te[-1] == libmaus2::lcs::AlignmentTraceContainer::STEP_INS && static_cast< ::std::ptrdiff_t >(aepos) < (rla)
				)
				{
					aepos += 1;

					NPL::te[-1] =
						ca[aepos-1] == cb[bepos-1]
						?
						libmaus2::lcs::AlignmentTraceContainer::STEP_MATCH
						:
						libmaus2::lcs::AlignmentTraceContainer::STEP_MISMATCH;


					assert ( NPL::checkAlignment(
						NPL::ta,NPL::te,
						ca+abpos,
						cb+bbpos
					) );
				}


				return TwoSidedNPLResult(abpos,aepos,bbpos,bepos);
			}
		};
	}
}
#endif
