/*
 * NFADeterminizer.cpp
 *
 *  Created on: 16. 1. 2014
 *	  Author: Jan Travnicek
 */

#include "common/RHDPDACommon.h"

#include <automaton/PDA/RealTimeHeightDeterministicNPDA.h>
#include <alib/set>
#include <global/GlobalData.h>

namespace automaton {

namespace determinize {

template < class InputSymbolType, class PushdownStoreSymbolType, class StateType >
ext::set<common::symbol_or_epsilon < InputSymbolType >> getRetPartitioning(const automaton::RealTimeHeightDeterministicNPDA < InputSymbolType, PushdownStoreSymbolType, StateType > & npda, const ext::set < ext::pair < StateType, StateType > > & state) {
	ext::set<common::symbol_or_epsilon < InputSymbolType >> ret;

	const ext::set<StateType> dSubSet = retrieveDSubSet(state);

	for(const auto& transition : npda.getReturnTransitions()) {
		if(dSubSet.count(std::get<0>(transition.first)))
			ret.insert(std::get<1>(transition.first));
	}

	return ret;
}

template < class InputSymbolType, class PushdownStoreSymbolType, class StateType >
ext::set<common::symbol_or_epsilon < InputSymbolType >> getCallPartitioning(const automaton::RealTimeHeightDeterministicNPDA < InputSymbolType, PushdownStoreSymbolType, StateType > & npda, const ext::set < ext::pair < StateType, StateType > > & state) {
	ext::set<common::symbol_or_epsilon < InputSymbolType >> call;

	const ext::set<StateType> dSubSet = retrieveDSubSet(state);

	for(const auto& transition : npda.getCallTransitions()) {
		if(dSubSet.count(transition.first.first))
			call.insert(transition.first.second);
	}

	return call;
}

template < class InputSymbolType, class PushdownStoreSymbolType, class StateType >
ext::set<common::symbol_or_epsilon < InputSymbolType >> getLocalPartitioning(const automaton::RealTimeHeightDeterministicNPDA < InputSymbolType, PushdownStoreSymbolType, StateType > & npda, const ext::set < ext::pair < StateType, StateType > > & state) {
	ext::set<common::symbol_or_epsilon < InputSymbolType >> local;

	const ext::set<StateType> dSubSet = retrieveDSubSet(state);

	for(const auto& transition : npda.getLocalTransitions()) {
		if(dSubSet.count(transition.first.first))
			local.insert(transition.first.second);
	}

	return local;
}

template < class InputSymbolType, class PushdownStoreSymbolType, class StateType >
automaton::RealTimeHeightDeterministicDPDA < InputSymbolType, ext::pair < ext::set < ext::pair < StateType, StateType > >, common::symbol_or_epsilon < InputSymbolType > >, ext::set < ext::pair < StateType, StateType > > > Determinize::determinize ( const automaton::RealTimeHeightDeterministicNPDA < InputSymbolType, PushdownStoreSymbolType, StateType > & npda ) {
	using DeterministicStateType = ext::set < ext::pair < StateType, StateType > >;
	using DeterministicPushdownStoreSymbolType = ext::pair < DeterministicStateType, common::symbol_or_epsilon < InputSymbolType > >;

	DeterministicStateType initialLabel = createIdentity(npda.getInitialStates());
	DeterministicPushdownStoreSymbolType bottom = ext::make_pair ( DeterministicStateType { }, common::symbol_or_epsilon < InputSymbolType > ( alphabet::BottomOfTheStackSymbol::instance < InputSymbolType > ( ) ) );

	automaton::RealTimeHeightDeterministicDPDA < InputSymbolType, DeterministicPushdownStoreSymbolType, DeterministicStateType > dpda ( initialLabel, bottom );
	dpda.setInputAlphabet ( npda.getInputAlphabet ( ) );

	ext::set < ext::pair < DeterministicStateType, DeterministicPushdownStoreSymbolType > > rubbishReturnTransitions;

	for(;;) {
		ext::set<ext::pair<DeterministicStateType, DeterministicPushdownStoreSymbolType>> stateSymbols = existsDirtyStateSymbol(dpda, npda);

		for ( const ext::pair < DeterministicStateType, DeterministicPushdownStoreSymbolType > & rubbishStateSymbols : rubbishReturnTransitions ) {
			stateSymbols.erase ( rubbishStateSymbols );
		}

		ext::set<DeterministicStateType> states = existsDirtyState(dpda, npda);

		if(stateSymbols.empty() && states.empty()) break;

		for ( const ext::pair<DeterministicStateType, DeterministicPushdownStoreSymbolType> & stateSymbol : stateSymbols ) {
			if ( common::GlobalData::verbose )
				common::Streams::log << "Dirty state symbol: " << stateSymbol << std::endl;

			ext::set<common::symbol_or_epsilon < InputSymbolType >> retPart = getRetPartitioning(npda, stateSymbol.first);

			const DeterministicStateType & state = stateSymbol.first;
			const DeterministicPushdownStoreSymbolType & pdaSymbol = stateSymbol.second;

			for(const common::symbol_or_epsilon < InputSymbolType > & input : retPart ) {
				DeterministicStateType to;

				if ( pdaSymbol == dpda.getBottomOfTheStackSymbol ( ) )
					to = retInitial ( state, input, npda );
				else
					to = ret ( state, pdaSymbol, input, npda );

				if ( ! to.empty ( ) ) {
					dpda.addState ( to );
					dpda.addReturnTransition ( state, input, pdaSymbol, std::move ( to ) );
				} else
					rubbishReturnTransitions.insert ( ext::make_pair ( state, pdaSymbol ) );
			}
		}

		for(const auto& state : states) {
			if ( common::GlobalData::verbose )
				common::Streams::log << "Dirty state: " << state << std::endl;

			ext::set<common::symbol_or_epsilon < InputSymbolType >> localPart = getLocalPartitioning(npda, state);
			ext::set<common::symbol_or_epsilon < InputSymbolType >> callPart = getCallPartitioning(npda, state);

			for ( const common::symbol_or_epsilon < InputSymbolType > & input : localPart ) {
				DeterministicStateType to = local ( state, input, npda );
				if ( ! to.empty ( ) ) {
					dpda.addState ( to );
					dpda.addLocalTransition ( state, input, std::move ( to ) );
				}
			}
			for ( const common::symbol_or_epsilon < InputSymbolType > & input : callPart ) {
				DeterministicStateType to = call ( state, input, npda );
				if ( ! to.empty ( ) ) {
					ext::pair < DeterministicStateType, common::symbol_or_epsilon < InputSymbolType > > pdaSymbol = ext::make_pair ( state, input );

					dpda.addState ( to );
					dpda.addPushdownStoreSymbol ( pdaSymbol );
					dpda.addCallTransition ( state, input, std::move ( to ), std::move ( pdaSymbol ) );
				}
			}
		}
	}

	const ext::set<StateType>& finalLabels = npda.getFinalStates();
	for(const DeterministicStateType & state : dpda.getStates()) {
		const ext::set<StateType> labels = retrieveDSubSet(state);

		if(!ext::excludes(finalLabels.begin(), finalLabels.end(), labels.begin(), labels.end())) {
			dpda.addFinalState(state);
		}
	}

	if ( common::GlobalData::verbose ) {
		common::Streams::log << "Rubbish return transitions: " << rubbishReturnTransitions << std::endl;
	}

	return dpda;
}

} /* namespace determinize */

} /* namespace automaton */
