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

#include <catch2/catch.hpp>
#include <alib/vector>
#include <stringology/indexing/SimpleFMIndexConstruct.h>

#include "testing/TimeoutAqlTest.hpp"
#include "testing/TestFiles.hpp"

using stringology::indexing::SimpleFMIndexConstruct;

const size_t PATTERN_SIZE = 7;
const size_t SUBJECT_SIZE = 100;
const size_t RANDOM_ITERATIONS = 20;

static std::string qExtendAlphabet ( const std::string & s1, const std::string & s2 ) {
    return "execute string::GeneralAlphabet::add $" + s1 + " <( string::GeneralAlphabet::get $" + s2 + " )";
}

static std::string qGenString ( size_t minLen, size_t maxLen, const std::string & alph, const std::string & var ) {
    std::ostringstream oss;
    oss << "execute string::generate::RandomStringFactory ";
    oss << "( size_t )" << rand ( ) % ( maxLen - minLen + 1 ) + minLen + 1;
    oss << "$" << alph << "> $" << var;
    return oss.str ( );
}

static std::string
qGenFMIndex ( unsigned superBucketSize, unsigned bucketSize, unsigned fttDensityPct, const std::string & str,
              const std::string & var ) {
    std::ostringstream oss;
    oss << "execute stringology::transform::BWTNaive $" << str;
    oss << " | stringology::indexing::SimpleFMIndexConstruct - " << superBucketSize << " " << bucketSize << " "
        << fttDensityPct;
    oss << " > $" << var;
    return oss.str ( );
}

TEST_CASE ( "BWT", "[bwt][integration]" ) {

    SECTION ( "BWTNaive <-> BWTFromSuffixArrayNaive cross check on random strings" ) {
        for ( size_t i = 0; i < RANDOM_ITERATIONS; i ++ ) {
            ext::vector < std::string > qs = {
                    "execute alphabet::generate::BinaryAlphabetFactory > $alph",
                    qGenString ( 0, SUBJECT_SIZE, "alph", "subject" ),
                    "execute stringology::transform::BWTNaive $subject > $res1",
                    "execute stringology::indexing::SuffixArrayNaive $subject | stringology::transform::BWTFromSuffixArrayNaive - > $res2",
                    "quit compare::StringCompare $res1 $res2"
            };
            TimeoutAqlTest ( 2s, qs );
        }
    }

    SECTION ( "BWT decode test on random strings" ) {
        for ( size_t i = 0; i < RANDOM_ITERATIONS; i ++ ) {
            ext::vector < std::string > qs = {
                    "execute alphabet::generate::BinaryAlphabetFactory > $alph",
                    qGenString ( 0, SUBJECT_SIZE, "alph", "subject" ),
                    "execute stringology::transform::BWTNaive $subject | stringology::transform::BWTDecodeNaive - > $res",
                    "quit compare::StringCompare $subject $res"
            };
            TimeoutAqlTest ( 2s, qs );
        }
    }

    SECTION ( "SimpleFMIndexConstruct test with different bucket and superbucket sizes (only count operation)" ) {
        unsigned bucketSize = GENERATE( 2, 10, 1000 );
        unsigned superBucketSizeMul = GENERATE ( 1, 2, 10 );
        unsigned superBucketSize = bucketSize * superBucketSizeMul;

        for ( size_t i = 0; i < RANDOM_ITERATIONS; i ++ ) {
            ext::vector < std::string > qs = {
                    "execute alphabet::generate::BinaryAlphabetFactory > $alph",
                    qGenString ( 0, SUBJECT_SIZE, "alph", "subject" ),
                    qGenString ( 1, PATTERN_SIZE, "alph", "pattern" ),
                    qGenFMIndex ( superBucketSize, bucketSize, 0, "subject", "index" ),
                    "execute stringology::query::FMIndexCount $index $pattern > $res1",
                    "execute stringology::exact::ExactFactorMatch $subject $pattern | stats::SizeStat - > $res2",
                    "quit compare::PrimitiveCompare $res1 $res2"
            };
            TimeoutAqlTest ( 2s, qs );
        }
    }

    SECTION ( "SimpleFMIndexConstruct test with different fttDensityPct (only match operation)" ) {
        unsigned fttDensityPct = GENERATE ( 1, 2, 20, 100 );

        for ( size_t i = 0; i < RANDOM_ITERATIONS; i ++ ) {
            ext::vector < std::string > qs = {
                    "execute alphabet::generate::BinaryAlphabetFactory > $alph",
                    qGenString ( 0, SUBJECT_SIZE, "alph", "subject" ),
                    qGenString ( 1, PATTERN_SIZE, "alph", "pattern" ),
                    qGenFMIndex ( SimpleFMIndexConstruct::DEFAULT_SUPER_BUCKET_SIZE,
                                  SimpleFMIndexConstruct::DEFAULT_BUCKET_SIZE, fttDensityPct, "subject", "index" ),
                    "execute stringology::query::FMIndexFactors $index $pattern > $res1",
                    "execute stringology::exact::ExactFactorMatch $subject $pattern > $res2",
                    "quit compare::SetCompare $res1 $res2"
            };
            TimeoutAqlTest ( 2s, qs );
        }
    }

    SECTION ( "BWT exact matching random test" ) {
        auto definition = GENERATE (
                as < std::tuple < std::string, std::string, bool > > ( ),
                std::make_tuple ( "BWT Boyer Moore",
                                  "stringology::transform::BWTNaive $subject | stringology::exact::BWTBoyerMoore - $pattern",
                                  true ),
                std::make_tuple ( "BWT Binary Search",
                                  "stringology::transform::BWTNaive $subject | stringology::exact::BWTBinarySearch - $pattern",
                                  false ),
                std::make_tuple ( "BWT Suffix Array",
                                  "stringology::transform::BWTNaive $subject | stringology::indexing::SuffixArrayFromBWTNaive - | stringology::query::SuffixArrayFactors - $pattern",
                                  false ),
                std::make_tuple ( "Simple FM Index",
                                  "stringology::transform::BWTNaive $subject | stringology::indexing::SimpleFMIndexConstruct - | stringology::query::FMIndexFactors - $pattern",
                                  false ));

        for ( size_t i = 0; i < RANDOM_ITERATIONS; i ++ ) {
            ext::vector < std::string > qs = {
                    "execute alphabet::generate::BinaryAlphabetFactory > $alph",
                    qGenString ( 0, SUBJECT_SIZE, "alph", "subject" ),
                    qGenString ( 1, PATTERN_SIZE, "alph", "pattern" ),
            };

            if ( std::get < 2 > ( definition ) )
                qs.push_back ( qExtendAlphabet ( "pattern", "subject" ) );

            qs.push_back ( "execute " + std::get < 1 > ( definition ) + " > $res1" );
            qs.push_back ( "execute stringology::exact::ExactFactorMatch $subject $pattern > $res2" );
            qs.push_back ( "quit compare::SetCompare $res1 $res2" );
            TimeoutAqlTest ( 2s, qs );
        }
    }

}