/*
 * SynchronizingWordExistence.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: 8. 3. 2020
 *	  Author: Tomas Pecka
 */

#pragma once

#include <alib/set>
#include <queue>

#include <automaton/FSM/DFA.h>
#include <automaton/transform/AutomataIntersectionCartesianProduct.h>

namespace automaton::properties {

/**
 * Algorithm determining the existence of a synchronizing word on an automaton.
 */
class SynchronizingWordExistence {
public:
	/**
	 * Checks whether the given DFA has a synchronizing word.
	 *
     * @param dfa automaton
     * @return boolean indicating whether the automaton has a synchronizing word
	 */
	template < class SymbolType, class StateType >
	static bool exists ( const automaton::DFA < SymbolType, StateType > & dfa );
};

template < class SymbolType, class StateType >
bool SynchronizingWordExistence::exists ( const automaton::DFA < SymbolType, StateType > & dfa ) {
	/*
	 * It must hold that (1) <=> (2)
	 * (1) DFA is synchronizing
	 * (2) Foreach q1, q2 in Q, exists a word w such that \delta(q1, w) = \delta(q2, w)
	 */

	/* Therefore the algorithm is as follows:
	 * Create a parallel run of the automaton with itseld
	 * All pairs (q1, q2) \in Q x Q MUST have a path to some state-pair (q, q).
	 *  -> For every pair (q,q) run a BFS search and check that all pairs (q1, q2) are visited
	 */

	automaton::DFA < SymbolType, ext::pair < StateType, StateType > > cart = automaton::transform::AutomataIntersectionCartesianProduct:: intersection ( dfa, dfa );

	std::queue < ext::pair < StateType, StateType > > q;
	ext::set < ext::pair < StateType, StateType > > visited;

	for ( const ext::pair < StateType, StateType > & state: cart.getStates ( ) ) {
		if ( state.first != state.second )
			continue;

		visited.insert ( state );
		q.push ( state );
	}

	while ( ! q.empty ( ) ) {
		const ext::pair < StateType, StateType > cstate = std::move ( q.front ( ) );
		q.pop ( );

		for ( const auto & transition : cart.getTransitionsToState ( cstate ) ) {
			const auto & srcState = transition.first.first;

			if ( visited.count ( srcState ) == 0 ) {
				visited.insert ( srcState );
				q.push ( srcState );
			}
		}
	}

	return visited == cart.getStates ( );
}

} /* namespace automaton::properties */

