/*
 * ToRegExpAlgebraic.h
 *
 *  Created on: 4. 3. 2014
 *	  Author: Tomas Pecka
 */

#ifndef GRAMMAR_TO_REG_EXP_ALGEBRAIC_H_
#define GRAMMAR_TO_REG_EXP_ALGEBRAIC_H_

#include <grammar/Regular/RightRG.h>
#include <grammar/Regular/LeftRG.h>

#include <equations/LeftRegularEquationSolver.h>
#include <equations/RightRegularEquationSolver.h>

#include <regexp/unbounded/UnboundedRegExp.h>
#include <regexp/unbounded/UnboundedRegExpElements.h>

namespace grammar {

namespace convert {

class ToRegExpAlgebraic {
public:
	/**
	 * Converts a regular grammar to regular expression using regular equations.
	 *
	 * @param grammar Grammar to convert
	 * @return regexp describing language generated by the @p grammar
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static regexp::UnboundedRegExp < TerminalSymbolType > convert ( const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar);

	/**
	 * \override
	 */
	template < class TerminalSymbolType, class NonterminalSymbolType >
	static regexp::UnboundedRegExp < TerminalSymbolType > convert(const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & grammar);
};

template < class TerminalSymbolType, class NonterminalSymbolType >
regexp::UnboundedRegExp < TerminalSymbolType > ToRegExpAlgebraic::convert(const grammar::LeftRG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	equations::LeftRegularEquationSolver < TerminalSymbolType, NonterminalSymbolType > solver;

	solver.setVariableSymbols(grammar.getNonterminalAlphabet());

	for(const auto & rule : grammar.getRules()) {
		const NonterminalSymbolType& lhs = rule.first;

		for(const auto& ruleRHS : rule.second) {
			if(ruleRHS.template is<TerminalSymbolType>()) {
				const TerminalSymbolType& rhs = ruleRHS.template get<TerminalSymbolType>();
				solver.addEquation(lhs, regexp::UnboundedRegExpSymbol < TerminalSymbolType > (rhs));
			} else {
				const ext::pair<NonterminalSymbolType, TerminalSymbolType>& rhs = ruleRHS.template get<ext::pair<NonterminalSymbolType, TerminalSymbolType>>();
				solver.addEquation(lhs, rhs.first, regexp::UnboundedRegExpSymbol < TerminalSymbolType > (rhs.second));
			}
		}
	}
	if(grammar.getGeneratesEpsilon())
		solver.addEquation(grammar.getInitialSymbol(), regexp::UnboundedRegExpEpsilon < TerminalSymbolType > ());

	return solver.solve(grammar.getInitialSymbol());
}

template < class TerminalSymbolType, class NonterminalSymbolType >
regexp::UnboundedRegExp < TerminalSymbolType > ToRegExpAlgebraic::convert ( const grammar::RightRG < TerminalSymbolType, NonterminalSymbolType > & grammar) {
	equations::RightRegularEquationSolver < TerminalSymbolType, NonterminalSymbolType > solver;

	solver.setVariableSymbols(grammar.getNonterminalAlphabet());

	for(const auto & rule : grammar.getRules()) {
		const NonterminalSymbolType & lhs = rule.first;

		for(const auto& ruleRHS : rule.second) {
			if ( ruleRHS.template is < TerminalSymbolType > ( ) ) {
				const TerminalSymbolType & rhs = ruleRHS.template get < TerminalSymbolType > ( );
				solver.addEquation ( lhs, regexp::UnboundedRegExpSymbol < TerminalSymbolType > ( rhs ) );
			} else {
				const ext::pair < TerminalSymbolType, NonterminalSymbolType > & rhs = ruleRHS.template get < ext::pair < TerminalSymbolType, NonterminalSymbolType > > ( );
				solver.addEquation ( lhs, rhs.second, regexp::UnboundedRegExpSymbol < TerminalSymbolType > ( rhs.first ) );
			}
		}
	}
	if(grammar.getGeneratesEpsilon())
		solver.addEquation ( grammar.getInitialSymbol ( ), regexp::UnboundedRegExpEpsilon < TerminalSymbolType > ( ) );

	return solver.solve ( grammar.getInitialSymbol ( ) );
}

} /* namespace covert */

} /* namespace grammar */

#endif /* GRAMMAR_TO_REG_EXP_ALGEBRAIC_H_ */
