/*
    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_LCS_POSMAPPING_HPP)
#define LIBMAUS2_LCS_POSMAPPING_HPP

#include <libmaus2/bitio/BitVector.hpp>
#include <libmaus2/lcs/AlignmentTraceContainer.hpp>

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

			libmaus2::bitio::IndexedBitVector::shared_ptr_type DBV;
			uint64_t DBV1;
			libmaus2::bitio::IndexedBitVector::shared_ptr_type IBV;
			uint64_t IBV1;
			uint64_t abpos;
			uint64_t aepos;
			uint64_t bbpos;
			uint64_t bepos;

			PosMapping()
			{

			}

			std::string toString() const
			{
				std::ostringstream ostr;

				ostr << "PosMapping("
					<< "abpos=" << abpos << ","
					<< "aepos=" << aepos << ","
					<< "bbpos=" << bbpos << ","
					<< "bepos=" << bepos << ")\n";

				ostr << "DBV=";
				for ( uint64_t i = 0; i < DBV->size(); ++i )
					ostr << DBV->get(i);
				ostr << "\n";

				ostr << "IBV=";
				for ( uint64_t i = 0; i < IBV->size(); ++i )
					ostr << IBV->get(i);
				ostr << "\n";

				assert ( DBV->size() == IBV->size() );

				uint64_t cd = 0, ci = 0;

				for ( uint64_t i = 0; i < DBV->size(); ++i )
				{
					ostr << "pm(" << abpos+cd << "," << bbpos+ci << ")\n";

					if ( DBV->get(i) )
						cd += 1;
					if ( IBV->get(i) )
						ci += 1;
				}

				return ostr.str();
			}

			PosMapping(
				libmaus2::lcs::AlignmentTraceContainer const & ATC,
				uint64_t const rabpos,
				uint64_t const raepos,
				uint64_t const rbbpos,
				uint64_t const rbepos
			) : DBV1(0), IBV1(0), abpos(rabpos), aepos(raepos), bbpos(rbbpos), bepos(rbepos)
			{
				uint64_t const n = ATC.te - ATC.ta;

				libmaus2::bitio::IndexedBitVector::shared_ptr_type tDBV(new libmaus2::bitio::IndexedBitVector(n+1));
				DBV = tDBV;
				libmaus2::bitio::IndexedBitVector::shared_ptr_type tIBV(new libmaus2::bitio::IndexedBitVector(n+1));
				IBV = tIBV;

				uint64_t i = 0;
				uint64_t cd = 0;
				uint64_t ci = 0;
				for ( libmaus2::lcs::AlignmentTraceContainer::step_type const * tc = ATC.ta; tc != ATC.te; ++tc, ++i )
				{
					libmaus2::lcs::AlignmentTraceContainer::step_type const step = *tc;

					switch ( step )
					{
						case libmaus2::lcs::AlignmentTraceContainer::STEP_MATCH:
						case libmaus2::lcs::AlignmentTraceContainer::STEP_MISMATCH:
						case libmaus2::lcs::AlignmentTraceContainer::STEP_DEL:
							DBV->set(i);
							DBV1 += 1;
							cd += 1;
							break;
						default:
							DBV->erase(i);
							break;
					}

					switch ( step )
					{
						case libmaus2::lcs::AlignmentTraceContainer::STEP_MATCH:
						case libmaus2::lcs::AlignmentTraceContainer::STEP_MISMATCH:
						case libmaus2::lcs::AlignmentTraceContainer::STEP_INS:
							IBV->set(i);
							IBV1 += 1;
							ci += 1;
							break;
						default:
							IBV->erase(i);
							break;
					}
				}

				DBV->set(i);
				DBV1 += 1;
				IBV->set(i);
				IBV1 += 1;

				i += 1;

				DBV->setupIndex();
				IBV->setupIndex();

				assert ( cd == aepos - abpos );
				assert ( ci == bepos - bbpos );
			}

			int64_t mapAB(uint64_t pos) const
			{
				if ( pos >= abpos && pos < aepos )
				{
					uint64_t const ypos = pos - abpos;
					// trace position on A
					uint64_t const p0 = DBV->select1(ypos);
					// position on A
					uint64_t const p1 = IBV->rankm1(p0);

					return bbpos + p1;
				}
				else
				{
					return -1;
				}
			}

			std::pair<int64_t,int64_t> mapBA(uint64_t pos) const
			{
				if ( pos >= bbpos && pos <= bepos )
				{
					uint64_t const ypos = pos - bbpos;
					// trace position on B
					assert ( ypos < IBV1 );
					uint64_t const p0 = IBV->select1(ypos);
					// position on A
					uint64_t const p1 = DBV->rankm1(p0);
					// shift
					assert ( p1 < DBV1 );
					uint64_t const p2 = DBV->select1(p1);

					return std::pair<int64_t,int64_t>(abpos + p1,p0-p2);
				}
				else
				{
					return std::pair<int64_t,int64_t>(-1,-1);
				}
			}
		};
	}
}
#endif
