/*
 * Compaction.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: 2. 11. 2014
 *	  Author: Tomas Pecka
 */

#ifndef COMPACTION_H_
#define COMPACTION_H_

#include <stack>
#include <alib/tuple>

#include <automaton/FSM/CompactDFA.h>
#include <automaton/FSM/CompactNFA.h>
#include <automaton/FSM/NFA.h>
#include <automaton/FSM/DFA.h>

namespace automaton {

namespace transform {

/**
 * Transformation of a finite automaton to a compact finite automaton.
 * The compact automaton allows to use strings in the transition function. The transitions are compacted.
 */
class Compaction {
	template < class T >
	using CompactAutomaton = typename std::conditional < isDFA < T >, automaton::CompactDFA < typename T::SymbolType, typename T::StateType >, automaton::CompactNFA < typename T::SymbolType, typename T::StateType > >::type;

public:
	/**
	 * Compaction of a finite automaton.
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 * @param automaton automaton to compact
	 * @return compact nondeterministic FA equivalent to @p automaton
	 */
	template < class T >
	static ext::require < isDFA < T > || isNFA < T >, Compaction::CompactAutomaton < T > > convert ( const T & automaton);

	/**
	 * @overload
	 */
	template < class T >
	static ext::require < isCompactDFA < T > || isCompactNFA < T >, T > convert ( const T & automaton );
};

template < class T >
ext::require < isCompactDFA < T > || isCompactNFA < T >, T > Compaction::convert ( const T & automaton ) {
	return automaton;
}

template < class T >
ext::require < isDFA < T > || isNFA < T >, Compaction::CompactAutomaton < T > > Compaction::convert ( const T & automaton) {
	using SymbolType = typename T::SymbolType;
	using StateType = typename T::StateType;

	CompactAutomaton < T > res(automaton.getInitialState());
	res.setInputAlphabet(automaton.getInputAlphabet());

	ext::set < ext::tuple < StateType, StateType, SymbolType > > visited;

	std::stack<ext::tuple<StateType, StateType, SymbolType>> stack; // state x lastFork x symbol we used to go to that state
	for(const auto& transition: automaton.getTransitionsFromState(automaton.getInitialState()))
		stack.push(ext::make_tuple(transition.second, automaton.getInitialState(), transition.first.second));

	if(automaton.getFinalStates().count(automaton.getInitialState()))
		res.addFinalState(automaton.getInitialState());

	while(!stack.empty()) {
		StateType q = std::move(std::get<0>(stack.top()));
		StateType lastFork = std::move(std::get<1>(stack.top()));
		SymbolType symbol = std::move(std::get<2>(stack.top()));
		stack.pop();

		ext::vector < SymbolType > path { std::move ( symbol ) };

		decltype ( automaton.getTransitionsFromState ( q ) ) transitions;
		// only 1 child and nonfinal
		while((transitions = automaton.getTransitionsFromState(q)).size() == 1 && automaton.getFinalStates().count(q) == 0) {
			const std::pair<ext::pair<StateType, SymbolType>, StateType>& transition = * transitions.begin();
			path.push_back(transition.first.second);
			q = transition.second;
		}

		for(const std::pair<const ext::pair<StateType, SymbolType>, StateType>& transition : transitions )
			if(visited.insert(ext::make_tuple(transition.second, q, transition.first.second)).second)
				stack.push(ext::make_tuple(transition.second, q, transition.first.second));

		// fork or final state
		res.addState(q);

		if(automaton.getFinalStates().count(q))
			res.addFinalState(q);

		res.addTransition ( std::move ( lastFork ), std::move ( path ), std::move ( q ) );

	}

	return res;
}

} /* namespace transform */

} /* namespace automaton */

#endif /* COMPACTION_H_ */
