/*
 * RHPDAToPDA.cpp
 *
 *  Created on: 23. 3. 2014
 *	  Author: Jan Travnicek
 */

#include "RHPDAToPDA.h"

#include <exception/CommonException.h>
#include <automaton/PDA/RealTimeHeightDeterministicDPDA.h>
#include <automaton/PDA/RealTimeHeightDeterministicNPDA.h>
#include <automaton/PDA/DPDA.h>
#include <automaton/PDA/NPDA.h>

#include <alphabet/BottomOfTheStackSymbol.h>

#include <alib/set>
#include <alib/map>
#include <alib/iostream>

#include <registration/CastRegistration.hpp>
#include <registration/AlgoRegistration.hpp>

namespace automaton {

namespace transform {

template < class T >
void constructTransitions ( const ext::tuple < DefaultStateType, DefaultSymbolType, ext::vector < DefaultSymbolType > > & stFirst, const ext::map < DefaultStateType, ext::set < ext::tuple < ext::vector < DefaultSymbolType >, DefaultStateType, ext::vector < DefaultSymbolType > > > > & epsilonTransitions, const DefaultStateType & toState, ext::vector < DefaultSymbolType > pops, ext::vector < DefaultSymbolType > pushes, T & res ) {
	auto epsIter = epsilonTransitions.find ( toState );

	if ( ( epsIter != epsilonTransitions.end ( ) ) && !epsIter->second.empty ( ) ) {
		if ( epsIter->second.size ( ) == 1 ) {
			const auto & epsilonT = * epsIter->second.begin ( );

			pops.insert ( pops.end ( ), std::get < 0 > ( epsilonT ).begin ( ), std::get < 0 > ( epsilonT ).end ( ) );
			pushes.insert ( pushes.begin ( ), std::get < 2 > ( epsilonT ).rbegin ( ), std::get < 2 > ( epsilonT ).rend ( ) );

			constructTransitions ( stFirst, epsilonTransitions, std::get < 1 > ( epsilonT ), std::move ( pops ), std::move ( pushes ), res );
		} else {
			for ( const auto & epsilonT : epsIter->second ) {
				ext::vector < DefaultSymbolType > popsCopy = pops;
				ext::vector < DefaultSymbolType > pushesCopy = pushes;

				popsCopy.insert ( popsCopy.end ( ), std::get < 0 > ( epsilonT ).begin ( ), std::get < 0 > ( epsilonT ).end ( ) );
				pushesCopy.insert ( pushesCopy.begin ( ), std::get < 2 > ( epsilonT ).rbegin ( ), std::get < 2 > ( epsilonT ).rend ( ) );

				constructTransitions ( stFirst, epsilonTransitions, std::get < 1 > ( epsilonT ), std::move ( popsCopy ), std::move ( pushesCopy ), res );
			}
		}
	} else {
		res.addState ( std::get < 0 > ( stFirst ) );
		res.addState ( toState );

		res.addTransition ( std::get < 0 > ( stFirst ), std::get < 1 > ( stFirst ), std::move ( pops ), toState, std::move ( pushes ) );
	}
}

automaton::DPDA < > automaton::transform::RHPDAToPDA::convert ( const automaton::RealTimeHeightDeterministicDPDA < > & pda ) {
	ext::map < ext::tuple < DefaultStateType, DefaultSymbolType, ext::vector < DefaultSymbolType > >, ext::set < std::pair < DefaultStateType, ext::vector < DefaultSymbolType > > > > readingTransitions;
	ext::map < DefaultStateType, ext::set < ext::tuple < ext::vector < DefaultSymbolType >, DefaultStateType, ext::vector < DefaultSymbolType > > > > epsilonTransitions;

	for ( const auto & transition : pda.getCallTransitions ( ) ) {
		if ( std::get < 1 > ( transition.first ).is_epsilon ( ) ) {
			auto & epsT = epsilonTransitions[std::get < 0 > ( transition.first )];
			const auto & to = transition.second;
			epsT.insert ( ext::make_tuple ( ext::vector < DefaultSymbolType > { }, to.first, ext::vector < DefaultSymbolType > { to.second } ) );
		} else {
			auto & readT = readingTransitions[ext::make_tuple ( std::get < 0 > ( transition.first ), std::get < 1 > ( transition.first ).getSymbol ( ), ext::vector < DefaultSymbolType > { } )];
			const auto & to = transition.second;
			readT.insert ( std::make_pair ( to.first, ext::vector < DefaultSymbolType > { to.second } ) );
		}
	}

	for ( const auto & transition : pda.getLocalTransitions ( ) ) {
		if ( std::get < 1 > ( transition.first ).is_epsilon ( ) ) {
			auto & epsT = epsilonTransitions[std::get < 0 > ( transition.first )];
			const auto & to = transition.second;
			epsT.insert ( ext::make_tuple ( ext::vector < DefaultSymbolType > { }, to, ext::vector < DefaultSymbolType > { } ) );
		} else {
			auto & readT = readingTransitions[ext::make_tuple ( std::get < 0 > ( transition.first ), std::get < 1 > ( transition.first ).getSymbol ( ), ext::vector < DefaultSymbolType > { } )];
			const auto & to = transition.second;
			readT.insert ( std::make_pair ( to, ext::vector < DefaultSymbolType > { } ) );
		}
	}

	for ( const auto & transition : pda.getReturnTransitions ( ) ) {
		if ( std::get < 2 > ( transition.first ) == alphabet::BottomOfTheStackSymbol::instance < DefaultSymbolType > ( ) ) continue;

		if ( std::get < 1 > ( transition.first ).is_epsilon ( ) ) {
			auto & epsT = epsilonTransitions[std::get < 0 > ( transition.first )];
			const auto & to = transition.second;
			epsT.insert ( ext::make_tuple ( ext::vector < DefaultSymbolType > { std::get < 2 > ( transition.first ) }, to, ext::vector < DefaultSymbolType > { } ) );
		} else {
			auto & readT = readingTransitions[ext::make_tuple ( std::get < 0 > ( transition.first ), std::get < 1 > ( transition.first ).getSymbol ( ), ext::vector < DefaultSymbolType > { std::get < 2 > ( transition.first ) } )];
			const auto & to = transition.second;
			readT.insert ( std::make_pair ( to, ext::vector < DefaultSymbolType > { } ) );
		}
	}

	for ( const auto & st : epsilonTransitions ) {
		bool allPops = true;

		for ( const auto & elems : st.second )
			allPops &= std::get < 2 > ( elems ).empty ( ); // if some pushes it will clear allPops variable

		if ( st.second.size ( ) == 1 ) continue;

		if ( ( st.second.size ( ) > 1 ) && allPops ) continue;

		throw exception::CommonException ( "Temporary states has more than one leaving transition" );
	}

	if ( epsilonTransitions[pda.getInitialState ( )].empty ( ) )
		throw exception::CommonException ( "Cannot determine initial pushdown store symbol" );

	// -------------------------------------------------------------------- initial state and initial stack symbol

	const auto & st = * epsilonTransitions[pda.getInitialState ( )].begin ( );

	ext::vector < DefaultSymbolType > pops ( std::get < 0 > ( st ).begin ( ), std::get < 0 > ( st ).end ( ) );
	ext::vector < DefaultSymbolType > pushes ( std::get < 2 > ( st ).rbegin ( ), std::get < 2 > ( st ).rend ( ) );

	DefaultStateType toState = std::get < 1 > ( st );

	while ( !epsilonTransitions[toState].empty ( ) ) {
		const auto & epsilonT = * epsilonTransitions[toState].begin ( );

		pops.insert ( pops.end ( ), std::get < 0 > ( epsilonT ).begin ( ), std::get < 0 > ( epsilonT ).end ( ) );
		pushes.insert ( pushes.begin ( ), std::get < 2 > ( epsilonT ).rbegin ( ), std::get < 2 > ( epsilonT ).rend ( ) );

		toState = std::get < 1 > ( epsilonT );
	}

	if ( ( !pops.empty ( ) ) && ( pushes.size ( ) != 1 ) )
		throw exception::CommonException ( "Cannot convert" );

	// -------------------------------------------------------------------- initial state and initial stack symbol

	automaton::DPDA < > res ( toState, pushes[0] );

	res.setInputAlphabet ( pda.getInputAlphabet ( ) );
	res.setPushdownStoreAlphabet ( pda.getPushdownStoreAlphabet ( ) );
	res.removePushdownStoreSymbol ( pda.getBottomOfTheStackSymbol ( ) );

	for ( const auto & transition : readingTransitions )
		for ( const auto & to : transition.second ) {
			pops = ext::vector < DefaultSymbolType > ( std::get < 2 > ( transition.first ).begin ( ), std::get < 2 > ( transition.first ).end ( ) );
			pushes = ext::vector < DefaultSymbolType > ( to.second.rbegin ( ), to.second.rend ( ) );

			constructTransitions ( transition.first, epsilonTransitions, to.first, std::move ( pops ), std::move ( pushes ), res );
		}

	res.setFinalStates ( pda.getFinalStates ( ) );

	return res;
}

 // This may not work correctly -- generation of initial state and initial symbol
automaton::NPDA < > automaton::transform::RHPDAToPDA::convert ( const automaton::RealTimeHeightDeterministicNPDA < > & pda ) {
	ext::map < ext::tuple < DefaultStateType, DefaultSymbolType, ext::vector < DefaultSymbolType > >, ext::set < std::pair < DefaultStateType, ext::vector < DefaultSymbolType > > > > readingTransitions;
	ext::map < DefaultStateType, ext::set < ext::tuple < ext::vector < DefaultSymbolType >, DefaultStateType, ext::vector < DefaultSymbolType > > > > epsilonTransitions;

	for ( const auto & transition : pda.getCallTransitions ( ) ) {
		if ( std::get < 1 > ( transition.first ).is_epsilon ( ) ) {
			auto & epsT = epsilonTransitions[std::get < 0 > ( transition.first )];

			epsT.insert ( ext::make_tuple ( ext::vector < DefaultSymbolType > { }, transition.second.first, ext::vector < DefaultSymbolType > { transition.second.second } ) );
		} else {
			auto & readT = readingTransitions[ext::make_tuple ( std::get < 0 > ( transition.first ), std::get < 1 > ( transition.first ).getSymbol ( ), ext::vector < DefaultSymbolType > { } )];

			readT.insert ( std::make_pair ( transition.second.first, ext::vector < DefaultSymbolType > { transition.second.second } ) );
		}
	}

	for ( const auto & transition : pda.getLocalTransitions ( ) ) {
		if ( std::get < 1 > ( transition.first ).is_epsilon ( ) ) {
			auto & epsT = epsilonTransitions[std::get < 0 > ( transition.first )];

			epsT.insert ( ext::make_tuple ( ext::vector < DefaultSymbolType > { }, transition.second, ext::vector < DefaultSymbolType > { } ) );
		} else {
			auto & readT = readingTransitions[ext::make_tuple ( std::get < 0 > ( transition.first ), std::get < 1 > ( transition.first ).getSymbol ( ), ext::vector < DefaultSymbolType > { } )];

			readT.insert ( std::make_pair ( transition.second, ext::vector < DefaultSymbolType > { } ) );
		}
	}

	for ( const auto & transition : pda.getReturnTransitions ( ) ) {
		if ( std::get < 2 > ( transition.first ) == alphabet::BottomOfTheStackSymbol::instance < DefaultSymbolType > ( ) ) continue;

		if ( std::get < 1 > ( transition.first ).is_epsilon ( ) ) {
			auto & epsT = epsilonTransitions[std::get < 0 > ( transition.first )];

			epsT.insert ( ext::make_tuple ( ext::vector < DefaultSymbolType > { std::get < 2 > ( transition.first ) }, transition.second, ext::vector < DefaultSymbolType > { } ) );
		} else {
			auto & readT = readingTransitions[ext::make_tuple ( std::get < 0 > ( transition.first ), std::get < 1 > ( transition.first ).getSymbol ( ), ext::vector < DefaultSymbolType > { std::get < 2 > ( transition.first ) } )];

			readT.insert ( std::make_pair ( transition.second, ext::vector < DefaultSymbolType > { } ) );
		}
	}

	for ( const auto & st : epsilonTransitions ) {
		bool allPops = true;

		for ( const auto & elems : st.second )
			allPops &= std::get < 2 > ( elems ).empty ( ); // if some pushes it will clear allPops variable

		if ( ( st.second.size ( ) != 1 ) && !allPops ) throw exception::CommonException ( "Temporary states has more than one leaving transition" );
	}

	if ( pda.getInitialStates ( ).size ( ) != 1 )
		throw exception::CommonException ( "Cannot convert" );

	const DefaultStateType & initialState = * pda.getInitialStates ( ).begin ( );

	if ( epsilonTransitions[initialState].empty ( ) )
		throw exception::CommonException ( "Cannot convert" );

	// -------------------------------------------------------------------- initial state and initial stack symbol

	const auto & st = * epsilonTransitions[initialState].begin ( );

	ext::vector < DefaultSymbolType > pops ( std::get < 0 > ( st ).begin ( ), std::get < 0 > ( st ).end ( ) );
	ext::vector < DefaultSymbolType > pushes ( std::get < 2 > ( st ).rbegin ( ), std::get < 2 > ( st ).rend ( ) );

	DefaultStateType toState = std::get < 1 > ( st );

	while ( !epsilonTransitions[toState].empty ( ) ) {
		const auto & epsilonT = * epsilonTransitions[toState].begin ( );

		pops.insert ( pops.end ( ), std::get < 0 > ( epsilonT ).begin ( ), std::get < 0 > ( epsilonT ).end ( ) );
		pushes.insert ( pushes.begin ( ), std::get < 2 > ( epsilonT ).rbegin ( ), std::get < 2 > ( epsilonT ).rend ( ) );

		toState = std::get < 1 > ( epsilonT );
	}

	if ( ( !pops.empty ( ) ) && ( pushes.size ( ) != 1 ) )
		throw exception::CommonException ( "Cannot convert" );

	// -------------------------------------------------------------------- initial state and initial stack symbol

	automaton::NPDA < > res ( toState, pushes[0] );

	res.setInputAlphabet ( pda.getInputAlphabet ( ) );
	res.setPushdownStoreAlphabet ( pda.getPushdownStoreAlphabet ( ) );
	res.removePushdownStoreSymbol ( pda.getBottomOfTheStackSymbol ( ) );

	for ( const auto & transition : readingTransitions )
		for ( const auto & to : transition.second ) {
			pops = ext::vector < DefaultSymbolType > ( std::get < 2 > ( transition.first ).begin ( ), std::get < 2 > ( transition.first ).end ( ) );
			pushes = ext::vector < DefaultSymbolType > ( to.second.rbegin ( ), to.second.rend ( ) );

			constructTransitions ( transition.first, epsilonTransitions, to.first, std::move ( pops ), std::move ( pushes ), res );
		}

	res.setFinalStates ( pda.getFinalStates ( ) );

	return res;
}

} /* namespace transform */

} /* namespace automaton */

namespace {

auto RHPDAToPDARealTimeHeightDeterministicDPDA = registration::AbstractRegister < automaton::transform::RHPDAToPDA, automaton::DPDA < >, const automaton::RealTimeHeightDeterministicDPDA < > & > ( automaton::transform::RHPDAToPDA::convert );

auto RHPDAToPDARealTimeHeightDeterministicNPDA = registration::AbstractRegister < automaton::transform::RHPDAToPDA, automaton::NPDA < >, const automaton::RealTimeHeightDeterministicNPDA < > & > ( automaton::transform::RHPDAToPDA::convert );

auto DPDAFromRealTimeHeightDeterministicNPDA = registration::CastRegister < automaton::DPDA < >, automaton::RealTimeHeightDeterministicDPDA < > > ( automaton::transform::RHPDAToPDA::convert );
auto NPDAFromRealTimeHeightDeterministicNPDA = registration::CastRegister < automaton::NPDA < >, automaton::RealTimeHeightDeterministicNPDA < > > ( automaton::transform::RHPDAToPDA::convert );

} /* namespace */
