/*
 * ToAutomaton.h
 *
 * Created on: 1. 11. 2013
 * Author: Tomas Pecka
 */

#ifndef _GRAMMAR_TO_AUTOMATON_H_
#define _GRAMMAR_TO_AUTOMATON_H_

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

#include <automaton/FSM/NFA.h>
#include <automaton/PDA/NPDA.h>

#include <common/createUnique.hpp>

#include <label/InitialStateLabel.h>
#include <label/FinalStateLabel.h>

namespace grammar {

namespace convert {

/**
 * Converts regular grammar to nondeterministic finite automaton.
 * Sources: Melichar 2.98 (RightRG -> NFA) and 2.102 (LeftRG -> NFA).
 */
class ToAutomaton {
public:
	/**
	 * Performs the conversion.
	 *
	 * \tparam TerminalSymbolType the type of terminal symbol of the grammar
	 * \tparam NonterminalSymbolType the type of nonterminal symbol of the grammar
	 *
	 * \param grammar the left regular grammar to convert.
	 *
	 * \return finite automaton accepting the language generated by @p grammar.
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static automaton::NFA < TerminalSymbolType, NonterminalSymbolType > convert ( const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * \overload
	 *
	 * \tparam TerminalSymbolType the type of terminal symbol of the grammar
	 * \tparam NonterminalSymbolType the type of nonterminal symbol of the grammar
	 *
	 * \param grammar the right regular grammar to convert.
	 *
	 * \return finite automaton accepting the language generated by @p grammar.
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static automaton::NFA < TerminalSymbolType, NonterminalSymbolType > convert ( const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar );

	/**
	 * \overload
	 *
	 * \tparam T the type of converted grammar
	 * \tparam TerminalSymbolType the type of terminal symbol of the grammar
	 * \tparam NonterminalSymbolType the type of nonterminal symbol of the grammar
	 *
	 * \param grammar some context free grammar.
	 *
	 * \return pushdown automaton accepting the language generated by @p grammar.
	 */
	template < class T, class TerminalSymbolType = typename grammar::TerminalSymbolTypeOfGrammar < T >, class NonterminalSymbolType = typename grammar::NonterminalSymbolTypeOfGrammar < T > >
	static automaton::NPDA < TerminalSymbolType, ext::variant < TerminalSymbolType, NonterminalSymbolType >, unsigned > convert ( const T & grammar );
};

template < class TerminalSymbolType, class NonterminalSymbolType >
automaton::NFA < TerminalSymbolType, NonterminalSymbolType > ToAutomaton::convert(const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	// step 1, 4
	NonterminalSymbolType q0 = common::createUnique ( label::InitialStateLabel::instance < NonterminalSymbolType > ( ), grammar.getNonterminalAlphabet ( ) );
	automaton::NFA < TerminalSymbolType, NonterminalSymbolType > automaton ( q0 );
	automaton.setInputAlphabet(grammar.getTerminalAlphabet());
	for ( const NonterminalSymbolType & nonterminal : grammar.getNonterminalAlphabet ( ) )
		automaton.addState ( nonterminal );

	// step 3
	for(const auto& rule : grammar.getRules()) {
		const NonterminalSymbolType& lhs = rule.first;
		for(const auto& ruleRHS : rule.second) {
			if ( ruleRHS.template is < ext::pair < NonterminalSymbolType, TerminalSymbolType > > ( ) ) { // if B->Ca => \delta(C,a)=B
				const ext::pair < NonterminalSymbolType, TerminalSymbolType > & rhs = ruleRHS.template get < ext::pair < NonterminalSymbolType, TerminalSymbolType > > ( );
				automaton.addTransition(rhs.first, rhs.second, lhs);
			}
			else { // if B->a => \delta(StartState,a)=B
				const TerminalSymbolType& rhs = ruleRHS.template get < TerminalSymbolType > ( );
				automaton.addTransition(q0, rhs, lhs);
			}
		}
	}

	// step 5
	automaton.addFinalState(grammar.getInitialSymbol());
	if(grammar.getGeneratesEpsilon())
		automaton.addFinalState(q0);

	return automaton;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
automaton::NFA < TerminalSymbolType, NonterminalSymbolType > ToAutomaton::convert(const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	// step 1, 4
	NonterminalSymbolType AState = common::createUnique(label::FinalStateLabel::instance < NonterminalSymbolType > ( ), grammar.getNonterminalAlphabet ( ) );
	automaton::NFA < > automaton(grammar.getInitialSymbol());
	for ( const NonterminalSymbolType & nonterminal : grammar.getNonterminalAlphabet ( ) )
		automaton.addState ( nonterminal );
	automaton.addState(AState);

	automaton.setInputAlphabet(grammar.getTerminalAlphabet());

	// step 3
	for(const auto& rule : grammar.getRules()) {
		const NonterminalSymbolType& lhs = rule.first;
		for(const auto& ruleRHS : rule.second) {
			if ( ruleRHS.template is < ext::pair < TerminalSymbolType, NonterminalSymbolType > > ( ) ) { // if B->aC => \delta(B,a)=C
				const ext::pair < TerminalSymbolType, NonterminalSymbolType > & rhs = ruleRHS.template get < ext::pair < TerminalSymbolType, NonterminalSymbolType > > ( );
				automaton.addTransition(lhs, rhs.first, rhs.second);
			}
			else { // if B->a => \delta(B,a)=AState
				const TerminalSymbolType& rhs = ruleRHS.template get < TerminalSymbolType > ( );
				automaton.addTransition(lhs, rhs, AState);
			}
		}
	}

	// step 5
	automaton.addFinalState(AState);
	if(grammar.getGeneratesEpsilon())
		automaton.addFinalState(automaton.getInitialState());

	return automaton;
}

template < class T, class TerminalSymbolType, class NonterminalSymbolType >
automaton::NPDA < TerminalSymbolType, ext::variant < TerminalSymbolType, NonterminalSymbolType >, unsigned > ToAutomaton::convert ( const T & grammar ) {
	automaton::NPDA < TerminalSymbolType, ext::variant < TerminalSymbolType, NonterminalSymbolType >, unsigned > automaton ( label::InitialStateLabel::instance < unsigned > ( ), grammar.getInitialSymbol());

	automaton.setInputAlphabet(grammar.getTerminalAlphabet());

	for ( const NonterminalSymbolType & nonterminal : grammar.getNonterminalAlphabet ( ) )
		automaton.addPushdownStoreSymbol ( nonterminal );
	for ( const TerminalSymbolType & terminal : grammar.getTerminalAlphabet ( ) )
		automaton.addPushdownStoreSymbol ( terminal );

	for(const std::pair<const NonterminalSymbolType, ext::set<ext::vector<ext::variant<TerminalSymbolType,NonterminalSymbolType>>>>& kv : grammar.getRules())
		for ( const ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > & rhs : kv.second ) {
			automaton.addTransition ( automaton.getInitialState ( ), ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { kv.first }, automaton.getInitialState ( ), rhs );
		}

	for(const TerminalSymbolType & symbol : grammar.getTerminalAlphabet())
		automaton.addTransition ( automaton.getInitialState ( ), symbol, ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { symbol }, automaton.getInitialState ( ), ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > { } );

	return automaton;
}

} /* namespace convert */

} /* namespace grammar */

#endif /* _GRAMMAR_TO_AUTOMATON_H_ */
