/*
 * SuffixAutomaton.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
 */

#pragma once

#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
 * Suffix automaton string index. Automaton representation of all suffixes. The automaton is general deterministic automaton. The class does not checks whether the automaton actually is a suffix automaton. The index alphabet is stored within the automaton.
 *
 * \tparam SymbolType type of symbols of indexed string
 */
template < class SymbolType = DefaultSymbolType >
class SuffixAutomaton final {
	/**
	 * Representation of the suffix automaton.
	 */
	automaton::DFA < SymbolType, unsigned > m_automaton;

	/**
	 * Additional information about the backbone length, i.e. the length of the indexed string.
	 */
	unsigned m_backboneLength;

public:
	/**
	 * Creates a new instance of the index with concrete suffix automaton and backbone length.
	 *
	 * \param automaton the suffix automaton representing the index
	 * \param backbone the length of the indexed string or the longest path in the automaton.
	 */
	explicit SuffixAutomaton ( automaton::DFA < SymbolType, unsigned > automaton, unsigned backboneLength );

	/**
	 * Getter of the underlying suffix automaton.
	 *
	 * \return raw automatn representing the index.
	 */
	const automaton::DFA < SymbolType, unsigned > & getAutomaton ( ) const &;

	/**
	 * Getter of the underlying suffix automaton.
	 *
	 * \return raw automatn representing the index.
	 */
	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.
	 *
	 * \param symbol a symbol to remove.
	 */
	bool removeSymbolFromAlphabet ( const SymbolType & symbol ) {
		return m_automaton.removeInputSymbol ( symbol );
	}

	/**
	 * Getter of the backbone length.
	 *
	 * \return the backbone length of the automaton
	 */
	unsigned getBackboneLength ( ) const {
		return m_backboneLength;
	}

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

	/**
	 * 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 SuffixAutomaton & other ) const {
		return std::tie ( getAutomaton ( ) ) == std::tie ( other.getAutomaton ( ) );
	}

	/**
	 * 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 SuffixAutomaton & instance ) {
		return out << "(SuffixAutomaton " << 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 raw automaton.
	 *
	 * \return the underlying raw deterministic automaton.
	 */
	explicit operator automaton::DFA < SymbolType, unsigned > ( ) const;
};

} /* namespace stringology */

} /* namespace indexes */

namespace indexes {

namespace stringology {

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

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

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

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

template < class SymbolType >
SuffixAutomaton < 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::SuffixAutomaton < SymbolType > > {
	static indexes::stringology::SuffixAutomaton < > eval ( indexes::stringology::SuffixAutomaton < 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 ), 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::SuffixAutomaton < > ( std::move ( res ), std::move ( value ).getBackboneLength ( ) );
	}
};

} /* namespace core */

