/*
 * ToAutomatonBototmUp.h
 *
 * Created on: 23. 11. 2020
 * Author: Tomas Pecka
 */

#pragma once

#include <grammar/Grammar.h>
#include <grammar/ContextFree/CFG.h>
#include <grammar/ContextFree/EpsilonFreeCFG.h>
#include <grammar/RawRules.h>

#include <automaton/PDA/NPDA.h>

#include <label/InitialStateLabel.h>

namespace grammar {

namespace convert {

/**
 */
class ToAutomatonTopDown {
public:
	/**
	 * Converts a context free grammar to a pushdown automaton by method of bottom up analysis.
	 *
	 * \tparam T the type of converted grammar
	 * \tparam TerminalSymbolType the type of terminals in the converted grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the converted grammar
	 *
	 * \param grammar the converted 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 T, class TerminalSymbolType, class NonterminalSymbolType >
automaton::NPDA < TerminalSymbolType, ext::variant < TerminalSymbolType, NonterminalSymbolType >, unsigned > ToAutomatonTopDown::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 */
