/*
 * RecursiveNonterminal.h
 *
 *  Created on: 16. 11. 2015
 *	  Author: Jan Travnicek
 */

#pragma once

#include "NullableNonterminals.h"

#include <alib/set>
#include <alib/deque>

#include <exception/CommonException.h>

#include <grammar/RawRules.h>

namespace grammar {

namespace properties {

/**
 * Implements algorithms from Melichar, chapter 3.3
 */
class RecursiveNonterminal {
public:
	/**
	 * Retrieves A \in { B : A->^+ B \alpha, where \alpha \in (NuT)* } for given grammar and nonterminal
	 *
	 * \tparam T the type of the tested grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the tested grammar
	 * \tparam TerminalSymbolType the type of terminals in the tested grammar
	 *
	 * \param grammar the tested grammar
	 * \param nonterminal the tested nonterminal
	 *
	 * \return bool which denote whether the nonterminal is recursive in the grammar
	 */
	template < class T, class NonterminalSymbolType, class TerminalSymbolType = typename grammar::TerminalSymbolTypeOfGrammar < T > >
	static bool isNonterminalRecursive ( const T & grammar, const NonterminalSymbolType & nonterminal );

};

template < class T, class NonterminalSymbolType, class TerminalSymbolType >
bool RecursiveNonterminal::isNonterminalRecursive ( const T & grammar, const NonterminalSymbolType & nonterminal ) {
	if ( grammar.getNonterminalAlphabet ( ).count ( nonterminal ) == 0 )
		throw exception::CommonException ( "Nonterminal symbol \"" + ( std::string ) nonterminal + "\" is not present in grammar." );

	ext::deque < ext::set < NonterminalSymbolType > > Ni;
	Ni.push_back ( ext::set < NonterminalSymbolType > { nonterminal } );
	unsigned i = 1;

	auto rawRules = grammar::RawRules::getRawRules ( grammar );
	auto nullable = grammar::properties::NullableNonterminals::getNullableNonterminals ( grammar );

	while ( i <= grammar.getNonterminalAlphabet ( ).size ( ) ) {
		Ni.push_back ( ext::set < NonterminalSymbolType > { } );

		for ( const NonterminalSymbolType & lhs : Ni.at ( i - 1 ) )
			if ( rawRules.find ( lhs ) != rawRules.end ( ) )
				for ( const ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > & rhs : rawRules.find ( lhs )->second )
					for ( const ext::variant < TerminalSymbolType, NonterminalSymbolType > & rhsSymbol : rhs ) {
						if ( grammar.getTerminalAlphabet ( ).count ( rhsSymbol ) )
							break;

						Ni.at ( i ).insert ( rhsSymbol.template get < NonterminalSymbolType > ( ) );

						if ( ! nullable.count ( rhsSymbol.template get < NonterminalSymbolType > ( ) ) )
							break;
					}

		if ( Ni.at ( i ).count ( nonterminal ) )
			return true;

		i += 1;
	}

	return false;
}

} /* namespace properties */

} /* namespace grammar */

