/*
 * SimpleFMIndexConstruct.h
 *
 *  Created on: 26. 02. 2021
 *      Author: Martin Prokopic
 */

#pragma once

#include <alib/vector>

#include <string/LinearStringTerminatingSymbolBWT.h>
#include <indexes/stringology/SimpleFMIndex.h>
#include "BWTAuxiliaryDataConstruct.h"

namespace stringology {

namespace indexing {

class SimpleFMIndexConstruct {
    /**
     * Find a symbol with the closest relative occurrence in % to fttDensityPct.
     */
    template < class SymbolType >
    static SymbolType
    getMarkedSymbol ( unsigned stringSize, const ext::map < SymbolType, unsigned > & kOcc, unsigned fttDensityPct );

public:
    static const unsigned DEFAULT_SUPER_BUCKET_SIZE = 16000;
    static const unsigned DEFAULT_BUCKET_SIZE = 1000;
    static const unsigned DEFAULT_FTT_DENSITY_PCT = 2;

    /**
     * Construct fat Simple FM Index from BWT-encoded string using default bucket sizes and marker density.
     *
     * @tparam SymbolType
     * @param str the input BWT-encoded string
     * @return the Simple FM Index instance
     */
    template < class SymbolType >
    static indexes::stringology::SimpleFMIndex < SymbolType >
    construct ( const string::LinearStringTerminatingSymbolBWT < SymbolType > & str ) {
        return construct ( str, DEFAULT_SUPER_BUCKET_SIZE, DEFAULT_BUCKET_SIZE, DEFAULT_FTT_DENSITY_PCT );
    }

    /**
     * Construct Simple FM Index from BWT-encoded string using custom bucket size and marker density.
     *
     * @tparam SymbolType
     * @param str the input BWT-encoded string
     * @param bucketSize the non-zero bucket size
     * @param superBucketSize the non-zero super bucket size. Must be a multiple of bucketSize.
     * @param fttDensityPct wanted density of marked rows in %. If 0, the index will support only the Count operation.
     * @return the Simple FM Index instance
     */
    template < class SymbolType >
    static indexes::stringology::SimpleFMIndex < SymbolType >
    construct ( const string::LinearStringTerminatingSymbolBWT < SymbolType > & str,
                unsigned superBucketSize, unsigned bucketSize, unsigned fttDensityPct ) {
        return construct ( str,
                           superBucketSize, bucketSize, fttDensityPct,
                           stringology::indexing::BWTAuxiliaryDataConstruct::construct ( str,
                                                                                         indexes::stringology::BWTAuxiliaryDataType::M_OCC_PREFIX_SUM |
                                                                                         indexes::stringology::BWTAuxiliaryDataType::I_FIRST_TO_TEXT ));
    }

    /**
     * Construct Simple FM Index from BWT-encoded string using custom bucket size and marker density, reusing precomputed auxiliary data.
     * Required data types are M_OCC_PREFIX_SUM and I_FIRST_TO_TEXT (if fttDensityPct > 0, thus creating a fat index).
     *
     * @tparam SymbolType
     * @param str the input BWT-encoded string
     * @param bucketSize the non-zero bucket size
     * @param superBucketSize the non-zero super bucket size. Must be a multiple of bucketSize.
     * @param fttDensityPct wanted density of marked rows in %. If 0, the index will support only the Count operation.
     * @param data precomputed auxiliary data
     * @return the Simple FM Index instance
     */
    template < class SymbolType >
    static indexes::stringology::SimpleFMIndex < SymbolType >
    construct ( const string::LinearStringTerminatingSymbolBWT < SymbolType > & str, unsigned superBucketSize,
                unsigned bucketSize, unsigned fttDensityPct,
                const indexes::stringology::BWTAuxiliaryData < SymbolType > & data );
};

template < class SymbolType >
indexes::stringology::SimpleFMIndex < SymbolType >
SimpleFMIndexConstruct::construct ( const string::LinearStringTerminatingSymbolBWT < SymbolType > & str,
                                    unsigned superBucketSize,
                                    unsigned bucketSize, unsigned fttDensityPct,
                                    const indexes::stringology::BWTAuxiliaryData < SymbolType > & data ) {
    if ( ! data.isDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::M_OCC_PREFIX_SUM ))
        throw exception::CommonException ( "Constructing Simple FM Index requires M array to be present." );

    if ( fttDensityPct > 100 )
        throw exception::CommonException ( "fttDensityPct cannot be higher than 100." );

    if ( fttDensityPct > 0 && ! data.isDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::I_FIRST_TO_TEXT ))
        throw exception::CommonException ( "Constructing fat Simple FM Index requires I array to be present." );

    if ( bucketSize == 0 )
        throw exception::CommonException ( "bucketSize must be non-zero." );

    if ( superBucketSize < bucketSize || superBucketSize % bucketSize != 0 )
        throw exception::CommonException ( "superBucketSize must be a >= 1 multiple of bucketSize." );

    measurements::start ( "SimpleFMIndexConstruct", measurements::Type::ALGORITHM );

    SymbolType markedSymbol = getMarkedSymbol ( str.size ( ), data.getKOcc ( ), fttDensityPct );
    ext::vector < unsigned > sparseFirstToText;

    ext::vector < ext::map < SymbolType, unsigned > > superBucketOcc;
    ext::vector < ext::map < SymbolType, unsigned > > bucketOcc;

    ext::map < SymbolType, unsigned > nextSuperBucketOcc;
    ext::map < SymbolType, unsigned > nextBucketOcc;
    for ( size_t i = 0; i < str.size ( ); i ++ ) {
        if ( i % superBucketSize == 0 ) {
            // start new superBucket and bucket
            superBucketOcc.push_back ( nextSuperBucketOcc );
            bucketOcc.push_back ( ext::map < SymbolType, unsigned > ( ) );
            nextBucketOcc = ext::map < SymbolType, unsigned > ( );
        } else if ( i % bucketSize == 0 ) {
            // start new bucket
            bucketOcc.push_back ( nextBucketOcc );
        }

        const auto & symbol = str.getContent ( )[ i ];
        auto[it, inserted] = nextSuperBucketOcc.insert ( symbol, 1 );
        if ( ! inserted )
            it->second ++;
        auto[it2, inserted2] = nextBucketOcc.insert ( symbol, 1 );
        if ( ! inserted2 )
            it2->second ++;

        if ( fttDensityPct > 0 && symbol == markedSymbol )
            sparseFirstToText.push_back ( data.getIFirstToText ( )[ i ] );
    }

    measurements::end ( );

    return indexes::stringology::SimpleFMIndex < SymbolType > ( str, superBucketSize, bucketSize,
                                                                superBucketOcc, bucketOcc,
                                                                data.getMOccPrefixSum ( ), data.getKOcc ( ),
                                                                markedSymbol, sparseFirstToText );
}

template < class SymbolType >
SymbolType
SimpleFMIndexConstruct::getMarkedSymbol ( unsigned int stringSize, const ext::map < SymbolType, unsigned int > & kOcc,
                                          unsigned int fttDensityPct ) {
    const SymbolType * closestSymbol = nullptr;
    long closestPct;

    for ( auto & [symbol, occ] : kOcc ) {
        long pct = occ * 100 / stringSize;
        if ( closestSymbol == nullptr ||
             ( std::abs ( fttDensityPct - pct ) < std::abs ( fttDensityPct - closestPct ) && occ > 0 )) {
            closestPct = pct;
            closestSymbol = &symbol;
        }
    }

    return *closestSymbol;
}

} /* namespace indexing */

} /* namespace stringology */



