/*
 * NFTADeterminizer.hxx
 *
 *  Created on: 30. 3. 2015
 *	  Author: Stepan Plachy
 */

#include <automaton/TA/NFTA.h>
#include <alib/deque>
#include <alib/algorithm>

namespace automaton {

namespace determinize {

template < class SymbolType, class StateType >
void constructTransitions ( const std::pair < const ext::pair < common::ranked_symbol < SymbolType > , ext::vector < StateType > >, StateType > & transition, unsigned i, const ext::multimap < StateType, ext::set < StateType > > & nftaStateToDftaStates, ext::vector < ext::set < StateType > > & transitionLHS, ext::map < ext::pair < common::ranked_symbol < SymbolType >, ext::vector < ext::set < StateType > > >, ext::set < StateType > > & resultTransition ) {
	if ( i == transition.first.second.size ( ) ) {
		resultTransition [ ext::make_pair ( transition.first.first, transitionLHS ) ].insert ( transition.second );
	} else {
		const StateType & nftaState = transition.first.second [ i ];

		for ( const std::pair < const StateType, ext::set < StateType > > & dftaState : nftaStateToDftaStates.equal_range ( nftaState ) ) {
			transitionLHS.push_back ( dftaState.second );
			constructTransitions ( transition, i + 1, nftaStateToDftaStates, transitionLHS, resultTransition );
			transitionLHS.pop_back ( );
		}
	}
}

template < class SymbolType, class StateType >
automaton::DFTA < SymbolType, ext::set < StateType > > Determinize::determinize ( const automaton::NFTA < SymbolType, StateType > & nfta ) {
	automaton::DFTA < SymbolType, ext::set < StateType > > res;

	res.setInputAlphabet ( nfta.getInputAlphabet ( ) );

	ext::multimap < StateType, ext::set < StateType > > nftaStateToDftaStates; // each dfta state must be inserted only once to each nfta d-subset state

	for ( const auto & symbol : nfta.getInputAlphabet ( ) ) {
		if ( symbol.getRank ( ) != 0 )
			continue;

		ext::set < StateType > dftaState;
		ext::vector < StateType > source { };
		for ( const auto & transition : nfta.getTransitions ( ).equal_range ( ext::tie ( symbol, source ) ) )
			dftaState.insert ( transition.second );

		for ( const auto & state : dftaState )
			nftaStateToDftaStates.insert ( state, dftaState );

		res.addState ( dftaState );

		res.addTransition ( symbol, ext::vector < ext::set < StateType > > { }, dftaState );
	}

	bool added = true;
	while ( added ) {
		added = false;

		ext::map < ext::pair < common::ranked_symbol < SymbolType >, ext::vector < ext::set < StateType > > >, ext::set < StateType > > transitions;

		for ( const auto & transition : nfta.getTransitions ( ) ) {
			ext::vector < ext::set < StateType > > transitionLHS;
			constructTransitions ( transition, 0, nftaStateToDftaStates, transitionLHS, transitions );
		}

		for ( const std::pair < const ext::pair < common::ranked_symbol < SymbolType >, ext::vector < ext::set < StateType > > >, ext::set < StateType > > & transition : transitions ) {
			if ( res.addState ( transition.second ) ) {
				added = true;

				for ( const auto & state : transition.second )
					nftaStateToDftaStates.insert ( state, transition.second );
			}
			res.addTransition ( transition.first.first, transition.first.second, transition.second );
		}

	}

	for ( const auto & dftaState : res.getStates ( ) ) {
		for ( const auto & state : dftaState ) {
			if ( nfta.getFinalStates ( ).count ( state ) ) {
				res.addFinalState ( dftaState );
				break;
			}
		}
	}

	return res;
}

} /* namespace determinize */

} /* namespace automaton */
