#include <catch2/catch.hpp>

#include <alib/vector>

#include "grammar/ContextFree/CFG.h"
#include "grammar/parsing/LRParser.h"
#include "grammar/parsing/LR0Parser.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 ( "LR0 Parser", "[unit][grammar]" ) {
	SECTION ( "Test Closure" ) {
		grammar::parsing::LR0Items kernelItems {
			{
				E,
				{
					{ 2 , { E, plus, T } }
				}
			},
		};

		grammar::parsing::LR0Items closure = grammar::parsing::LR0Parser::getClosure( kernelItems, getExpressionGrammar ( ) );

		grammar::parsing::LR0Items correctClosure {
			{
				E,
				{
					{ 2 , { E, plus, T } }
				}
			},
			{
				T,
				{
					{ 0 , { T, times, F } },
					{ 0 , { F } }
				}
			},
			{
				F,
				{
					{ 0 , { leftParenthesis, E, rightParenthesis } },
					{ 0 , { identifier } }
				}
			}
		};

		CHECK ( closure == correctClosure );
	}

	SECTION ( "Test Next State Items" ) {
		grammar::parsing::LR0Items currentStateItems {
			{
				E,
				{
					{ 1 , { E, plus, T } }
				}
			},
		};

		grammar::parsing::LR0Items nextStateItems = grammar::parsing::LR0Parser::getNextStateItems( currentStateItems, plus, getExpressionGrammar ( ) );

		grammar::parsing::LR0Items correctNextStateItems {
			{
				E,
				{
					{ 2 , { E, plus, T } }
				}
			},
			{
				T,
				{
					{ 0 , { T, times, F } },
					{ 0 , { F } }
				}
			},
			{
				F,
				{
					{ 0 , { leftParenthesis, E, rightParenthesis } },
					{ 0 , { identifier } }
				}
			}
		};

		CHECK ( nextStateItems == correctNextStateItems );
	}

	SECTION ( "Test Automaton" ) {
		ext::vector < grammar::parsing::LR0Items > items(12);

		grammar::CFG < > augmentedExpressionGrammar = grammar::parsing::LRParser::getAugmentedGrammar ( getExpressionGrammar ( ) );

		items[0] = {
			{
				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 } }
				}
			}
		};

		items[1] = {
			{
				augmentedExpressionGrammar.getInitialSymbol(),
				{
					{ 1 , { E } }
				}
			},
			{
				E,
				{
					{ 1 , { E, plus, T } }
				}
			}
		};

		items[2] = {
			{
				E,
				{
					{ 1 , { T } }
				}
			},
			{
				T,
				{
					{ 1 , { T, times, F } },
				}
			}
		};

		items[3] = {
			{
				T,
				{
					{ 1 , { F } }
				}
			}
		};

		items[4] = {
			{
				E,
				{
					{ 0 , { E, plus, T } },
					{ 0 , { T } }
				}
			},
			{
				T,
				{
					{ 0 , { T, times, F } },
					{ 0 , { F } }
				}
			},
			{
				F,
				{
					{ 0 , { leftParenthesis, E, rightParenthesis } },
					{ 1 , { leftParenthesis, E, rightParenthesis } },
					{ 0 , { identifier } }
				}
			}
		};

		items[5] = {
			{
				F,
				{
					{ 1 , { identifier } }
				}
			}
		};

		items[6] = {
			{
				E,
				{
					{ 2 , { E, plus, T } },
				}
			},
			{
				T,
				{
					{ 0 , { T, times, F } },
					{ 0 , { F } }
				}
			},
			{
				F,
				{
					{ 0 , { leftParenthesis, E, rightParenthesis } },
					{ 0 , { identifier } }
				}
			}
		};

		items[7] = {
			{
				T,
				{
					{ 2 , { T, times, F } }
				}
			},
			{
				F,
				{
					{ 0 , { leftParenthesis, E, rightParenthesis } },
					{ 0 , { identifier } }
				}
			}
		};

		items[8] = {
			{
				E,
				{
					{ 1 , { E, plus, T } }
				}
			},
			{
				F,
				{
					{ 2 , { leftParenthesis, E, rightParenthesis } },
				}
			}
		};

		items[9] = {
			{
				E,
				{
					{ 3 , { E, plus, T } }
				}
			},
			{
				T,
				{
					{ 1 , { T, times, F } }
				}
			}
		};

		items[10] = {
			{
				T,
				{
					{ 3 , { T, times, F } }
				}
			}
		};

		items[11] = {
			{
				F,
				{
					{ 3 , { leftParenthesis, E, rightParenthesis } }
				}
			}
		};

		automaton::DFA < ext::variant < DefaultSymbolType, DefaultSymbolType >, grammar::parsing::LR0Items > parsingAutomaton ( items[0] );
		for ( const DefaultSymbolType & nonterminal : augmentedExpressionGrammar.getNonterminalAlphabet ( ) )
			parsingAutomaton.addInputSymbol ( nonterminal );
		for ( const DefaultSymbolType & terminal : augmentedExpressionGrammar.getTerminalAlphabet ( ) )
			parsingAutomaton.addInputSymbol ( terminal );

		for (const grammar::parsing::LR0Items & state : items) {
			parsingAutomaton.addState(state);
		}

		parsingAutomaton.addTransition(items[0], E, items[1]);
		parsingAutomaton.addTransition(items[0], T, items[2]);
		parsingAutomaton.addTransition(items[0], identifier, items[5]);
		parsingAutomaton.addTransition(items[0], leftParenthesis, items[4]);
		parsingAutomaton.addTransition(items[0], F, items[3]);

		parsingAutomaton.addTransition(items[1], plus, items[6]);

		parsingAutomaton.addTransition(items[2], times, items[7]);

		parsingAutomaton.addTransition(items[4], E, items[8]);
		parsingAutomaton.addTransition(items[4], F, items[3]);
		parsingAutomaton.addTransition(items[4], T, items[2]);
		parsingAutomaton.addTransition(items[4], leftParenthesis, items[4]);
		parsingAutomaton.addTransition(items[4], identifier, items[5]);

		parsingAutomaton.addTransition(items[6], T, items[9]);
		parsingAutomaton.addTransition(items[6], F, items[3]);
		parsingAutomaton.addTransition(items[6], leftParenthesis, items[4]);
		parsingAutomaton.addTransition(items[6], identifier, items[5]);

		parsingAutomaton.addTransition(items[7], F, items[10]);
		parsingAutomaton.addTransition(items[7], leftParenthesis, items[4]);
		parsingAutomaton.addTransition(items[7], identifier, items[5]);

		parsingAutomaton.addTransition(items[8], rightParenthesis, items[11]);
		parsingAutomaton.addTransition(items[8], plus, items[6]);

		parsingAutomaton.addTransition(items[9], times, items[7]);

		CHECK ( grammar::parsing::LR0Parser::getAutomaton(getExpressionGrammar()) == parsingAutomaton );
	}
}
