/*
 * EpsilonClosure.h
 *
 * This file is part of Algorithms library toolkit.
 * Copyright (C) 2017 Jan Travnicek (jan.travnicek@fit.cvut.cz)

 * Algorithms library toolkit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Algorithms library toolkit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Algorithms library toolkit.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  Created on: 23. 3. 2014
 *	  Author: Tomas Pecka
 */

#ifndef EPSILON_CLOSURE_H_
#define EPSILON_CLOSURE_H_

#include <alib/algorithm>
#include <queue>
#include <alib/set>
#include <alib/map>

#include <automaton/FSM/ExtendedNFA.h>
#include <automaton/FSM/CompactNFA.h>
#include <automaton/FSM/EpsilonNFA.h>
#include <automaton/FSM/MultiInitialStateNFA.h>
#include <automaton/FSM/NFA.h>
#include <automaton/FSM/DFA.h>

#include <regexp/properties/RegExpEpsilon.h>

namespace automaton {

namespace properties {

/**
 * Algorithm computing an epsilon closure of a single state in a finite automaton.
 */
class EpsilonClosure {
public:
	/**
	 * Computes epsilon closure for a state of a nondeterministic finite automaton with epsilon transitions.
	 * Implemented using breadth-first search.
	 *
	 * @tparam SymbolType Type for the input symbols.
	 * @tparam StateType Type for the states.
	 * @param fsm nondeterministic finite automaton with epsilon transitions
	 * @param state state for which we want to compute the closure
	 * @return set of states representing the epsilon closures of a @p state of @p fsm
	 * @throws exception::CommonException if state is not in the set of @p fsm states
	 */
	template < class SymbolType, class StateType >
	static ext::set<StateType> epsilonClosure( const automaton::EpsilonNFA < SymbolType, StateType > & fsm, const StateType & q );

	/**
	 * Computes epsilon closure for given states of a nondeterministic finite automaton with multiple initial states.
	 * Epsilon closure of a state q of an automaton without epsilon transitions is eps-closure(q) = {q}.
	 *
	 * @overload
	 *
	 * @tparam T type of tested automaton.
	 * @param fsm (nondeterministic) finite automaton (with multiple initial states)
	 * @param state state for which we want to compute the closure
	 * @return mapping of states to set of states representing the epsilon closures for each state of @p fsm
	 */
	template < class T >
	static ext::require < isDFA < T > || isNFA < T > || isMultiInitialStateNFA < T >, ext::set < typename T::StateType > > epsilonClosure ( const T & fsm, const typename T::StateType & q );

	/**
	 * Computes epsilon closure for a state of an extended nondeterministic finite automaton.
	 * Any regexp that can denote epsilon word is considered as an epsilon transition for the purpose of this algorithm.
	 *
	 * @tparam SymbolType Type for the input symbols.
	 * @tparam StateType Type for the states.
	 * @param fsm extended nondeterministic finite automaton
	 * @param state state for which we want to compute the closure
	 * @return set of states representing the epsilon closures of a @p state of @p fsm
	 * @throws exception::CommonException if state is not in the set of @p fsm states
	 */
	template < class SymbolType, class StateType >
	static ext::set<StateType> epsilonClosure( const automaton::ExtendedNFA < SymbolType, StateType > & fsm, const StateType & q );

	/**
	 * Computes epsilon closure for a state of an compact nondeterministic finite automaton.
	 * Any string of size 0 is considered as epsilon.
	 *
	 * @tparam SymbolType Type for the input symbols.
	 * @tparam StateType Type for the states.
	 * @param fsm compact nondeterministic finite automaton
	 * @param state state for which we want to compute the closure
	 * @return set of states representing the epsilon closures of a @p state of @p fsm
	 * @throws exception::CommonException if state is not in the set of @p fsm states
	 */
	template < class SymbolType, class StateType >
	static ext::set<StateType> epsilonClosure( const automaton::CompactNFA < SymbolType, StateType > & fsm, const StateType & q );
};

template < class SymbolType, class StateType >
ext::set<StateType> EpsilonClosure::epsilonClosure( const automaton::EpsilonNFA < SymbolType, StateType > & fsm, const StateType & q ) {
	if(! fsm.getStates().count(q) ) throw exception::CommonException("State is not in the automaton");

	ext::set<StateType> closure;
	std::queue<StateType> queue;
	ext::map<StateType, bool> visited;

	for( const auto & p : fsm.getStates( ) )
		visited[ p ] = false;

	queue.push( q );
	while( ! queue.empty( ) ) {
		StateType p = queue.front( );
		queue.pop( );
		visited[ p ] = true;
		closure.insert( p );

		auto tos = fsm.getTransitions( ).equal_range(ext::make_pair ( p, common::symbol_or_epsilon < SymbolType > ( ) ) );
		if ( tos.empty ( ) )
			continue;

		for( const auto & transition : tos )
			if( ! visited [ transition.second ] )
				queue.push( transition.second );
	}

	return closure;
}

template < class T >
ext::require < isDFA < T > || isNFA < T > || isMultiInitialStateNFA < T >, ext::set < typename T::StateType > > EpsilonClosure::epsilonClosure ( const T & fsm, const typename T::StateType & q ) {
	if(! fsm.getStates().count(q) ) throw exception::CommonException("State is not in the automaton");

	ext::set < typename T::StateType > closure;
	closure.insert(q);
	return closure;
}

template < class SymbolType, class StateType >
ext::set<StateType> EpsilonClosure::epsilonClosure( const automaton::ExtendedNFA < SymbolType, StateType > & fsm, const StateType & q ) {
	if(! fsm.getStates().count(q) ) throw exception::CommonException("State is not in the automaton");

	ext::set<StateType> closure;
	std::queue<StateType> queue;
	ext::map<StateType, bool> visited;

	for( const auto & p : fsm.getStates( ) )
		visited[ p ] = false;

	queue.push( q );
	while( ! queue.empty( ) ) {
		StateType p = queue.front( );
		queue.pop( );
		visited[ p ] = true;
		closure.insert( p );

		for( const auto & transition : fsm.getTransitionsFromState( p ) )
			if( regexp::properties::RegExpEpsilon::languageContainsEpsilon( transition.first.second ) )
				if( ! visited [ transition.second ] )
					queue.push( transition.second );
	}

	return closure;
}

template < class SymbolType, class StateType >
ext::set<StateType> EpsilonClosure::epsilonClosure( const automaton::CompactNFA < SymbolType, StateType > & fsm, const StateType & q ) {
	if(! fsm.getStates().count(q) ) throw exception::CommonException("State is not in the automaton");

	ext::set<StateType> closure;
	std::queue<StateType> queue;
	ext::map<StateType, bool> visited;

	for( const auto & p : fsm.getStates( ) )
		visited[ p ] = false;

	queue.push( q );
	while( ! queue.empty( ) ) {
		StateType p = queue.front( );
		queue.pop( );
		visited[ p ] = true;
		closure.insert( p );

		for( const auto & transition : fsm.getTransitionsFromState( p ) )
			if( transition.first.second.empty ( ) )
				if( ! visited [ transition.second ] )
					queue.push( transition.second );
	}

	return closure;
}

} /* namespace properties */

} /* namespace automaton */

#endif /* EPSILON_CLOSURE_H_ */
