#ifndef RHDPDA_VPA_COMMON_H_
#define RHDPDA_VPA_COMMON_H_

#include "common/DefaultStateType.h"
#include "common/DefaultSymbolType.h"
#include <alib/set>
#include <alib/map>
#include "automaton/PDA/VisiblyPushdownDPDA.h"
#include "automaton/PDA/RealTimeHeightDeterministicDPDA.h"

namespace automaton {

namespace determinize {

template < class StateType >
ext::set<StateType> retrieveDSubSet(const ext::set<ext::pair<StateType, StateType>>& localOperation) {
	ext::set<StateType> id;
	for(const ext::pair < StateType, StateType > & entry : localOperation) {
		id.insert(entry.second);
	}
	return id;
}

template < class StateType >
ext::set<ext::pair<StateType, StateType>> createIdentity(const ext::set<StateType>& states) {
	ext::set<ext::pair<StateType, StateType>> id;
	for(const StateType& state : states) {
		id.insert(ext::make_pair(state, state));
	}
	return id;
}

template < class InputSymbolType, class StateType, class N >
ext::set < ext::pair < StateType, StateType > > retInitial ( const ext::set < ext::pair < StateType, StateType > > & S, const InputSymbolType & input, const N & nondeterministic ) {
	ext::set<ext::pair<StateType, StateType>> S1;

	for ( const auto & entry : S ) {
		const StateType& q = entry.first;
		const StateType& q2 = entry.second;

		auto returnTransitionEntries = nondeterministic.getReturnTransitions ( ).equal_range ( ext::make_tuple ( q2, input, nondeterministic.getBottomOfTheStackSymbol ( ) ) );
		if ( ! returnTransitionEntries.empty ( ) )
			for ( const auto & transition : returnTransitionEntries ) {
				const StateType q1 = transition.second;

				S1.insert ( ext::make_pair ( q, q1 ) );
			}
	}

	return S1;
}

template < class InputSymbolType, class DeterministicPushdownStoreSymbolType, class StateType, class N >
ext::set < ext::pair < StateType, StateType > > ret ( const ext::set < ext::pair < StateType, StateType > > & S, const DeterministicPushdownStoreSymbolType & pdaSymbol, const InputSymbolType & input, const N & nondeterministic ) {
	ext::set<ext::pair<StateType, StateType>> update;

	for(const auto& transition : nondeterministic.getCallTransitions()) {
		if(pdaSymbol.second != std::get<1>(transition.first)) continue;

		const StateType& q = std::get<0>(transition.first);
		const StateType& q1 = transition.second.first;
		const auto & gamma = transition.second.second;

		for(const auto& entry : S) {
			if(q1 != entry.first) continue;
			const StateType& q2 = entry.second;

			auto returnTransitionEntries = nondeterministic.getReturnTransitions ( ).equal_range ( ext::make_tuple ( q2, input, gamma ) );
			if ( ! returnTransitionEntries.empty ( ) )
				for ( const auto & returnTransition : returnTransitionEntries ) {
					const StateType & qI = returnTransition.second;
					update.insert(ext::make_pair(q, qI));
				}
		}
	}

	const ext::set<ext::pair<StateType, StateType>>& S1 = pdaSymbol.first;

	ext::set<ext::pair<StateType, StateType>> S2;
	for(const auto& entry : S1) {
		const StateType& q = entry.first;
		const StateType& q3 = entry.second;
		for(const auto& entry2 : update) {
			if(q3 != entry2.first) continue;

			const StateType& qI = entry2.second;

			S2.insert(ext::make_pair(q, qI));
		}
	}

	return S2;
}

template < class InputSymbolType, class StateType, class N >
ext::set < ext::pair < StateType, StateType > > call ( const ext::set < ext::pair < StateType, StateType > > & S, const InputSymbolType & input, const N & nondeterministic ) {
	ext::set < StateType > R1;

	for(const StateType& q : retrieveDSubSet ( S ) ) {
		auto callTransitionEntries = nondeterministic.getCallTransitions ( ).equal_range ( ext::make_pair ( q, input ) );
		if ( ! callTransitionEntries.empty ( ) )
			for ( const auto & to : callTransitionEntries ) {
				const StateType& q1 = to.second.first;

				R1.insert(q1);
			}
	}

	return createIdentity ( std::move ( R1 ) );
}

template < class InputSymbolType, class StateType, class N >
ext::set < ext::pair < StateType, StateType > > local ( const ext::set < ext::pair < StateType, StateType > > & S, const InputSymbolType & input, const N & nondeterministic ) {
	ext::set<ext::pair<StateType, StateType>> S1;

	for ( const auto & entry : S ) {
		const StateType & q = entry.first;
		const StateType & q2 = entry.second;

		auto localTransitionEntries = nondeterministic.getLocalTransitions ( ).equal_range ( ext::make_pair ( q2, input ) );
		if ( ! localTransitionEntries.empty ( ) ) {
			for ( const auto & transition : localTransitionEntries ) {
				const StateType & q1 = transition.second;
				S1.insert(ext::make_pair(q, q1));
			}
		}
	}

	return S1;
}

template<class T, class R>
ext::set < typename T::StateType_t > existsDirtyState(const T& d, const R& n) {
	ext::set < typename T::StateType_t > dirtyStates;

	for(const typename T::StateType_t & state : d.getStates ( ) ) {
		auto dSubSet = retrieveDSubSet(state);

		bool originalCallsLocals = false;
		for(const auto& transition : n.getCallTransitions()) {
			if(dSubSet.count(transition.first.first)) {
				originalCallsLocals = true;
				break;
			}
		}
		if(!originalCallsLocals) for(const auto& transition : n.getLocalTransitions()) {
			if(dSubSet.count(transition.first.first)) {
				originalCallsLocals = true;
				break;
			}
		}

		if(!originalCallsLocals) continue;
		bool deterministicCallLocals = false;

		for(const auto& transition : d.getCallTransitions ( ) ) {
			if(state == transition.first.first) {
				deterministicCallLocals = true;
				break;
			}
		}
		if(deterministicCallLocals) for(const auto& transition : d.getLocalTransitions ( ) ) {
			if(state == transition.first.first) {
				deterministicCallLocals = true;
				break;
			}
		}

		if(originalCallsLocals && !deterministicCallLocals) dirtyStates.insert(state);

	}
	return dirtyStates;
}

template < class T, class DeterministicStateType >
ext::set < DeterministicStateType > localClosure ( const DeterministicStateType & initState, const T & d ) {
	ext::set < DeterministicStateType > closed { initState };
	ext::deque < DeterministicStateType > queue { initState };

	while ( ! queue.empty ( ) ) {
		DeterministicStateType state = std::move ( queue.back ( ) );
		queue.pop_back ( );

		for ( const auto & transition : d.getLocalTransitions ( ) ) {
			if ( transition.second != state )
				continue;

			if ( closed.insert ( transition.first.first ).second )
				queue.push_back ( transition.first.first );
		}

		for ( const auto & transition : d.getReturnTransitions ( ) ) {
			if ( transition.second != state )
				continue;

			const auto & popSymbol = std::get < 2 > ( transition.first );
			if ( popSymbol == d.getBottomOfTheStackSymbol ( ) )
				continue;

			const DeterministicStateType & statePart = popSymbol.first;
			if ( closed.insert ( statePart ).second )
				queue.push_back ( statePart );
		}
	}

	return closed;
}

template < class T, class R >
ext::set < ext::pair < typename T::StateType_t, typename T::PushdownStoreSymbolType_t > > existsDirtyStateSymbol ( const T & d, const R & n ) {
	ext::set < ext::pair < typename T::StateType_t, typename T::PushdownStoreSymbolType_t > > dirtyStateSymbols;

	for ( const typename T::StateType_t & state : d.getStates ( ) ) {
		bool originalPops = false;
		auto dSubSet = retrieveDSubSet ( state );

		for ( const auto & transition : n.getReturnTransitions ( ) ) {
			if ( dSubSet.count ( std::get < 0 > ( transition.first ) ) ) {
				originalPops = true;
				break;
			}
		}
		if ( ! originalPops )
			continue;

		ext::set < typename T::PushdownStoreSymbolType_t > topSymbols;
		for ( const typename T::StateType_t & localState : localClosure ( state, d ) ) {
			for ( const auto & transition : d.getCallTransitions ( ) ) {
				const auto & to = transition.second;
				if ( localState != to.first )
					continue;

				topSymbols.insert ( to.second );
			}

			if ( d.getInitialState ( ) == localState )
				topSymbols.insert ( d.getBottomOfTheStackSymbol ( ) );
		}

		for ( const auto & transition : d.getReturnTransitions ( ) ) {
			if ( state != std::get < 0 > ( transition.first ) )
				continue;

			topSymbols.erase(std::get<2>(transition.first));
		}

		for ( const typename T::PushdownStoreSymbolType_t & topSymbol : topSymbols )
			dirtyStateSymbols.insert ( ext::make_pair ( state, topSymbol ) );
	}

	return dirtyStateSymbols;
}


} /* namespace determinize */

} /* namespace automaton */

#endif /* RHDPDA_VPA_COMMON_H_ */
