#include <catch2/catch.hpp>

#include <stringology/matching/GeneralizedLevenshteinMatchingAutomaton.h>
#include <automaton/FSM/EpsilonNFA.h>
#include <string/LinearString.h>
#include <automaton/simplify/UnreachableStatesRemover.h>


TEST_CASE ( "Generalized Levenshtein Matching Automaton", "[unit][algo][stringology][matching]" ) {
	SECTION ( "Test construction" ) {
		ext::set<char> alphabet{'a', 'b', 'c', 'd'};
		string::LinearString <char> input_string(alphabet, ext::vector<char>{'a', 'b', 'c'});
		auto resulting_automata = stringology::matching::GeneralizedLevenshteinMatchingAutomaton::construct(input_string, 2);

		typedef ext::pair<unsigned int, unsigned int> State;

		automaton::EpsilonNFA < char, State > res(ext::make_pair(0,0));
		res.setInputAlphabet(ext::set<char>{'a', 'b', 'c', 'd'});

		State q0 = ext::make_pair(0,0);
		State q1 = ext::make_pair(1,0);
		State q2 = ext::make_pair(2,0);
		State q3 = ext::make_pair(3,0);
		State q4 = ext::make_pair(1,1);
		State q5 = ext::make_pair(2,1);
		State q6 = ext::make_pair(3,1);
		State q7 = ext::make_pair(2,2);
		State q8 = ext::make_pair(3,2);

		State r3 = ext::make_pair(5,1);
		State r2 = ext::make_pair(5,0);
		State r1 = ext::make_pair(4,0);

		res.setStates(ext::set<State> {q0, q1, q2, q3, q4, q5, q6, q7, q8, r1, r2, r3});
		res.setFinalStates(ext::set<State> {q3, q6, q8});

		res.addTransition(q0, 'a', q1); // vertical transitions (exact matching automata)

		res.addTransition(q1, 'b', q2);
		res.addTransition(q4, 'b', q5);

		res.addTransition(q2, 'c', q3);
		res.addTransition(q5, 'c', q6);
		res.addTransition(q7, 'c', q8);

		res.addTransition(q0, 'a', q0); // loops in initial state
		res.addTransition(q0, 'b', q0);
		res.addTransition(q0, 'c', q0);
		res.addTransition(q0, 'd', q0);

		res.addTransition(q0, 'b', q4); // diagonal transitions representing replace
		res.addTransition(q0, 'c', q4);
		res.addTransition(q0, 'd', q4);

		res.addTransition(q0, q4); // deletion

		res.addTransition(q1, 'a', q5);
		res.addTransition(q1, 'c', q5);
		res.addTransition(q1, 'd', q5);
		res.addTransition(q4, 'a', q7);
		res.addTransition(q4, 'c', q7);
		res.addTransition(q4, 'd', q7);

		res.addTransition(q1, q5); // deletion
		res.addTransition(q4, q7);

		res.addTransition(q2, 'a', q6);
		res.addTransition(q2, 'b', q6);
		res.addTransition(q2, 'd', q6);
		res.addTransition(q5, 'a', q8);
		res.addTransition(q5, 'b', q8);
		res.addTransition(q5, 'd', q8);

		res.addTransition(q2, q6); // deletion
		res.addTransition(q5, q8);

		res.addTransition(q1, 'a', q4); // insertions
		res.addTransition(q1, 'b', q4);
		res.addTransition(q1, 'c', q4);
		res.addTransition(q1, 'd', q4);

		res.addTransition(q2, 'a', q5);
		res.addTransition(q2, 'b', q5);
		res.addTransition(q2, 'c', q5);
		res.addTransition(q2, 'd', q5);

		res.addTransition(q5, 'a', q7);
		res.addTransition(q5, 'b', q7);
		res.addTransition(q5, 'c', q7);
		res.addTransition(q5, 'd', q7);

		res.addTransition(q0, 'b', r1); // transposition
		res.addTransition(r1, 'a', q5);

		res.addTransition(q1, 'c', r2);
		res.addTransition(r2, 'b', q6);

		res.addTransition(q4, 'c', r3);
		res.addTransition(r3, 'b', q8);

		CHECK(res == automaton::simplify::UnreachableStatesRemover::remove(resulting_automata));
	}

	SECTION ( "Test Wildcard construction" ) {
		ext::set<char> alphabet{'a', 'b', '@'};
		string::WildcardLinearString <char> input_string(alphabet, ext::vector<char>{'a', '@', 'b'}, '@');
		auto resulting_automata = stringology::matching::GeneralizedLevenshteinMatchingAutomaton::construct(input_string, 2);

		typedef ext::pair<unsigned int, unsigned int> State;

		automaton::EpsilonNFA < char, State > res(ext::make_pair(0,0));
		res.setInputAlphabet(alphabet);

		State q0 = ext::make_pair(0,0);
		State q1 = ext::make_pair(1,0);
		State q2 = ext::make_pair(2,0);
		State q3 = ext::make_pair(3,0);
		State q4 = ext::make_pair(1,1);
		State q5 = ext::make_pair(2,1);
		State q6 = ext::make_pair(3,1);
		State q7 = ext::make_pair(2,2);
		State q8 = ext::make_pair(3,2);

		State r3 = ext::make_pair(5,1);
		State r2 = ext::make_pair(5,0);
		State r1 = ext::make_pair(4,0);

		res.setStates(ext::set<State> {q0, q1, q2, q3, q4, q5, q6, q7, q8, r1, r2, r3});
		res.setFinalStates(ext::set<State> {q3, q6, q8});

		res.addTransition(q0, 'a', q0); // initial loops
		res.addTransition(q0, 'b', q0);

		res.addTransition(q0, 'a', q1); // 3 simple matching automatas

		res.addTransition(q1, 'a', q2);
		res.addTransition(q1, 'b', q2);
		res.addTransition(q4, 'a', q5);
		res.addTransition(q4, 'b', q5);

		res.addTransition(q2, 'b', q3);
		res.addTransition(q5, 'b', q6);
		res.addTransition(q7, 'b', q8);

		res.addTransition(q0, 'b', q4); // error transitions for replace

		res.addTransition(q2, 'a', q6);
		res.addTransition(q5, 'a', q8);

		res.addTransition(q0, q4); // delete transition
		res.addTransition(q1, q5);
		res.addTransition(q2, q6);

		res.addTransition(q4, q7);
		res.addTransition(q5, q8);

		res.addTransition(q1, 'a', q4); // insert transitions
		res.addTransition(q1, 'b', q4);

		res.addTransition(q2, 'a', q5);
		res.addTransition(q2, 'b', q5);

		res.addTransition(q5, 'a', q7);
		res.addTransition(q5, 'b', q7);

		res.addTransition(q0, 'a', r1); // transpose states
		res.addTransition(q0, 'b', r1);
		res.addTransition(r1, 'a', q5);

		res.addTransition(q1, 'b', r2);
		res.addTransition(r2, 'a', q6);
		res.addTransition(r2, 'b', q6);

		res.addTransition(q4, 'b', r3);
		res.addTransition(r3, 'a', q8);
		res.addTransition(r3, 'b', q8);

		CHECK(res == automaton::simplify::UnreachableStatesRemover::remove(resulting_automata));
	}
}
