#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 qSetError ( const size_t error,  const std::string & var ) {
	return "execute " + std::to_string ( error ) + " > $" + var;
}

static std::string qPrepareString ( const std::string &file, const std::string & var ) {
	return "execute < " + file + " > $" + var;
}

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

static std::string qCreateMatchingAutomaton ( const std::string & algo, const std::string & pattern, const std::string & error, const std::string & var ) {
	std::ostringstream oss;
	oss << "execute ";
	oss << algo << " $" + pattern + " $" + error + " | ";
	oss << "automaton::simplify::efficient::EpsilonRemoverIncoming - | ";
	oss << "automaton::determinize::Determinize - ";
	oss << "> $" << var;
	return oss.str ( );
}

static std::string qRunAutomaton ( const std::string & automaton, const std::string & subject, const std::string & res ) {
	return "execute automaton::run::Occurrences $" + automaton + " $" + subject + " > $" + res;
}

static std::string qRunDynamicAlgorithm ( const std::string & algo, const std::string &pattern, const std::string & subject, const std::string & error, const std::string & res ) {
	std::ostringstream oss;
	oss << "execute " << algo << " ";
	oss << "$" << subject << " ";
	oss << "$" << pattern << " ";
	oss << "$" << error << " ";
	oss << "> $" << res;
	return oss.str ( );
}

TEST_CASE ( "Approximate Matching", "[integration]" ) {
	auto definition = GENERATE ( as < std::pair < std::string, std::string > > ( ),
			std::make_pair ( "stringology::matching::HammingMatchingAutomaton", "stringology::simulations::HammingDynamicProgramming" ),
			std::make_pair ( "stringology::matching::HammingMatchingAutomaton", "stringology::simulations::HammingBitParalelism" ),
			std::make_pair ( "stringology::matching::LevenshteinMatchingAutomaton", "stringology::simulations::LevenshteinDynamicProgramming" ),
			std::make_pair ( "stringology::matching::LevenshteinMatchingAutomaton", "stringology::simulations::LevenshteinBitParalelism" ),
			std::make_pair ( "stringology::matching::GeneralizedLevenshteinMatchingAutomaton", "stringology::simulations::GeneralizedLevenshteinDynamicProgramming" ),
			std::make_pair ( "stringology::matching::GeneralizedLevenshteinMatchingAutomaton", "stringology::simulations::GeneralizedLevenshteinBitParalelism" ) );
	auto error = GENERATE ( 0, 1, 2, 3, 4, 5, 6 );

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

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

			ext::vector < std::string > qs = {
				qSetError ( error, "error" ),
				qPrepareString ( patternFile, "pattern" ),
				qPrepareString ( subjectFile, "subject" ),
				qExtendAlphabet ( "pattern", "subject" ),
				qCreateMatchingAutomaton ( std::get < 0 > ( definition ), "pattern", "error", "automaton" ),
				qRunAutomaton ( "automaton", "subject", "res1" ),
				qRunDynamicAlgorithm ( std::get < 1 > ( definition ), "pattern", "subject", "error", "res2" ),
				"quit compare::SetCompare $res1 $res2",
			};

			TimeoutAqlTest ( 2s, qs );
		}
	}

	SECTION ( "Random tests" ) {
		for ( size_t i = 0; i < RANDOM_ITERATIONS; i++ ) {

			ext::vector < std::string > qs = {
				qSetError ( error, "error" ),
				qGenString ( PATTERN_SIZE, ALPHABET_SIZE, "pattern" ),
				qGenString ( SUBJECT_SIZE, ALPHABET_SIZE, "subject" ),
				qExtendAlphabet ( "pattern", "subject" ),
				qCreateMatchingAutomaton ( std::get < 0 > ( definition ), "pattern", "error", "automaton" ),
				qRunAutomaton ( "automaton", "subject", "res1" ),
				qRunDynamicAlgorithm ( std::get < 1 > ( definition ), "pattern", "subject", "error", "res2" ),
				"quit compare::SetCompare $res1 $res2",
			};

			TimeoutAqlTest ( 5s, qs );
		}
	}
}
