/*
    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) {}
			};

			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
			)
			{
				assert ( static_cast<int64_t>(seed_a) < rla );
				assert ( static_cast<int64_t>(seed_b) < rlb );

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

				assert ( static_cast<int64_t>(seed_end_a) <= rla );
				assert ( static_cast<int64_t>(seed_end_b) <= rlb );

				// 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();

				assert ( SLR.first  <= seed_end_a );
				assert ( SLR.second <= seed_end_b );

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

				assert ( static_cast<int64_t>(abpos) <= rla );
				assert ( static_cast<int64_t>(bbpos) <= rlb );

				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
