/*
    libmaus2
    Copyright (C) 2009-2013 German Tischler
    Copyright (C) 2011-2013 Genome Research Limited

    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_ALIGNMENTPRINT_HPP)
#define LIBMAUS2_LCS_ALIGNMENTPRINT_HPP

#include <libmaus2/lcs/BaseConstants.hpp>
#include <libmaus2/util/NumberSerialisation.hpp>
#include <iostream>
#include <sstream>

namespace libmaus2
{
	namespace lcs
	{
		struct AlignmentPrint : public BaseConstants
		{
			virtual ~AlignmentPrint() {}

			static std::string stepToString(step_type const s)
			{
				switch ( s )
				{
					case STEP_MATCH: return "+";
					case STEP_MISMATCH: return "-";
					case STEP_DEL: return "D";
					case STEP_INS: return "I";
					default: return "?";
				}
			}

			template<typename alignment_iterator>
			static std::ostream & printTrace(
				std::ostream & out,
				alignment_iterator const rta,
				alignment_iterator const rte,
				uint64_t const offset = 0
			)
			{
				out << std::string(offset,' ');
				for ( alignment_iterator tc = rta; tc != rte; ++tc )
					out << stepToString(*tc);
				return out;
			}

			template<typename string_iterator, typename alignment_iterator>
			static std::ostream & printAlignment(
				std::ostream & out,
				string_iterator ita,
				string_iterator itb,
				alignment_iterator const rta,
				alignment_iterator const rte
			)
			{
				printTrace(out,rta,rte);
				out << std::endl;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_DEL:
							out << (*ita++);
							break;
						case STEP_INS:
							out << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				out << std::endl;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_INS:
							out << (*itb++);
							break;
						case STEP_DEL:
							out << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				out << std::endl;

				return out;
			}

			template<typename alignment_iterator>
			static std::ostream & printAlignmentLines(
				std::ostream & out, std::string const & a, std::string const & b,
				uint64_t const rlinewidth,
				alignment_iterator const rta,
				alignment_iterator const rte
			)
			{
				std::ostringstream astr;

				std::string::const_iterator ita = a.begin();

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_DEL:
							if ( ita == a.end() )
								std::cerr << "accessing a beyond end." << std::endl;
							astr << (*ita++);
							break;
						case STEP_INS:
							astr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				astr << std::string(ita,a.end());

				std::ostringstream bstr;
				// out << std::string(SPR.aclip,' ') << std::endl;

				std::string::const_iterator itb = b.begin();

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_INS:
							if ( itb == b.end() )
								std::cerr << "accessing b beyond end." << std::endl;
							bstr << (*itb++);
							break;
						case STEP_DEL:
							bstr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				bstr << std::string(itb,b.end());

				std::ostringstream cstr;
				printTrace(cstr,rta,rte);

				std::string const aa = astr.str();
				std::string const ba = bstr.str();
				std::string const ca = cstr.str();
				uint64_t const linewidth = rlinewidth-2;
				uint64_t const numlines = (std::max(aa.size(),ba.size()) + linewidth-1) / linewidth;

				for ( uint64_t i = 0; i < numlines; ++i )
				{
					uint64_t pl = i*linewidth;

					out << "A ";
					if ( pl < aa.size() )
					{
						uint64_t const alen = std::min(linewidth,aa.size()-pl);
						out << aa.substr(pl,alen);
					}
					out << std::endl;

					out << "B ";
					if ( pl < ba.size() )
					{
						uint64_t const blen = std::min(linewidth,ba.size()-pl);
						out << ba.substr(pl,blen);
					}
					out << std::endl;

					out << "  ";
					if ( pl < ca.size() )
					{
						uint64_t const clen = std::min(linewidth,ca.size()-pl);
						out << ca.substr(pl,clen);
					}
					out << std::endl;
				}

				return out;
			}

			template<typename alignment_iterator, typename iterator_a, typename iterator_b>
			static std::ostream & printAlignmentLines(
				std::ostream & out,
				iterator_a a,
				size_t const an,
				iterator_b b,
				size_t const bn,
				uint64_t const rlinewidth,
				alignment_iterator const rta,
				alignment_iterator const rte,
				uint64_t const rposa = 0,
				uint64_t const rposb = 0
			)
			{
				std::ostringstream astr;

				iterator_a ita = a;
				iterator_a itae = a + an;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_DEL:
							if ( ita == itae )
								std::cerr << "accessing a beyond end." << std::endl;
							astr << (*ita++);
							break;
						case STEP_INS:
							astr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				astr << std::string(ita,itae);

				std::ostringstream bstr;
				// out << std::string(SPR.aclip,' ') << std::endl;

				iterator_b itb = b;
				iterator_b itbe = b + bn;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_INS:
							if ( itb == itbe )
								std::cerr << "accessing b beyond end." << std::endl;
							bstr << (*itb++);
							break;
						case STEP_DEL:
							bstr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				bstr << std::string(itb,itbe);

				std::ostringstream cstr;
				printTrace(cstr,rta,rte);

				std::string const aa = astr.str();
				std::string const ba = bstr.str();
				std::string const ca = cstr.str();
				uint64_t const linewidth = rlinewidth-2;
				uint64_t const numlines = (std::max(aa.size(),ba.size()) + linewidth-1) / linewidth;
				uint64_t posa = rposa;
				uint64_t posb = rposb;
				uint64_t const numwidth = libmaus2::util::NumberSerialisation::formatNumber(std::max(rposa+an,rposb+bn),6).size();

				for ( uint64_t i = 0; i < numlines; ++i )
				{
					uint64_t pl = i*linewidth;

					out << "A " << std::setw(numwidth) << posa << std::setw(0) << " ";
					if ( pl < aa.size() )
					{
						uint64_t const alen = std::min(linewidth,aa.size()-pl);
						std::string const pr = aa.substr(pl,alen);
						out << pr;
						for ( uint64_t i = 0; i < pr.size(); ++i )
							if ( pr[i] != ' ' )
								posa++;
					}
					out << std::endl;

					out << "B " << std::setw(numwidth) << posb << std::setw(0) << " ";
					if ( pl < ba.size() )
					{
						uint64_t const blen = std::min(linewidth,ba.size()-pl);
						std::string const pr = ba.substr(pl,blen);
						out << pr;
						for ( uint64_t i = 0; i < pr.size(); ++i )
							if ( pr[i] != ' ' )
								posb++;
					}
					out << std::endl;

					out << std::string(numwidth + 3,' ');
					if ( pl < ca.size() )
					{
						uint64_t const clen = std::min(linewidth,ca.size()-pl);
						out << ca.substr(pl,clen);
					}
					out << std::endl;
				}

				return out;
			}

			template<typename map_function_t>
			static std::string mapString(std::string s, map_function_t map_function)
			{
				for ( uint64_t i = 0; i < s.size(); ++i )
					s[i] = map_function(s[i]);
				return s;
			}

			static char charIdMap(int const c)
			{
				return c;
			}

			template<typename alignment_iterator, typename iterator_a, typename iterator_b, typename map_function_t>
			static std::ostream & printAlignmentLines(
				std::ostream & out,
				iterator_a a,
				size_t const an,
				iterator_b b,
				size_t const bn,
				uint64_t const rlinewidth,
				alignment_iterator const rta,
				alignment_iterator const rte,
				map_function_t map_function,
				uint64_t const rposa = 0,
				uint64_t const rposb = 0,
				std::string const & aid = "A",
				std::string const & bid = "B"
			)
			{
				uint64_t const aidlen = aid.size();
				uint64_t const bidlen = bid.size();
				uint64_t const midlen = std::max(aidlen,bidlen);

				std::string const paid = std::string(midlen - aid.size(),' ') + aid;
				std::string const pbid = std::string(midlen - bid.size(),' ') + bid;

				std::ostringstream astr;

				iterator_a ita = a;
				iterator_a itae = a + an;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_DEL:
							if ( ita == itae )
								std::cerr << "accessing a beyond end." << std::endl;
							astr << map_function(*ita++);
							break;
						case STEP_INS:
							astr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				astr << mapString(std::string(ita,itae),map_function);

				std::ostringstream bstr;
				// out << std::string(SPR.aclip,' ') << std::endl;

				iterator_b itb = b;
				iterator_b itbe = b + bn;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_INS:
							if ( itb == itbe )
								std::cerr << "accessing b beyond end." << std::endl;
							bstr << map_function(*itb++);
							break;
						case STEP_DEL:
							bstr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				bstr << mapString(std::string(itb,itbe),map_function);

				std::ostringstream cstr;
				printTrace(cstr,rta,rte);

				std::string const aa = astr.str();
				std::string const ba = bstr.str();
				std::string const ca = cstr.str();
				uint64_t const linewidth = rlinewidth-2;
				uint64_t const numlines = (std::max(aa.size(),ba.size()) + linewidth-1) / linewidth;
				uint64_t const numwidth = libmaus2::util::NumberSerialisation::formatNumber(std::max(rposa+an,rposb+bn),6).size();

				uint64_t posa = rposa;
				uint64_t posb = rposb;

				for ( uint64_t i = 0; i < numlines; ++i )
				{
					uint64_t pl = i*linewidth;

					out << paid << ' ' << std::setw(numwidth) << posa << std::setw(0) << " ";
					if ( pl < aa.size() )
					{
						uint64_t const alen = std::min(linewidth,aa.size()-pl);
						std::string const pr = aa.substr(pl,alen);
						out << pr;
						for ( uint64_t i = 0; i < pr.size(); ++i )
							if ( pr[i] != ' ' )
								posa++;
					}
					out << std::endl;

					out << pbid << ' ' << std::setw(numwidth) << posb << std::setw(0) << " ";
					if ( pl < ba.size() )
					{
						uint64_t const blen = std::min(linewidth,ba.size()-pl);
						std::string const pr = ba.substr(pl,blen);
						out << pr;
						for ( uint64_t i = 0; i < pr.size(); ++i )
							if ( pr[i] != ' ' )
								posb++;
					}
					out << std::endl;

					out
						<< std::string(midlen,' ') // id
						<< std::string(1,' ') // space
						<< std::string(numwidth,' ') // pos
						<< std::string(1,' ') // space
						;
					if ( pl < ca.size() )
					{
						uint64_t const clen = std::min(linewidth,ca.size()-pl);
						std::string const sub = ca.substr(pl,clen);

						out << sub;

						uint64_t const numop = clen;
						uint64_t nummatches = 0;
						for ( uint64_t i = 0; i < sub.size(); ++i )
							if ( sub[i] == '+' )
								++nummatches;
						uint64_t const numerrors = numop - nummatches;
						double const e = numerrors / static_cast<double>(numop);

						out << " (" << e << ")";
					}
					out << std::endl;
				}

				return out;
			}

			template<typename alignment_iterator, typename iterator_a, typename iterator_aq, typename iterator_b, typename iterator_bq, typename map_function_t>
			static std::ostream & printAlignmentLinesQual(
				std::ostream & out,
				iterator_a a,
				iterator_aq aq,
				size_t const an,
				iterator_b b,
				iterator_bq bq,
				size_t const bn,
				uint64_t const rlinewidth,
				alignment_iterator const rta,
				alignment_iterator const rte,
				map_function_t map_function,
				uint64_t const rposa = 0,
				uint64_t const rposb = 0,
				std::string const & aid = "A",
				std::string const & bid = "B"
			)
			{
				uint64_t const aidlen = aid.size();
				uint64_t const bidlen = bid.size();
				uint64_t const midlen = std::max(aidlen,bidlen);

				std::string const paid = std::string(midlen - aid.size(),' ') + aid;
				std::string const pbid = std::string(midlen - bid.size(),' ') + bid;

				std::ostringstream astr;
				std::ostringstream aqstr;

				iterator_a ita = a;
				iterator_a itae = a + an;
				iterator_aq itaq = aq;
				iterator_aq itaqe = aq + an;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_DEL:
							if ( ita == itae )
								std::cerr << "accessing a beyond end." << std::endl;
							astr << map_function(*ita++);
							aqstr << (*(itaq++));
							break;
						case STEP_INS:
							astr << " ";
							aqstr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				astr << mapString(std::string(ita,itae),map_function);
				aqstr << std::string(itaq,itaqe);

				std::ostringstream bstr;
				std::ostringstream bqstr;
				// out << std::string(SPR.aclip,' ') << std::endl;

				iterator_b itb = b;
				iterator_b itbe = b + bn;
				iterator_bq itbq = bq;
				iterator_bq itbqe = bq + bn;

				for ( alignment_iterator ta = rta; ta != rte; ++ta )
				{
					switch ( *ta )
					{
						case STEP_MATCH:
						case STEP_MISMATCH:
						case STEP_INS:
							if ( itb == itbe )
								std::cerr << "accessing b beyond end." << std::endl;
							bstr << map_function(*itb++);
							bqstr << (*(itbq++));
							break;
						case STEP_DEL:
							bstr << " ";
							bqstr << " ";
							// ita++;
							break;
						case STEP_RESET:
							break;
					}
				}
				bstr << mapString(std::string(itb,itbe),map_function);
				bqstr << std::string(itbq,itbqe);

				std::ostringstream cstr;
				printTrace(cstr,rta,rte);

				std::string const aa = astr.str();
				std::string const ba = bstr.str();
				std::string const ca = cstr.str();
				std::string const qa = aqstr.str();
				std::string const qb = bqstr.str();
				uint64_t const linewidth = rlinewidth-2;
				uint64_t const numlines = (std::max(aa.size(),ba.size()) + linewidth-1) / linewidth;
				uint64_t const numwidth = libmaus2::util::NumberSerialisation::formatNumber(std::max(rposa+an,rposb+bn),6).size();

				uint64_t posa = rposa;
				uint64_t posb = rposb;

				for ( uint64_t i = 0; i < numlines; ++i )
				{
					uint64_t pl = i*linewidth;

					out << paid << ' ' << std::setw(numwidth) << posa << std::setw(0) << " ";
					if ( pl < aa.size() )
					{
						uint64_t const alen = std::min(linewidth,aa.size()-pl);
						std::string const pr = aa.substr(pl,alen);
						out << pr;
						for ( uint64_t i = 0; i < pr.size(); ++i )
							if ( pr[i] != ' ' )
								posa++;
					}
					out << std::endl;

					out
						<< std::string(midlen,' ') // id
						<< std::string(1,' ') // space
						<< std::string(numwidth,' ') // pos
						<< std::string(1,' ') // space
						;
					if ( pl < qa.size() )
					{
						uint64_t const alen = std::min(linewidth,qa.size()-pl);
						std::string const pr = qa.substr(pl,alen);
						out << pr;
					}
					out << std::endl;

					out << pbid << ' ' << std::setw(numwidth) << posb << std::setw(0) << " ";
					if ( pl < ba.size() )
					{
						uint64_t const blen = std::min(linewidth,ba.size()-pl);
						std::string const pr = ba.substr(pl,blen);
						out << pr;
						for ( uint64_t i = 0; i < pr.size(); ++i )
							if ( pr[i] != ' ' )
								posb++;
					}
					out << std::endl;

					out
						<< std::string(midlen,' ') // id
						<< std::string(1,' ') // space
						<< std::string(numwidth,' ') // pos
						<< std::string(1,' ') // space
						;
					if ( pl < qb.size() )
					{
						uint64_t const blen = std::min(linewidth,qb.size()-pl);
						std::string const pr = qb.substr(pl,blen);
						out << pr;
					}
					out << std::endl;


					out
						<< std::string(midlen,' ') // id
						<< std::string(1,' ') // space
						<< std::string(numwidth,' ') // pos
						<< std::string(1,' ') // space
						;
					if ( pl < ca.size() )
					{
						uint64_t const clen = std::min(linewidth,ca.size()-pl);
						std::string const sub = ca.substr(pl,clen);

						out << sub;

						uint64_t const numop = clen;
						uint64_t nummatches = 0;
						for ( uint64_t i = 0; i < sub.size(); ++i )
							if ( sub[i] == '+' )
								++nummatches;
						uint64_t const numerrors = numop - nummatches;
						double const e = numerrors / static_cast<double>(numop);

						out << " (" << e << ")";
					}
					out << std::endl;
				}

				return out;
			}
		};
	}
}
#endif
