//

// Created by hruskraj on 1. 5. 2020.
//

#pragma once

#include <alib/pair>
#include <alib/set>
#include <alib/vector>
#include <string/LinearString.h>

namespace stringology::cover {

/**
 * Auxiliary abstract class used to extract common functions used by both
 * ApproximateEnhancedCoversComputation and
 * RelaxedApproximateEnhancedCoversComputation.
 */
class ApproximateEnhancedCoversCommon {
public:
	/**
	 * The purpose of this virtual destructor is to make the whole class abstract.
	 */
	virtual ~ApproximateEnhancedCoversCommon ( ) = 0;

protected:
	/**
	 * A simple structure used to represent the element of d-subset corresponding
	 * to a state q of a nondeterministic approximate suffix automaton.
	 */
	struct Element {
		unsigned depth, level;
		Element ( ) = default;
		Element ( unsigned d, unsigned l ) : depth ( d ), level ( l ) { }
	};

	/**
	 * State of the deterministic trie-like k-approximate suffix automaton.
	 */
	struct State {
		 // depth is equal to the number of transitions from the starting state to
		 // this state
		unsigned depth = 0;

		 // Pair of two integers - depth of the element with level 0 and length of
		 // corresponding factor
		ext::pair < unsigned, unsigned > lfactor;

		 // d-subset of this state
		ext::vector < Element > elements;

		State ( ) = default;
		~State ( ) = default;

		 // default move constructor
		State ( State && other ) = default;

		 // default move assignment operator
		State & operator =( State && other ) = default;
	};

	/**
	 * Computes the number of covered positions by the given state.
	 *
	 * @param q state of the deterministic k-approximate suffix automaton
	 * @return the number of covered positions
	 */
	static unsigned distEnhCov ( const State & q ) {
		unsigned r = 0;
		unsigned m = q.elements[0].depth;

		for ( size_t i = 1; i < q.elements.size ( ); ++i ) {
			if ( q.elements[i].depth - q.elements[i - 1].depth < m )
				r += q.elements[i].depth - q.elements[i - 1].depth;
			else
				r += m;
		}

		return r + m;
	}

	/**
	 * Computes the number of covered positions by the given state and updates the
	 * set of found (relaxed) k-approximate enhanced covers if necessary.
	 *
	 * @param state
	 * @param enhCovers set of lfactors representing all found (relaxed)
	 * k-approximate enhanced covers
	 * @param h the maximum number of covered position
	 */
	static void updateEnhCov ( const State & state, ext::set < ext::pair < unsigned, unsigned > > & enhCovers, unsigned & h ) {
		unsigned hNext = distEnhCov ( state );

		 // update the current set if this state covers more positions
		if ( hNext > h ) {
			h = hNext;
			enhCovers.clear ( );
		}

		if ( hNext == h )
			enhCovers.insert ( state.lfactor );
	}

	/**
	 * Constructs the first state of deterministic k-approximate suffix automaton
	 * obtained by reading symbol "symbol".
	 *
	 * @param x the original string
	 * @param k the maximum number of allowed errors
	 * @param symbol the first read symbol
	 * @return the first state of deterministic k-approximate suffix automaton
	 */
	template < class SymbolType >
	static State constrFirstState ( const string::LinearString < SymbolType > & x, unsigned k, const SymbolType & symbol ) {
		State firstState;

		firstState.depth = 1;
		firstState.lfactor = ext::pair < unsigned, unsigned > ( 1, 1 );

		for ( size_t i = 0; i < x.getContent ( ).size ( ); ++i ) {
			if ( symbol == x.getContent ( )[i] )
				firstState.elements.emplace_back ( i + 1, 0 );
			else if ( k > 0 )
				firstState.elements.emplace_back ( i + 1, 1 );
		}

		return firstState;
	}

	/**
	 * Constructs the next state of deterministic k-approximate suffix automaton
	 * approachable by the previous state by reading the symbol "symbol".
	 *
	 * @param x the original string
	 * @param previousState
	 * @param k the maximum number of allowed errors
	 * @param symbol read symbol
	 * @return the next state of deterministic k-approximate suffix automaton
	 */
	template < class SymbolType >
	static State constrNextState ( const string::LinearString < SymbolType > & x, const State & previousState, unsigned k, const SymbolType & symbol ) {
		State nextState;

		nextState.depth = previousState.depth + 1;
		nextState.lfactor = previousState.lfactor;

		for ( const auto & element : previousState.elements )
			if ( element.depth < x.getContent ( ).size ( ) ) {
				if ( symbol == x.getContent ( )[element.depth] ) {
					nextState.elements.emplace_back ( element.depth + 1, element.level );
					nextState.lfactor = ext::pair < unsigned, unsigned > ( element.depth + 1, nextState.depth );
				} else if ( element.level < k ) {
					nextState.elements.emplace_back ( element.depth + 1, element.level + 1 );
				}
			}

		return nextState;
	}

	/**
	 * Checks if a string represented by the state of the
	 * k-approximate deterministic suffix automaton is an exact border.
	 *
	 * @param state
	 * @param x the original string
	 * @param k maximum number of allowed errors
	 * @return true if state represents border, false otherwise
	 */
	template < class SymbolType >
	static bool isBorder ( const State & state, const string::LinearString < SymbolType > & x, unsigned k ) {
		Element lastElement = state.elements[state.elements.size ( ) - 1];
		Element firstElement = state.elements[0];

		return firstElement.depth == state.depth && firstElement.level == 0 && lastElement.depth == x.getContent ( ).size ( ) && lastElement.level == 0 && state.depth > k;
	}

	/**
	 * Makes actual set of (relaxed) approximate enhanced covers from the set of
	 * lfactors, so the set of pairs of end position of factor and length of
	 * factor
	 *
	 * @param x the original string
	 * @param enhCovers set of pairs of end position of factor and length of
	 * factor
	 * @return the set of (relaxed) approximate enhanced covers
	 */
	template < class SymbolType >
	static ext::set < string::LinearString < SymbolType > > getFactors ( const string::LinearString < SymbolType > & x, ext::set < ext::pair < unsigned int, unsigned int > > & enhCovers ) {
		ext::set < string::LinearString < SymbolType > > result;

		for ( const auto & p : enhCovers )
			result.insert ( string::LinearString ( ext::vector < SymbolType > ( x.getContent ( ).cbegin ( ) + p.first - p.second, x.getContent ( ).cbegin ( ) + p.first ) ) );

		return result;
	}

};

} // namespace stringology::cover

