#include <catch2/catch.hpp>

#include <grammar/Grammar.h>
#include <grammar/ContextFree/CFG.h>
#include <grammar/Regular/LeftLG.h>

#include "grammar/properties/NullableNonterminals.h"
#include "grammar/properties/NonterminalUnitRuleCycle.h"
#include "grammar/properties/RecursiveNonterminal.h"

TEST_CASE ( "Grammar properties", "[unit][algo][grammar][properties]" ) {
	SECTION ( "Test Nullable" ) {
		DefaultSymbolType X = DefaultSymbolType('X');
		DefaultSymbolType Y = DefaultSymbolType('Y');
		DefaultSymbolType Z = DefaultSymbolType('Z');
		DefaultSymbolType d = DefaultSymbolType('d');

		grammar::CFG < > grammar(X);
		grammar.setTerminalAlphabet({d});
		grammar.setNonterminalAlphabet({{X, Y, Z}});
		grammar.setInitialSymbol(X);

		grammar.addRule(X, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{ d });
		grammar.addRule(X, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{ Y });
		grammar.addRule(Y, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{ d });
		grammar.addRule(Y, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{ });
		grammar.addRule(Z, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{ d });
		grammar.addRule(Z, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{ X, Y, Z });

		ext::set<DefaultSymbolType> res = {X, Y};
		CHECK(res == grammar::properties::NullableNonterminals::getNullableNonterminals(grammar));
	}
	SECTION ( "Test Unit rules" ) {
		DefaultSymbolType S = DefaultSymbolType('S');
		DefaultSymbolType A = DefaultSymbolType('A');
		DefaultSymbolType B = DefaultSymbolType('B');
		DefaultSymbolType C = DefaultSymbolType('C');
		DefaultSymbolType a = DefaultSymbolType('a');
		DefaultSymbolType b = DefaultSymbolType('b');

		grammar::LeftLG < > llg(S);
		llg.setTerminalAlphabet({a, b});
		llg.setNonterminalAlphabet({S, A, B, C});
		llg.setInitialSymbol(S);

		llg.addRule(S, ext::make_pair(A, ext::vector<DefaultSymbolType>{}));
		llg.addRule(S, ext::make_pair(B, ext::vector<DefaultSymbolType>{}));
		llg.addRule(A, ext::make_pair(A, ext::vector<DefaultSymbolType>{a}));
		llg.addRule(A, ext::make_pair(B, ext::vector<DefaultSymbolType>{}));
		llg.addRule(A, ext::make_pair(C, ext::vector<DefaultSymbolType>{}));
		llg.addRule(B, ext::make_pair(B, ext::vector<DefaultSymbolType>{b, a}));
		llg.addRule(B, ext::make_pair(C, ext::vector<DefaultSymbolType>{b}));
		llg.addRule(C, ext::vector<DefaultSymbolType>{b});
		llg.addRule(C, ext::make_pair(C, ext::vector<DefaultSymbolType>{a}));

		ext::set<DefaultSymbolType> N_S = {S, A, B, C};
		ext::set<DefaultSymbolType> N_A = {A, B, C};
		ext::set<DefaultSymbolType> N_B = {B};
		ext::set<DefaultSymbolType> N_C = {C};

		CHECK(N_S == grammar::properties::NonterminalUnitRuleCycle::getNonterminalUnitRuleCycle(llg, S));
		CHECK(N_A == grammar::properties::NonterminalUnitRuleCycle::getNonterminalUnitRuleCycle(llg, A));
		CHECK(N_B == grammar::properties::NonterminalUnitRuleCycle::getNonterminalUnitRuleCycle(llg, B));
		CHECK(N_C == grammar::properties::NonterminalUnitRuleCycle::getNonterminalUnitRuleCycle(llg, C));
	}

	SECTION ( "Test recursive nonterminals" ) {
		{
			DefaultSymbolType A = DefaultSymbolType('A');
			DefaultSymbolType B = DefaultSymbolType('B');
			DefaultSymbolType C = DefaultSymbolType('C');
			DefaultSymbolType D = DefaultSymbolType('D');

			DefaultSymbolType a = DefaultSymbolType('a');
			DefaultSymbolType b = DefaultSymbolType('b');

			grammar::CFG < > cfg(A);
			cfg.setTerminalAlphabet({a, b});
			cfg.setNonterminalAlphabet({A, B, C, D});
			cfg.setInitialSymbol(A);

			cfg.addRule(A, {B});
			cfg.addRule(B, {C});
			cfg.addRule(C, {D});
			cfg.addRule(D, {A});
			cfg.addRule(D, {});

			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, A));
			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, B));
			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, C));
			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, D));
		}
		{
			DefaultSymbolType A = DefaultSymbolType('A');
			DefaultSymbolType B = DefaultSymbolType('B');
			DefaultSymbolType C = DefaultSymbolType('C');
			DefaultSymbolType D = DefaultSymbolType('D');

			DefaultSymbolType a = DefaultSymbolType('a');
			DefaultSymbolType b = DefaultSymbolType('b');

			grammar::CFG < > cfg(A);
			cfg.setTerminalAlphabet({a, b});
			cfg.setNonterminalAlphabet({A, B, C, D});
			cfg.setInitialSymbol(A);

			cfg.addRule(A, {B});
			cfg.addRule(B, {C});
			cfg.addRule(C, {D});
			cfg.addRule(D, {B});
			cfg.addRule(D, {});

			CHECK(!grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, A));
			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, B));
			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, C));
			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, D));
		}
		{
			DefaultSymbolType A = DefaultSymbolType('A');
			DefaultSymbolType B = DefaultSymbolType('B');
			DefaultSymbolType C = DefaultSymbolType('C');
			DefaultSymbolType D = DefaultSymbolType('D');

			DefaultSymbolType a = DefaultSymbolType('a');
			DefaultSymbolType b = DefaultSymbolType('b');

			grammar::CFG < > cfg(A);
			cfg.setTerminalAlphabet({a, b});
			cfg.setNonterminalAlphabet({A, B, C, D});
			cfg.setInitialSymbol(A);

			cfg.addRule(A, {B});
			cfg.addRule(B, {C, D, A});
			cfg.addRule(C, {a});
			cfg.addRule(C, {});
			cfg.addRule(D, {b});
			cfg.addRule(D, {});

			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, A));
			CHECK(grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, B));
			CHECK(!grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, C));
			CHECK(!grammar::properties::RecursiveNonterminal::isNonterminalRecursive(cfg, D));
		}
	}
}
