/*
 * LatexConverter.h
 *
 *  Created on: 6. 11. 2017
 *      Author: Jan Travnicek, Tomas Pecka
 */

#ifndef LATEX_TABLE_H_
#define LATEX_TABLE_H_

#include <ostream>
#include <string/String.h>

#include <automaton/FSM/DFA.h>
#include <automaton/FSM/NFA.h>
#include <automaton/FSM/EpsilonNFA.h>
#include <automaton/FSM/MultiInitialStateNFA.h>
#include <automaton/FSM/MultiInitialStateEpsilonNFA.h>

#include <grammar/ContextFree/CFG.h>
#include <grammar/Regular/RightRG.h>

#include "common/converterCommon.hpp"

#include <factory/StringDataFactory.hpp>

namespace convert {

class LatexConverter {
public:
	template < class FiniteAutomatonType >
	static void convertFSM ( std::ostream & out, const FiniteAutomatonType & automaton, bool wideTable = false );

	template < class FiniteAutomatonType >
	static std::string convertFSM ( const FiniteAutomatonType & automaton ) {
		return convertFSM ( automaton, false );
	}

	template < class FiniteAutomatonType >
	static std::string convertFSM ( const FiniteAutomatonType & automaton, bool wideTable ) {
		std::stringstream ss;
		convertFSM ( ss, automaton, wideTable );
		return ss.str ( );
	}

	/* --------------------------------------------------------------------- */

	template < class TerminalSymbolType, class NonterminalSymbolType >
	static void rules ( std::ostream & out, const grammar::CFG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	template < class TerminalSymbolType, class NonterminalSymbolType >
	static void rules ( std::ostream & out, const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	template < class GrammarType >
	static void convertGrammar ( std::ostream & out, const GrammarType & grammar );

	template < class GrammarType >
	static std::string convertGrammar ( const GrammarType & grammar ) {
		std::stringstream ss;
		convertGrammar ( ss, grammar );
		return ss.str ( );
	}

private:
	template < class SymbolType, class StateType >
	static constexpr const char* automatonType ( const automaton::DFA < SymbolType, StateType > & ) {
		return "DFA";
	}

	template < class SymbolType, class StateType >
	static constexpr const char* automatonType ( const automaton::NFA < SymbolType, StateType > & ) {
		return "NFA";
	}

	template < class SymbolType, class StateType >
	static constexpr const char* automatonType ( const automaton::EpsilonNFA < SymbolType, StateType > & ) {
		return "$\\varepsilon$-NFA";
	}

	template < class SymbolType, class StateType >
	static constexpr const char* automatonType ( const automaton::MultiInitialStateNFA < SymbolType, StateType > & ) {
		return "NFA";
	}

	template < class SymbolType, class StateType >
	static constexpr const char* automatonType ( const automaton::MultiInitialStateEpsilonNFA < SymbolType, StateType > & ) {
		return "$\\varepsilon$-NFA";
	}

	/* --------------------------------------------------------------------- */

	template < class FiniteAutomatonType, class StateType >
	static inline ext::require < automaton::isMultiInitialStateNFA < FiniteAutomatonType > || automaton::isMultiInitialStateEpsilonNFA < FiniteAutomatonType >, bool > isInitialState ( const FiniteAutomatonType & automaton, const StateType & state ) {
		return automaton.getInitialStates ( ).count ( state );
	}

	template < class FiniteAutomatonType, class StateType >
	static inline ext::require < automaton::isDFA < FiniteAutomatonType > || automaton::isNFA < FiniteAutomatonType > || automaton::isEpsilonNFA < FiniteAutomatonType >, bool > isInitialState ( const FiniteAutomatonType & automaton, const StateType & state ) {
		return automaton.getInitialState ( ) == state;
	}

	/* --------------------------------------------------------------------- */

	template < class FiniteAutomatonType, class StateType >
	static ext::require < automaton::isDFA < FiniteAutomatonType >, void > transitionsRow ( std::ostream & out, const FiniteAutomatonType & automaton, const StateType & state ) {
		for ( const auto & symbol : automaton.getInputAlphabet ( ) ) {
			out << " & ";

			auto transition = automaton.getTransitions ( ).find ( ext::make_pair ( state, symbol ) );
			if ( transition == automaton.getTransitions ( ).end ( ) )
				out << "-";
			else
				out << replace ( factory::StringDataFactory::toString ( transition -> second ), "\"", "\\\"" );
		}
	}

	template < class FiniteAutomatonType, class StateType >
	static ext::require < automaton::isNFA < FiniteAutomatonType > || automaton::isMultiInitialStateNFA < FiniteAutomatonType >, void > transitionsRow ( std::ostream & out, const FiniteAutomatonType & automaton, const StateType & state ) {
		for ( const auto & symbol : automaton.getInputAlphabet ( ) ) {
			out << " & ";

			auto transitions = automaton.getTransitions ( ).equal_range ( ext::make_pair ( state, symbol ) );

			for ( auto it = transitions.begin ( ); it != transitions.end ( ); ++ it ) {
				if ( it != transitions.begin ( ) )
					out << ",";
				out << replace ( factory::StringDataFactory::toString ( it->second ), "\"", "\\\"" );
			}

			if ( transitions.empty ( ) )
				out << "-";
		}
	}

	template < class FiniteAutomatonType, class StateType >
	static ext::require < automaton::isEpsilonNFA < FiniteAutomatonType > || automaton::isMultiInitialStateEpsilonNFA < FiniteAutomatonType >, void > transitionsRow ( std::ostream & out, const FiniteAutomatonType & automaton, const StateType & state ) {
		const auto symTransitions = automaton.getSymbolTransitions ( );
		const auto epsTransitions = automaton.getEpsilonTransitions ( );

		/* columns for symbols */
		for ( const auto &  symbol : automaton.getInputAlphabet ( ) ) {
			out << " & ";

			auto tr = symTransitions.equal_range ( ext::make_pair ( state, symbol ) );

			if ( tr.empty ( ) )
				out << "-";

			for ( auto it = tr.begin ( ); it != tr.end ( ); ++ it ) {
				if ( it != tr.begin ( ) )
					out << ",";
				out << replace ( factory::StringDataFactory::toString ( it -> second ), "\"", "\\\"" );
			}
		}

		/* column for epsilon */
		out << " & ";
		auto tr = epsTransitions.equal_range ( state );

		if ( tr.empty ( ) )
			out << "-";

		for ( auto it = tr.begin ( ); it != tr.end ( ); ++ it ) {
			if ( it != tr.begin ( ) )
				out << ",";
			out << replace ( factory::StringDataFactory::toString ( it->second ), "\"", "\\\"" );
		}
	}

};

template < class FiniteAutomatonType >
void LatexConverter::convertFSM ( std::ostream & out, const FiniteAutomatonType & automaton, bool wideTable ) {
	constexpr bool isEpsilonType = automaton::isMultiInitialStateEpsilonNFA < FiniteAutomatonType > || automaton::isEpsilonNFA < FiniteAutomatonType >;

	if( wideTable )
		out << "\\begin{tabular*}{\\textwidth}{|rl||";
	else
		out << "\\begin{tabular}{|rl||";

	for ( size_t i = 0; i < automaton.getInputAlphabet ( ).size ( ) + isEpsilonType ; ++ i )
		out << "c|";
	out << "}" << std::endl;

	out << "\\hline" << std::endl;
	out << "\\multicolumn{2}{|c||}{" << automatonType ( automaton ) << "}";

	for ( const auto & symbol : automaton.getInputAlphabet ( ) )
		out << " & " << replace ( factory::StringDataFactory::toString ( symbol ), "\"", "\\\"" );

	if constexpr ( isEpsilonType )
		out << " & $\\varepsilon$";

	// out << "\\hspace*{14cm}\\\\" << std::endl;
	out << "\\\\\\hline" << std::endl;

	for ( const auto & state : automaton.getStates ( ) ) {
		if ( automaton.getFinalStates ( ).count ( state ) && isInitialState ( automaton, state ) )
			out << "$\\leftrightarrow$ & ";
		else if ( automaton.getFinalStates ( ).count ( state ) )
			out << "$\\leftarrow$      & ";
		else if ( isInitialState ( automaton, state ) )
			out << "$\\rightarrow$     & ";
		else
			out << "                  & ";

		out << replace ( factory::StringDataFactory::toString ( state ), "\"", "\\\"" );
		transitionsRow ( out, automaton, state );
		out << " \\\\\\hline" << std::endl;
	}

	if ( wideTable )
		out << "\\end{tabular*}" << std::endl;
	else
		out << "\\end{tabular}" << std::endl;
}

/* ----------------------------------------------------------------------------------------------------------------- */

template < class TerminalSymbolType, class NonterminalSymbolType >
void LatexConverter::rules ( std::ostream & out, const grammar::CFG < TerminalSymbolType, NonterminalSymbolType > & grammar ) {
	for ( const auto & kv : grammar.getRules ( ) ) {
		const auto & lhs = kv.first;

		if ( kv.second.size ( ) > 0 )
			out << replace ( factory::StringDataFactory::toString ( lhs ), "\"", "\\\"" ) << " & \\rightarrow & ";

		for ( auto itRhs = kv.second.begin ( ); itRhs != kv.second.end ( ); ++ itRhs ) {
			if ( itRhs != kv.second.begin ( ) )
				out << " \\mid ";

			for ( const auto & symb : *itRhs ) {
				if ( symb.template is < TerminalSymbolType > ( ) )
					out << replace ( factory::StringDataFactory::toString ( symb.template get < TerminalSymbolType > ( ) ), "\"", "\\\"" ) << " ";
				else if ( symb.template is < NonterminalSymbolType > ( ) )
					out << replace ( factory::StringDataFactory::toString ( symb.template get < NonterminalSymbolType > ( ) ), "\"", "\\\"" ) << " ";
			}
			if ( itRhs -> empty ( ) )
				out << "\\varepsilon";
		}
		out << "\\\\" << std::endl;
	}
}

template < class TerminalSymbolType, class NonterminalSymbolType >
void LatexConverter::rules ( std::ostream & out, const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar ) {
	for ( const auto & kv : grammar.getRules ( ) ) {
		const NonterminalSymbolType & lhs = kv.first;

		if ( kv.second.size ( ) > 0 )
			out << replace ( factory::StringDataFactory::toString ( lhs ), "\"", "\\\"" ) << " & \\rightarrow & ";

		for ( auto itRhs = kv.second.begin ( ); itRhs != kv.second.end ( ); ++ itRhs ) {
			if ( itRhs != kv.second.begin ( ) )
				out << " \\mid ";

			if ( itRhs -> template is < TerminalSymbolType > ( ) )
				out << replace ( factory::StringDataFactory::toString ( itRhs -> template get < TerminalSymbolType > ( ) ), "\"", "\\\"" ) << " ";
			else {
				const ext::pair < TerminalSymbolType, NonterminalSymbolType > & rhs = itRhs -> template get < ext::pair < TerminalSymbolType, NonterminalSymbolType > > ( );
				out << replace ( factory::StringDataFactory::toString ( rhs.first ), "\"", "\\\"" ) << " ";
				out << replace ( factory::StringDataFactory::toString ( rhs.second ), "\"", "\\\"" ) << " ";
			}
		}
		if ( lhs == grammar.getInitialSymbol ( ) && grammar.getGeneratesEpsilon ( ) )
			out << " \\mid \\varepsilon";
		out << "\\\\" << std::endl;
	}
}

template < class GrammarType >
void LatexConverter::convertGrammar ( std::ostream & out, const GrammarType & grammar ) {
	out << "";
	out << "$$G = (\\{";
	for ( auto it = grammar.getNonterminalAlphabet ( ).begin ( ); it != grammar.getNonterminalAlphabet ( ).end ( ); ++ it ) {
		if ( it != grammar.getNonterminalAlphabet ( ).begin ( ) )
			out << ", ";
		out << replace ( factory::StringDataFactory::toString ( *it ), "\"", "\\\"" );
	}
	out << "\\}, \\{";
	for ( auto it = grammar.getTerminalAlphabet ( ).begin ( ); it != grammar.getTerminalAlphabet ( ).end ( ); ++ it ) {
		if ( it != grammar.getTerminalAlphabet ( ).begin ( ) )
			out << ", ";
		out << replace ( factory::StringDataFactory::toString ( *it ), "\"", "\\\"" );
	}
	out << "\\}, P, ";
	out << replace ( factory::StringDataFactory::toString ( grammar.getInitialSymbol ( ) ), "\"", "\\\"");
	out << ")$$" << std::endl << std::endl;

	out << "\\begin{eqnarray*}" << std::endl;
	rules ( out, grammar );
	out << "\\end{eqnarray*}" << std::endl;
}

} /* namespace convert */

#endif /* LATEX_TABLE_H_ */
