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

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

enum class EGenerateType {
	PATTERN,
	SUBTREE,
	NONLINEAR_PATTERN,
	NONLINEAR_PATTERN_SINGLE_VAR,
	UNORDERED_PATTERN,
	UNORDERED_SUBTREE,

	SUBJECT,
};

std::ostream& operator << ( std::ostream& os, const EGenerateType& type ) {
	switch ( type ) {
		case EGenerateType::PATTERN:
			return ( os << "PATTERN" );
		case EGenerateType::SUBTREE:
			return ( os << "SUBTREE" );
		case EGenerateType::NONLINEAR_PATTERN:
			return ( os << "NONLINEAR_PATTERN" );
		case EGenerateType::NONLINEAR_PATTERN_SINGLE_VAR:
			return ( os << "NONLINEAR_PATTERN_SINGLE_VAR");
		case EGenerateType::SUBJECT:
			return ( os << "SUBJECT" );
		case EGenerateType::UNORDERED_PATTERN:
			return ( os << "UNORDERED_PATTERN" );
		case EGenerateType::UNORDERED_SUBTREE:
			return ( os << "UNORDERED_SUBTREE" );
		default:
			return ( os << "Unhandled EGenerateType" );
	}
}

const size_t PATTERN_SIZE = 4;
const size_t ALPHABET_SIZE = 3;
const size_t SUBJECT_HEIGHT = 25;
const size_t PATTERN_HEIGHT = 2;
const size_t RANDOM_ITERATIONS = 20;

static std::string qGen ( const EGenerateType & type, int height, int nodes, int alphSize, const std::string & output ) {
	std::ostringstream oss;
	oss << "execute ";

	if ( type == EGenerateType::SUBJECT ) {
		oss << "tree::generate::RandomRankedTreeFactory";
	} else if ( type == EGenerateType::PATTERN ) {
		oss << "tree::generate::RandomRankedPatternFactory";
	} else if ( type == EGenerateType::SUBTREE ) {
		oss << "tree::generate::RandomRankedTreeFactory";
	} else if ( type == EGenerateType::NONLINEAR_PATTERN ) {
		oss << "tree::generate::RandomRankedNonlinearPatternFactory";
	} else if ( type == EGenerateType::NONLINEAR_PATTERN_SINGLE_VAR ) {
		oss << "tree::generate::RandomRankedNonlinearPatternFactory";
	} else if ( type == EGenerateType::UNORDERED_PATTERN ) {
		oss << "(UnorderedRankedPattern) tree::generate::RandomRankedPatternFactory";
	} else if ( type == EGenerateType::UNORDERED_SUBTREE ) {
		oss << "(UnorderedRankedTree) tree::generate::RandomRankedTreeFactory";
	}

	oss << " (int)" << height;
	oss << " (int)" << nodes;
	oss << " (int)" << rand ( ) % alphSize + 1;
	oss << " (bool) false"; /* alphRand */
	if ( type == EGenerateType::NONLINEAR_PATTERN || type == EGenerateType::NONLINEAR_PATTERN_SINGLE_VAR )
		oss << " (bool)" << ( type == EGenerateType::NONLINEAR_PATTERN_SINGLE_VAR );
	oss << " (int)" << 2; /* rank */
	oss << "> $" << output;
	return oss.str ( );
}

static std::string search_replace ( std::string text, const std::string & search, const std::string & replace ) {
	size_t pos = text.find ( search );
	text.replace ( pos, search.size ( ), replace );
	return text;
}

static std::vector < std::pair < std::string, std::string > > pair_pattern_subject ( const std::vector < std::string > & files, const std::string & pattern, const std::string & subj ) {
	std::vector < std::pair < std::string, std::string > > res;
	for ( const auto & file : files )
		res.push_back ( std::make_pair ( file, search_replace ( file, pattern, subj ) ) );

	return res;
}


void runTest ( const std::string & exactPipeline, const std::string &pipeline, const std::string &pFile, const std::string &sFile ) {
	ext::vector < std::string > qs = {
		"execute < " + pFile + " > $pattern",
		"execute < " + sFile + " > $subject",
		"execute " + exactPipeline + " > $res1",
		"execute " + pipeline + " > $res2",
		"quit compare::PrimitiveCompare <(stats::SizeStat $res1) <(stats::SizeStat $res2)",
	};

	TimeoutAqlTest ( 10s, qs );
}

void runRandom ( const std::string & exactPipeline, const std::string &pipeline, const EGenerateType &patternType, const size_t& subjSize ) {
	ext::vector < std::string > qs = {
		qGen ( patternType, PATTERN_HEIGHT, PATTERN_SIZE, ALPHABET_SIZE, "pattern" ),
		qGen ( EGenerateType::SUBJECT, SUBJECT_HEIGHT, subjSize, ALPHABET_SIZE, "subject" ),
		"execute " + exactPipeline + " > $res1",
		"execute " + pipeline + " > $res2",
		"quit compare::PrimitiveCompare <(stats::SizeStat $res1) <(stats::SizeStat $res2)",
	};

	TimeoutAqlTest ( 10s, qs );
}

TEST_CASE ( "Arbology tests | nonlinear pattern", "[integration]" ) {
	auto definition = GENERATE ( as < std::tuple < std::string, std::string, size_t > > ( ),
		std::make_tuple ( "Exact Nonlinear Pattern Matching Using Compressed Bit Vectors (PrefixRankedBar)",
				"arbology::indexing::NonlinearCompressedBitParallelIndexConstruction (PrefixRankedBarTree)$subject | arbology::query::NonlinearCompressedBitParallelismPatterns - (PrefixRankedBarNonlinearPattern)$pattern", 1000 ),
		std::make_tuple ( "Exact Nonlinear Pattern Matching Using Full And Linear Index (PrefixRanked)",
				"arbology::indexing::NonlinearFullAndLinearIndexConstruction (PrefixRankedTree)$subject | arbology::query::NonlinearFullAndLinearIndexPatterns - (PrefixRankedNonlinearPattern) $pattern", 1000 ),
		std::make_tuple ( "Exact Nonlinear Pattern Matching Using Full And Linear Index (PrefixRankedBar)",
				"arbology::indexing::NonlinearFullAndLinearIndexConstruction (PrefixRankedBarTree)$subject | arbology::query::NonlinearFullAndLinearIndexPatterns - (PrefixRankedBarNonlinearPattern) $pattern", 1000 ),
		std::make_tuple ( "Exact Nonlinear Tree Pattern Automaton (PrefixRankedBar)",
				"arbology::exact::ExactNonlinearTreePatternAutomaton (PrefixRankedBarTree)$subject <(tree::SubtreeWildcard::get $pattern) <(tree::NonlinearAlphabet::get $pattern) <(tree::VariablesBarSymbol::get (PrefixRankedBarNonlinearPattern)$pattern) | "
				"automaton::determinize::Determinize - | (SetOfObjects)automaton::run::Result - (LinearString)(PrefixRankedBarNonlinearPattern)$pattern (DefaultStateType){ :Object }", 80 ),
		std::make_tuple ( "Exact Pattern Match (NonlinearPattern PrefixRankedBar)",
				"arbology::exact::ExactPatternMatch (PrefixRankedBarTree)$subject (PrefixRankedBarNonlinearPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Boyer Moore Horspool (NonlinearPattern PrefixRankedBar)",
				"arbology::exact::BoyerMooreHorspool (PrefixRankedBarTree)$subject (PrefixRankedBarNonlinearPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Reversed Boyer Moore Horspool (NonlinearPattern PrefixRankedBar)",
				"arbology::exact::ReversedBoyerMooreHorspool (PrefixRankedBarTree)$subject (PrefixRankedBarNonlinearPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Reversed Boyer Moore Horspool (NonlinearPattern PrefixRanked)",
				"arbology::exact::ReversedBoyerMooreHorspool (PrefixRankedTree)$subject (PrefixRankedNonlinearPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ) );

	auto exact = "arbology::exact::ExactPatternMatch $subject $pattern";
	auto pattern = EGenerateType::NONLINEAR_PATTERN;


	SECTION ( "Test files" ) {
		for ( const std::pair < std::string, std::string > & files : pair_pattern_subject ( TestFiles::Get ( "/tree/aarbology.test[0-9]+.nonlinear.pattern.xml$" ), ".nonlinear.pattern.xml", ".subject.xml" ) ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), files.first, files.second );
			runTest ( exact, std::get < 1 > ( definition ), files.first, files.second );
		}
	}

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
			runRandom ( exact, std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
		}
	}
}

// --------------------------------------------------------------------------------------------------------------------

TEST_CASE ( "Arbology tests | nonlinear pattern ends", "[integration]" ) {
	auto definition = GENERATE ( as < std::tuple < std::string, std::string, size_t > > ( ),
		std::make_tuple ( "Exact Nonlinear Tree Pattern Automaton (PrefixRanked)",
				"arbology::exact::ExactNonlinearTreePatternAutomaton (PrefixRankedTree)$subject <(tree::SubtreeWildcard::get $pattern) <(tree::NonlinearAlphabet::get $pattern) | automaton::determinize::Determinize - |"
				"(SetOfObjectPairs)automaton::run::Result - (LinearString)(PrefixRankedNonlinearPattern) $pattern (DefaultStateType) { :Object } | dataAccess::PairSetSecond -", 80 ) );

	auto exact = "arbology::exact::ExactPatternMatch $subject $pattern | arbology::transform::BeginToEndIndex (PrefixRankedTree) $subject -";
	auto pattern = EGenerateType::NONLINEAR_PATTERN_SINGLE_VAR;

	SECTION ( "Test files" ) {
		for ( const std::pair < std::string, std::string > & files : pair_pattern_subject ( TestFiles::Get ( "/tree/aarbology.test[0-9]+.nonlinear.pattern.xml$" ), ".nonlinear.pattern.xml", ".subject.xml" ) ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), files.first, files.second );
			runTest ( exact, std::get < 1 > ( definition ), files.first, files.second );
		}
	}

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
			runRandom ( exact, std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
		}
	}
}

// --------------------------------------------------------------------------------------------------------------------

TEST_CASE ( "Arbology tests | pattern", "[integration]" ) {
	auto definition = GENERATE ( as < std::tuple < std::string, std::string, size_t > > ( ),
		std::make_tuple ( "Exact Pattern Matching Using Full And Linear Index (PrefixRankedBar)",
				"arbology::indexing::FullAndLinearIndexConstruction (PrefixRankedBarTree) $subject | arbology::query::FullAndLinearIndexPatterns - (PrefixRankedBarPattern) $pattern", 1000 ),
		std::make_tuple ( "Exact Pattern Matching Using Full And Linear Index (PrefixRanked)",
				"arbology::indexing::FullAndLinearIndexConstruction (PrefixRankedTree) $subject | arbology::query::FullAndLinearIndexPatterns - (PrefixRankedPattern) $pattern", 1000 ),
		std::make_tuple ( "Exact Pattern Matching Using Compressed Bit Vectors (PrefixRankedBar)",
				"arbology::indexing::CompressedBitParallelIndexConstruction (PrefixRankedBarTree)$subject | arbology::query::CompressedBitParallelismPatterns - (PrefixRankedBarPattern)$pattern", 1000 ),
		std::make_tuple ( "Exact Knuth Morris Pratt (Pattern PrefixRankedBar)",
				"arbology::exact::KnuthMorrisPratt (PrefixRankedBarTree)$subject (PrefixRankedBarPattern)$pattern", 1000 ),
		std::make_tuple ( "Exact Knuth Morris Pratt (Pattern PrefixRanked)",
				"arbology::exact::KnuthMorrisPratt (PrefixRankedTree)$subject (PrefixRankedPattern)$pattern", 1000 ),
		std::make_tuple ( "Exact Boyer Moore Horspool (Pattern PrefixRankedBar)",
				"arbology::exact::BoyerMooreHorspool (PrefixRankedBarTree)$subject (PrefixRankedBarPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Reversed Boyer Moore Horspool (Pattern PrefixRankedBar)",
				"arbology::exact::ReversedBoyerMooreHorspool (PrefixRankedBarTree)$subject (PrefixRankedBarPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Reversed Boyer Moore Horspool (Pattern PrefixRanked)",
				"arbology::exact::ReversedBoyerMooreHorspool (PrefixRankedTree)$subject (PrefixRankedPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Quick Search (Pattern PrefixRankedBar)",
				"arbology::exact::QuickSearch (PrefixRankedBarTree)$subject (PrefixRankedBarPattern)<(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Reversed Quick Search (Pattern PrefixRankedBar)",
				"arbology::exact::ReversedQuickSearch (PrefixRankedBarTree)$subject (PrefixRankedBarPattern)<(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Reversed Quick Search (Pattern PrefixRanked)",
				"arbology::exact::ReversedQuickSearch (PrefixRankedTree)$subject (PrefixRankedPattern)<(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Dead Zone Using Bad Character Shift And Border Array (Pattern PrefixRanked)",
				"arbology::exact::DeadZoneUsingBadCharacterShiftAndBorderArray (PrefixRankedTree)$subject (PrefixRankedPattern)<(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Dead Zone Using Bad Character Shift And Border Array (Pattern PrefixRankedBar)",
				"arbology::exact::DeadZoneUsingBadCharacterShiftAndBorderArray (PrefixRankedBarTree)$subject (PrefixRankedBarPattern)<(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ),
		std::make_tuple ( "Exact Pattern Matching Automaton (Pattern Tree)",
				"automaton::run::Occurrences <(arbology::exact::ExactPatternMatchingAutomaton <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject)) | automaton::determinize::Determinize - ) $subject", 1000 ),
		std::make_tuple ( "Exact Pattern Matching Automaton (PrefixRankedBar)",
				"arbology::exact::ExactPatternMatchingAutomaton (PrefixRankedBarPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject)) | automaton::determinize::Determinize - | automaton::run::Occurrences - (LinearString)(PrefixRankedBarTree)$subject", 1000 ) );

	auto exact = "arbology::exact::ExactPatternMatch $subject $pattern";
	auto pattern = EGenerateType::PATTERN;

	SECTION ( "Test files" ) {
		for ( const std::pair < std::string, std::string > & files : pair_pattern_subject ( TestFiles::Get ( "/tree/aarbology.test[0-9]+.pattern.xml$" ), ".pattern.xml", ".subject.xml" ) ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), files.first, files.second );
			runTest ( exact, std::get < 1 > ( definition ), files.first, files.second );
		}
	}

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
			runRandom ( exact, std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
		}
	}
}

// --------------------------------------------------------------------------------------------------------------------

TEST_CASE ( "Arbology tests | unordered pattern", "[integration]" ) {
	auto definition = GENERATE ( as < std::tuple < std::string, std::string, size_t > > ( ),
		std::make_tuple ( "Exact Pattern Matching Automaton (Pattern Tree)",
				"automaton::run::Occurrences <(arbology::exact::ExactPatternMatchingAutomaton <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject)) | automaton::determinize::Determinize - ) $subject", 1000 ) );

	auto exact = "arbology::exact::ExactPatternMatch $subject $pattern";
	auto pattern = EGenerateType::UNORDERED_PATTERN;

/*	SECTION ( "Test files" ) {
		for ( const std::pair < std::string, std::string > & files : pair_pattern_subject ( TestFiles::Get ( "/tree/aarbology.test[0-9]+.pattern.xml$" ), ".pattern.xml", ".subject.xml" ) ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), files.first, files.second );
			runTest ( exact, std::get < 1 > ( definition ), files.first, files.second );
		}
	}*/

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
			runRandom ( exact, std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
		}
	}
}

// --------------------------------------------------------------------------------------------------------------------

TEST_CASE ( "Arbology tests | pattern ends ", "[integration]" ) {
	auto definition = GENERATE ( as < std::tuple < std::string, std::string, size_t > > ( ),
		std::make_tuple ( "Exact Pattern Matching Using Compressed Bit Vectors (PrefixRanked)",
				"arbology::indexing::CompressedBitParallelIndexConstruction (PrefixRankedTree)$subject | arbology::query::CompressedBitParallelismPatterns - (PrefixRankedPattern)$pattern", 1000 ),

		std::make_tuple ( "Exact Pattern Matching Automaton (PrefixRanked)",
				"arbology::exact::ExactPatternMatchingAutomaton (PrefixRankedPattern) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject) ) | automaton::determinize::Determinize - | "
				"automaton::run::Occurrences - (LinearString)(PrefixRankedTree) $subject", 1000 ),

		std::make_tuple ( "Exact Tree Pattern Automaton (PrefixRanked)",
				"arbology::exact::ExactTreePatternAutomaton (PrefixRankedTree)$subject <(tree::SubtreeWildcard::get $pattern) | automaton::determinize::Determinize - | (SetOfObjects) automaton::run::Result - (LinearString)(PrefixRankedPattern)$pattern (DefaultStateType) { :Object }", 120 ) );

	auto exact = "arbology::exact::ExactPatternMatch (PrefixRankedTree)$subject (PrefixRankedPattern)$pattern | arbology::transform::BeginToEndIndex (PrefixRankedTree)$subject -";
	auto pattern = EGenerateType::PATTERN;

	SECTION ( "Test files" ) {
		for ( const std::pair < std::string, std::string > & files : pair_pattern_subject ( TestFiles::Get ( "/tree/aarbology.test[0-9]+.pattern.xml$" ), ".pattern.xml", ".subject.xml" ) ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), files.first, files.second );
			runTest ( exact, std::get < 1 > ( definition ), files.first, files.second );
		}
	}

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
			runRandom ( exact, std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
		}
	}
}
// --------------------------------------------------------------------------------------------------------------------

TEST_CASE ( "Arbology tests | subtree", "[integration]" ) {
	auto definition = GENERATE ( as < std::tuple < std::string, std::string, size_t > > ( ),
		std::make_tuple ( "Exact Subtree Automaton (Tree)",
				"automaton::run::Occurrences <(arbology::exact::ExactSubtreeMatchingAutomaton $pattern | automaton::determinize::Determinize -) $subject", 1000 ),
		std::make_tuple ( "Exact Minimized Subtree Automaton (Tree)",
				"automaton::run::Occurrences <(arbology::exact::ExactSubtreeMatchingAutomaton $pattern | automaton::determinize::Determinize - | "
				"automaton::simplify::Trim - | automaton::simplify::Minimize -) $subject", 1000 ),
		std::make_tuple ( "Exact Boyer Moore Horspool (Subtree PrefixRankedBar)",
				"arbology::exact::BoyerMooreHorspool (PrefixRankedBarTree)$subject (PrefixRankedBarTree) <(tree::GeneralAlphabet::add $pattern <(tree::GeneralAlphabet::get $subject))", 1000 ) );


	auto exact = "arbology::exact::ExactSubtreeMatch $subject $pattern";
	auto pattern = EGenerateType::SUBTREE;

	SECTION ( "Test files" ) {
		for ( const std::pair < std::string, std::string > & files : pair_pattern_subject ( TestFiles::Get ( "/tree/aarbology.test[0-9]+.subtree.xml$" ), ".subtree.xml", ".subject.xml" ) ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), files.first, files.second );
			runTest ( exact, std::get < 1 > ( definition ), files.first, files.second );
		}
	}

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {
			CAPTURE ( std::get < 0 > ( definition ), std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
			runRandom ( exact, std::get < 1 > ( definition ), pattern, std::get < 2 > ( definition ) );
		}
	}
}
