//
// Created by shushiri on 18.2.19.
//

#include <catch2/catch.hpp>

#include <alib/set>
#include <string/LinearString.h>
#include <stringology/indexing/NondeterministicApproximateSuffixEpsilonAutomatonForHammingDistance.h>
#include <stringology/indexing/NondeterministicApproximateSuffixAutomatonForHammingDistance.h>

#include "automaton/determinize/Determinize.h"
#include "automaton/simplify/Minimize.h"
#include "automaton/simplify/Normalize.h"
#include "automaton/simplify/Trim.h"
#include "automaton/simplify/EpsilonRemoverIncoming.h"


TEST_CASE ( "Nondeterministic Approximate Suffix Automaton For Hamming Distance", "[unit][stringology][indexing]" ) {
	SECTION ( "Test 1" ) {
		string::LinearString < char > pattern ( "abcc" );
		unsigned int k = 1;
		auto result = stringology::indexing::NondeterministicApproximateSuffixEpsilonAutomatonForHammingDistance::construct( pattern, k);

		auto state00 = ext::make_pair( 0, 0 );
		automaton::EpsilonNFA < char, ext::pair < unsigned int, unsigned int > > ref ( state00 );
		ref.setInputAlphabet(ext::set < char > { 'a', 'b', 'c' });

		auto state10 = ext::make_pair( 1, 0 );
		auto state20 = ext::make_pair( 2, 0 );
		auto state30 = ext::make_pair( 3, 0 );
		auto state40 = ext::make_pair( 4, 0 );
		auto state11 = ext::make_pair( 1, 1 );
		auto state21 = ext::make_pair( 2, 1 );
		auto state31 = ext::make_pair( 3, 1 );
		auto state41 = ext::make_pair( 4, 1 );

		ext::set < ext::pair < unsigned int, unsigned int > > states = { state00, state10, state20, state30, state40, state11, state21, state31, state41 };
		ref.setStates( states );

		ref.setFinalStates( ext::set < ext::pair < unsigned int, unsigned int > > { state40, state41 });

		//first stage
		ref.addTransition( state00, 'a', state10 );
		ref.addTransition( state10, 'b', state20 );
		ref.addTransition( state20, 'c', state30 );
		ref.addTransition( state30, 'c', state40 );
		//second stage
		ref.addTransition( state11, 'b', state21 );
		ref.addTransition( state21, 'c', state31 );
		ref.addTransition( state31, 'c', state41 );
		//diagonal
		ref.addTransition( state00, 'b', state11 );
		ref.addTransition( state00, 'c', state11 );
		ref.addTransition( state10, 'a', state21 );
		ref.addTransition( state10, 'c', state21 );
		ref.addTransition( state20, 'a', state31 );
		ref.addTransition( state20, 'b', state31 );
		ref.addTransition( state30, 'a', state41 );
		ref.addTransition( state30, 'b', state41 );

		//epsilon
		CHECK( ref.addTransition( state00, state10 ) == 1 );
		CHECK( ref.addTransition( state00, state20 ) == 1 );
		ref.addTransition( state00, state30 );
		ref.addTransition( state00, state40 );

		CHECK( ref == result );
	}

	SECTION ( "Test 2" ) {
		string::LinearString < char > pattern ( "aba" );
		unsigned int k = 2;
		auto result = stringology::indexing::NondeterministicApproximateSuffixEpsilonAutomatonForHammingDistance::construct( pattern, k);

		auto state00 = ext::make_pair( 0, 0 );
		automaton::EpsilonNFA < char, ext::pair < unsigned int, unsigned int > > ref ( state00 );
		ref.setInputAlphabet(ext::set < char > { 'a', 'b' });

		auto state10 = ext::make_pair( 1, 0 );
		auto state20 = ext::make_pair( 2, 0 );
		auto state30 = ext::make_pair( 3, 0 );
		auto state11 = ext::make_pair( 1, 1 );
		auto state21 = ext::make_pair( 2, 1 );
		auto state31 = ext::make_pair( 3, 1 );
		auto state22 = ext::make_pair( 2, 2 );
		auto state32 = ext::make_pair( 3, 2 );

		ext::set < ext::pair < unsigned int, unsigned int > > states = { state00, state10, state20, state30, state11, state21, state31, state22, state32 };
		ref.setStates( states );

		ref.setFinalStates( ext::set < ext::pair < unsigned int, unsigned int > > { state30, state31, state32 });

		//first stage
		ref.addTransition( state00, 'a', state10 );
		ref.addTransition( state10, 'b', state20 );
		ref.addTransition( state20, 'a', state30 );
		//second stage
		ref.addTransition( state11, 'b', state21 );
		ref.addTransition( state21, 'a', state31 );
		//third stage
		ref.addTransition( state22, 'a', state32 );
		//diagonal
		ref.addTransition( state00, 'b', state11 );
		ref.addTransition( state10, 'a', state21 );
		ref.addTransition( state20, 'b', state31 );
		ref.addTransition( state11, 'a', state22 );
		ref.addTransition( state21, 'b', state32 );

		//epsilon
		ref.addTransition( state00, state10);
		ref.addTransition( state00, state20 );
		ref.addTransition( state00, state30 );

		CHECK( ref == result );
	}

	SECTION ( "Test 3" ) {
		string::LinearString < char > pattern ( "" );
		unsigned int k = 1;
		auto result = stringology::indexing::NondeterministicApproximateSuffixEpsilonAutomatonForHammingDistance::construct( pattern, k);

		auto state00 = ext::make_pair( 0, 0 );
		automaton::EpsilonNFA < char, ext::pair < unsigned int, unsigned int > > ref ( state00 );
		ref.setInputAlphabet( pattern.getAlphabet() );
		ref.setFinalStates( ext::set < ext::pair < unsigned int, unsigned int > > { state00 });

		CHECK( ref == result );
	}

	SECTION ( "Test equality" ) {
		auto pattern = GENERATE ( string::LinearString < char > ( "" ), string::LinearString < char > ( "acacaca" ), string::LinearString < char > ( "abcdef" ) );
		for ( size_t k = 0; k < 5; k++ ) {
			auto sa_enfa = stringology::indexing::NondeterministicApproximateSuffixEpsilonAutomatonForHammingDistance::construct ( pattern, k );
			auto sa_nfa  = stringology::indexing::NondeterministicApproximateSuffixAutomatonForHammingDistance::construct ( pattern, k );

			CAPTURE ( pattern, k );
			CHECK ( automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( sa_nfa ) ) ) ) ==
					automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( automaton::simplify::EpsilonRemoverIncoming::remove ( sa_enfa ) ) ) ) ) );


		}
	}
}
