#include <catch2/catch.hpp>

#include "grammar/simplify/ToGNF.h"
#include "grammar/generate/GenerateUpToLength.h"

#include "grammar/ContextFree/GNF.h"
#include "grammar/string/ContextFree/GNF.h"
#include "grammar/ContextFree/EpsilonFreeCFG.h"

#include <factory/StringDataFactory.hpp>
#include <common/createUnique.hpp>

#include "container/string/ObjectsVariant.h"

TEST_CASE ( "Test to GNF", "[unit][algo][grammar][simplify]" ) {
	SECTION ( "Test CFG" ) {
		{
			DefaultSymbolType S = DefaultSymbolType("S");
			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::EpsilonFreeCFG < > grammar1(S);
			grammar1.setNonterminalAlphabet({S, A, B, C, D});
			grammar1.setTerminalAlphabet({a, b});

			DefaultSymbolType aprimed = common::createUnique(a, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());
			DefaultSymbolType bprimed = common::createUnique(b, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());

			grammar::GNF < DefaultSymbolType, ext::variant < DefaultSymbolType, DefaultSymbolType > > grammar2 = grammar::simplify::ToGNF::convert(grammar1);

			grammar::GNF < DefaultSymbolType, ext::variant < DefaultSymbolType, DefaultSymbolType > > grammar3(S);
			grammar3.setNonterminalAlphabet({S, A, B, C, D, aprimed, bprimed});
			grammar3.setTerminalAlphabet({a, b});
			grammar3.addRule(aprimed, ext::make_pair(a, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(bprimed, ext::make_pair(b, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));

			CAPTURE ( grammar2, grammar3 ) ;
			CHECK(grammar2 == grammar3);
		}
		{
			DefaultSymbolType S = DefaultSymbolType("S");
			DefaultSymbolType A = DefaultSymbolType("A");
			DefaultSymbolType B = DefaultSymbolType("B");
			DefaultSymbolType C = DefaultSymbolType("C");
			DefaultSymbolType D = DefaultSymbolType("D");

			DefaultSymbolType a = DefaultSymbolType("a");
			DefaultSymbolType b = DefaultSymbolType("b");
			DefaultSymbolType c = DefaultSymbolType("c");

			grammar::EpsilonFreeCFG < > grammar1(S);
			grammar1.setNonterminalAlphabet({S, A, B, C, D});
			grammar1.setTerminalAlphabet({a, b, c});
			grammar1.addRule(S, {A});
			grammar1.addRule(A, {A, a});
			grammar1.addRule(A, {A, b});
			grammar1.addRule(A, {c});

			DefaultSymbolType Aprimed = common::createUnique(A, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());
			DefaultSymbolType aprimed = common::createUnique(a, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());
			DefaultSymbolType bprimed = common::createUnique(b, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());
			DefaultSymbolType cprimed = common::createUnique(c, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());

			grammar::GNF < DefaultSymbolType, ext::variant < DefaultSymbolType, DefaultSymbolType > > grammar2 = grammar::simplify::ToGNF::convert(grammar1);

			grammar::GNF < DefaultSymbolType, ext::variant < DefaultSymbolType, DefaultSymbolType > > grammar3(S);
			grammar3.setNonterminalAlphabet({S, A, Aprimed, B, C, D, aprimed, bprimed, cprimed});
			grammar3.setTerminalAlphabet({a, b, c});
			grammar3.addRule(S, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{Aprimed}));
			grammar3.addRule(S, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(A, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{Aprimed}));
			grammar3.addRule(A, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(Aprimed, ext::make_pair(a, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{Aprimed}));
			grammar3.addRule(Aprimed, ext::make_pair(a, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(Aprimed, ext::make_pair(b, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{Aprimed}));
			grammar3.addRule(Aprimed, ext::make_pair(b, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(aprimed, ext::make_pair(a, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(bprimed, ext::make_pair(b, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(cprimed, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));

			CAPTURE ( grammar1, grammar2, grammar3 ) ;

			CHECK(grammar2 == grammar3);
		}
		{
			DefaultSymbolType S = DefaultSymbolType("S");
			DefaultSymbolType A = DefaultSymbolType("A");
			DefaultSymbolType B = DefaultSymbolType("B");
			DefaultSymbolType C = DefaultSymbolType("C");

			DefaultSymbolType a = DefaultSymbolType("a");
			DefaultSymbolType b = DefaultSymbolType("b");
			DefaultSymbolType c = DefaultSymbolType("c");

			grammar::EpsilonFreeCFG < > grammar1(S);
			grammar1.setNonterminalAlphabet({S, A, B, C});
			grammar1.setTerminalAlphabet({a, b, c});
			grammar1.addRule(S, {A});
			grammar1.addRule(A, {B, a});
			grammar1.addRule(B, {A, b});
			grammar1.addRule(A, {c});

			DefaultSymbolType Bprimed = common::createUnique(B, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());
			DefaultSymbolType aprimed = common::createUnique(a, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());
			DefaultSymbolType bprimed = common::createUnique(b, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());
			DefaultSymbolType cprimed = common::createUnique(c, grammar1.getTerminalAlphabet(), grammar1.getNonterminalAlphabet());

			grammar::GNF < DefaultSymbolType, ext::variant < DefaultSymbolType, DefaultSymbolType > > grammar2 = grammar::simplify::ToGNF::convert(grammar1);

			grammar::GNF < DefaultSymbolType, ext::variant < DefaultSymbolType, DefaultSymbolType > > grammar3(S);
			grammar3.setNonterminalAlphabet({S, A, B, Bprimed, C, aprimed, bprimed, cprimed});
			grammar3.setTerminalAlphabet({a, b, c});
			grammar3.addRule(S, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(S, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed, Bprimed, aprimed}));
			grammar3.addRule(S, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed, aprimed}));
			grammar3.addRule(A, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed, aprimed}));
			grammar3.addRule(A, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed, Bprimed, aprimed}));
			grammar3.addRule(A, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(B, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed}));
			grammar3.addRule(B, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed, Bprimed}));
			grammar3.addRule(Bprimed, ext::make_pair(a, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed, Bprimed}));
			grammar3.addRule(Bprimed, ext::make_pair(a, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{bprimed}));
			grammar3.addRule(aprimed, ext::make_pair(a, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(bprimed, ext::make_pair(b, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));
			grammar3.addRule(cprimed, ext::make_pair(c, ext::vector<ext::variant<DefaultSymbolType,DefaultSymbolType>>{}));

			CAPTURE ( grammar1, grammar2, grammar3 );
			CHECK(grammar2 == grammar3);
		}
		{
			DefaultSymbolType A = DefaultSymbolType("A");
			DefaultSymbolType B = DefaultSymbolType("B");
			DefaultSymbolType C = DefaultSymbolType("C");

			DefaultSymbolType a = DefaultSymbolType("a");
			DefaultSymbolType b = DefaultSymbolType("b");

			grammar::EpsilonFreeCFG < > grammar1(A);
			grammar1.setNonterminalAlphabet({A, B, C});
			grammar1.setTerminalAlphabet({a, b});
			grammar1.addRule(A, {B, C});
			grammar1.addRule(A, {a});
			grammar1.addRule(B, {C, A});
			grammar1.addRule(B, {A, b});
			grammar1.addRule(C, {A, B});
			grammar1.addRule(C, {C, C});
			grammar1.addRule(C, {a});

			grammar::GNF < DefaultSymbolType, ext::variant < DefaultSymbolType, DefaultSymbolType > > grammar2 = grammar::simplify::ToGNF::convert(grammar1);

			CHECK(grammar::generate::GenerateUpToLength::generate(grammar1, 7) == grammar::generate::GenerateUpToLength::generate(grammar2, 7));
		}
	}
}
