/*
 * FactorOracleAutomaton.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: Jan 8, 2017
 *      Author: Jan Travnicek
 */

#ifndef FACTOR_ORACLE_AUTOMATON_H_
#define FACTOR_ORACLE_AUTOMATON_H_

#include <alib/string>
#include <alib/set>
#include <alib/iostream>
#include <sstream>

#include <common/DefaultSymbolType.h>

#include <alphabet/common/SymbolNormalize.h>

#include <automaton/FSM/DFA.h>

namespace indexes {

namespace stringology {

class GeneralAlphabet;

/**
 * \brief Factor oracle automaton string index. Stores a deterministic finite automaton. The automaton is of exactly linear size as the indexed string. The automaton represents at least all factors of the indexed string. The class does not check whether the automaton represent valid index.
 *
 * \tparam SymbolType type of symbols of indexed string
 */
template < class SymbolType = DefaultSymbolType >
class FactorOracleAutomaton final : public ext::CompareOperators < FactorOracleAutomaton < SymbolType > > {
	/**
	 * Representation of underlying automaton.
	 */
	automaton::DFA < SymbolType, unsigned > m_automaton;

public:
	/**
	 * Creates a new instance of the index based on the raw factor oracle automaton.
	 *
	 * \param automaton the factor oracle automaton
	 */
	explicit FactorOracleAutomaton ( automaton::DFA < SymbolType, unsigned > automaton );

	/**
	 * Getter of the raw factor oracle automaton
	 *
	 * @return the raw factor oracle automaton
	 */
	const automaton::DFA < SymbolType, unsigned > & getAutomaton ( ) const &;

	/**
	 * Getter of the raw factor oracle automaton
	 *
	 * @return the raw factor oracle automaton
	 */
	automaton::DFA < SymbolType, unsigned > && getAutomaton ( ) &&;

	/**
	 * Getter of the alphabet of the indexed string.
	 *
	 * \returns the alphabet of the indexed string
	 */
	const ext::set < SymbolType > & getAlphabet ( ) const & {
		return m_automaton.getInputAlphabet ( );
	}

	/**
	 * Getter of the alphabet of the indexed string.
	 *
	 * \returns the alphabet of the indexed string
	 */
	ext::set < SymbolType > && getAlphabet ( ) && {
		return std::move ( m_automaton ).getInputAlphabet ( );
	}

	/**
	 * Remover of a symbol from the alphabet. The symbol can be removed if it is not used in any of bit vector keys.
	 *
	 * \param symbol a symbol to remove.
	 */
	bool removeSymbolFromAlphabet ( const SymbolType & symbol ) {
		return m_automaton.removeInputSymbol ( symbol );
	}

	/**
	 * Getter of the backbone length of the automaton. The length is equal to the longest path through the automaton.
	 * \return the length of the backbone
	 */
	unsigned getBackboneLength ( ) const {
		return m_automaton.getStates ( ).size ( ) - 1;
	}

	/**
	 * The actual compare method.
	 *
	 * \param other the other instance
	 *
	 * \returns the actual relation between two by type same index instances
	 */
	int compare ( const FactorOracleAutomaton & other ) const;

	/**
	 * 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 FactorOracleAutomaton & instance ) {
		return out << "(FactorOracleAutomaton " << instance.m_automaton << ")";
	}

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

	/**
	 * Cast operator to the underlying autoamton.
	 *
	 * \return the raw automaton
	 */
	explicit operator automaton::DFA < SymbolType, unsigned > ( ) const;
};

} /* namespace stringology */

} /* namespace indexes */

namespace indexes {

namespace stringology {

template < class SymbolType >
FactorOracleAutomaton < SymbolType >::FactorOracleAutomaton ( automaton::DFA < SymbolType, unsigned > automaton ) : m_automaton ( std::move ( automaton ) ) {
}

template < class SymbolType >
const automaton::DFA < SymbolType, unsigned > & FactorOracleAutomaton < SymbolType >::getAutomaton ( ) const & {
	return m_automaton;
}

template < class SymbolType >
automaton::DFA < SymbolType, unsigned > && FactorOracleAutomaton < SymbolType >::getAutomaton ( ) && {
	return std::move ( m_automaton );
}

template < class SymbolType >
int FactorOracleAutomaton < SymbolType >::compare ( const FactorOracleAutomaton & other ) const {
	auto first = ext::tie ( getAutomaton ( ) );
	auto second = ext::tie ( other.getAutomaton ( ) );

	static ext::compare < decltype ( first ) > comp;

	return comp ( first, second );
}

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

template < class SymbolType >
FactorOracleAutomaton < SymbolType >::operator automaton::DFA < SymbolType, unsigned > ( ) const {
	return getAutomaton ( );
}

} /* namespace stringology */

} /* namespace indexes */

namespace core {

/**
 * Helper for normalisation of types specified by templates used as internal datatypes of symbols.
 *
 * \returns new instance of the index with default template parameters or unmodified instance if the template parameters were already the default ones
 */
template < class SymbolType >
struct normalize < indexes::stringology::FactorOracleAutomaton < SymbolType > > {
	static indexes::stringology::FactorOracleAutomaton < > eval ( indexes::stringology::FactorOracleAutomaton < SymbolType > && value ) {
		ext::set < DefaultSymbolType > alphabet = alphabet::SymbolNormalize::normalizeAlphabet ( std::move ( std::move ( value ).getAutomaton ( ) ).getInputAlphabet ( ) );
		ext::set < unsigned > states = std::move ( std::move ( value ).getAutomaton ( ) ).getStates ( );
		unsigned initialState = std::move ( std::move ( value ).getAutomaton ( ) ).getInitialState ( );
		ext::set < unsigned > finalStates = std::move ( std::move ( value ).getAutomaton ( ) ).getFinalStates ( );

		automaton::DFA < DefaultStateType, unsigned > res ( std::move ( states ), std::move ( alphabet ), std::move ( initialState ), std::move ( finalStates ) );

		for ( std::pair < ext::pair < unsigned, SymbolType >, unsigned > && transition : ext::make_mover ( std::move ( std::move ( value ).getAutomaton ( ) ).getTransitions ( ) ) ) {
			unsigned from = transition.first.first;
			DefaultSymbolType input = alphabet::SymbolNormalize::normalizeSymbol ( std::move ( transition.first.second ) );
			unsigned to = transition.second;

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

		return indexes::stringology::FactorOracleAutomaton < > ( std::move ( res ) );
	}
};

} /* namespace core */

#endif /* FACTOR_ORACLE_AUTOMATON_H_ */
