/*
 * Minimize.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: Dec 9, 2013
 *	  Author: Jan Travnicek
 */

#pragma once

#include <automaton/FSM/DFA.h>
#include <automaton/TA/DFTA.h>

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

#include <global/GlobalData.h>

namespace automaton {

namespace simplify {

/**
 * Minimization of automata.
 *
 * For finite automata, we implement Hopcroft's subset minimization.
 * For finite tree automata, we implement ???.
 *
 * @sa automaton::simplify::MinimizeBrzozowski
 * @sa automaton::simplify::MinimizeDistinguishableStates
 */
class Minimize {
public:
	/**
	 * Minimizes deterministic finite autmaton.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 * @param dfa deterministic finite automaton to minimize.
	 * @return Minimal deterministic finite automaton equivalent to @p dfa
	 */
	template < class SymbolType, class StateType >
	static automaton::DFA < SymbolType, StateType > minimize(const automaton::DFA < SymbolType, StateType >& dfa) {
		size_t steps;
		return minimize ( dfa, steps );
	}

	/**
	 * Minimizes deterministic finite autmaton, also reports number of iterations it took.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam StateType Type for states.
	 * @param dfa deterministic finite automaton to minimize.
	 * @param[out] steps Number of steps in the subset minimization performed until finished
	 * @return Minimal deterministic finite automaton equivalent to @p dfa
	 */
	template < class SymbolType, class StateType >
	static automaton::DFA < SymbolType, StateType > minimize(const automaton::DFA < SymbolType, StateType >& dfa, size_t & steps);

	/**
	 * Minimizes deterministic finite tree autmaton.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam RankType Type for rank (arity) in ranked alphabet.
	 * @tparam StateType Type for states.
	 * @param dfta deterministic finite tree automaton to minimize.
	 * @return Minimal deterministic finite automaton equivalent to @p dfa
	 */
	template < class SymbolType, class StateType >
	static automaton::DFTA < SymbolType, StateType > minimize(const automaton::DFTA < SymbolType, StateType >& dfta) {
		size_t steps;
		return minimize ( dfta, steps );
	}

	/**
	 * Minimizes deterministic finite tree autmaton, also reports number of iterations it took.
	 *
	 * @tparam SymbolType Type for input symbols.
	 * @tparam RankType Type for rank (arity) in ranked alphabet.
	 * @tparam StateType Type for states.
	 * @param dfta deterministic finite tree automaton to minimize.
	 * @param[out] steps Number of steps in the subset minimization performed until finished
	 * @return Minimal deterministic finite automaton equivalent to @p dfa
	 */
	template < class SymbolType, class StateType >
	static automaton::DFTA < SymbolType, StateType > minimize(const automaton::DFTA < SymbolType, StateType >& dfta, size_t & steps);

private:
	template < class SymbolType, class StateType >
	static void print_progress(const automaton::DFA < SymbolType, StateType >& dfa, const ext::map<std::pair<StateType, ext::set<std::pair<SymbolType, StateType> > >, ext::set<StateType> >& minimizedTransitionFunction, size_t iter);
};

template < class SymbolType, class StateType >
automaton::DFA < SymbolType, StateType > Minimize::minimize(const automaton::DFA < SymbolType, StateType >& dfa, size_t & steps ) {
	steps = 0;

	if(dfa.getFinalStates().empty ( )) {
		automaton::DFA < SymbolType, StateType > result(dfa.getInitialState());
		result.setInputAlphabet(dfa.getInputAlphabet());
		return result;
	}

	ext::map<StateType, ext::map<SymbolType, StateType > > refactor;

	for(const StateType& state : dfa.getStates())
		refactor.insert(std::make_pair(state, ext::map<SymbolType, StateType>()));

	for(const std::pair<const ext::pair<StateType, SymbolType>, StateType>& transition : dfa.getTransitions())
		refactor[transition.first.first].insert(std::make_pair(transition.first.second, transition.second));

	ext::map<StateType, StateType> toEquivalentStates; //original state to equivalent state
	ext::map<std::pair<StateType, ext::set<std::pair<SymbolType, StateType> > >, ext::set<StateType> > minimizedTransitionFunction; //mapped to the original state

	const StateType *firstFinal = nullptr;
	const StateType *firstNonfinal = nullptr;
	for(const StateType& state : dfa.getStates()) {
		if(dfa.getFinalStates().count(state) == 0) { // not a final state
			if(!firstNonfinal)
				firstNonfinal = &state;
			toEquivalentStates.insert(std::pair<StateType, StateType>(state, *firstNonfinal));
		} else {
			if(!firstFinal)
				firstFinal = &state;
			toEquivalentStates.insert(std::pair<StateType, StateType>(state, *firstFinal));
		}
	}

	unsigned prevSize = 0;
	while ( true ) {
		for(const std::pair<const StateType, ext::map<SymbolType, StateType> >& transition : refactor) {
			const StateType& from = toEquivalentStates.find(transition.first)->second;
			ext::set<std::pair<SymbolType, StateType> > transitionFunction;

			for(const std::pair<const SymbolType, StateType> & transitionFromState : transition.second)
				transitionFunction.insert(std::make_pair(transitionFromState.first, toEquivalentStates.find(transitionFromState.second)->second));

			minimizedTransitionFunction[std::make_pair(from, transitionFunction)].insert(transition.first);
		}

		if(common::GlobalData::verbose)
			print_progress ( dfa, minimizedTransitionFunction, steps );

		if (minimizedTransitionFunction.size() == prevSize)
			break;

		prevSize = minimizedTransitionFunction.size();
		toEquivalentStates.clear();

		for(const std::pair<const std::pair<StateType, ext::set<std::pair<SymbolType, StateType> > >, ext::set<StateType> >& transition : minimizedTransitionFunction)
			for(const StateType& target : transition.second)
				toEquivalentStates.insert(std::make_pair(target, *(transition.second.begin())));

		minimizedTransitionFunction.clear();

		++ steps;
	}

	auto findInitialGroup = [ ] ( const ext::map < std::pair < StateType, ext::set < std::pair < SymbolType, StateType > > >, ext::set < StateType > > & transitions, const StateType & initialState ) {
		for ( const std::pair < const std::pair < StateType, ext::set < std::pair < SymbolType, StateType > > >, ext::set < StateType > > & transition : transitions )
			if ( transition.second.count ( initialState ) )
				return transition.first.first;
		throw std::logic_error ( "Initial group not identified." );
	};

	automaton::DFA < SymbolType, StateType > result( findInitialGroup ( minimizedTransitionFunction, dfa.getInitialState ( ) ) );

	result.setInputAlphabet(dfa.getInputAlphabet());

	for(const std::pair<const std::pair<StateType, ext::set<std::pair<SymbolType, StateType> > >, ext::set<StateType> >& transition : minimizedTransitionFunction) {
		result.addState(transition.first.first);
		if(dfa.getFinalStates().count(transition.first.first))
			result.addFinalState(transition.first.first);
	}

	for(const std::pair<const std::pair<StateType, ext::set<std::pair<SymbolType, StateType> > >, ext::set<StateType> >& transition : minimizedTransitionFunction)
		for(const std::pair<SymbolType, StateType>& transitionFromState : transition.first.second)
			result.addTransition(transition.first.first, transitionFromState.first, transitionFromState.second);

	return result;
}

#define RESETSS(x) {(x).clear(); (x).str("");}

template < class SymbolType, class StateType >
void Minimize::print_progress(const automaton::DFA < SymbolType, StateType >& dfa, const ext::map<std::pair<StateType, ext::set<std::pair<SymbolType, StateType> > >, ext::set<StateType> >& minimizedTransitionFunction, size_t iter) {
	common::Streams::log << "delta " << iter << std::endl;

	//ext::map<std::pair<StateType, ext::set<std::pair<SymbolType, StateType> > >, ext::set<StateType> > minimizedTransitionFunction; //mapped to the original state

	/* need to restruct this first so we have table like:	orig state | new state | trans_symb_1 | trans_symb_2 | ... | trans_symb_n */
	// we surely have DFA here (transition map hence)
	ext::map<std::pair<StateType, StateType>, ext::map<SymbolType, StateType>> printMap;
	for(const auto& kv: minimizedTransitionFunction) {
		for(const auto& state : kv.second) {
			ext::map<SymbolType, StateType> stateTransMap;
			for(const auto& transition : kv.first.second) {
				stateTransMap.insert(std::make_pair(transition.first, transition.second));
			}
			printMap.insert(std::make_pair(std::make_pair(state, kv.first.first), stateTransMap));
		}
	}

	size_t stateWidth = 1;
	size_t stateMapWidth = 1;
	ext::map<SymbolType, size_t> colWidths;
	std::ostringstream ss;

	for(const SymbolType& symbol : dfa.getInputAlphabet()) {
		ss << symbol;
		colWidths[symbol] = ss.str().size();
		RESETSS(ss);
	}
	for(const auto& kv : printMap) {
		ss << kv.first.first;
		stateWidth = std::max(stateWidth, ss.str().size());
		RESETSS(ss);

		ss << kv.first.second;
		stateMapWidth = std::max(stateMapWidth, ss.str().size());
		RESETSS(ss);

		for(const SymbolType& symbol : dfa.getInputAlphabet()) {
			auto it = kv.second.find(symbol);
			if(it != kv.second.end()) {
				ss << it -> second;
				colWidths[symbol] = std::max(colWidths[symbol], ss.str().size());
				RESETSS(ss);
			}
		}
	}

	common::Streams::log << std::setw(stateWidth) << "";
	common::Streams::log << " | ";
	common::Streams::log << std::setw(stateMapWidth) << "";
	common::Streams::log << " | ";
	for(const SymbolType& symbol : dfa.getInputAlphabet()) {
		ss << symbol;
		common::Streams::log << std::setw(colWidths[symbol]) << ss.str() << " | ";
		RESETSS(ss);
	}
	common::Streams::log << std::endl;
	for(const auto& kv : printMap) {
		ss << kv.first.first;
		common::Streams::log << std::setw(stateWidth) << ss.str() << " | ";
		RESETSS(ss);

		ss << kv.first.second;
		common::Streams::log << std::setw(stateMapWidth) << ss.str() << " | ";
		RESETSS(ss);

		for(const auto& symbol : dfa.getInputAlphabet()) {
			auto it = kv.second.find(symbol);
			if(it != kv.second.end()) {
				ss << it -> second;
				common::Streams::log << std::setw(colWidths[symbol]) << ss.str();
				RESETSS(ss);
			}
			else {
				common::Streams::log << std::setw(colWidths[symbol]) << "";
			}
			common::Streams::log << " | ";
		}
		common::Streams::log << std::endl;
	}
	common::Streams::log << std::endl;
}

template < class SymbolType, class StateType >
automaton::DFTA < SymbolType, StateType > Minimize::minimize(const automaton::DFTA < SymbolType, StateType >& dfta, size_t & steps ) {
	automaton::DFTA<SymbolType, StateType> result;
	result.setInputAlphabet(dfta.getInputAlphabet());

	if (dfta.getFinalStates().empty ( ))
		return result;

	typedef std::pair < const ext::pair < common::ranked_symbol < SymbolType >, ext::vector < StateType > >, StateType > Transition;

	ext::map<StateType, ext::map<Transition, ext::vector<int>>> stateOccurences;

	for (const auto & state : dfta.getStates())
		stateOccurences[state];

	for(auto & transition : dfta.getTransitions()) {
		const ext::vector<StateType> & from = transition.first.second;
		for (int i = 0; i < (int) from.size(); ++i)
			stateOccurences[from[i]][transition].push_back(i);
	}

	ext::map <StateType, StateType> toEquivalentStates;
	ext::map<std::pair<StateType, ext::map<ext::tuple<common::ranked_symbol<SymbolType>, ext::vector<StateType>, StateType >, ext::set<int> > >, ext::set<StateType> > minimizedTransitionFunction; //mapped to the original states
	const StateType *firstFinal = nullptr;
	const StateType *firstNonfinal = nullptr;
	for (const StateType &state : dfta.getStates()) {
		if (dfta.getFinalStates().count(state) == 0) { // not a final state
			if (!firstNonfinal)
				firstNonfinal = &state;

			toEquivalentStates.insert(std::pair<StateType, StateType>(state, *firstNonfinal));
		} else {
			if (!firstFinal)
				firstFinal = &state;

			toEquivalentStates.insert(std::pair<StateType, StateType>(state, *firstFinal));
		}
	}

	unsigned prevSize = 0;
	steps = 0;
	while(true) {
		for(const auto & occurencesOfState : stateOccurences) {
			const StateType & state = occurencesOfState.first;
			const StateType & equivalentState = toEquivalentStates.find(state) -> second;
			ext::map<ext::tuple<common::ranked_symbol<SymbolType>, ext::vector<StateType>, StateType >, ext::set<int> > keyTransitionsPart;

			for(const auto & transitionOccurences : occurencesOfState.second) {
				const auto & transition = transitionOccurences.first;
				for (int i : transitionOccurences.second) {
					ext::vector<StateType> fromWithoutCurrent (transition.first.second);
					fromWithoutCurrent.erase(fromWithoutCurrent.begin()+i);
					keyTransitionsPart[ext::make_tuple(transition.first.first, fromWithoutCurrent, toEquivalentStates.find(transition.second)->second)].insert(i);
				}
			}

			minimizedTransitionFunction [ std::make_pair ( equivalentState, keyTransitionsPart ) ].insert(state);
		}

		if (minimizedTransitionFunction.size() == prevSize)
			break;

		prevSize = minimizedTransitionFunction.size();
		toEquivalentStates.clear();
		for(const auto & transition : minimizedTransitionFunction)
			for(const StateType & target : transition.second)
				toEquivalentStates.insert(std::make_pair ( target, * transition.second.begin() ) );

		minimizedTransitionFunction.clear();

		++ steps;
	}

	for (const auto & transition : minimizedTransitionFunction) {
		const auto & state = *(transition.second.begin());
		result.addState(state);
		if(dfta.getFinalStates().count(state))
			result.addFinalState(state);
	}

	for(const auto & transition : dfta.getTransitions()) {
		ext::vector<StateType> from;
		from.reserve(transition.first.second.size());
		for (const StateType & state : transition.first.second)
			from.push_back(toEquivalentStates.find(state)->second);

		result.addTransition(transition.first.first, from, toEquivalentStates.find(transition.second)->second);
	}

	return result;
}

} /* namespace simplify */

} /* namespace automaton */

