#include <catch2/catch.hpp>

#include "string/LinearString.h"
#include "stringology/indexing/ExactSuffixAutomaton.h"
#include "stringology/indexing/NondeterministicExactSuffixAutomaton.h"
#include "stringology/indexing/NondeterministicExactSuffixEpsilonAutomaton.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 ( "Exact Suffix Automaton", "[unit][algo][stringology][indexing]" ) {
	SECTION ( "Test construction" ) {

		string::LinearString < char > pattern ( "atatac" );

		indexes::stringology::SuffixAutomaton < char > suffixAutomaton = stringology::indexing::ExactSuffixAutomaton::construct ( pattern );
		automaton::NFA < char, unsigned > sa = stringology::indexing::NondeterministicExactSuffixAutomaton::construct ( pattern );
		automaton::EpsilonNFA < char, unsigned > ensa = stringology::indexing::NondeterministicExactSuffixEpsilonAutomaton::construct ( pattern );

		automaton::DFA < char, unsigned > tmp ( 0 );

		tmp.setInputAlphabet ( pattern.getAlphabet ( ) );

		for ( unsigned i = 1; i <= 6; ++i ) {
			tmp.addState ( i );
		}

		tmp.addFinalState ( 0 );
		tmp.addFinalState ( 6 );

		tmp.addTransition ( 0, 'a', 1 );
		tmp.addTransition ( 0, 't', 2 );
		tmp.addTransition ( 0, 'c', 6 );
		tmp.addTransition ( 1, 't', 2 );
		tmp.addTransition ( 1, 'c', 6 );
		tmp.addTransition ( 2, 'a', 3 );
		tmp.addTransition ( 3, 't', 4 );
		tmp.addTransition ( 3, 'c', 6 );
		tmp.addTransition ( 4, 'a', 5 );
		tmp.addTransition ( 5, 'c', 6 );

		indexes::stringology::SuffixAutomaton < char > refSuffixAutomaton ( std::move ( tmp ), 6 );
		REQUIRE ( suffixAutomaton == refSuffixAutomaton );

		const automaton::DFA < char, unsigned > & sa_dfa = suffixAutomaton.getAutomaton ( );

		CHECK ( automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( sa ) ) ) ) ==
		        automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( sa_dfa ) ) ) ) );

		CHECK ( automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( automaton::simplify::EpsilonRemoverIncoming::remove ( ensa ) ) ) ) ) ==
		        automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( sa_dfa ) ) ) ) );

		core::normalize < indexes::stringology::SuffixAutomaton < char > >::eval ( std::move ( refSuffixAutomaton ) );
	}

	SECTION ( "Test equality" ) {
		auto pattern = GENERATE ( string::LinearString < char > ( "" ), string::LinearString < char > ( "acacaca" ), string::LinearString < char > ( "hello world" ) );

		indexes::stringology::SuffixAutomaton < char > suffixAutomaton = stringology::indexing::ExactSuffixAutomaton::construct ( pattern );
		automaton::NFA < char, unsigned > sa = stringology::indexing::NondeterministicExactSuffixAutomaton::construct ( pattern );
		automaton::EpsilonNFA < char, unsigned > ensa = stringology::indexing::NondeterministicExactSuffixEpsilonAutomaton::construct ( pattern );

		const automaton::DFA < char, unsigned > & sa_dfa = suffixAutomaton.getAutomaton ( );

		CHECK ( automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( sa ) ) ) ) ==
		        automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( sa_dfa ) ) ) ) );

		CHECK ( automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( automaton::simplify::EpsilonRemoverIncoming::remove ( ensa ) ) ) ) ) ==
		        automaton::simplify::Normalize::normalize ( automaton::simplify::Minimize::minimize ( automaton::simplify::Trim::trim ( automaton::determinize::Determinize::determinize ( sa_dfa ) ) ) ) );
	}
}
