#include <catch2/catch.hpp>

#include "sax/SaxParseInterface.h"
#include "sax/SaxComposeInterface.h"

#include "automaton/FSM/DFA.h"
#include "automaton/xml/FSM/DFA.h"
#include "automaton/FSM/ExtendedNFA.h"
#include "automaton/xml/FSM/ExtendedNFA.h"
#include "automaton/PDA/SinglePopDPDA.h"
#include "automaton/xml/PDA/SinglePopDPDA.h"
#include "automaton/PDA/DPDA.h"
#include "automaton/xml/PDA/DPDA.h"
#include "automaton/PDA/NPDA.h"
#include "automaton/xml/PDA/NPDA.h"
#include "automaton/PDA/RealTimeHeightDeterministicNPDA.h"
#include "automaton/xml/PDA/RealTimeHeightDeterministicNPDA.h"
#include "automaton/TA/NFTA.h"
#include "automaton/xml/TA/NFTA.h"

#include "automaton/AutomatonException.h"

#include "factory/XmlDataFactory.hpp"

#include "alphabet/BottomOfTheStackSymbol.h"

#include "regexp/RegExp.h"
#include "regexp/unbounded/UnboundedRegExp.h"
#include "regexp/unbounded/UnboundedRegExpElements.h"

#include <primitive/xml/Integer.h>
#include <primitive/xml/Character.h>
#include <primitive/xml/Unsigned.h>

TEST_CASE ( "Automaton", "[unit][data][automaton]" ) {
	SECTION ( "DFA Parser" ) {
	automaton::DFA < char, int > automaton ( 1 );

	automaton.addState ( 1 );
	automaton.addState ( 2 );
	automaton.addState ( 3 );
	automaton.addInputSymbol ( 'a' );
	automaton.addInputSymbol ( 'b' );

	automaton.addTransition ( 1, 'a', 2);
	automaton.addTransition ( 2, 'b', 1);

	automaton.addFinalState ( 3 );

	CHECK( automaton == automaton );
	{
		ext::deque<sax::Token> tokens = factory::XmlDataFactory::toTokens(automaton);
		std::string tmp = sax::SaxComposeInterface::composeMemory ( tokens );

		ext::deque<sax::Token> tokens2 = sax::SaxParseInterface::parseMemory ( tmp );
		automaton::DFA < char, int > automaton2 = factory::XmlDataFactory::fromTokens (std::move(tokens2));

		CHECK( automaton == automaton2 );
	}
}

SECTION ( "NFA Insert transitions" ) {
	automaton::NFA < char, int > automaton ( 1 );

	automaton.addState ( 1 );
	automaton.addState ( 2 );
	automaton.addState ( 3 );
	automaton.addInputSymbol ( 'a' );
	automaton.addInputSymbol ( 'b' );

	automaton.addTransition ( 1, 'a', 2 );
	automaton.addTransition ( 1, 'a', 1 );

	automaton.addTransition ( 2, 'b', 3 );
	automaton.addTransition ( 2, 'b', 2 );

	automaton.addFinalState ( 3 );

	automaton::NFA < char, int > automaton2 ( 1 );

	automaton2.addState ( 1 );
	automaton2.addState ( 2 );
	automaton2.addState ( 3 );
	automaton2.addInputSymbol ( 'a' );
	automaton2.addInputSymbol ( 'b' );

	automaton2.addTransition ( 1, 'a', 1 );
	automaton2.addTransition ( 1, 'a', 2 );
	automaton2.addTransition ( 2, 'b', 2 );
	automaton2.addTransition ( 2, 'b', 3 );

	automaton2.addFinalState ( 3 );

	CHECK ( automaton == automaton2 );

	automaton.removeTransition ( 2, 'b', 3 );
	automaton2.removeTransition ( 2, 'b', 3 );

	CHECK ( automaton == automaton2 );
	CHECK ( automaton.getTransitions ( ).size ( ) == 3 );
}

SECTION ( "SinglePopDPDA Transitions" ) {
	automaton::SinglePopDPDA < char, char, int > automaton ( 1, 'S' );

	automaton.addState(1);
	automaton.addState(2);
	automaton.addState(3);

	automaton.addInputSymbol('a');
	automaton.addInputSymbol('b');

	automaton.addPushdownStoreSymbol('S');
	automaton.addPushdownStoreSymbol('X');
	automaton.addPushdownStoreSymbol('Y');

	automaton.addTransition(3, /* eps, */ 'X', 1, { 'X' } );

	CHECK(!automaton.addTransition(3, /* eps, */ 'X', 1, { 'X' } ) );

	automaton.addTransition(3, 'a', 'Y', 1, { 'X' } );

	CHECK_THROWS_AS(
		automaton.addTransition(3, 'a', 'X', 1, { 'X' } ),
		automaton::AutomatonException
	);

	automaton.addTransition(1, 'a', 'X', 2, { 'Y' } );
	automaton.addTransition(1, 'a', 'X', 2, { 'Y' } );
	automaton.addTransition(1, /* eps, */ 'Y', 2, { 'Y' } );

	automaton.addTransition(2, 'b', 'X', 1, { 'X' } );
	CHECK_THROWS_AS(
		automaton.addTransition(2, /* eps, */ 'X', 1, { 'X' } ),
		automaton::AutomatonException
	);

	automaton.addFinalState(3);
}

SECTION ( "DPDA Transitions" ) {
	automaton::DPDA < char, char, int > automaton ( 1, 'S' );

	automaton.addState(1);
	automaton.addState(2);
	automaton.addState(3);
	automaton.addState(4);

	automaton.addInputSymbol('a');
	automaton.addInputSymbol('b');

	automaton.addPushdownStoreSymbol('S');
	automaton.addPushdownStoreSymbol('X');
	automaton.addPushdownStoreSymbol('Y');

	automaton.addTransition(3, /* eps, */ { 'X' }, 1, { 'X' } );

	CHECK(!automaton.addTransition(3, /* eps, */ { 'X' }, 1, { 'X' } ) );

	automaton.addTransition(3, 'a', { 'Y' }, 1, { 'X' } );

	CHECK_THROWS_AS(
		automaton.addTransition(3, 'a', { 'X' }, 1, { 'X' } ),
		automaton::AutomatonException
	);

	automaton.addTransition(1, 'a', { 'X' }, 2, { 'Y' } );
	automaton.addTransition(1, 'a', { 'X' }, 2, { 'Y' } );
	automaton.addTransition(1, /* eps, */ { 'Y' }, 2, { 'Y' } );

	automaton.addTransition(2, 'b', { 'X' }, 1, { 'X' } );
	CHECK_THROWS_AS(
		automaton.addTransition(2, /* eps, */ { 'X' }, 1, { 'X' } ),
		automaton::AutomatonException
	);

	automaton.addTransition(4, 'a', { 'X', 'Y' }, 2, { 'Y' } );
	automaton.addTransition(4, 'a', { 'X', 'S' }, 2, { 'Y' } );
	automaton.addTransition(4, /* eps, */ { 'X', 'X' }, 2, { 'Y' } );
	CHECK(!automaton.addTransition(4, /* eps, */ { 'X', 'X' }, 2, { 'Y' } ));
	CHECK_THROWS_AS(
		automaton.addTransition(4, /* eps, */ { 'X', 'Y' }, 2, { 'Y' } ),
		automaton::AutomatonException
	);
	automaton.addTransition(4, 'b', { 'X', 'Y' }, 2, { 'Y' } );
	CHECK_THROWS_AS(
		automaton.addTransition(4, 'a', { 'X' }, 2, { 'Y' } ),
		automaton::AutomatonException
	);

	automaton.addFinalState(3);
}

SECTION ( "Extended NFA Alphabet" ) {
	automaton::ExtendedNFA < char, int > automaton (0);
	automaton.addState(0);
	automaton.addState(1);

	automaton.setInputAlphabet({'a', 'b' });

	regexp::UnboundedRegExpConcatenation < char > goodConcat1, goodConcat2;
	regexp::UnboundedRegExpConcatenation < char > badConcat;

	goodConcat1.appendElement(regexp::UnboundedRegExpSymbol < char > ('a'));
	goodConcat1.appendElement(regexp::UnboundedRegExpSymbol < char > ('b'));

	goodConcat2.appendElement(regexp::UnboundedRegExpSymbol < char > ('b'));
	goodConcat2.appendElement(regexp::UnboundedRegExpSymbol < char > ('b'));

	badConcat.appendElement(regexp::UnboundedRegExpSymbol < char > ('b'));
	badConcat.appendElement(regexp::UnboundedRegExpSymbol < char > (char('d')));

	CHECK_THROWS_AS(automaton.addTransition(0, regexp::UnboundedRegExpStructure < char > (badConcat), 1), automaton::AutomatonException);
	CHECK_NOTHROW(automaton.addTransition(0, regexp::UnboundedRegExpStructure < char > (goodConcat1), 1));
	CHECK_NOTHROW(automaton.addTransition(0, regexp::UnboundedRegExpStructure < char > (goodConcat2), 1));
	CHECK_NOTHROW(automaton.addTransition(0, regexp::UnboundedRegExpStructure < char > (regexp::UnboundedRegExpEpsilon < char > ()), 1));
}

SECTION ( "NPDA Transitions" ) {
	automaton::NPDA < common::ranked_symbol < char >, char, unsigned > automaton(0, 'T');

	common::ranked_symbol < char > a2 ( 'a', 2 );
	common::ranked_symbol < char > a1 ( 'a', 1 );
	common::ranked_symbol < char > a0 ( 'a', 0 );

	automaton.setStates({0, 1, 2, 3, 4});
	automaton.setInputAlphabet({a2, a1, a0});
	automaton.setPushdownStoreAlphabet({'T', 'R'});

	automaton.addTransition(0, a2, {'T'}, 0, {'T', 'T'});
	automaton.addTransition(0, a1, {'T'}, 0, {'T'});
	automaton.addTransition(0, a0, {'T'}, 0, {});

	automaton.addTransition(0, a2, {'T'}, 1, {'R', 'T'});

	automaton.addTransition(1, a2, {'T'}, 1, {'T', 'T'});
	automaton.addTransition(1, a2, {'R'}, 1, {'T', 'R'});
	automaton.addTransition(1, a1, {'T'}, 1, {'T'});
	automaton.addTransition(1, a1, {'R'}, 1, {'R'});
	automaton.addTransition(1, a0, {'T'}, 1, {});
	automaton.addTransition(1, a0, {'R'}, 2, {});

	automaton.addTransition(2, a1, {'T'}, 3, {'R'});

	automaton.addTransition(3, a2, {'T'}, 3, {'T', 'T'});
	automaton.addTransition(3, a2, {'R'}, 3, {'T', 'R'});
	automaton.addTransition(3, a1, {'T'}, 3, {'T'});
	automaton.addTransition(3, a1, {'R'}, 3, {'R'});
	automaton.addTransition(3, a0, {'T'}, 3, {});
	automaton.addTransition(3, a0, {'R'}, 4, {});

	automaton.setFinalStates({4});
	factory::XmlDataFactory::toStdout(automaton);

	CHECK(!(automaton < automaton));
	CHECK(automaton == automaton);
}

SECTION ( "RHPDA Transitions" ) {
	automaton::RealTimeHeightDeterministicNPDA < char, char, unsigned > automaton { 'S' };
	automaton.setStates({1, 2, 3});
	automaton.setInitialStates({1});

	automaton.setInputAlphabet({'a', 'b'});
	automaton.setPushdownStoreAlphabet({'S', 'X', 'Y'});

	automaton.addCallTransition(1, 'a', 2, 'X');
	automaton.addCallTransition(2, 3, 'X');
	automaton.addReturnTransition(3, 'Y', 1);

	automaton.setFinalStates({3});
	factory::XmlDataFactory::toStdout(automaton);

	CHECK(!(automaton < automaton));
	CHECK(automaton == automaton);
}

SECTION ( "NFTA Parser" ) {
	common::ranked_symbol < char > a ( 'a', 2 );
	common::ranked_symbol < char > b ( 'b', 1 );
	common::ranked_symbol < char > c ( 'c', 0 );

	automaton::NFTA < char, int > automaton;

	automaton.addState ( 2 );
	automaton.addState ( 1 );
	automaton.addState ( 0 );
	automaton.addInputSymbol(a);
	automaton.addInputSymbol(b);
	automaton.addInputSymbol(c);

	ext::vector < int > states1 = {1, 0};
	automaton.addTransition(a, states1, 2);
	automaton.addTransition(a, states1, 1);
	ext::vector < int > states2 = {0};
	automaton.addTransition(b, states2, 1);
	automaton.addTransition(b, states2, 0);
	ext::vector < int > states3 = {};
	automaton.addTransition(c, states3, 0);

	automaton.addFinalState(2);

	CHECK( automaton == automaton );
	{
		ext::deque<sax::Token> tokens = factory::XmlDataFactory::toTokens(automaton);
		std::string tmp = sax::SaxComposeInterface::composeMemory ( tokens );
		std::cout << tmp << std::endl;

		ext::deque<sax::Token> tokens2 = sax::SaxParseInterface::parseMemory ( tmp );
		automaton::NFTA < char, int > automaton2 = factory::XmlDataFactory::fromTokens (std::move(tokens2));

		CHECK( automaton == automaton2 );
	}
}

SECTION ( "NFTA Transitions" ) {
	common::ranked_symbol < char > a ( 'a', 2 );
	common::ranked_symbol < char > b ( 'b', 1 );
	common::ranked_symbol < char > c ( 'c', 0 );
	common::ranked_symbol < char > d ( 'd', 0 );

	automaton::NFTA < char, int > automaton;

	automaton.addState ( 2 );
	automaton.addState ( 1 );
	automaton.addState ( 0 );
	automaton.addInputSymbol ( a );
	automaton.addInputSymbol ( b );
	automaton.addInputSymbol ( c );

	ext::vector < int > states1 = { 1, 0 };
	automaton.addTransition(a, states1, 2);
	automaton.addTransition(a, states1, 1);
	ext::vector < int > states2 = { 0 };
	automaton.addTransition(b, states2, 1);
	automaton.addTransition(b, states2, 0);
	ext::vector < int > states3 = {};
	automaton.addTransition(c, states3, 0);

	automaton.addFinalState ( 2 );

	CHECK_THROWS_AS(automaton.removeInputSymbol(a), exception::CommonException);
	CHECK_NOTHROW(automaton.removeInputSymbol(d));
	CHECK_NOTHROW(automaton.removeTransition(b, states1, 2));
	CHECK_THROWS_AS(automaton.addTransition(b, states1, 1), exception::CommonException);
	CHECK_THROWS_AS(automaton.addTransition(a, states2, 1), exception::CommonException);
	CHECK_THROWS_AS(automaton.addTransition(d, states3, 0), exception::CommonException);
}
}
