#include <catch2/catch.hpp>
#include <alib/list>

#include <automaton/FSM/NFA.h>
#include <automaton/TA/NFTA.h>
#include <automaton/PDA/InputDrivenNPDA.h>
#include <automaton/PDA/InputDrivenDPDA.h>
#include <automaton/PDA/VisiblyPushdownNPDA.h>
#include <automaton/PDA/VisiblyPushdownDPDA.h>
#include <alphabet/BottomOfTheStackSymbol.h>

#include <automaton/determinize/Determinize.h>

TEST_CASE ( "Determinize", "[unit][algo][automaton][determinize]" ) {
	SECTION ( "NFA" ) {
		automaton::NFA < > automaton(DefaultStateType(1));

		automaton.addState(DefaultStateType(1));
		automaton.addState(DefaultStateType(2));
		automaton.addState(DefaultStateType(3));
		automaton.addInputSymbol(DefaultSymbolType("a"));
		automaton.addInputSymbol(DefaultSymbolType("b"));

		automaton.addTransition(DefaultStateType(1), DefaultSymbolType("a"), DefaultStateType(2));
		automaton.addTransition(DefaultStateType(2), DefaultSymbolType("b"), DefaultStateType(1));

		automaton.addFinalState(DefaultStateType(3));

		automaton::DFA < DefaultSymbolType, ext::set < DefaultStateType > > determinized = automaton::determinize::Determinize::determinize(automaton);

		CHECK(determinized.getStates().size() == 3);

	}

	SECTION ( "IDPDA" ) {
		automaton::InputDrivenNPDA < > automaton(DefaultStateType(1), DefaultSymbolType('S'));

		automaton.addInputSymbol(DefaultSymbolType("a"));
		automaton.addInputSymbol(DefaultSymbolType("b"));

		automaton.setPushdownStoreOperation(DefaultSymbolType("a"), ext::vector<DefaultSymbolType>{}, ext::vector<DefaultSymbolType> {});
		automaton.setPushdownStoreOperation(DefaultSymbolType("b"), ext::vector<DefaultSymbolType>{}, ext::vector<DefaultSymbolType> {});

		automaton.addState(DefaultStateType(1));
		automaton.addState(DefaultStateType(2));
		automaton.addState(DefaultStateType(3));
		automaton.addTransition(DefaultStateType(1), DefaultSymbolType("a"), DefaultStateType(2));
		automaton.addTransition(DefaultStateType(2), DefaultSymbolType("b"), DefaultStateType(1));

		automaton.addFinalState(DefaultStateType(3));

		automaton::InputDrivenDPDA < DefaultSymbolType, DefaultSymbolType, ext::set < DefaultStateType > > determinized = automaton::determinize::Determinize::determinize(automaton);

		CHECK(determinized.getStates().size() == 3);
	}

	SECTION ( "VPA" ) {
		automaton::VisiblyPushdownNPDA < > automaton(DefaultSymbolType(alphabet::BottomOfTheStackSymbol {}));

		automaton.addCallInputSymbol(DefaultSymbolType('a'));
		automaton.addReturnInputSymbol(DefaultSymbolType('^'));

		automaton.addPushdownStoreSymbol(DefaultSymbolType('A'));
		automaton.addPushdownStoreSymbol(DefaultSymbolType('B'));
		automaton.addPushdownStoreSymbol(DefaultSymbolType('C'));
		automaton.addPushdownStoreSymbol(DefaultSymbolType('D'));
		automaton.addPushdownStoreSymbol(DefaultSymbolType('E'));
		automaton.addPushdownStoreSymbol(DefaultSymbolType('F'));
		automaton.addPushdownStoreSymbol(DefaultSymbolType('T'));

		automaton.addState(DefaultStateType(0));
		automaton.addState(DefaultStateType(1));
		automaton.addState(DefaultStateType(2));
		automaton.addState(DefaultStateType(3));
		automaton.addState(DefaultStateType(4));
		automaton.addState(DefaultStateType(5));
		automaton.addState(DefaultStateType(6));


		automaton.addCallTransition(DefaultStateType(0), DefaultSymbolType('a'), DefaultStateType(0), DefaultSymbolType('A'));
		automaton.addReturnTransition(DefaultStateType(0), DefaultSymbolType('^'), DefaultSymbolType('A'), DefaultStateType(0));

		automaton.addCallTransition(DefaultStateType(0), DefaultSymbolType('a'), DefaultStateType(1), DefaultSymbolType('B'));

		automaton.addCallTransition(DefaultStateType(1), DefaultSymbolType('a'), DefaultStateType(2), DefaultSymbolType('C'));
		automaton.addCallTransition(DefaultStateType(2), DefaultSymbolType('a'), DefaultStateType(2), DefaultSymbolType('D'));
		automaton.addReturnTransition(DefaultStateType(2), DefaultSymbolType('^'), DefaultSymbolType('D'), DefaultStateType(2));
		automaton.addReturnTransition(DefaultStateType(2), DefaultSymbolType('^'), DefaultSymbolType('C'), DefaultStateType(3));

		automaton.addCallTransition(DefaultStateType(3), DefaultSymbolType('a'), DefaultStateType(4), DefaultSymbolType('E'));
		automaton.addCallTransition(DefaultStateType(4), DefaultSymbolType('a'), DefaultStateType(4), DefaultSymbolType('F'));
		automaton.addReturnTransition(DefaultStateType(4), DefaultSymbolType('^'), DefaultSymbolType('F'), DefaultStateType(4));
		automaton.addReturnTransition(DefaultStateType(4), DefaultSymbolType('^'), DefaultSymbolType('E'), DefaultStateType(5));

		automaton.addReturnTransition(DefaultStateType(5), DefaultSymbolType('^'), DefaultSymbolType('B'), DefaultStateType(6));

		automaton.addInitialState(DefaultStateType(0));
		automaton.addFinalState(DefaultStateType(4));

		auto determinized = automaton::determinize::Determinize::determinize ( automaton );
		CHECK ( determinized.getStates().size() == 28 );
		CHECK ( determinized.getFinalStates().size() == 14 );
		CHECK ( determinized.getCallTransitions().size() == 28 );
		CHECK ( determinized.getReturnTransitions().size() == 196 );
		CHECK ( determinized.getLocalTransitions().size() == 0 );
		CHECK ( determinized.getCallTransitions().size() == 28 );

	}

	SECTION ( "NFTA" ) {
		automaton::NFTA < char, int> automaton;

		const common::ranked_symbol < char > a ('a', 2);
		const common::ranked_symbol < char > b ('b', 1);
		const common::ranked_symbol < char > c ('c', 0);
		const ext::set<common::ranked_symbol < char > > alphabet {a, b, c};
		automaton.setInputAlphabet(alphabet);

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

		ext::vector<int> a1States = {1, 3};
		automaton.addTransition(a, a1States, 1);
		automaton.addTransition(a, a1States, 3);
		ext::vector<int> a2States = {3, 3};
		automaton.addTransition(a, a2States, 2);
		ext::vector<int> bStates = {2};
		automaton.addTransition(b, bStates, 1);
		ext::vector<int> cStates;
		automaton.addTransition(c, cStates, 3);

		automaton.addFinalState(3);

		automaton::DFTA < char, ext::set < int > > determinized = automaton::determinize::Determinize::determinize(automaton);

		CHECK(determinized.getStates().size() == 5);
		CHECK(determinized.getFinalStates().size() == 3);
		CHECK(determinized.getTransitions().size() == 15);
	}
}
