/*
 * UnorderedNFTA.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: Mar 21, 2015
 *      Author: Stepan Plachy
 */

#pragma once

#include <ostream>
#include <sstream>

#include <alib/multimap>
#include <alib/set>
#include <alib/multiset>

#include <core/components.hpp>

#include <core/normalize.hpp>

#include <common/DefaultStateType.h>
#include <common/DefaultSymbolType.h>

#include <automaton/AutomatonException.h>

#include <core/normalize.hpp>
#include <alphabet/common/SymbolNormalize.h>
#include <automaton/common/AutomatonNormalize.h>

#include "UnorderedDFTA.h"

namespace automaton {

class InputAlphabet;
class States;
class FinalStates;

/**
 * \brief
 * Nondeterministic finite tree automaton without epsilon transitions. Accepts regular tree languages.

 * \details
 * Definition is classical definition of finite automata.
 * A = (Q, T, \delta, F),
 * Q (States) = nonempty finite set of states,
 * T (TerminalAlphabet) = finite set of terminal ranked symbols - having this empty won't let automaton do much though,
 * \delta = transition function of the form (A, B, C, ...) \times a -> P(Q), where A, B, C, ... \in Q, a \in T, and P(Q) is a powerset of states,
 * F (FinalStates) = set of final states
 *
 * Elements of the \delta multimapping must meet following criteria. The size of the state list must equal the rank of the ranked symbol.
 *
 * \tparam SymbolTypeT used for the symbol part of the ranked symbol
 * \tparam StateTypeT used to the states, and the initial state of the automaton.
 */
template < class SymbolTypeT = DefaultSymbolType, class StateTypeT = DefaultStateType >
class UnorderedNFTA final : public core::Components < UnorderedNFTA < SymbolTypeT, StateTypeT >, ext::set < common::ranked_symbol < SymbolTypeT > >, component::Set, InputAlphabet, ext::set < StateTypeT >, component::Set, std::tuple < States, FinalStates > > {
public:
	typedef SymbolTypeT SymbolType;
	typedef StateTypeT StateType;

private:
	/**
	 * Transition function as multimapping from a list of states times an input symbol on the left hand side to a set of states.
	 */
	ext::multimap < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > transitions;

public:
	/**
	 * \brief Creates a new instance of the automaton.
	 */
	explicit UnorderedNFTA ( );

	/**
	 * \brief Creates a new instance of the automaton with a concrete set of states, input alphabet, and  a set of final states.
	 *
	 * \param states the initial set of states of the automaton
	 * \param inputAlphabet the initial input alphabet
	 * \param finalStates the initial set of final states of the automaton
	 */
	explicit UnorderedNFTA ( ext::set < StateType > states, ext::set < common::ranked_symbol < SymbolType > > inputAlphabet, ext::set < StateType > finalStates );

	/*
	 * \brief Creates a new instance of the automaton based on the Deterministic finite tree automaton.
	 *
	 * \param other the Deterministic finite tree automaton
	 */
	explicit UnorderedNFTA ( const UnorderedDFTA < SymbolType, StateType > & other );

	/**
	 * Getter of states.
	 *
	 * \returns the states of the automaton
	 */
	const ext::set < StateType > & getStates ( ) const & {
		return this->template accessComponent < States > ( ).get ( );
	}

	/**
	 * Getter of states.
	 *
	 * \returns the states of the automaton
	 */
	ext::set < StateType > && getStates ( ) && {
		return std::move ( this->template accessComponent < States > ( ).get ( ) );
	}

	/**
	 * Adder of a state.
	 *
	 * \param state the new state to be added to a set of states
	 *
	 * \returns true if the state was indeed added
	 */
	bool addState ( StateType state ) {
		return this->template accessComponent < States > ( ).add ( std::move ( state ) );
	}

	/**
	 * Setter of states.
	 *
	 * \param states completely new set of states
	 */
	void setStates ( ext::set < StateType > states ) {
		this->template accessComponent < States > ( ).set ( std::move ( states ) );
	}

	/**
	 * Remover of a state.
	 *
	 * \param state a state to be removed from a set of states
	 *
	 * \returns true if the state was indeed removed
	 */
	void removeState ( const StateType & state ) {
		this->template accessComponent < States > ( ).remove ( state );
	}

	/**
	 * Getter of final states.
	 *
	 * \returns the final states of the automaton
	 */
	const ext::set < StateType > & getFinalStates ( ) const & {
		return this->template accessComponent < FinalStates > ( ).get ( );
	}

	/**
	 * Getter of final states.
	 *
	 * \returns the final states of the automaton
	 */
	ext::set < StateType > && getFinalStates ( ) && {
		return std::move ( this->template accessComponent < FinalStates > ( ).get ( ) );
	}

	/**
	 * Adder of a final state.
	 *
	 * \param state the new state to be added to a set of final states
	 *
	 * \returns true if the state was indeed added
	 */
	bool addFinalState ( StateType state ) {
		return this->template accessComponent < FinalStates > ( ).add ( std::move ( state ) );
	}

	/**
	 * Setter of final states.
	 *
	 * \param states completely new set of final states
	 */
	void setFinalStates ( ext::set < StateType > states ) {
		this->template accessComponent < FinalStates > ( ).set ( std::move ( states ) );
	}

	/**
	 * Remover of a final state.
	 *
	 * \param state a state to be removed from a set of final states
	 *
	 * \returns true if the state was indeed removed
	 */
	void removeFinalState ( const StateType & state ) {
		this->template accessComponent < FinalStates > ( ).remove ( state );
	}

	/**
	 * Getter of the input alphabet.
	 *
	 * \returns the input alphabet of the automaton
	 */
	const ext::set < common::ranked_symbol < SymbolType > > & getInputAlphabet ( ) const & {
		return this->template accessComponent < InputAlphabet > ( ).get ( );
	}

	/**
	 * Getter of the input alphabet.
	 *
	 * \returns the input alphabet of the automaton
	 */
	ext::set < common::ranked_symbol < SymbolType > > && getInputAlphabet ( ) && {
		return std::move ( this->template accessComponent < InputAlphabet > ( ).get ( ) );
	}

	/**
	 * Adder of a input symbol.
	 *
	 * \param symbol the new symbol to be added to an input alphabet
	 *
	 * \returns true if the symbol was indeed added
	 */
	bool addInputSymbol ( common::ranked_symbol < SymbolType > symbol ) {
		return this->template accessComponent < InputAlphabet > ( ).add ( std::move ( symbol ) );
	}

	/**
	 * Adder of input symbols.
	 *
	 * \param symbols new symbols to be added to an input alphabet
	 */
	void addInputSymbols ( ext::set < common::ranked_symbol < SymbolType > > symbols ) {
		this->template accessComponent < InputAlphabet > ( ).add ( std::move ( symbols ) );
	}

	/**
	 * Setter of input alphabet.
	 *
	 * \param symbols completely new input alphabet
	 */
	void setInputAlphabet ( ext::set < common::ranked_symbol < SymbolType > > symbols ) {
		this->template accessComponent < InputAlphabet > ( ).set ( std::move ( symbols ) );
	}

	/**
	 * Remover of an input symbol.
	 *
	 * \param symbol a symbol to be removed from an input alphabet
	 *
	 * \returns true if the symbol was indeed removed
	 */
	void removeInputSymbol ( const common::ranked_symbol < SymbolType > & symbol ) {
		this->template accessComponent < InputAlphabet > ( ).remove ( symbol );
	}

	/**
	 * \brief Add a transition to the automaton.
	 *
	 * \details The transition is in a form ( A, B, C, ... ) \times a -> X, where A, B, C, ..., X \in Q and a \in T
	 *
	 * \param children the source states (A, B, C, ...)
	 * \param current the input symbol (a)
	 * \param next the target state (B)
	 *
	 * \throws AutomatonException when transition contains state or symbol not present in the automaton components
	 *
	 * \returns true if the transition was indeed added
	 */
	bool addTransition ( common::ranked_symbol < SymbolType > symbol, ext::multiset < StateType > prevStates, StateType next );

	/**
	 * \brief Removes a transition from the automaton.
	 *
	 * \details The transition is in a form ( A, B, C, ... ) \times a -> X, where A, B, C, ..., X \in Q and a \in T
	 *
	 * \param children the source states (A, B, C, ...)
	 * \param current the input symbol (a)
	 * \param next the target state (B)
	 *
	 * \returns true if the transition was indeed removed
	 */
	bool removeTransition ( const common::ranked_symbol < SymbolType > & symbol, const ext::multiset < StateType > & states, const StateType & next );

	/**
	 * Get the transition function of the automaton in its natural form.
	 *
	 * \returns transition function of the automaton
	 */
	const ext::multimap < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > & getTransitions ( ) const & {
		return transitions;
	}

	/**
	 * Get the transition function of the automaton in its natural form.
	 *
	 * \returns transition function of the automaton
	 */
	ext::multimap < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > && getTransitions ( ) && {
		return std::move ( transitions );
	}

	/**
	 * \returns transitions to state @p q
	 */
	ext::multimap < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > getTransitionsToState ( const StateType & q ) const {
		ext::multimap < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > res;

		for ( const auto & transition : getTransitions ( ) )
			if ( transition.second == q )
				res.insert ( transition );

		return res;
	}

	/**
	 * \returns transitions from state @p q
	 */
	ext::multimap < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > getTransitionsFromState ( const StateType & q ) const {
		ext::multimap < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > res;

		for ( const auto & transition : getTransitions ( ) ) {
			if ( std::find ( transition.first.second.begin ( ), transition.first.second.end ( ), q ) != transition.first.second.end ( ) )
				res.insert ( transition );
		}

		return res;
	}

	/**
	 * \brief Determines whether the automaton is deterministic.
	 *
	 * the automaton is deterministic if and only if:
	 * \li \c size of transition function \delta (from states, input symbol) \leq 1
	 *
	 * \return true if the automaton is deterministic, false otherwise
	 */
	bool isDeterministic ( ) const;

	/**
	 * The three way comparison implementation
	 *
	 * \param other the other instance
	 *
	 * \returns the ordering between this object and the @p other.
	 */
	auto operator <=> ( const UnorderedNFTA & other ) const {
		return std::tie(getStates(), getInputAlphabet(), getFinalStates(), transitions) <=> std::tie(other.getStates(), other.getInputAlphabet(), other.getFinalStates(), other.transitions);
	}

	/**
	 * The equality comparison implementation.
	 *
	 * \param other the other object to compare with.
	 *
	 * \returns true if this and other objects are equal, false othervise
	 */
	bool operator == ( const UnorderedNFTA & other ) const {
		return std::tie(getStates(), getInputAlphabet(), getFinalStates(), transitions) == std::tie(other.getStates(), other.getInputAlphabet(), other.getFinalStates(), other.transitions);
	}

	/**
	 * Print this object as raw representation to ostream.
	 *
	 * \param out ostream where to print
	 * \param instance object to print
	 *
	 * \returns modified output stream
	 */
	friend std::ostream & operator << ( std::ostream & out, const UnorderedNFTA & instance ) {
		return out << "(UnorderedNFTA "
			   << " states = " << instance.getStates ( )
			   << " inputAlphabet = " << instance.getInputAlphabet ( )
			   << " finalStates = " << instance.getFinalStates ( )
			   << " transitions = " << instance.getTransitions ( )
			   << ")";
	}

	/**
	 * Casts this instance to as compact as possible string representation.
	 *
	 * \returns string representation of the object
	 */
	operator std::string ( ) const;
};

/**
 * Trait to detect whether the type parameter T is or is not UnorderedNFTA. Derived from std::false_type.
 *
 * \tparam T the tested type parameter
 */
template < class T >
class isUnorderedNFTA_impl : public std::false_type {};

/**
 * Trait to detect whether the type parameter T is or is not DFA. Derived from std::true_type.
 *
 * Specialisation for UnorderedNFTA.
 *
 * \tparam SymbolType used for the terminal alphabet of the automaton
 * \tparam StateType used for the terminal alphabet of the automaton
 */
template < class SymbolType, class StateType >
class isUnorderedNFTA_impl < UnorderedNFTA < SymbolType, StateType > > : public std::true_type {};

/**
 * Constexpr true if the type parameter T is UnorderedNFTA, false otherwise.
 *
 * \tparam T the tested type parameter
 */
template < class T >
constexpr bool isUnorderedNFTA = isUnorderedNFTA_impl < T > { };

template < class SymbolType, class StateType >
UnorderedNFTA < SymbolType, StateType >::UnorderedNFTA ( ext::set < StateType > states, ext::set < common::ranked_symbol < SymbolType > > inputAlphabet, ext::set < StateType > finalStates ) : core::Components < UnorderedNFTA, ext::set < common::ranked_symbol < SymbolType > >, component::Set, InputAlphabet, ext::set < StateType >, component::Set, std::tuple < States, FinalStates > > ( std::move ( inputAlphabet ), std::move ( states ), std::move ( finalStates ) ) {
}

template < class SymbolType, class StateType >
UnorderedNFTA < SymbolType, StateType >::UnorderedNFTA() : UnorderedNFTA ( ext::set < StateType > { }, ext::set < common::ranked_symbol < SymbolType > > { }, ext::set < StateType > { } ) {
}

template < class SymbolType, class StateType >
UnorderedNFTA < SymbolType, StateType >::UnorderedNFTA(const UnorderedDFTA < SymbolType, StateType > & other) : UnorderedNFTA ( other.getStates(), other.getInputAlphabet(), other.getFinalStates() ) {
	transitions.insert ( other.getTransitions ( ).begin ( ), other.getTransitions ( ).end ( ) );
}

template < class SymbolType, class StateType >
bool UnorderedNFTA < SymbolType, StateType >::addTransition ( common::ranked_symbol < SymbolType > symbol, ext::multiset<StateType> prevStates, StateType next) {
	if (prevStates.size() != ( size_t ) symbol.getRank() )
		throw AutomatonException("Number of states doesn't match rank of the symbol");

	if (! getInputAlphabet().count(symbol))
		throw AutomatonException("Input symbol \"" + ext::to_string ( symbol ) + "\" doesn't exist.");

	if (! getStates().count(next))
		throw AutomatonException("State \"" + ext::to_string ( next ) + "\" doesn't exist.");

	for (const StateType& it : prevStates) {
		if (! getStates().count(it))
			throw AutomatonException("State \"" + ext::to_string ( it ) + "\" doesn't exist.");
	}

	auto upper_bound = transitions.upper_bound ( ext::tie ( symbol, prevStates ) );
	auto lower_bound = transitions.lower_bound ( ext::tie ( symbol, prevStates ) );
	auto iter = std::lower_bound ( lower_bound, upper_bound, next, [ ] ( const auto & transition, const auto & target ) { return transition.second < target; } );
	if ( iter != upper_bound && next >= iter->second )
		return false;

	ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > > key = ext::make_pair ( std::move ( symbol ), std::move ( prevStates ) );
	transitions.insert ( iter, std::make_pair ( std::move ( key ), std::move ( next ) ) );
	return true;
}

template < class SymbolType, class StateType >
bool UnorderedNFTA < SymbolType, StateType >::removeTransition(const common::ranked_symbol < SymbolType > & symbol, const ext::multiset<StateType> & states, const StateType & next) {
	auto upper_bound = transitions.upper_bound ( ext::tie ( symbol, states ) );
	auto lower_bound = transitions.lower_bound ( ext::tie ( symbol, states ) );
	auto iter = std::find_if ( lower_bound, upper_bound, [ & ] ( const auto & transition ) { return transition.second == next; } );
	if ( iter == upper_bound )
		return false;

	transitions.erase ( iter );
	return true;
}

template < class SymbolType, class StateType >
bool UnorderedNFTA < SymbolType, StateType >::isDeterministic() const {
	if ( transitions.empty ( ) )
		return true;

	for ( auto iter = transitions.begin ( ); std::next ( iter ) != transitions.end ( ); ++ iter )
		if ( iter->first == std::next ( iter )->first )
			return false;

	return true;
}

template < class SymbolType, class StateType >
UnorderedNFTA < SymbolType, StateType >::operator std::string ( ) const {
	std::stringstream ss;
	ss << *this;
	return ss.str();
}

} /* namespace automaton */

namespace core {

/**
 * Helper class specifying constraints for the automaton's internal input alphabet component.
 *
 * \tparam SymbolType used for the symbol part of the ranked symbol
 * \tparam StateType used for the terminal alphabet of the automaton.
 */
template < class SymbolType, class StateType >
class SetConstraint< automaton::UnorderedNFTA < SymbolType, StateType >, common::ranked_symbol < SymbolType >, automaton::InputAlphabet > {
public:
	/**
	 * Returns true if the symbol is still used in some transition of the automaton.
	 *
	 * \param automaton the tested automaton
	 * \param symbol the tested symbol
	 *
	 * \returns true if the symbol is used, false othervise
	 */
	static bool used ( const automaton::UnorderedNFTA < SymbolType, StateType > & automaton, const common::ranked_symbol < SymbolType > & symbol ) {
		for ( const std::pair<const ext::pair<common::ranked_symbol < SymbolType >, ext::multiset<StateType> >, StateType > & t : automaton.getTransitions())
			if (t.first.first == symbol)
				return true;

		return false;
	}

	/**
	 * Returns true as all symbols are possibly available to be elements of the input alphabet.
	 *
	 * \param automaton the tested automaton
	 * \param symbol the tested symbol
	 *
	 * \returns true
	 */
	static bool available ( const automaton::UnorderedNFTA < SymbolType, StateType > &, const common::ranked_symbol < SymbolType > & ) {
		return true;
	}

	/**
	 * All symbols are valid as input symbols.
	 *
	 * \param automaton the tested automaton
	 * \param symbol the tested symbol
	 */
	static void valid ( const automaton::UnorderedNFTA < SymbolType, StateType > &, const common::ranked_symbol < SymbolType > & ) {
	}
};

/**
 * Helper class specifying constraints for the automaton's internal states component.
 *
 * \tparam SymbolType used for the symbol part of the ranked symbol
 * \tparam StateType used for the terminal alphabet of the automaton.
 */
template < class SymbolType, class StateType >
class SetConstraint< automaton::UnorderedNFTA < SymbolType, StateType >, StateType, automaton::States > {
public:
	/**
	 * Returns true if the state is still used in some transition of the automaton.
	 *
	 * \param automaton the tested automaton
	 * \param state the tested state
	 *
	 * \returns true if the state is used, false othervise
	 */
	static bool used ( const automaton::UnorderedNFTA < SymbolType, StateType > & automaton, const StateType & state ) {
		if ( automaton.getFinalStates ( ).count ( state ) )
			return true;

		for ( const std::pair<const ext::pair<common::ranked_symbol < SymbolType >, ext::multiset<StateType> >, StateType >& t : automaton.getTransitions())
			if(ext::contains(t.first.second.begin(), t.first.second.end(), state ) || t . second == state )
				return true;

		return false;
	}

	/**
	 * Returns true as all states are possibly available to be elements of the states.
	 *
	 * \param automaton the tested automaton
	 * \param state the tested state
	 *
	 * \returns true
	 */
	static bool available ( const automaton::UnorderedNFTA < SymbolType, StateType > &, const StateType & ) {
		return true;
	}

	/**
	 * All states are valid as a state of the automaton.
	 *
	 * \param automaton the tested automaton
	 * \param state the tested state
	 */
	static void valid ( const automaton::UnorderedNFTA < SymbolType, StateType > &, const StateType & ) {
	}
};

/**
 * Helper class specifying constraints for the automaton's internal final states component.
 *
 * \tparam SymbolType used for the symbol part of the ranked symbol
 * \tparam StateType used for the terminal alphabet of the automaton.
 */
template < class SymbolType, class StateType >
class SetConstraint< automaton::UnorderedNFTA < SymbolType, StateType >, StateType, automaton::FinalStates > {
public:
	/**
	 * Returns false. Final state is only a mark that the automaton itself does require further.
	 *
	 * \param automaton the tested automaton
	 * \param state the tested state
	 *
	 * \returns false
	 */
	static bool used ( const automaton::UnorderedNFTA < SymbolType, StateType > &, const StateType & ) {
		return false;
	}

	/**
	 * Determines whether the state is available in the automaton's states set.
	 *
	 * \param automaton the tested automaton
	 * \param state the tested state
	 *
	 * \returns true if the state is already in the set of states of the automaton
	 */
	static bool available ( const automaton::UnorderedNFTA < SymbolType, StateType > & automaton, const StateType & state ) {
		return automaton.template accessComponent < automaton::States > ( ).get ( ).count ( state );
	}

	/**
	 * All states are valid as a final state of the automaton.
	 *
	 * \param automaton the tested automaton
	 * \param state the tested state
	 */
	static void valid ( const automaton::UnorderedNFTA < SymbolType, StateType > &, const StateType & ) {
	}
};

/**
 * Helper for normalisation of types specified by templates used as internal datatypes of symbols and states.
 *
 * \returns new instance of the automaton with default template parameters or unmodified instance if the template parameters were already the default ones
 */
template < class SymbolType, class StateType >
struct normalize < automaton::UnorderedNFTA < SymbolType, StateType > > {
	static automaton::UnorderedNFTA < > eval ( automaton::UnorderedNFTA < SymbolType, StateType > && value ) {
		ext::set < common::ranked_symbol < > > alphabet = alphabet::SymbolNormalize::normalizeRankedAlphabet ( std::move ( value ).getInputAlphabet ( ) );
		ext::set < DefaultStateType > states = automaton::AutomatonNormalize::normalizeStates ( std::move ( value ).getStates ( ) );
		ext::set < DefaultStateType > finalStates = automaton::AutomatonNormalize::normalizeStates ( std::move ( value ).getFinalStates ( ) );

		automaton::UnorderedNFTA < > res ( std::move ( states ), std::move ( alphabet ), std::move ( finalStates ) );

		for ( std::pair < ext::pair < common::ranked_symbol < SymbolType >, ext::multiset < StateType > >, StateType > && transition : ext::make_mover ( std::move ( value ).getTransitions ( ) ) ) {
			common::ranked_symbol < DefaultSymbolType > input = alphabet::SymbolNormalize::normalizeRankedSymbol ( std::move ( transition.first.first ) );
			ext::multiset < DefaultStateType > from = automaton::AutomatonNormalize::normalizeStates ( std::move ( transition.first.second ) );
			DefaultStateType to = automaton::AutomatonNormalize::normalizeState ( std::move ( transition.second ) );

			res.addTransition ( std::move ( input ), std::move ( from ), std::move ( to ) );
		}

		return res;
	}
};

} /* namespace core */

extern template class automaton::UnorderedNFTA < >;

