/*
    libmaus2
    Copyright (C) 2019 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_PROTEIN_CODONMAPPER_HPP)
#define LIBMAUS2_PROTEIN_CODONMAPPER_HPP

#include <libmaus2/autoarray/AutoArray.hpp>
#include <libmaus2/fastx/acgtnMap.hpp>
#include <cassert>
#include <sstream>

namespace libmaus2
{
	namespace protein
	{
		struct CodonMapper
		{
			static char const codonArray[64];
			static char const codonArrayMT[64];
			static char const stopArray[64];
			static char const stopArrayMT[64];

			static std::string mapString(std::string const & s)
			{
				uint64_t const full = s.size() / 3;
				uint64_t const frac = s.size() % 3;

				std::ostringstream ostr;

				for ( uint64_t i = 0; i < full; ++i )
					ostr.put(
						map(s[3*i+0],s[3*i+1],s[3*i+2])
					);

				if ( frac )
					ostr.put('X');

				return ostr.str();
			}

			static std::string mapStringMT(std::string const & s)
			{
				uint64_t const full = s.size() / 3;
				uint64_t const frac = s.size() % 3;

				std::ostringstream ostr;

				for ( uint64_t i = 0; i < full; ++i )
					ostr.put(
						mapMT(s[3*i+0],s[3*i+1],s[3*i+2])
					);

				if ( frac )
					ostr.put('X');

				return ostr.str();
			}

			template<typename iterator>
			static uint64_t map(libmaus2::autoarray::AutoArray<char> & VP, iterator it_a, iterator it_e)
			{
				uint64_t o = 0;
				while ( it_a != it_e )
				{
					char const a = *(it_a++);
					if ( it_a == it_e )
					{
						VP.push(o,'X');
					}
					else
					{
						char const b = *(it_a++);

						if ( it_a == it_e )
						{
							VP.push(o,'X');
						}
						else
						{
							char const c = *(it_a++);
							VP.push(o,map(a,b,c));
						}
					}
				}

				return o;
			}


			template<typename iterator>
			static uint64_t mapMT(libmaus2::autoarray::AutoArray<char> & VP, iterator it_a, iterator it_e)
			{
				uint64_t o = 0;
				while ( it_a != it_e )
				{
					char const a = *(it_a++);
					if ( it_a == it_e )
					{
						VP.push(o,'X');
					}
					else
					{
						char const b = *(it_a++);

						if ( it_a == it_e )
						{
							VP.push(o,'X');
						}
						else
						{
							char const c = *(it_a++);
							VP.push(o,mapMT(a,b,c));
						}
					}
				}

				return o;
			}

			// map codon triple to amino acid
			static char map(char const a, char const b, char const c)
			{
				char const ma = libmaus2::fastx::mapChar(a);
				char const mb = libmaus2::fastx::mapChar(b);
				char const mc = libmaus2::fastx::mapChar(c);

				char const cd = ma | mb | mc;
				assert ( !(cd & (~3)) );

				int const index = (ma<<4) | (mb<<2) | mc;

				assert ( static_cast<unsigned int>(index) < sizeof(codonArray)/sizeof(codonArray[0]) );

				return codonArray[index];
			}

			// map codon triple to amino acid
			static char mapMT(char const a, char const b, char const c)
			{
				char const ma = libmaus2::fastx::mapChar(a);
				char const mb = libmaus2::fastx::mapChar(b);
				char const mc = libmaus2::fastx::mapChar(c);

				char const cd = ma | mb | mc;
				assert ( !(cd & (~3)) );

				int const index = (ma<<4) | (mb<<2) | mc;

				assert ( static_cast<unsigned int>(index) < sizeof(codonArrayMT)/sizeof(codonArrayMT[0]) );

				return codonArrayMT[index];
			}

			// is the given triple a stop codon?
			static bool isStop(char const a, char const b, char const c)
			{
				char const ma = libmaus2::fastx::mapChar(a);
				char const mb = libmaus2::fastx::mapChar(b);
				char const mc = libmaus2::fastx::mapChar(c);

				char const cd = ma | mb | mc;
				assert ( !(cd & (~3)) );

				int const index = (ma<<4) | (mb<<2) | mc;

				assert ( static_cast<unsigned int>(index) < sizeof(codonArray)/sizeof(codonArray[0]) );

				return stopArray[index];
			}

			// is the given triple a stop codon?
			static bool isStopMT(char const a, char const b, char const c)
			{
				char const ma = libmaus2::fastx::mapChar(a);
				char const mb = libmaus2::fastx::mapChar(b);
				char const mc = libmaus2::fastx::mapChar(c);

				char const cd = ma | mb | mc;
				assert ( !(cd & (~3)) );

				int const index = (ma<<4) | (mb<<2) | mc;

				assert ( static_cast<unsigned int>(index) < sizeof(codonArrayMT)/sizeof(codonArrayMT[0]) );

				return stopArrayMT[index];
			}
		};
	}
}
#endif
