#include <catch2/catch.hpp>

#include <alphabet/BottomOfTheStackSymbol.h>
#include <grammar/ContextFree/CFG.h>

#include "grammar/convert/ToAutomaton.h"
#include "grammar/convert/ToAutomatonBottomUp.h"

#include <factory/XmlDataFactory.hpp>

#include <label/InitialStateLabel.h>
#include <label/FinalStateLabel.h>

TEST_CASE ( "CFG to PDA", "[unit][algo][grammar][convert]" ) {
	SECTION ( "Top down" ) {
		std::string nE = "E";
		std::string nT = "T";
		std::string nF = "F";

		char tP = '+';
		char tS = '*';
		char tL = '(';
		char tR = ')';
		char tA = 'a';

		grammar::CFG < char, std::string > grammar(nE);
		grammar.setTerminalAlphabet ( ext::set < char > { tP, tS, tL, tR, tA } );
		grammar.setNonterminalAlphabet ( ext::set < std::string > { nE, nT, nF } );
		grammar.addRule ( nE, ext::vector < ext::variant < char, std::string > > { nE, tP, nT } );
		grammar.addRule ( nE, ext::vector < ext::variant < char, std::string > > { nT } );
		grammar.addRule ( nT, ext::vector < ext::variant < char, std::string > > { nT, tS, nF } );
		grammar.addRule ( nT, ext::vector < ext::variant < char, std::string > > { nF } );
		grammar.addRule ( nF, ext::vector < ext::variant < char, std::string > > { tL, nE, tR } );
		grammar.addRule ( nF, ext::vector < ext::variant < char, std::string > > { tA } );

		unsigned q = label::InitialStateLabel::instance < unsigned > ( );

		automaton::NPDA < char, ext::variant < char, std::string >, unsigned > pda ( q, nE );
		pda.setInputAlphabet ( ext::set < char > { tP, tS, tL, tR, tA } );
		pda.setPushdownStoreAlphabet ( ext::set < ext::variant < char, std::string > > { tP, tS, tL, tR, tA, nE, nT, nF } );

		pda.addTransition ( q, ext::vector < ext::variant < char, std::string > > { nE }, q, ext::vector < ext::variant < char, std::string > > { nE, tP, nT } );
		pda.addTransition ( q, ext::vector < ext::variant < char, std::string > > { nE }, q, ext::vector < ext::variant < char, std::string > > { nT } );
		pda.addTransition ( q, ext::vector < ext::variant < char, std::string > > { nT }, q, ext::vector < ext::variant < char, std::string > > { nT, tS, nF } );
		pda.addTransition ( q, ext::vector < ext::variant < char, std::string > > { nT }, q, ext::vector < ext::variant < char, std::string > > { nF } );
		pda.addTransition ( q, ext::vector < ext::variant < char, std::string > > { nF }, q, ext::vector < ext::variant < char, std::string > > { tL, nE, tR } );
		pda.addTransition ( q, ext::vector < ext::variant < char, std::string > > { nF }, q, ext::vector < ext::variant < char, std::string > > { tA } );

		for ( char symbol : pda.getInputAlphabet ( ) )
			pda.addTransition ( q, symbol, ext::vector<ext::variant < char, std::string >>{symbol}, q, ext::vector<ext::variant < char, std::string >>{});

		CHECK (pda == grammar::convert::ToAutomaton::convert(grammar));
	}

	SECTION ( "Bottom up" ) {
		DefaultSymbolType nE = DefaultSymbolType('E');
		DefaultSymbolType nT = DefaultSymbolType('T');
		DefaultSymbolType nF = DefaultSymbolType('F');

		DefaultSymbolType tP = DefaultSymbolType('+');
		DefaultSymbolType tS = DefaultSymbolType('*');
		DefaultSymbolType tL = DefaultSymbolType('(');
		DefaultSymbolType tR = DefaultSymbolType(')');
		DefaultSymbolType tA = DefaultSymbolType('a');

		grammar::CFG < > grammar(nE);
		grammar.setTerminalAlphabet(ext::set<DefaultSymbolType>{tP, tS, tL, tR, tA});
		grammar.setNonterminalAlphabet(ext::set<DefaultSymbolType>{nE, nT, nF});
		grammar.addRule(nE, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{nE, tP, nT});
		grammar.addRule(nE, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{nT});
		grammar.addRule(nT, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{nT, tS, nF});
		grammar.addRule(nT, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{nF});
		grammar.addRule(nF, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{tL, nE, tR});
		grammar.addRule(nF, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{tA});


		unsigned q = label::InitialStateLabel::instance < unsigned > ( );
		unsigned r = label::FinalStateLabel::instance < unsigned > ( );
		DefaultSymbolType bots( alphabet::BottomOfTheStackSymbol::instance < DefaultSymbolType > ( ) );

		automaton::NPDA < DefaultStateType, ext::variant < DefaultSymbolType, DefaultSymbolType >, unsigned > pda(q, bots);
		pda.addState(r);
		pda.addFinalState(r);
		pda.setInputAlphabet(ext::set<DefaultSymbolType>{tP, tS, tL, tR, tA});
		pda.setPushdownStoreAlphabet ( ext::set < ext::variant < DefaultSymbolType, DefaultSymbolType > > { tP, tS, tL, tR, tA, nE, nT, nF, bots } );

		pda.addTransition(q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nT, tP, nE}, q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nE});
		pda.addTransition(q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nT}, q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nE});
		pda.addTransition(q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nF, tS, nT}, q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nT});
		pda.addTransition(q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nF}, q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nT});
		pda.addTransition(q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{tR, nE, tL}, q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nF});
		pda.addTransition(q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{tA}, q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nF});

		for(const auto& symbol: pda.getInputAlphabet())
			pda.addTransition(q, symbol, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{}, q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{symbol});

		pda.addTransition(q, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{nE, bots}, r, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > >{});

		CHECK (pda == grammar::convert::ToAutomatonBottomUp::convert(grammar));
	}
}
