/*
 * MinimizeByPartitioning.cpp
 *
 * 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: 27. 3. 2019
 *	  Author: Tomas Pecka
 */

#ifndef MINIMIZE_BY_PARTITIONING_H_
#define MINIMIZE_BY_PARTITIONING_H_

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

#include <automaton/FSM/DFA.h>
#include <automaton/FSM/NFA.h>
#include <automaton/TA/DFTA.h>
#include <automaton/TA/NFTA.h>
#include <automaton/TA/UnorderedDFTA.h>
#include <automaton/TA/UnorderedNFTA.h>

namespace automaton {

namespace simplify {

/**
 * Minimization of finite automata using distinguishable states method from Hopcroft 4.4.1 - 4.4.3 for finite automata
 *
 * @sa automaton::simplify::Minimize
 * @sa automaton::simplify::MinimizeBrzozowski
 */
class MinimizeByPartitioning {
public:
	/**
	 * Aggregates the given automaton with respect to a state partitioning.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 *
	 * @param nfa deterministic finite automaton to minimize.
	 * @param partitions state partitioning
	 *
	 * @return Minimal deterministic finite automaton equivalent to @p nfa
	 */
	template < class SymbolType, class StateType >
	static automaton::NFA < SymbolType, ext::set < StateType > > minimize ( const automaton::NFA < SymbolType, StateType > & nfa, const ext::set < ext::set < StateType > > & partitions );

	/**
	 * Aggregates the given automaton with respect to a state partitioning.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 *
	 * @param dfa deterministic finite automaton to minimize.
	 * @param partitions state partitioning
	 *
	 * @return Minimal deterministic finite automaton equivalent to @p dfa
	 */
	template < class SymbolType, class StateType >
	static automaton::DFA < SymbolType, ext::set < StateType > > minimize ( const automaton::DFA < SymbolType, StateType > & dfa, const ext::set < ext::set < StateType > > & partitions );

	/**
	 * Aggregates the given automaton with respect to a state partitioning.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 *
	 * @param dfta deterministic finite tree automaton to minimize.
	 * @param partitions state partitioning
	 *
	 * @return Minimal deterministic finite tree automaton equivalent to @p dfa
	 */
	template < class SymbolType, class StateType >
	static automaton::DFTA < SymbolType, ext::set < StateType > > minimize ( const automaton::DFTA < SymbolType, StateType > & dfta, const ext::set < ext::set < StateType > > & partitions );

	/**
	 * Aggregates the given automaton with respect to a state partitioning.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 *
	 * @param fta nondeterministic finite tree automaton to aggregate.
	 * @param partitions state partitioning
	 *
	 * @return smaller nonderministic finite tree automaton equivalent to @p nfa
	 */
	template < class SymbolType, class StateType >
	static automaton::NFTA < SymbolType, ext::set < StateType > > minimize ( const automaton::NFTA < SymbolType, StateType > & fta, const ext::set < ext::set < StateType > > & partitions );

	/**
	 * Aggregates the given automaton with respect to a state partitioning.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 *
	 * @param dfta deterministic unordered finite tree automaton to minimize.
	 * @param partitions state partitioning
	 *
	 * @return Minimal deterministic unordered finite tree automaton equivalent to @p dfa
	 */
	template < class SymbolType, class StateType >
	static automaton::UnorderedDFTA < SymbolType, ext::set < StateType > > minimize ( const automaton::UnorderedDFTA < SymbolType, StateType > & dfta, const ext::set < ext::set < StateType > > & partitions );

	/**
	 * Aggregates the given automaton with respect to a state partitioning.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 *
	 * @param fta nondeterministic unordered finite tree automaton to aggregate.
	 * @param partitions state partitioning
	 *
	 * @return smaller nonderministic unordered finite tree automaton equivalent to @p nfa
	 */
	template < class SymbolType, class StateType >
	static automaton::UnorderedNFTA < SymbolType, ext::set < StateType > > minimize ( const automaton::UnorderedNFTA < SymbolType, StateType > & fta, const ext::set < ext::set < StateType > > & partitions );

private:
	/**
	 * Helper method to create mapping from state to the corresponding partition
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 * @param partitions state partitioning
	 *
	 * @sa DistinguishableStates
	 *
	 * @return state partitioning of undistinguishable states
	 */
	template < class StateType >
	static ext::map < StateType, ext::set < StateType > > partitionMap ( const ext::set < ext::set < StateType > > & partitions );
};

template < class StateType >
ext::map < StateType, ext::set < StateType > > MinimizeByPartitioning::partitionMap ( const ext::set < ext::set < StateType > > & partitions ) {
	ext::map < StateType, ext::set < StateType > > res;

	for ( const auto & partition : partitions )
		for ( const StateType & state : partition )
			res [ state ] = partition;

	return res;
}

template < class SymbolType, class StateType >
automaton::NFA < SymbolType, ext::set < StateType > > MinimizeByPartitioning::minimize ( const automaton::NFA < SymbolType, StateType > & nfa, const ext::set < ext::set < StateType > > & partitions ) {
	const ext::map < StateType, ext::set < StateType > > statePartitionMap = partitionMap ( partitions );

	automaton::NFA < SymbolType, ext::set < StateType > > res ( statePartitionMap.at ( nfa.getInitialState ( ) ) );
	res.setStates ( partitions );
	res.addInputSymbols ( nfa.getInputAlphabet ( ) );
	for ( const StateType & state : nfa.getFinalStates ( ) )
		res.addFinalState ( statePartitionMap.at ( state ) );

	for ( const auto & transition : nfa.getTransitions ( ) )
		res.addTransition ( statePartitionMap.at ( transition.first.first ), transition.first.second, statePartitionMap.at ( transition.second ) );

	return res;
}

template < class SymbolType, class StateType >
automaton::DFA < SymbolType, ext::set < StateType > > MinimizeByPartitioning::minimize ( const automaton::DFA < SymbolType, StateType > & dfa, const ext::set < ext::set < StateType > > & partitions ) {
	const ext::map < StateType, ext::set < StateType > > statePartitionMap = partitionMap ( partitions );

	automaton::DFA < SymbolType, ext::set < StateType > > res ( statePartitionMap.at ( dfa.getInitialState ( ) ) );
	res.setStates ( partitions );
	res.addInputSymbols ( dfa.getInputAlphabet ( ) );
	for ( const StateType & state : dfa.getFinalStates ( ) )
		res.addFinalState ( statePartitionMap.at ( state ) );

	for ( const auto & transition : dfa.getTransitions ( ) )
		res.addTransition ( statePartitionMap.at ( transition.first.first ), transition.first.second, statePartitionMap.at ( transition.second ) );

	return res;
}

template < class SymbolType, class StateType >
automaton::DFTA < SymbolType, ext::set < StateType > > MinimizeByPartitioning::minimize ( const automaton::DFTA < SymbolType, StateType > & dfta, const ext::set < ext::set < StateType > > & partitions ) {
	const ext::map < StateType, ext::set < StateType > > statePartitionMap = partitionMap ( partitions );

	automaton::DFTA < SymbolType, ext::set < StateType > > res;
	res.setStates ( partitions );
	res.addInputSymbols ( dfta.getInputAlphabet ( ) );
	for ( const StateType & state : dfta.getFinalStates ( ) )
		res.addFinalState ( statePartitionMap.at ( state ) );

	for ( const auto & transition : dfta.getTransitions ( ) ) {
		ext::vector < ext::set < StateType > > minimalFrom;
		for ( const StateType & from : transition.first.second )
			minimalFrom.push_back ( statePartitionMap.at ( from ) );
		res.addTransition ( transition.first.first, minimalFrom, statePartitionMap.at ( transition.second ) );
	}

	return res;
}

template < class SymbolType, class StateType >
automaton::NFTA < SymbolType, ext::set < StateType > > MinimizeByPartitioning::minimize ( const automaton::NFTA < SymbolType, StateType > & dfta, const ext::set < ext::set < StateType > > & partitions ) {
	const ext::map < StateType, ext::set < StateType > > statePartitionMap = partitionMap ( partitions );

	automaton::NFTA < SymbolType, ext::set < StateType > > res;
	res.setStates ( partitions );
	res.addInputSymbols ( dfta.getInputAlphabet ( ) );
	for ( const StateType & state : dfta.getFinalStates ( ) )
		res.addFinalState ( statePartitionMap.at ( state ) );

	for ( const auto & transition : dfta.getTransitions ( ) ) {
		ext::vector < ext::set < StateType > > minimalFrom;
		for ( const StateType & from : transition.first.second )
			minimalFrom.push_back ( statePartitionMap.at ( from ) );
		res.addTransition ( transition.first.first, minimalFrom, statePartitionMap.at ( transition.second ) );
	}

	return res;
}

template < class SymbolType, class StateType >
automaton::UnorderedDFTA < SymbolType, ext::set < StateType > > MinimizeByPartitioning::minimize ( const automaton::UnorderedDFTA < SymbolType, StateType > & dfta, const ext::set < ext::set < StateType > > & partitions ) {
	const ext::map < StateType, ext::set < StateType > > statePartitionMap = partitionMap ( partitions );

	automaton::UnorderedDFTA < SymbolType, ext::set < StateType > > res;
	res.setStates ( partitions );
	res.addInputSymbols ( dfta.getInputAlphabet ( ) );
	for ( const StateType & state : dfta.getFinalStates ( ) )
		res.addFinalState ( statePartitionMap.at ( state ) );

	for ( const auto & transition : dfta.getTransitions ( ) ) {
		ext::multiset < ext::set < StateType > > minimalFrom;
		for ( const StateType & from : transition.first.second )
			minimalFrom.insert ( statePartitionMap.at ( from ) );
		res.addTransition ( transition.first.first, minimalFrom, statePartitionMap.at ( transition.second ) );
	}

	return res;
}

template < class SymbolType, class StateType >
automaton::UnorderedNFTA < SymbolType, ext::set < StateType > > MinimizeByPartitioning::minimize ( const automaton::UnorderedNFTA < SymbolType, StateType > & dfta, const ext::set < ext::set < StateType > > & partitions ) {
	const ext::map < StateType, ext::set < StateType > > statePartitionMap = partitionMap ( partitions );

	automaton::UnorderedNFTA < SymbolType, ext::set < StateType > > res;
	res.setStates ( partitions );
	res.addInputSymbols ( dfta.getInputAlphabet ( ) );
	for ( const StateType & state : dfta.getFinalStates ( ) )
		res.addFinalState ( statePartitionMap.at ( state ) );

	for ( const auto & transition : dfta.getTransitions ( ) ) {
		ext::multiset < ext::set < StateType > > minimalFrom;
		for ( const StateType & from : transition.first.second )
			minimalFrom.insert ( statePartitionMap.at ( from ) );
		res.addTransition ( transition.first.first, minimalFrom, statePartitionMap.at ( transition.second ) );
	}

	return res;
}

} /* namespace simplify */

} /* namespace automaton */

#endif /* MINIMIZE_BY_PARTITIONING_H_ */
