#include <catch2/catch.hpp>

#include <alib/variant>
#include <alib/vector>

#include "grammar/parsing/LRParser.h"
#include "grammar/parsing/SLR1ParseTable.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 ( "SLR1 Parse Table", "[unit][grammar]" ) {
	SECTION ( "Test Action Table" ) {
		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 } }
				}
			}
		};

		DefaultSymbolType endOfInput = grammar::parsing::LRParser::getEndOfInputSymbol ( getExpressionGrammar ( ) );
		grammar::parsing::LRActionTable correctActionTable {
			{ { items[0], identifier }, { grammar::parsing::LRAction::Shift, items[5] } },
			{ { items[0], leftParenthesis }, { grammar::parsing::LRAction::Shift, items[4] } },
			{ { items[1], plus }, { grammar::parsing::LRAction::Shift, items[6] } },
			{ { items[1], endOfInput }, { grammar::parsing::LRAction::Accept, items[0] } },
			{ { items[2], times }, { grammar::parsing::LRAction::Shift, items[7] } },
			{ { items[4], identifier }, { grammar::parsing::LRAction::Shift, items[5] } },
			{ { items[4], leftParenthesis }, { grammar::parsing::LRAction::Shift, items[4] } },
			{ { items[6], identifier }, { grammar::parsing::LRAction::Shift, items[5] } },
			{ { items[6], leftParenthesis }, { grammar::parsing::LRAction::Shift, items[4] } },
			{ { items[7], identifier }, { grammar::parsing::LRAction::Shift, items[5] } },
			{ { items[7], leftParenthesis }, { grammar::parsing::LRAction::Shift, items[4] } },
			{ { items[8], plus }, { grammar::parsing::LRAction::Shift, items[6] } },
			{ { items[8], rightParenthesis }, { grammar::parsing::LRAction::Shift, items[11] } },
			{ { items[9], times }, { grammar::parsing::LRAction::Shift, items[7] } }
		};

		ext::pair < DefaultSymbolType, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > > reduceBy = ext::make_pair ( E, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { T } );
		correctActionTable.insert ( { { items[2], plus },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[2], rightParenthesis },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[2], endOfInput },  { grammar::parsing::LRAction::Reduce, reduceBy } } );

		reduceBy = ext::make_pair ( T, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { F } );
		correctActionTable.insert ( { { items[3], plus },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[3], times },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[3], rightParenthesis },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[3], endOfInput },  { grammar::parsing::LRAction::Reduce, reduceBy } } );

		reduceBy = ext::make_pair (  F, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { identifier } );
		correctActionTable.insert ( { { items[5], plus },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[5], times },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[5], rightParenthesis },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[5], endOfInput },  { grammar::parsing::LRAction::Reduce, reduceBy } } );

		reduceBy = ext::make_pair ( E, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { E, plus, T } );
		correctActionTable.insert ( { { items[9], plus },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[9], rightParenthesis },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[9], endOfInput },  { grammar::parsing::LRAction::Reduce, reduceBy } } );

		reduceBy = ext::make_pair ( T, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { T, times, F } );
		correctActionTable.insert ( { { items[10], plus },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[10], times },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[10], rightParenthesis },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[10], endOfInput },  { grammar::parsing::LRAction::Reduce, reduceBy } } );

		reduceBy = ext::make_pair ( F, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > { leftParenthesis, E, rightParenthesis } );
		correctActionTable.insert ( { { items[11], plus },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[11], times },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[11], rightParenthesis },  { grammar::parsing::LRAction::Reduce, reduceBy } } );
		correctActionTable.insert ( { { items[11], endOfInput },  { grammar::parsing::LRAction::Reduce, reduceBy } } );

		grammar::parsing::LRActionTable actionTable = grammar::parsing::SLR1ParseTable::getActionTable ( getExpressionGrammar ( ) );

		bool correct = true;
		for ( const grammar::parsing::LRActionTable::value_type & actionTablePair : correctActionTable ) {
			grammar::parsing::LRActionTable::iterator value = actionTable.find ( actionTablePair.first );
			if ( value == actionTable.end ( ) ) {
				correct = false;
				break;
			}

			if ( actionTablePair.second.first != value->second.first ) {
				correct = false;
				break;
			}

			bool correctContent = true;
			switch ( actionTablePair.second.first ) {
				case grammar::parsing::LRAction::Shift:
					if ( actionTablePair.second.second.get < grammar::parsing::LR0Items > ( ) != value->second.second.get < grammar::parsing::LR0Items > ( ) ) {
						correctContent = false;
					}
					break;

				case grammar::parsing::LRAction::Reduce:
					if ( actionTablePair.second.second.get < ext::pair < DefaultSymbolType, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > > > ( ) != value->second.second.get < ext::pair < DefaultSymbolType, ext::vector < ext::variant < DefaultSymbolType, DefaultSymbolType > > > > ( ) ) {
						correctContent = false;
					}
					break;

				case grammar::parsing::LRAction::Accept:
					break;
			}

			if ( !correctContent ) {
				correct = false;
				break;
			}
		}

		if (actionTable.size() != correctActionTable.size()) {
			correct = false;
		}

		CHECK ( correct );
	}

	SECTION ( "Test Goto table" ) {
		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 } }
				}
			}
		};

		grammar::parsing::LRGotoTable gotoTable {
			{ { items[0], E }, items[1] },
			{ { items[0], T }, items[2] },
			{ { items[0], F }, items[3] },
			{ { items[4], E }, items[8] },
			{ { items[4], T }, items[2] },
			{ { items[4], F }, items[3] },
			{ { items[6], T }, items[9] },
			{ { items[6], F }, items[3] },
			{ { items[7], F }, items[10] }
		};

		CHECK ( grammar::parsing::SLR1ParseTable::getGotoTable ( getExpressionGrammar ( ) ) == gotoTable );
	}

	SECTION ( "Test Unambiguous Not SLR1 Grammar" ) {
		DefaultSymbolType S = DefaultSymbolType ( 'S' );
		DefaultSymbolType L = DefaultSymbolType ( 'L' );
		DefaultSymbolType R = DefaultSymbolType ( 'R' );

		DefaultSymbolType assignment = DefaultSymbolType ( '=' );

		grammar::CFG < > unambiguousNotSLR1Grammar(
			{ S, L, R },
			{ assignment, times, identifier },
			S
		);

		unambiguousNotSLR1Grammar.addRule ( S, { L, assignment, R } );
		unambiguousNotSLR1Grammar.addRule ( S, { R } );
		unambiguousNotSLR1Grammar.addRule ( L, { times, R } );
		unambiguousNotSLR1Grammar.addRule ( L, { identifier } );
		unambiguousNotSLR1Grammar.addRule ( R, { L } );

		CHECK_THROWS_AS ( grammar::parsing::SLR1ParseTable::getActionTable ( unambiguousNotSLR1Grammar ), exception::CommonException );
	}
}
