/*
 * NonterminalUnitRuleCycle.h
 *
 *  Created on: 22. 3. 2014
 *	  Author: Tomas Pecka
 */

#pragma once

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

#include <exception/CommonException.h>

#include <grammar/RawRules.h>

namespace grammar {

namespace properties {

/**
 * Implements algorithms from Melichar, chapter 2.2
 */
class NonterminalUnitRuleCycle {
public:
	/**
	 * Retrieves set N = {B : A->^* B} for given @p grammar and @p nonterminal
	 *
	 * Source: Melichar, algorithm 2.6, step 1
	 *
	 * \tparam T the type of tested grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the tested grammar
	 *
	 * \param grammar grammar
	 * \param nonterminal nonterminal
	 * \return set of nonterminals that can be derived from the given nonterminal in finite number of steps
	 */
	template < class T, class NonterminalSymbolType >
	static ext::set < NonterminalSymbolType > getNonterminalUnitRuleCycle ( const T& grammar, const NonterminalSymbolType& nonterminal);
};

template<class T, class NonterminalSymbolType >
ext::set<NonterminalSymbolType> NonterminalUnitRuleCycle::getNonterminalUnitRuleCycle(const T& grammar, const NonterminalSymbolType& nonterminal) {
	if(grammar.getNonterminalAlphabet().count(nonterminal) == 0) {
		throw exception::CommonException("Nonterminal symbol \"" + ext::to_string ( nonterminal ) + "\" is not present in grammar.");
	}

	auto rawRules = grammar::RawRules::getRawRules ( grammar );

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

	int i = 0;

	do {
		i += 1;

		Ni.push_back(Ni.at(i-1));
		for ( const auto & rule : rawRules ) {
			const NonterminalSymbolType& lhs = rule.first;

			for(const auto& rhs : rule.second) {
				if(Ni.at(i-1).count(lhs) && rhs.size() == 1 && rhs.front ( ).template is < NonterminalSymbolType > ( ) && grammar.getNonterminalAlphabet().count(rhs.front().template get < NonterminalSymbolType > ( ) )) {
					Ni.at(i).insert(rhs.front().template get < NonterminalSymbolType > ( ) );
				}
			}
		}

	} while ( Ni.at ( i ) != Ni.at ( i - 1 ) );

	return Ni.at(i);
}

} /* namespace properties */

} /* namespace grammar */

