/*
 * LinearStringTerminatingSymbolBWT.h
 *
 *  Created on: 15. 2. 2021
 *      Author: Martin Prokopic
 */

#pragma once

#include <sstream>

#include <alib/set>
#include <alib/vector>
#include <alib/algorithm>

#include <core/components.hpp>

#include <alphabet/EndSymbol.h>
#include <common/DefaultSymbolType.h>

#include <exception/CommonException.h>

#include <core/normalize.hpp>
#include <alphabet/common/SymbolNormalize.h>
#include <alphabet/common/SymbolCompare.h>
#include <string/LinearString.h>

#include "LinearStringTerminatingSymbolBWT.h"

namespace string {

class GeneralAlphabet;

class TerminatingSymbol;

/**
 * \brief
 * Linear string with exactly one terminating symbol.

 * \details
 * S = (A, T, C),
 * A (Alphabet) = finite set of symbols,
 * T (Terminating symbol) = a terminating symbol, T \\in A,
 * C (Content) = representation of the string content
 *
 * \tparam SymbolType used for the terminal alphabet
 */
template < class SymbolType = DefaultSymbolType >
class LinearStringTerminatingSymbolBWT final
        : public core::Components < LinearStringTerminatingSymbolBWT < SymbolType >, ext::set < SymbolType >, component::Set, GeneralAlphabet, SymbolType, component::Value, TerminatingSymbol > {
    /**
     * Representation of the string content.
     */
    ext::vector < SymbolType > m_Data;

public:

    /**
     * \brief Creates a new instance of the string with a concrete alphabet, content and end symbol.
     *
     * \param alphabet the initial alphabet of the string
     * \param terminatingSymbol the explicit terminating symbol
     * \param str the initial content of the string
     */
    explicit LinearStringTerminatingSymbolBWT ( ext::set < SymbolType > alphabet, SymbolType terminatingSymbol,
                                                ext::vector < SymbolType > str );

    /**
     * \brief Creates a new instance from a copy of a LinearString and an explicit end symbol.
     *
     * \param string the linear string to copy the contents from
     * \param terminatingSymbol the explicit wildcard symbol
     */
    explicit LinearStringTerminatingSymbolBWT ( SymbolType terminatingSymbol,
                                                const LinearString <SymbolType> & string );

    /**
     * Getter of the alphabet.
     *
     * \returns the alphabet of the string
     */
    const ext::set < SymbolType > & getAlphabet ( ) const & {
        return this->template accessComponent < GeneralAlphabet > ( ).get ( );
    }

    /**
     * Getter of the alphabet.
     *
     * \returns the alphabet of the string
     */
    ext::set < SymbolType > && getAlphabet ( ) && {
        return std::move ( this->template accessComponent < GeneralAlphabet > ( ).get ( ));
    }

    /**
     * Compute and get alphabet with the correct order (using alphabet::common::SymbolCompare).
     *
     * \returns the correctly ordered alphabet
     */
    ext::set < SymbolType, alphabet::SymbolCompare < SymbolType > > getOrderedAlphabet ( ) const {
        return ext::set < SymbolType, alphabet::SymbolCompare < SymbolType > >(getAlphabet().begin(), getAlphabet().end());
    }

    /**
     * Adder of an alphabet symbols.
     *
     * \param symbols the new symbols to be added to the alphabet
     */
    void extendAlphabet ( const ext::set < SymbolType > & symbols ) {
        this->template accessComponent < GeneralAlphabet > ( ).add ( symbols );
    }

    /**
     * Getter of the end symbol
     *
     * \returns the wildcard symbol of the string
     */
    const SymbolType & getTerminatingSymbol ( ) const & {
        return this->template accessComponent < TerminatingSymbol > ( ).get ( );
    }

    /**
     * Getter of the end symbol
     *
     * \returns the wildcard symbol of the string
     */
    SymbolType && getTerminatingSymbol ( ) && {
        return std::move ( this->template accessComponent < TerminatingSymbol > ( ).get ( ));
    }

    /**
     * Setter of the end symbol
     *
     * \param end new end symbol of the string
     *
     * \returns true if the end symbol was indeed changed
     */
    bool setTerminatingSymbol ( SymbolType end ) {
        return this->template accessComponent < TerminatingSymbol > ( ).set ( std::move ( end ));
    }

    /**
     * Appends symbol at th end of the string.
     *
     * \param symbol the symbol to be added to the end of the string
     */
    void appendSymbol ( SymbolType symbol );

    /**
     * Getter of the string content.
     *
     * \return List of symbols forming string.
     */
    const ext::vector < SymbolType > & getContent ( ) const &;

    /**
     * Getter of the string content.
     *
     * \return List of symbols forming string.
     */
    ext::vector < SymbolType > && getContent ( ) &&;

    /**
     * Setter of the string content.
     *
     * \throws CommonException when new string contains symbols not present in the alphabet
     * \throws CommonException when new string doesn't contain exactly one end symbol
     *
     * \param new List of symbols forming string.
     */
    void setContent ( ext::vector < SymbolType > str );

    /**
     * Test function to determine whether the string is empty
     *
     * \return true if string is an empty word (vector length is 0)
     */
    bool empty ( ) const;

    /**
     * Returns length of the content.
     *
     * \return the length of the string.
     */
    size_t size ( ) const;

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

    /**
     * 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 LinearStringTerminatingSymbolBWT & other ) const {
        return std::tie ( m_Data, getAlphabet ( ), getTerminatingSymbol( ) ) == std::tie ( other.m_Data, other.getAlphabet ( ), other.getTerminatingSymbol ( ) );
    }

    /**
     * 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 LinearStringTerminatingSymbolBWT & instance ) {
        out << "(LinearStringTerminatingSymbolBWT";
        out << " content = " << instance.getContent ( );
        out << " alphabet = " << instance.getAlphabet ( );
        out << " terminatingSymbol = " << instance.getTerminatingSymbol ( );
        out << ")";
        return out;
    }

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

};

template < class SymbolType = DefaultSymbolType >
string::LinearString < SymbolType > toLinearString ( const LinearStringTerminatingSymbolBWT < SymbolType > & str ) {
    return LinearString ( str.getAlphabet ( ), str.getContent ( ));
}

template < class SymbolType >
LinearStringTerminatingSymbolBWT < SymbolType >::LinearStringTerminatingSymbolBWT ( ext::set < SymbolType > alphabet,
                                                                                    SymbolType terminatingSymbol,
                                                                                    ext::vector < SymbolType > str )
        : core::Components < LinearStringTerminatingSymbolBWT, ext::set < SymbolType >, component::Set, GeneralAlphabet, SymbolType, component::Value, TerminatingSymbol > (
        std::move ( alphabet ), std::move ( terminatingSymbol )) {
    setContent ( std::move ( str ));
}

template < class SymbolType >
LinearStringTerminatingSymbolBWT < SymbolType >::LinearStringTerminatingSymbolBWT (
        SymbolType terminatingSymbol, const LinearString <SymbolType> & string )
        : LinearStringTerminatingSymbolBWT ( string.getAlphabet ( ) + ext::set < SymbolType >{ terminatingSymbol },
                                             terminatingSymbol,
                                             string.getContent ( )) {
}

template < class SymbolType >
void LinearStringTerminatingSymbolBWT < SymbolType >::appendSymbol ( SymbolType symbol ) {
    if ( getAlphabet ( ).count ( symbol ) == 0 )
        throw exception::CommonException ( "Input symbol \"" + ext::to_string ( symbol ) + "\" not in the alphabet." );

    m_Data.push_back ( std::move ( symbol ));
}

template < class SymbolType >
const ext::vector < SymbolType > & LinearStringTerminatingSymbolBWT < SymbolType >::getContent ( ) const & {
    return this->m_Data;
}

template < class SymbolType >
ext::vector < SymbolType > && LinearStringTerminatingSymbolBWT < SymbolType >::getContent ( ) && {
    return std::move ( this->m_Data );
}

template < class SymbolType >
void LinearStringTerminatingSymbolBWT < SymbolType >::setContent ( ext::vector < SymbolType > str ) {
    ext::set < SymbolType > minimalAlphabet ( str.begin ( ), str.end ( ));
    ext::set < SymbolType > unknownSymbols;
    std::set_difference ( minimalAlphabet.begin ( ), minimalAlphabet.end ( ), getAlphabet ( ).begin ( ),
                          getAlphabet ( ).end ( ), std::inserter ( unknownSymbols, unknownSymbols.end ( )));

    if ( ! unknownSymbols.empty ( ))
        throw exception::CommonException ( "Input symbols not in the alphabet." );

    size_t terminatingSymbolCount = 0;
    for ( const auto & symbol : str ) {
        if ( symbol == getTerminatingSymbol ( ))
            terminatingSymbolCount ++;
    }

    if ( terminatingSymbolCount == 0 )
        throw exception::CommonException ( "Input doesn't contain terminating symbol." );
    else if ( terminatingSymbolCount > 1 )
        throw exception::CommonException ( "Input contains multiple terminating symbols." );

    this->m_Data = std::move ( str );
}

template < class SymbolType >
bool LinearStringTerminatingSymbolBWT < SymbolType >::empty ( ) const {
    return this->m_Data.empty ( );
}

template < class SymbolType >
size_t LinearStringTerminatingSymbolBWT < SymbolType >::size ( ) const {
    return this->m_Data.size ( );
}

template < class SymbolType >
LinearStringTerminatingSymbolBWT < SymbolType >::operator std::string ( ) const {
    std::stringstream ss;
    ss << "\"";

    for ( const SymbolType & symbol : this->m_Data )
        ss << symbol;

    ss << "\"";
    return std::move ( ss ).str ( );
}

} /* namespace string */

namespace core {

/**
 * Helper class specifying constraints for the string's internal alphabet component.
 *
 * \tparam SymbolType used for the alphabet of the string.
 */
template < class SymbolType >
class SetConstraint < string::LinearStringTerminatingSymbolBWT < SymbolType >, SymbolType, string::GeneralAlphabet > {
public:
    /**
     * Returns true if the symbol is still used in the string.
     *
     * \param string the tested string
     * \param symbol the tested symbol
     *
     * \returns true if the symbol is used, false othervise
     */
    static bool
    used ( const string::LinearStringTerminatingSymbolBWT < SymbolType > & str, const SymbolType & symbol ) {
        const ext::vector < SymbolType > & content = str.getContent ( );
        return std::find ( content.begin ( ), content.end ( ), symbol ) != content.end ( );
    }

    /**
     * Returns true as all symbols are possibly available to be in an alphabet.
     *
     * \param string the tested string
     * \param symbol the tested symbol
     *
     * \returns true
     */
    static bool available ( const string::LinearStringTerminatingSymbolBWT < SymbolType > &, const SymbolType & ) {
        return true;
    }

    /**
     * All symbols are valid as symbols of an alphabet.
     *
     * \param string the tested string
     * \param symbol the tested symbol
     */
    static void valid ( const string::LinearStringTerminatingSymbolBWT < SymbolType > &, const SymbolType & ) {
    }
};

/**
 * Helper class specifying constraints for the string's internal end element.
 *
 * \tparam SymbolType used for the alphabet of the string.
 */
template < class SymbolType >
class ElementConstraint < string::LinearStringTerminatingSymbolBWT < SymbolType >, SymbolType, string::TerminatingSymbol > {
public:
    /**
     * Determines whether the symbol is available in the string's alphabet.
     *
     * \param string the tested string
     * \param symbol the tested symbol
     *
     * \returns true if the symbol is already in the alphabet of the string
     */
    static bool
    available ( const string::LinearStringTerminatingSymbolBWT < SymbolType > & string, const SymbolType & symbol ) {
        return string.getAlphabet ( ).count ( symbol );
    }

    /**
     * All symbols are valid as an initial end symbol of the string.
     *
     * \param string the tested string
     * \param symbol the tested symbol
     */
    static void valid ( const string::LinearStringTerminatingSymbolBWT < SymbolType > &, const SymbolType & ) {
    }

};

/**
 * Helper for normalisation of types specified by templates used as internal datatypes of symbols.
 *
 * \returns new instance of the string with default template parameters or unmodified instance if the template parameters were already default ones
 */
template < class SymbolType >
struct normalize < string::LinearStringTerminatingSymbolBWT < SymbolType > > {
    static string::LinearStringTerminatingSymbolBWT < >
    eval ( string::LinearStringTerminatingSymbolBWT < SymbolType > && value ) {
        ext::set < DefaultSymbolType > alphabet = alphabet::SymbolNormalize::normalizeAlphabet (
                std::move ( value ).getAlphabet ( ));
        ext::vector < DefaultSymbolType > content = alphabet::SymbolNormalize::normalizeSymbols (
                std::move ( value ).getContent ( ));
        DefaultSymbolType end = alphabet::SymbolNormalize::normalizeSymbol (
                std::move ( value ).getTerminatingSymbol ( ));
        return string::LinearStringTerminatingSymbolBWT < > ( std::move ( alphabet ), std::move ( end ),
                                                              std::move ( content ));
    }
};

} /* namespace core */

extern template
class string::LinearStringTerminatingSymbolBWT < >;
