/*
    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_FM_BIINDEX_HPP)
#define LIBMAUS2_FM_BIINDEX_HPP

#include <libmaus2/lf/ImpCompactHuffmanWaveletLF.hpp>
#include <libmaus2/fastx/acgtnMap.hpp>

namespace libmaus2
{
	namespace fm
	{
		struct BiIndex
		{
			struct Bi
			{
				std::pair<uint64_t,uint64_t> F;
				std::pair<uint64_t,uint64_t> R;

				Bi() {}
				Bi(
					std::pair<uint64_t,uint64_t> rF,
					std::pair<uint64_t,uint64_t> rR
				) :F(rF), R(rR)
				{

				}

				Bi swap() const
				{
					return Bi(R,F);
				}
			};

			libmaus2::lf::ImpCompactHuffmanWaveletLF const & I;

			BiIndex(libmaus2::lf::ImpCompactHuffmanWaveletLF const & rI) : I(rI) {}

			Bi epsilon() const
			{
				std::pair<uint64_t,uint64_t> P(0,I.W->size());
				return Bi(P,P);
			}

			Bi extendLeft(Bi const & B, int64_t const sym) const
			{
				std::pair<int64_t,uint64_t> P[7];

				libmaus2::wavelet::ImpCompactHuffmanWaveletTree const & W = I.getW();
				uint64_t const n = W.enumerateSymbolsInRangeSorted(B.F.first, B.F.second, &P[0]);

				assert ( sym < 4 );
				// int64_t const rsym = sym^3;

				uint64_t smaller = 0;
				uint64_t eq = 0;

				for ( uint64_t i = 0; i < n; ++i )
				{
					int64_t  const lsym  = P[i].first;
					uint64_t const lfreq = P[i].second;

					if ( lsym == sym )
						eq += lfreq;
					else
					{
						if ( lsym > sym && lsym < 4 )
							smaller += lfreq;
					}
				}

				// std::cerr << "sym=" << sym << " rsym=" << rsym << " smaller=" << smaller << " eq=" << eq << std::endl;

				return Bi(
					I.step(sym,B.F),
					std::pair<uint64_t,uint64_t>(
						B.R.first + smaller,
						B.R.first + smaller + eq
					)
				);
			}

			Bi extendRight(Bi const & B, int64_t const sym) const
			{
				return extendLeft(B.swap(),sym^3).swap();
			}

			std::string decodeKMer(uint64_t const r0, uint64_t const maxdepth) const
			{
				uint64_t r = r0;
				std::ostringstream ostr;

				for ( uint64_t i = 0; i < maxdepth; ++i )
				{
					r = I.phi(r);
					ostr.put ( libmaus2::fastx::remapChar((*(I.W))[r]) );
				}

				return ostr.str();
			}
		};
	}
}
#endif
