#include <catch2/catch.hpp>

#include <alib/vector>

#include "grammar/parsing/LRParser.h"
#include "grammar/parsing/SLR1ParseTable.h"
#include "grammar/ContextFree/CFG.h"

static DefaultSymbolType E = DefaultSymbolType ( 'E' );
static DefaultSymbolType T = DefaultSymbolType ( 'T' );
static DefaultSymbolType F = DefaultSymbolType ( 'F' );

static DefaultSymbolType plus = DefaultSymbolType ( '+' );
static DefaultSymbolType times = DefaultSymbolType ( '*' );
static DefaultSymbolType leftParenthesis = DefaultSymbolType ( '(' );
static DefaultSymbolType rightParenthesis = DefaultSymbolType ( ')' );
static DefaultSymbolType identifier = DefaultSymbolType ( "id" );

static grammar::CFG < > getExpressionGrammar ( ) {
	grammar::CFG < > expressionGrammar (
		{ E, T, F },
		{ plus, times, leftParenthesis, rightParenthesis, identifier },
		E
	);

	expressionGrammar.addRule ( E, { E, plus, T } );
	expressionGrammar.addRule ( E, { T } );
	expressionGrammar.addRule ( T, { T, times, F } );
	expressionGrammar.addRule ( T, { F } );
	expressionGrammar.addRule ( F, { leftParenthesis, E, rightParenthesis } );
	expressionGrammar.addRule ( F, { identifier } );

	return expressionGrammar;
}

TEST_CASE ( "LR Parser", "[unit][grammar]" ) {
	SECTION ( "Test End of Input Symbol" ) {
		grammar::CFG < > expressionGrammar = getExpressionGrammar ( );
		DefaultSymbolType endOfInput = grammar::parsing::LRParser::getEndOfInputSymbol ( expressionGrammar );

		bool correct = true;
		if ( expressionGrammar.getTerminalAlphabet ( ) . find ( endOfInput ) != expressionGrammar.getTerminalAlphabet ( ) . end ( ) ) {
			correct = false;
		}

		if ( expressionGrammar.getNonterminalAlphabet ( ) . find ( endOfInput ) != expressionGrammar.getNonterminalAlphabet ( ) . end ( ) ) {
			correct = false;
		}

		CHECK ( correct );
	}

	SECTION ( "Test Parse Correct Input" ) {
		grammar::CFG < > augmentedExpressionGrammar = grammar::parsing::LRParser::getAugmentedGrammar ( getExpressionGrammar ( ) );

		grammar::parsing::LR0Items initialState {
			{
				augmentedExpressionGrammar.getInitialSymbol(),
				{
					{ 0 , { E } }
				}
			},
			{
				E,
				{
					{ 0 , { E, plus, T } },
					{ 0 , { T } }
				}
			},
			{
				T,
				{
					{ 0 , { T, times, F } },
					{ 0 , { F } }
				}
			},
			{
				F,
				{
					{ 0 , { leftParenthesis, E, rightParenthesis } },
					{ 0 , { identifier } }
				}
			}
		};

		ext::vector < DefaultSymbolType > correctInput {
			leftParenthesis,
			identifier,
			plus,
			leftParenthesis,
			identifier,
			times,
			identifier ,
			rightParenthesis,
			rightParenthesis,
			grammar::parsing::LRParser::getEndOfInputSymbol ( grammar::parsing::LRParser::getAugmentedGrammar ( getExpressionGrammar ( ) ) )
		};

		bool parsingResult = grammar::parsing::LRParser::parse (
			grammar::parsing::SLR1ParseTable::getActionTable ( getExpressionGrammar ( ) ),
			grammar::parsing::SLR1ParseTable::getGotoTable ( getExpressionGrammar ( ) ),
			initialState,
			correctInput
		);

		CHECK ( parsingResult == true );
	}

	SECTION ( "Test Parse Incorrect Input" ) {
		grammar::CFG < > augmentedExpressionGrammar = grammar::parsing::LRParser::getAugmentedGrammar ( getExpressionGrammar ( ) );

		grammar::parsing::LR0Items initialState {
			{
				augmentedExpressionGrammar.getInitialSymbol(),
				{
					{ 0 , { E } }
				}
			},
			{
				E,
				{
					{ 0 , { E, plus, T } },
					{ 0 , { T } }
				}
			},
			{
				T,
				{
					{ 0 , { T, times, F } },
					{ 0 , { F } }
				}
			},
			{
				F,
				{
					{ 0 , { leftParenthesis, E, rightParenthesis } },
					{ 0 , { identifier } }
				}
			}
		};

		ext::vector < DefaultSymbolType > incorrectInput {
			leftParenthesis,
			identifier,
			plus,
			leftParenthesis,
			identifier,
			times,
			identifier ,
			rightParenthesis,
			grammar::parsing::LRParser::getEndOfInputSymbol ( grammar::parsing::LRParser::getAugmentedGrammar ( getExpressionGrammar ( ) ) )
		};

		bool parsingResult = grammar::parsing::LRParser::parse(
			grammar::parsing::SLR1ParseTable::getActionTable ( getExpressionGrammar ( ) ),
			grammar::parsing::SLR1ParseTable::getGotoTable ( getExpressionGrammar ( ) ),
			initialState,
			incorrectInput
		);

		CHECK ( parsingResult == false );
	}
}
