/*
 * BWTAuxillaryArraysGenerate.h
 *
 *  Created on: 16. 02. 2021
 *      Author: Martin Prokopic
 */

#pragma once

#include <indexes/stringology/BWTAuxiliaryData.h>
#include <string/LinearStringTerminatingSymbolBWT.h>
#include <alib/measure>

namespace stringology {

namespace indexing {

class BWTAuxiliaryDataConstruct {
    template < class SymbolType >
    static void constructMArray ( indexes::stringology::BWTAuxiliaryData < SymbolType > & data );

    template < class SymbolType >
    static void constructDecodingArrays ( indexes::stringology::BWTAuxiliaryData < SymbolType > & data,
                                          const string::LinearStringTerminatingSymbolBWT < SymbolType > & str );

    template < class SymbolType >
    static void constructMappingArrays ( indexes::stringology::BWTAuxiliaryData < SymbolType > & data,
                                         const string::LinearStringTerminatingSymbolBWT < SymbolType > & str );

public:
    /**
     * Construct auxiliary data specified by dataTypeBitmask for BWT-encoded string str.
     *
     * @tparam SymbolType
     * @param str the input BWT-encoded string
     * @param dataTypeBitmask the data types to construct, a bitmask of BWTAuxiliaryDataType items
     * @return the constructed auxiliary data
     */
    template < class SymbolType >
    static indexes::stringology::BWTAuxiliaryData < SymbolType > construct (
            const string::LinearStringTerminatingSymbolBWT < SymbolType > & str,
            unsigned dataTypeBitmask );

    /**
     * Construct all auxiliary data for BWT-encoded string str.
     *
     * @tparam SymbolType
     * @param str the input BWT-encoded string
     * @return the constructed auxiliary data
     */
    template < class SymbolType >
    static indexes::stringology::BWTAuxiliaryData < SymbolType > construct (
            const string::LinearStringTerminatingSymbolBWT < SymbolType > & str ) {
        return construct ( str, indexes::stringology::BWTAuxiliaryDataType::ALL );
    }
};

template < class SymbolType >
indexes::stringology::BWTAuxiliaryData < SymbolType >
BWTAuxiliaryDataConstruct::construct ( const string::LinearStringTerminatingSymbolBWT < SymbolType > & str,
                                       unsigned dataTypeBitmask ) {
    indexes::stringology::BWTAuxiliaryData < SymbolType > data;

    measurements::start("BWTAuxiliaryDataConstruct", measurements::Type::AUXILIARY);

    measurements::start("OrderedAlphabet, K & C counting arrays", measurements::Type::AUXILIARY);
    // reorder alphabet and initialize m_kOcc
    data.setOrderedAlphabet ( str.getOrderedAlphabet ( ));
    ext::map < SymbolType, unsigned > kOcc;
    for ( const auto & symbol : data.getOrderedAlphabet ( ))
        kOcc[ symbol ] = 0;

    ext::vector < unsigned > cPrefixOcc ( str.size ( ));
    for ( size_t i = 0; i < str.size ( ); i ++ ) {
        const auto & symbol = str.getContent ( )[ i ];
        cPrefixOcc[ i ] = kOcc[ symbol ];
        kOcc[ symbol ] ++;
        if ( symbol == str.getTerminatingSymbol ( ))
            data.setTerminatingSymbolIndex ( i );
    }
    data.setKOcc ( kOcc );
    data.setCPrefixOcc ( cPrefixOcc );
    measurements::end();

    if ( dataTypeBitmask & ( indexes::stringology::BWTAuxiliaryDataType::V_REVERSE_DECODE |
                             indexes::stringology::BWTAuxiliaryDataType::W_FORWARD_DECODE ))
        constructDecodingArrays ( data, str );

    if ( dataTypeBitmask & indexes::stringology::BWTAuxiliaryDataType::M_OCC_PREFIX_SUM )
        constructMArray ( data );

    if ( dataTypeBitmask & ( indexes::stringology::BWTAuxiliaryDataType::HR_TEXT_TO_FIRST |
                             indexes::stringology::BWTAuxiliaryDataType::I_FIRST_TO_TEXT ))
        constructMappingArrays ( data, str );

    measurements::end();

    return data;
}

template < class SymbolType >
inline void
BWTAuxiliaryDataConstruct::constructMArray ( indexes::stringology::BWTAuxiliaryData < SymbolType > & data ) {
    if ( data.isDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::M_OCC_PREFIX_SUM ))
        return;

    measurements::start("M counting array", measurements::Type::AUXILIARY);
    ext::map < SymbolType, unsigned > mOccPrefixSum;
    unsigned sum = 0;
    for ( const auto & symbol : data.getOrderedAlphabet ( )) {
        mOccPrefixSum[ symbol ] = sum; // radix sort of str
        sum += data.getKOcc ( ).at( symbol );
    }
    data.setMOccPrefixSum ( mOccPrefixSum );
    data.setDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::M_OCC_PREFIX_SUM );
    measurements::end();
}

template < class SymbolType >
void BWTAuxiliaryDataConstruct::constructDecodingArrays ( indexes::stringology::BWTAuxiliaryData < SymbolType > & data,
                                                          const string::LinearStringTerminatingSymbolBWT < SymbolType > & str ) {
    constructMArray ( data );

    measurements::start("V & W decoding arrays", measurements::Type::AUXILIARY);
    ext::vector < unsigned > vReverseDecode ( str.size ( ));
    ext::vector < unsigned > wForwardDecode ( str.size ( ));
    size_t i = data.getTerminatingSymbolIndex ( );
    for ( size_t j = str.size(); j -- > 0; ) { // loop through original string in reverse
        const auto & symbol = str.getContent ( )[ i ];
        size_t prevIndex = data.getCPrefixOcc ( )[ i ] + data.getMOccPrefixSum ( ).at( symbol );
        vReverseDecode[ i ] = prevIndex;
        wForwardDecode[ prevIndex ] = i;
        i = prevIndex;
    }
    data.setVReverseDecode ( vReverseDecode );
    data.setWForwardDecode ( wForwardDecode );
    data.setDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::V_REVERSE_DECODE );
    data.setDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::W_FORWARD_DECODE );
    measurements::end();
}

template < class SymbolType >
void BWTAuxiliaryDataConstruct::constructMappingArrays ( indexes::stringology::BWTAuxiliaryData < SymbolType > & data,
                                                         const string::LinearStringTerminatingSymbolBWT < SymbolType > & str ) {
    constructMArray ( data );

    // modified algorithm to construct I and Hr from C and M (without modifying M)
    measurements::start("Hr & I mapping arrays", measurements::Type::AUXILIARY);
    ext::vector < unsigned > hrTextToFirst ( str.size ( ));
    ext::vector < unsigned > iFirstToText ( str.size ( ));
    size_t i = data.getTerminatingSymbolIndex ( );
    for ( size_t j = str.size(); j -- > 0; ) { // loop through original string in reverse
        const auto & symbol = str.getContent ( )[ i ];
        size_t firstIndex = ( j + 1 ) % str.size ( ); // wrap around from last to first column of shift matrix
        hrTextToFirst[ firstIndex ] = i;
        iFirstToText[ i ] = firstIndex;
        i = data.getCPrefixOcc ( )[ i ] + data.getMOccPrefixSum ( ).at( symbol );
    }
    data.setHrTextToFirst ( hrTextToFirst );
    data.setIFirstToText ( iFirstToText );
    data.setDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::HR_TEXT_TO_FIRST );
    data.setDataTypePresent ( indexes::stringology::BWTAuxiliaryDataType::I_FIRST_TO_TEXT );
    measurements::end();
}

} /* namespace properties */

} /* namespace stringology */

