#include <catch2/catch.hpp>

#include "grammar/ContextFree/CFG.h"
#include "grammar/parsing/LeftFactorize.h"

TEST_CASE ( "Left Factorize", "[unit][grammar]" ) {
	SECTION ( "Test 1" ) {
		DefaultSymbolType S = DefaultSymbolType ( "S" );
		DefaultSymbolType Sp = DefaultSymbolType ( object::AnyObject < std::string > ( "S", 1 ) );
		DefaultSymbolType B = DefaultSymbolType ( "B" );
		DefaultSymbolType Bp = DefaultSymbolType ( object::AnyObject < std::string > ( "B", 1 ) );
		DefaultSymbolType C = DefaultSymbolType ( "C" );
		DefaultSymbolType Cp = DefaultSymbolType ( object::AnyObject < std::string > ( "C", 1 ) );

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

		grammar::CFG < > grammar ( S );

		grammar.setTerminalAlphabet ( { a, b, c } );
		grammar.setNonterminalAlphabet ( { S, B, C } );
		grammar.setInitialSymbol ( S );

		grammar.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { a, B } );
		grammar.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { a, C } );
		grammar.addRule ( B, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b, B } );
		grammar.addRule ( B, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b } );
		grammar.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { c, C } );
		grammar.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { c } );

		grammar::CFG < > res = grammar;
		grammar::parsing::LeftFactorize::leftFactorize ( res, a, S);
		grammar::parsing::LeftFactorize::leftFactorize ( res, b, B);
		grammar::parsing::LeftFactorize::leftFactorize ( res, c, C);

		grammar::CFG < > comp ( S );

		comp.setTerminalAlphabet ( { a, b, c } );
		comp.setNonterminalAlphabet ( { S, Sp, B, Bp, C, Cp } );
		comp.setInitialSymbol ( S );

		comp.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { a, Sp } );
		comp.addRule ( Sp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { B } );
		comp.addRule ( Sp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { C } );
		comp.addRule ( B, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b, Bp } );
		comp.addRule ( Bp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { B } );
		comp.addRule ( Bp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { } );
		comp.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { c, Cp } );
		comp.addRule ( Cp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { C } );
		comp.addRule ( Cp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { } );

		CHECK ( res == comp );
	}

	SECTION ( "Test 2" ) {
		DefaultSymbolType S = DefaultSymbolType ( "S" );
		DefaultSymbolType Sp = DefaultSymbolType ( object::AnyObject < std::string > ( "S", 1 ) );
		DefaultSymbolType B = DefaultSymbolType ( "B" );
		DefaultSymbolType Bp = DefaultSymbolType ( object::AnyObject < std::string > ( "B", 1 ) );
		DefaultSymbolType C = DefaultSymbolType ( "C" );
		DefaultSymbolType Cp = DefaultSymbolType ( object::AnyObject < std::string > ( "C", 1 ) );
		DefaultSymbolType D = DefaultSymbolType ( "D" );

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

		grammar::CFG < > grammar ( S );

		grammar.setTerminalAlphabet ( { a, b, c, d } );
		grammar.setNonterminalAlphabet ( { S, B, C, D } );
		grammar.setInitialSymbol ( S );

		grammar.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { a, B } );
		grammar.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { a, C } );
		grammar.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { D } );
		grammar.addRule ( B, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b, B } );
		grammar.addRule ( B, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b } );
		grammar.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { c, C } );
		grammar.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { c } );
		grammar.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b } );
		grammar.addRule ( D, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { d } );

		grammar::CFG < > res = grammar;
		grammar::parsing::LeftFactorize::leftFactorize ( res, a, S);
		grammar::parsing::LeftFactorize::leftFactorize ( res, b, B);
		grammar::parsing::LeftFactorize::leftFactorize ( res, c, C);

		grammar::CFG < > comp ( S );

		comp.setTerminalAlphabet ( { a, b, c, d } );
		comp.setNonterminalAlphabet ( { S, Sp, B, Bp, C, Cp, D} );
		comp.setInitialSymbol ( S );

		comp.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { a, Sp } );
		comp.addRule ( S, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { D } );
		comp.addRule ( Sp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { B } );
		comp.addRule ( Sp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { C } );
		comp.addRule ( B, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b, Bp } );
		comp.addRule ( Bp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { B } );
		comp.addRule ( Bp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { } );
		comp.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { c, Cp } );
		comp.addRule ( C, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { b } );
		comp.addRule ( Cp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { C } );
		comp.addRule ( Cp, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { } );
		comp.addRule ( D, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { d } );

		CHECK ( res == comp );
	}
}
