/*
 * bwtperftest.cpp
 *
 *  Created on: 01. 03. 2021
 *      Author: Martin Prokopic
 */

#include <filesystem>
#include <alib/measure>
#include <alib/vector>

#include <object/Void.h>
#include <factory/RawDataFactory.hpp>
#include <string/raw/LinearString.h>
#include <string/LinearString.h>
#include <stringology/transform/BWTNaive.h>
#include <string/generate/RandomSubstringFactory.h>
#include <stringology/exact/BWTBoyerMoore.h>

#include <configure_tests.hpp>
#include <stringology/indexing/SuffixArrayNaive.h>
#include <stringology/indexing/SuffixArrayFromBWTNaive.h>
#include <stringology/transform/BWTFromSuffixArrayNaive.h>
#include <stringology/query/SuffixArrayFactors.h>
#include <stringology/exact/BWTBinarySearch.h>
#include <stringology/exact/BoyerMoore.h>
#include <stringology/exact/ExactFactorMatch.h>
#include <stringology/transform/BWTDecodeNaive.h>
#include <stringology/indexing/SimpleFMIndexConstruct.h>
#include <stringology/query/FMIndexCount.h>
#include <stringology/query/FMIndexFactors.h>
#include "helpers.h"
#include "algorithms.h"

using std::filesystem::path;
using object::Object;
using object::Void;
using factory::RawDataFactory;
using string::LinearString;
using string::LinearStringTerminatingSymbolBWT;
using string::generate::RandomSubstringFactory;
using indexes::stringology::BWTAuxiliaryDataType;
using stringology::transform::BWTFromSuffixArrayNaive;
using stringology::transform::BWTDecodeNaive;
using stringology::indexing::SuffixArrayNaive;
using stringology::indexing::SimpleFMIndexConstruct;
using stringology::indexing::BWTAuxiliaryDataConstruct;
using stringology::indexing::SuffixArrayFromBWTNaive;
using stringology::exact::BoyerMoore;
using stringology::exact::BWTBoyerMoore;
using stringology::exact::BWTBinarySearch;
using stringology::query::SuffixArrayFactors;
using stringology::query::FMIndexCount;
using stringology::query::FMIndexFactors;

auto dataDir = path ( CMAKE_CURRENT_SOURCE_DIR ) / "data";

const size_t NUM_ITERATIONS = 20;
const size_t MAX_NUM_PATTERNS = 100;
const size_t MIN_PATTERN_LENGTH = 4;
const size_t MAX_PATTERN_LENGTH = 100;
const size_t DEFAULT_PATTERN_LENGTH = 20;

template < class SymbolType >
void locateTimeByFttDensity(const LinearString < SymbolType > & subject ) {
    auto bwt = BWTFromSuffixArrayNaive::construct ( SuffixArrayNaive::construct ( subject ));

    ext::set < unsigned > densities;
    {
        auto data = BWTAuxiliaryDataConstruct::construct ( bwt );
        for(auto [symbol, count] : data.getKOcc())
            densities.insert(count * 100 / bwt.size());
    }

    ext::vector < LinearString < SymbolType > > patterns;
    for ( unsigned i = 0; i < NUM_ITERATIONS; i ++ )
        patterns.push_back (
                RandomSubstringFactory::generateSubstring ( helpers::rand ( MIN_PATTERN_LENGTH, MAX_PATTERN_LENGTH ), subject ));


    for(unsigned fttDensityPct : densities) {
        measurements::reset ( );
        measurements::start ( "Overall", measurements::Type::OVERALL );
        auto fmIndex = SimpleFMIndexConstruct::construct ( bwt, SimpleFMIndexConstruct::DEFAULT_SUPER_BUCKET_SIZE, SimpleFMIndexConstruct::DEFAULT_BUCKET_SIZE, fttDensityPct );
        measurements::end ( );
        helpers::writeResults ( "Overall",
                                ext::vector < Object >{ Object("locateTimeByFttDensity"), Object ( "SimpleFMIndexConstruct" ), Object ( fttDensityPct ),
                                                        Object ( - 1 ) } );

        for ( size_t iter = 0; iter < NUM_ITERATIONS; iter ++ ) {
            measurements::reset();
            measurements::start ( "Overall", measurements::Type::OVERALL );
            FMIndexFactors::match(fmIndex, patterns[iter]);
            measurements::end ( );
            helpers::writeResults ( "Overall",
                                    ext::vector < Object >{ Object("locateTimeByFttDensity"), Object ( "FMIndexFactors" ), Object ( fttDensityPct ),
                                                            Object ( iter ) } );
        }
    }
}

template < class SymbolType >
void timeByNumPatterns ( const LinearString < SymbolType > & subject, unsigned patternLength ) {
    auto bwt = BWTFromSuffixArrayNaive::construct ( SuffixArrayNaive::construct ( subject ));
    auto fmIndex = SimpleFMIndexConstruct::construct ( bwt );

    for ( size_t numPatterns = 1; numPatterns <= MAX_NUM_PATTERNS; numPatterns ++ ) {
        for ( size_t iter = 0; iter < NUM_ITERATIONS; iter ++ ) {
            ext::vector < LinearString < SymbolType > > patterns;
            for ( unsigned i = 0; i < numPatterns; i ++ )
                patterns.push_back (
                        RandomSubstringFactory::generateSubstring ( patternLength, subject ));
            ext::vector < Object > info = {
                    Object ( __func__ ), Object ( numPatterns ), Object ( patternLength ), Object ( iter )
            };

            algorithms::measureDecodeBoyerMoore ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTBoyerMoore ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTBinarySearch ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTSuffixArray ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureFMIndexCount ( fmIndex, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureFMIndexFactors ( fmIndex, patterns.begin ( ), patterns.end ( ), info );
        }
    }
}

template < class SymbolType>
void timeByPatternLength(const LinearString < SymbolType > & subject) {
    auto bwt = BWTFromSuffixArrayNaive::construct ( SuffixArrayNaive::construct ( subject ));
    auto fmIndex = SimpleFMIndexConstruct::construct ( bwt );

    for ( size_t patternLength = 1; patternLength <= MAX_PATTERN_LENGTH; patternLength ++ ) {
        for ( size_t iter = 0; iter < NUM_ITERATIONS; iter ++ ) {
            ext::array < string::LinearString < SymbolType >, 1 > patterns{
                    RandomSubstringFactory::generateSubstring ( patternLength, subject ) };
            ext::vector < Object > info = {
                    Object ( __func__ ), Object ( patternLength ),
                    Object ( static_cast<std::string>(patterns.front ( ))), Object ( iter )
            };

            algorithms::measureDecodeBoyerMoore ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTBoyerMoore ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTBinarySearch ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTSuffixArray ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureFMIndexCount ( fmIndex, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureFMIndexFactors ( fmIndex, patterns.begin ( ), patterns.end ( ), info );
        }
    }
}

template < class SymbolType >
void timeBySubjectLength ( const LinearString < SymbolType > & subject, unsigned patternLength ) {
    for ( size_t subjectLength = patternLength; subjectLength <= subject.size ( ); subjectLength += 10 ) {
        auto slice = string::LinearString ( subject.getAlphabet ( ),
                                            ext::vector < SymbolType > ( subject.getContent ( ).begin ( ),
                                                                         subject.getContent ( ).begin ( ) +
                                                                         subjectLength ));
        measurements::reset();
        measurements::start("Overall", measurements::Type::OVERALL);
        measurements::start("BWTConstruct", measurements::Type::ALGORITHM);
        auto bwt = BWTFromSuffixArrayNaive::construct ( SuffixArrayNaive::construct ( slice ));
        measurements::end();
        measurements::start("SimpleFMIndexConstruct", measurements::Type::ALGORITHM);
        auto fmIndex = SimpleFMIndexConstruct::construct ( bwt );
        measurements::end();
        measurements::end();
        helpers::writeResults("BWTConstruct", {
            Object(__func__), Object(subjectLength), Object(""), Object(0),
            Object("SuffixArrayNaive+BWTFromSuffixArrayNaive"), Object(0), Object("Overall")
        });
        helpers::writeResults("SimpleFMIndexConstruct", {
                Object(__func__), Object(subjectLength), Object(""), Object(0),
                Object("SimpleFMIndexConstruct"), Object(0), Object("Overall")
        });

        for ( size_t iter = 0; iter < NUM_ITERATIONS; iter ++ ) {
            ext::array < string::LinearString < SymbolType >, 1 > patterns{
                    RandomSubstringFactory::generateSubstring ( patternLength, slice ) };
            ext::vector < Object > info = {
                    Object ( __func__ ), Object ( subjectLength ),
                    Object ( static_cast<std::string>(patterns.front ( ))), Object ( iter )
            };

            algorithms::measureDecodeBoyerMoore ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTBoyerMoore ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTBinarySearch ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureBWTSuffixArray ( bwt, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureFMIndexCount ( fmIndex, patterns.begin ( ), patterns.end ( ), info );
            algorithms::measureFMIndexFactors ( fmIndex, patterns.begin ( ), patterns.end ( ), info );
        }
    }
}

template < class SymbolType >
void
countTimeByBucketSize ( const LinearString < SymbolType > & subject, unsigned numPatterns, unsigned patternLength ) {
    auto bwt = BWTFromSuffixArrayNaive::construct ( SuffixArrayNaive::construct ( subject ));

    for ( size_t bucketSize = 1; bucketSize < 10000; bucketSize += 100 ) {

        for ( size_t iter = 0; iter < NUM_ITERATIONS; iter ++ ) {
            ext::vector < LinearString < SymbolType > > patterns;
            for ( unsigned i = 0; i < numPatterns; i ++ )
                patterns.push_back ( RandomSubstringFactory::generateSubstring ( patternLength, subject ));

            measurements::reset ( );
            measurements::start ( "Overall", measurements::Type::OVERALL );
            auto fmIndex = SimpleFMIndexConstruct::construct ( bwt, bucketSize * 16, bucketSize, 0 );
            unsigned res = 0;
            for(const auto & pattern : patterns)
                res += FMIndexCount::count(fmIndex, pattern);
            measurements::end();

            auto countFrame = helpers::sumFrames(helpers::findFramesByName(measurements::results(), "FMIndexCount"));
            auto constructFrame = helpers::findFrameByName(measurements::results(), "SimpleFMIndexConstruct");
            countFrame.memory.highWatermark += constructFrame.memory.highWatermark;
            helpers::writeResults(countFrame, {
                Object(__func__), Object(bucketSize), Object(static_cast<std::string>(patterns.front())), Object(iter),
                Object("FMIndexCount"), Object(res), Object("Overall")
            });
        }
    }
}

int main ( int argc, char * argv[] ) {
    auto usage = [ & ] ( ) {
        std::cerr << "Usage: " << argv[ 0 ] << " INPUT_FILE TEST_NAME" << std::endl;
        std::cerr << "Available tests: timeByPatternLength,timeByNumPatterns,countTimeByBucketSize,timeBySubjectLength"
                  << std::endl;
    };

    if ( argc != 3 ) {
        usage ( );
        return 1;
    }

    helpers::printCsvRow (
            ext::vector < std::string >{ "fn", "arg1", "arg2", "iteration", "algorithm", "result", "frame", "time",
                                         "memory" } );

    measurements::reset ( );
    measurements::start ( "Overall", measurements::Type::OVERALL );
    string::LinearString < char > subject = RawDataFactory::fromFile ( argv[ 1 ] );
    measurements::end ( );
    helpers::writeResults ( "Overall", {
            Object ( __func__ ), Object ( - 1 ), Object ( - 1 ), Object ( 0 ), Object ( "RawDataFactors::fromFile" ),
            Object ( subject.size ( )), Object ( "Overall" )
    } );

    std::string testName = argv[ 2 ];
    if ( testName == "timeByPatternLength" )
        timeByPatternLength ( subject );
    else if ( testName == "timeByNumPatterns" )
        timeByNumPatterns ( subject, 10 );
    else if ( testName == "countTimeByBucketSize" )
        countTimeByBucketSize ( subject, 50, 10 );
    else if ( testName == "timeBySubjectLength" )
        timeBySubjectLength ( subject, 10 );
    else {
        std::cerr << "Unknown test: " << testName << std::endl;
        usage ( );
        return 1;
    }

    return 0;
}
