/*
    libmaus2
    Copyright (C) 2018 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_NPLLINMEM_HPP)
#define LIBMAUS2_LCS_NPLLINMEM_HPP

#include <libmaus2/lcs/NPLNoTrace.hpp>
#include <libmaus2/lcs/NPLinMem.hpp>

namespace libmaus2
{
	namespace lcs
	{
		struct NPLLinMem : public libmaus2::lcs::NPLinMem
		{
			typedef NPLLinMem this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;

			libmaus2::lcs::NPLNoTrace nplnt;

			NPLLinMem() : nplnt() {}

			template<typename iter_a, typename iter_b>
			int64_t np(iter_a const a, iter_a const ae, iter_b const b, iter_b const be,
				libmaus2::lcs::NPLNoTrace::index_type const maxd =
					std::numeric_limits<libmaus2::lcs::NPLNoTrace::index_type>::max()
			)
			{
				libmaus2::lcs::NPLNoTrace::ReturnValue const RV = nplnt.np(a,ae,b,be,maxd);

				if ( RV.valid )
					return libmaus2::lcs::NPLinMem::npTemplate<iter_a,iter_b,false /* self check */>(a,a+RV.alen,b,b+RV.blen);
				else
					return std::numeric_limits<int64_t>::min();
			}

                        void align(uint8_t const * a, size_t const l_a, uint8_t const * b, size_t const l_b)
                        {
                        	np(a,a+l_a,b,b+l_b);
                        }

			uint64_t byteSize() const
			{
				return libmaus2::lcs::NPLinMem::byteSize() + nplnt.byteSize();
			}

			template<typename iter_a, typename iter_b>
			uint64_t alignPrefix(iter_a const a, iter_a const ae, iter_b const b, iter_b const be)
			{
				np(a,ae,b,be);

				std::pair<uint64_t,uint64_t> SL = getStringLengthUsed(ta,te);

				if ( static_cast< ::std::ptrdiff_t >(SL.second) != (be-b) )
				{
					assert ( static_cast< ::std::ptrdiff_t >(SL.second) < (be-b) );

					uint64_t const missing = (be-b) - SL.second;
					libmaus2::lcs::AlignmentTraceContainer ATC(missing);

					for ( uint64_t i = 0; i < missing; ++i )
						*--ATC.ta = STEP_INS;

					libmaus2::lcs::AlignmentTraceContainer::push(ATC);
				}

				SL = getStringLengthUsed(ta,te);

				assert(static_cast< ::std::ptrdiff_t>(SL.second) == (be-b));

				return SL.first;
			}

			template<typename iter_a, typename iter_b>
			uint64_t alignSuffix(iter_a const a, iter_a const ae, iter_b const b, iter_b const be)
			{
				std::reverse_iterator<iter_a> r_a(ae);
				std::reverse_iterator<iter_a> r_ae(a);
				std::reverse_iterator<iter_b> r_b(be);
				std::reverse_iterator<iter_b> r_be(b);
				np(r_a,r_ae,r_b,r_be);

				std::pair<uint64_t,uint64_t> SL = getStringLengthUsed(ta,te);

				if ( static_cast< ::std::ptrdiff_t >(SL.second) != (be-b) )
				{
					assert ( static_cast< ::std::ptrdiff_t >(SL.second) < (be-b) );

					uint64_t const missing = (be-b) - SL.second;
					libmaus2::lcs::AlignmentTraceContainer ATC(missing);

					for ( uint64_t i = 0; i < missing; ++i )
						*--ATC.ta = STEP_INS;

					libmaus2::lcs::AlignmentTraceContainer::push(ATC);
				}

				SL = getStringLengthUsed(ta,te);

				assert(static_cast< ::std::ptrdiff_t>(SL.second) == (be-b));

				return SL.first;
			}

			template<typename iter_a, typename iter_b>
			std::pair<uint64_t,uint64_t> alignInfix(iter_a const a, iter_a const ae, uint64_t const seedpos_a, iter_b const b, iter_b const be, uint64_t const seedpos_b)
			{
				uint64_t const la_pref = alignPrefix(a+seedpos_a,ae,b+seedpos_b,be);
				uint64_t const end_a = seedpos_a + la_pref;
				uint64_t const la_suf  = alignSuffix(a,a+end_a,b,be);
				uint64_t const start_a = end_a - la_suf;
				return std::make_pair(start_a,end_a);
			}
		};
	}
}
#endif
