/*
 * SingleInitialState.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: 20. 9. 2014
 *	  Author: Tomas Pecka
 */

#ifndef _SINGLE_INITIAL_STATE_H_
#define _SINGLE_INITIAL_STATE_H_

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

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

#include <label/InitialStateLabel.h>
#include <common/createUnique.hpp>

namespace automaton {

namespace simplify {

/**
 * Algorithm for the conversion of multi-initial state finite automata to single-initial state finite automata.
 * This algorithm is an implementation of Melichar: Jazyky a překlady 2.46.
 *
 * @sa automaton::simplify::SingleInitialStateEpsilonTransition
 */
class SingleInitialState {
	template < class T >
	struct NFATra {
		using type = automaton::NFA < typename T::SymbolType, typename T::StateType >;
	};

	template < class T >
	struct EpsilonNFATra {
		using type = automaton::EpsilonNFA < typename T::SymbolType, typename T::StateType >;
	};

	template < class T >
	using ConvertedAutomaton = typename ext::casional <
			ext::boolean < isMultiInitialStateNFA < T > >, NFATra < T >,
			ext::boolean < isMultiInitialStateEpsilonNFA < T > >, EpsilonNFATra < T >
		>::type::type;

public:
	/**
	 * Converts multi-initial state automaton to a single-initial state automaton.
	 *
	 * @tparam T type of the converted automaton.
	 *
	 * @param automaton automaton to convert
	 *
	 * @return an automaton equivalent to @p with only one initial state
	 */
	template < class T >
	static ext::require < isMultiInitialStateNFA < T > || isMultiInitialStateEpsilonNFA < T >, SingleInitialState::ConvertedAutomaton < T > > convert ( const T & fsm );

	/**
	 * @overload
	 */
	template < class T >
	static ext::require < isDFA < T > || isNFA < T > || isEpsilonNFA < T > || isExtendedNFA < T > || isCompactNFA < T >, T > convert ( const T & fsm );
};

template < class T >
ext::require < isMultiInitialStateNFA < T > || isMultiInitialStateEpsilonNFA < T >, SingleInitialState::ConvertedAutomaton < T > > SingleInitialState::convert ( const T & fsm ) {
	using StateType = typename T::StateType;

	// step 1, 3
	StateType q0 = common::createUnique ( label::InitialStateLabel::instance < StateType > ( ), fsm.getStates ( ) );

	SingleInitialState::ConvertedAutomaton < T > res ( q0 );
	res.setInputAlphabet ( fsm.getInputAlphabet ( ) );

	for( const StateType & q : fsm.getStates ( ) )
		res.addState ( q );

	// step 2
	for ( const auto & q : fsm.getInitialStates ( ) )
		for(const auto & kv : fsm.getTransitionsFromState ( q ) )
			res.addTransition ( q0, kv.first.second, kv.second );

	for ( const auto & t : fsm.getTransitions ( ) )
		res.addTransition ( t.first.first, t.first.second, t.second );

	res.setFinalStates ( fsm.getFinalStates ( ) );

	// step 4, 5
	if ( ! ext::excludes ( fsm.getFinalStates ( ).begin ( ), fsm.getFinalStates ( ).end ( ), fsm.getInitialStates ( ).begin ( ), fsm.getInitialStates ( ).end ( ) ) )
		res.addFinalState ( q0 );

	return res;
}

template < class T >
ext::require < isDFA < T > || isNFA < T > || isEpsilonNFA < T > || isExtendedNFA < T > || isCompactNFA < T >, T > SingleInitialState::convert ( const T & fsm ) {
	return fsm;
}

} /* namespace simplify */

} /* namespace automaton */

#endif /* _SINGLE_INITIAL_STATE_H_ */
