/*
 * FMIndexCount.h
 *
 *  Created on: 27. 02. 2021
 *      Author: Martin Prokopic
 */

#pragma once

#include <alib/measure>

#include <string/LinearStringTerminatingSymbolBWT.h>
#include <indexes/stringology/SimpleFMIndex.h>
#include <stringology/common/FMIndexCommon.h>

namespace stringology {

namespace query {

class FMIndexCount {
public:
    /**
     * Return the lower (inclusive) and upper bounds (non-inclusive) of row indices in the cyclic shift table represented by fmIndex, which have pattern as a prefix. If lb == ub, then pattern is not present in the original (decoded) string.
     *
     * @tparam SymbolType
     * @param fmIndex the input Simple FM Index used for querying
     * @param pattern the pattern to search for
     * @return range <lb, ub) of rows in the cyclic shift table, which have pattern as a prefix
     */
    template < class FMIndex, class SymbolType >
    static std::pair < unsigned, unsigned > getBounds (const FMIndex & fmIndex,
                                                       const string::LinearString < SymbolType > & pattern);

    /**
     * Count the number of occurrences of pattern in the input string using Simple FM Index.
     *
     * @tparam SymbolType
     * @param fmIndex the input Simple FM Index used for querying
     * @param pattern the pattern to search for
     * @return number of occurrences of pattern in the original (decoded) string
     */
    template < class FMIndex, class SymbolType >
    static unsigned count (const FMIndex & fmIndex, const string::LinearString < SymbolType > & pattern);
};

template < class FMIndex, class SymbolType >
unsigned FMIndexCount::count ( const FMIndex & fmIndex,
                               const string::LinearString < SymbolType > & pattern ) {
    auto [ sp, ep ] = getBounds(fmIndex, pattern);
    return ep - sp;
}

template < class FMIndex, class SymbolType >
std::pair < unsigned, unsigned >
FMIndexCount::getBounds ( const FMIndex & fmIndex,
                          const string::LinearString < SymbolType > & pattern ) {
    if ( pattern.empty ( ))
        throw exception::CommonException ( "Pattern cannot be empty." );

    ext::set < SymbolType > unknownSymbols;
    std::set_difference ( pattern.getAlphabet ( ).begin ( ), pattern.getAlphabet ( ).end ( ),
                          fmIndex.getString().getAlphabet ( ).begin ( ), fmIndex.getString().getAlphabet ( ).end ( ),
                          std::inserter ( unknownSymbols, unknownSymbols.end ( )));
    if ( ! unknownSymbols.empty ( ))
        return std::make_pair (0, 0);

    measurements::start ( "FMIndexCount", measurements::Type::ALGORITHM );
    size_t i = pattern.size ( ) - 1;
    const auto & c = pattern.getContent ( )[ i ];
    size_t sp = stringology::common::FMIndexCommon::m(fmIndex, c);
    size_t ep = stringology::common::FMIndexCommon::mNext(fmIndex, c); // M[c+1]
    while ( sp < ep && i >= 1 ) {
        const auto & c2 = pattern.getContent ( )[ i - 1 ];
        sp = stringology::common::FMIndexCommon::m(fmIndex, c2) + stringology::common::FMIndexCommon::occ ( fmIndex, c2, sp - 1 );
        ep = stringology::common::FMIndexCommon::m(fmIndex, c2) + stringology::common::FMIndexCommon::occ ( fmIndex, c2, ep - 1 );
        i --;
    }
    measurements::end ( );

    return std::make_pair(sp, ep);
}

} /* namespace query */

} /* namespace stringology */

