#include <catch2/catch.hpp>
#include <alib/vector>

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

const size_t PATTERN_SIZE = 7;
const size_t SUBJECT_SIZE = 100;
const size_t ALPHABET_SIZE = 4;
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 min_len, size_t max_len, size_t alph_len, const std::string & var ) {
	std::ostringstream oss;
	oss << "execute string::generate::RandomStringFactory ";
	oss << "( size_t )" << rand ( ) % ( max_len - min_len + 1 ) + min_len + 1;
	oss << "( size_t )" << rand ( ) % alph_len + 1;
	oss << "true | ";
	oss << "string::simplify::NormalizeAlphabet - > $" + var;
	return oss.str ( );
}

TEST_CASE ( "ExactMatching", "[exact][integration]" ) {
	auto definition = GENERATE ( as < std::tuple < std::string, std::string, bool > > ( ),
			std::make_tuple ( "Exact Not So Naive", "stringology::exact::NotSoNaive $subject $pattern", false ),
			std::make_tuple ( "Exact Tailed Substring", "stringology::exact::TailedSubstring $subject $pattern", false ),
			std::make_tuple ( "Exact Quite Naive", "stringology::exact::QuiteNaive $subject $pattern", false ),
			std::make_tuple ( "Exact Galil Seiferas", "stringology::exact::GalilSeiferas $subject $pattern", false ),
			std::make_tuple ( "Exact Sequential Sampling", "stringology::exact::SequentialSampling $subject $pattern", false ),
			std::make_tuple ( "Exact CGR", "stringology::exact::CGR $subject $pattern", false ),
			std::make_tuple ( "Exact Boyer Moore", "stringology::exact::BoyerMoore $subject $pattern", true ),
			std::make_tuple ( "Exact Knuth Morris Pratt", "stringology::exact::KnuthMorrisPratt $subject $pattern", false ),
			std::make_tuple ( "Exact Boyer Moore Horspool", " stringology::exact::BoyerMooreHorspool $subject $pattern", true ),
			std::make_tuple ( "Exact Reversed Boyer Moore Horspool", " stringology::exact::ReversedBoyerMooreHorspool $subject $pattern", true ),
			std::make_tuple ( "Quick Search", "stringology::exact::QuickSearch $subject $pattern", true ),
			std::make_tuple ( "Exact Dead Zone Using Bad Character Shift", "stringology::exact::DeadZoneUsingBadCharacterShift $subject $pattern", true ),
			std::make_tuple ( "Exact Matching Automaton", "automaton::run::Occurrences <(stringology::matching::ExactMatchingAutomaton $pattern | automaton::determinize::Determinize -) $subject | stringology::transform::EndToBeginIndex $pattern -", true ),
			std::make_tuple ( "DAWG Factors", "stringology::indexing::ExactSuffixAutomaton $subject | stringology::query::SuffixAutomatonFactors - $pattern", false ),
			std::make_tuple ( "BNDM Matcher", "stringology::matching::BNDMMatcherConstruction $pattern | stringology::query::BNDMOccurrences - $subject", false ),
			std::make_tuple ( "Compressed Bit Parallelism Factors", "stringology::indexing::CompressedBitParallelIndexConstruction $subject | stringology::query::CompressedBitParallelismFactors - $pattern", false ),
			std::make_tuple ( "Bit Parallelism Factors", "stringology::indexing::BitParallelIndexConstruction $subject | stringology::query::BitParallelismFactors - $pattern", false ),
			std::make_tuple ( "Position Heap Factors", "stringology::indexing::PositionHeapNaive $subject | stringology::query::PositionHeapFactors - $pattern", false ),
			std::make_tuple ( "Suffix Array Factors", "stringology::indexing::SuffixArrayNaive $subject | stringology::query::SuffixArrayFactors - $pattern", false ),
			std::make_tuple ( "Suffix Trie Factors", "stringology::indexing::SuffixTrieNaive $subject | stringology::query::SuffixTrieFactors - $pattern", false ),
			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 ));

	SECTION ( "Test files" ) {
		for ( const std::string & patternFile : TestFiles::Get ( "/string/astringology.test.*.pattern.xml$" ) ) {
			static const std::string p ( ".pattern." );
			static const std::string s ( ".subject." );
			std::string subjectFile = patternFile;

			size_t pos = subjectFile.find ( p );
			subjectFile.replace ( pos, s.size ( ), s );

			ext::vector < std::string > qs = {
				"execute < " + patternFile + " > $pattern",
				"execute < " + subjectFile + " > $subject",
				"execute stringology::exact::ExactFactorMatch $subject $pattern > $res1"
			};

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

			qs.push_back ( "execute " + std::get < 1 > ( definition ) + " > $res2" );
            qs.push_back ( "quit compare::SetCompare $res1 $res2" );
//			qs.push_back ( "quit compare::PrimitiveCompare <(stats::SizeStat $res1) <(stats::SizeStat $res2)" );

			INFO ( std::get < 0 > ( definition ) );
			TimeoutAqlTest ( 2s, qs );
		}
	}

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {
			ext::vector < std::string > qs = {
				qGenString ( 0, PATTERN_SIZE, ALPHABET_SIZE, "pattern" ),
				qGenString ( PATTERN_SIZE, SUBJECT_SIZE, ALPHABET_SIZE, "subject" ),
				"execute stringology::exact::ExactFactorMatch $subject $pattern > $res1"
			};

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

			qs.push_back ( "execute " + std::get < 1 > ( definition ) + " > $res2" );
            qs.push_back ( "quit compare::SetCompare $res1 $res2" );
//			qs.push_back ( "quit compare::PrimitiveCompare <(stats::SizeStat $res1) <(stats::SizeStat $res2)" );

			INFO ( std::get < 0 > ( definition ) );
			TimeoutAqlTest ( 2s, qs );
		}
	}
}
