/*
 * GrammarFromStringParserCommon.h
 *
 * Created on: Sep 27, 2017
 * Author: Jan Travnicek
 */

#ifndef _GRAMMAR_FROM_STRING_PARSER_COMMON_H_
#define _GRAMMAR_FROM_STRING_PARSER_COMMON_H_

#include <alib/set>
#include <alib/vector>
#include <alib/map>
#include <alib/tuple>

#include <exception/CommonException.h>

#include <grammar/Grammar.h>

#include <core/stringApi.hpp>

#include <grammar/AddRawRule.h>

namespace grammar {

class GrammarFromStringParserCommon {
	template < class SymbolType >
	static ext::set < SymbolType > parseSet  ( std::istream & input );

	template < class TerminalSymbolType, class NonterminalSymbolType >
	static ext::map < NonterminalSymbolType, ext::set < ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > > parseCFLikeRules ( std::istream & input );

	template < class SymbolType >
	static ext::map < ext::vector < SymbolType >, ext::set < ext::vector < SymbolType > > > parseCSLikeRules ( std::istream & input );

	template < class SymbolType >
	static ext::map < ext::tuple < ext::vector < SymbolType >, SymbolType, ext::vector < SymbolType > >, ext::set < ext::vector < SymbolType > > > parsePreservingCSLikeRules ( std::istream & input );


public:
	template < class T, class TerminalSymbolType = typename grammar::TerminalSymbolTypeOfGrammar < T >, class NonterminalSymbolType = typename grammar::NonterminalSymbolTypeOfGrammar < T > >
	static T parseCFLikeGrammar ( std::istream & input );

	template < class T, class SymbolType = typename grammar::TerminalSymbolTypeOfGrammar < T > >
	static T parseCSLikeGrammar ( std::istream & input );

	template < class T, class SymbolType = typename grammar::TerminalSymbolTypeOfGrammar < T > >
	static T parsePreservingCSLikeGrammar ( std::istream & input );
};

template < class SymbolType >
ext::set<SymbolType> GrammarFromStringParserCommon::parseSet (std::istream& input) {
	ext::set<SymbolType> res;

	grammar::GrammarFromStringLexer::Token token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_BEGIN) {
		throw exception::CommonException("Expected SET_BEGIN token.");
	}

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		grammar::GrammarFromStringLexer::putback(input, token);
		while(true) {
			SymbolType symbol = core::stringApi<SymbolType>::parse(input);
			res.insert(symbol);

			token = grammar::GrammarFromStringLexer::next(input);
			if(token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
				break;
			}
			if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
				throw exception::CommonException("Expected SET_END or COMMA token");
			}
		}
	}

	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		throw exception::CommonException("Expected SET_END token");
	}

	return res;
}

template < class TerminalSymbolType, class NonterminalSymbolType >
ext::map < NonterminalSymbolType, ext::set < ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > > GrammarFromStringParserCommon::parseCFLikeRules ( std::istream & input ) {
	ext::map < NonterminalSymbolType, ext::set < ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > > result;

	grammar::GrammarFromStringLexer::Token token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_BEGIN) {
		throw exception::CommonException("Expected SET_BEGIN token.");
	}

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		grammar::GrammarFromStringLexer::putback(input, token);
		while(true) {
			NonterminalSymbolType lhs = core::stringApi < NonterminalSymbolType >::parse ( input );

			token = grammar::GrammarFromStringLexer::next(input);
			if(token.type != grammar::GrammarFromStringLexer::TokenType::MAPS_TO) {
				throw exception::CommonException("Expected MAPS_TO token.");
			}

			while(true) {
				ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > rhs;

				token = grammar::GrammarFromStringLexer::next(input);
				if ( token.type != grammar::GrammarFromStringLexer::TokenType::COMMA
				  && token.type != grammar::GrammarFromStringLexer::TokenType::SET_END
				  && token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR ) {
					if ( token.type == grammar::GrammarFromStringLexer::TokenType::EPSILON ) {
						token = grammar::GrammarFromStringLexer::next(input);
					} else while(true) {
						grammar::GrammarFromStringLexer::putback(input, token);

						rhs.push_back ( core::stringApi < ext::variant < TerminalSymbolType, NonterminalSymbolType > >::parse ( input ) );
						token = grammar::GrammarFromStringLexer::next(input);
						if ( token.type == grammar::GrammarFromStringLexer::TokenType::SEPARATOR
						  || token.type == grammar::GrammarFromStringLexer::TokenType::COMMA
						  || token.type == grammar::GrammarFromStringLexer::TokenType::SET_END ) {
							break;
						}
					}
				}
				result[lhs].insert(rhs);
				if(token.type == grammar::GrammarFromStringLexer::TokenType::COMMA || token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
					break;
				}
				if(token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) {
					throw exception::CommonException("Expected SEPARATOR, SETEND or COMMA token");
				}
			}

			if(token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
				break;
			}
			if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
				throw exception::CommonException("Expected SET_END or COMMA token");
			}
		}
	}

	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		throw exception::CommonException("Expected SET_END token");
	}
	return result;
}

template< class T, class TerminalSymbolType, class NonterminalSymbolType >
T GrammarFromStringParserCommon::parseCFLikeGrammar(std::istream& input) {
	grammar::GrammarFromStringLexer::Token token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::TUPLE_BEGIN) {
		throw exception::CommonException("Unrecognised Tuple begin token.");
	}

	ext::set < NonterminalSymbolType > nonterminals = parseSet < NonterminalSymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	ext::set < TerminalSymbolType > terminals = parseSet < TerminalSymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	ext::map < NonterminalSymbolType, ext::set < ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > > > rules = parseCFLikeRules < TerminalSymbolType, NonterminalSymbolType > ( input );

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	NonterminalSymbolType initialSymbol = core::stringApi < NonterminalSymbolType >::parse ( input );

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::TUPLE_END) {
		throw exception::CommonException("Unrecognised Tuple end token.");
	}

	T grammar(nonterminals, terminals, initialSymbol);
	for(const auto& rule : rules) {
		for(const ext::vector < ext::variant < TerminalSymbolType, NonterminalSymbolType > > & rhs : rule.second) {
			grammar::AddRawRule::addRawRule ( grammar, rule.first, rhs );
		}
	}
	return grammar;
}

template < class SymbolType >
ext::map<ext::vector<SymbolType>, ext::set<ext::vector<SymbolType>>> GrammarFromStringParserCommon::parseCSLikeRules (std::istream& input) {
	ext::map<ext::vector<SymbolType>, ext::set<ext::vector<SymbolType>>> result;

	grammar::GrammarFromStringLexer::Token token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_BEGIN) {
		throw exception::CommonException("Expected SET_BEGIN token.");
	}

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		grammar::GrammarFromStringLexer::putback(input, token);
		while(true) {
			ext::vector<SymbolType> lhs;
			while(true) {
				lhs.push_back(core::stringApi<SymbolType>::parse(input));
				token = grammar::GrammarFromStringLexer::next(input);
				if(token.type == grammar::GrammarFromStringLexer::TokenType::MAPS_TO) {
					break;
				}
				grammar::GrammarFromStringLexer::putback(input, token);
			}

			if(token.type != grammar::GrammarFromStringLexer::TokenType::MAPS_TO) {
				throw exception::CommonException("Expected MAPS_TO token.");
			}

			while(true) {
				ext::vector<SymbolType> rhs;

				token = grammar::GrammarFromStringLexer::next(input);
				if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA && token.type != grammar::GrammarFromStringLexer::TokenType::SET_END && token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) while(true) {
					grammar::GrammarFromStringLexer::putback(input, token);

					rhs.push_back(core::stringApi<SymbolType>::parse(input));
					token = grammar::GrammarFromStringLexer::next(input);
					if(token.type == grammar::GrammarFromStringLexer::TokenType::SEPARATOR || token.type == grammar::GrammarFromStringLexer::TokenType::COMMA || token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
						break;
					}
				}
				result[lhs].insert(rhs);
				if(token.type == grammar::GrammarFromStringLexer::TokenType::COMMA || token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
					break;
				}
				if(token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) {
					throw exception::CommonException("Expected SEPARATOR, SETEND or COMMA token");
				}
			}

			if(token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
				break;
			}
			if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
				throw exception::CommonException("Expected SET_END or COMMA token");
			}
		}
	}

	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		throw exception::CommonException("Expected SET_END token");
	}
	return result;
}

template < class T, class SymbolType >
T GrammarFromStringParserCommon::parseCSLikeGrammar(std::istream& input) {
	grammar::GrammarFromStringLexer::Token token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::TUPLE_BEGIN) {
		throw exception::CommonException("Unrecognised Tuple begin token.");
	}

	ext::set<SymbolType> nonterminals = parseSet < SymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	ext::set<SymbolType> terminals = parseSet < SymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	ext::map<ext::vector<SymbolType>, ext::set<ext::vector<SymbolType>>> rules = parseCSLikeRules < SymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	SymbolType initialSymbol = core::stringApi<SymbolType>::parse(input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::TUPLE_END) {
		throw exception::CommonException("Unrecognised Tuple end token.");
	}

	T grammar(nonterminals, terminals, initialSymbol);
	for(const auto& rule : rules) {
		for(const auto& ruleRHS : rule.second) {
			grammar.addRule(rule.first, ruleRHS);
		}
	}
	return grammar;
}

template < class SymbolType >
ext::map<ext::tuple<ext::vector<SymbolType>, SymbolType, ext::vector<SymbolType>>, ext::set<ext::vector<SymbolType>>> GrammarFromStringParserCommon::parsePreservingCSLikeRules (std::istream& input) {
	ext::map<ext::tuple<ext::vector<SymbolType>, SymbolType, ext::vector<SymbolType>>, ext::set<ext::vector<SymbolType>>> result;

	grammar::GrammarFromStringLexer::Token token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_BEGIN) {
		throw exception::CommonException("Expected SET_BEGIN token.");
	}

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		grammar::GrammarFromStringLexer::putback(input, token);
		while(true) {
			ext::vector<SymbolType> lContext;
			token = grammar::GrammarFromStringLexer::next(input);
			if(token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) while(true) {
				grammar::GrammarFromStringLexer::putback(input, token);
				lContext.push_back(core::stringApi<SymbolType>::parse(input));
				token = grammar::GrammarFromStringLexer::next(input);
				if(token.type == grammar::GrammarFromStringLexer::TokenType::SEPARATOR) {
					break;
				}
			}

			if(token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) {
				throw exception::CommonException("Expected SEPARATOR token.");
			}

			SymbolType lhs = core::stringApi<SymbolType>::parse(input);

			token = grammar::GrammarFromStringLexer::next(input);
			if(token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) {
				throw exception::CommonException("Expected SEPARATOR token.");
			}

			ext::vector<SymbolType> rContext;
			token = grammar::GrammarFromStringLexer::next(input);
			if(token.type != grammar::GrammarFromStringLexer::TokenType::MAPS_TO) while(true) {
				grammar::GrammarFromStringLexer::putback(input, token);
				rContext.push_back(core::stringApi<SymbolType>::parse(input));
				token = grammar::GrammarFromStringLexer::next(input);
				if(token.type == grammar::GrammarFromStringLexer::TokenType::MAPS_TO) {
					break;
				}
			}

			if(token.type != grammar::GrammarFromStringLexer::TokenType::MAPS_TO) {
				throw exception::CommonException("Expected MAPS_TO token.");
			}

			ext::tuple<ext::vector<SymbolType>, SymbolType, ext::vector<SymbolType>> key = ext::make_tuple(lContext, lhs, rContext);

			while(true) {
				ext::vector<SymbolType> rhs;

				token = grammar::GrammarFromStringLexer::next(input);
				if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA && token.type != grammar::GrammarFromStringLexer::TokenType::SET_END && token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) while(true) {
					grammar::GrammarFromStringLexer::putback(input, token);

					rhs.push_back(core::stringApi<SymbolType>::parse(input));
					token = grammar::GrammarFromStringLexer::next(input);
					if(token.type == grammar::GrammarFromStringLexer::TokenType::SEPARATOR || token.type == grammar::GrammarFromStringLexer::TokenType::COMMA || token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
						break;
					}
				}
				result[key].insert(rhs);
				if(token.type == grammar::GrammarFromStringLexer::TokenType::COMMA || token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
					break;
				}
				if(token.type != grammar::GrammarFromStringLexer::TokenType::SEPARATOR) {
					throw exception::CommonException("Expected SEPARATOR, SETEND or COMMA token");
				}
			}

			if(token.type == grammar::GrammarFromStringLexer::TokenType::SET_END) {
				break;
			}
			if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
				throw exception::CommonException("Expected SET_END or COMMA token");
			}
		}
	}

	if(token.type != grammar::GrammarFromStringLexer::TokenType::SET_END) {
		throw exception::CommonException("Expected SET_END token");
	}
	return result;
}

template < class T, class SymbolType >
T GrammarFromStringParserCommon::parsePreservingCSLikeGrammar(std::istream& input) {
	grammar::GrammarFromStringLexer::Token token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::TUPLE_BEGIN) {
		throw exception::CommonException("Unrecognised Tuple begin token.");
	}

	ext::set<SymbolType> nonterminals = parseSet < SymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	ext::set<SymbolType> terminals = parseSet < SymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	ext::map<ext::tuple<ext::vector<SymbolType>, SymbolType, ext::vector<SymbolType>>, ext::set<ext::vector<SymbolType>>> rules = parsePreservingCSLikeRules < SymbolType > (input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::COMMA) {
		throw exception::CommonException("Unrecognised Comma token.");
	}

	SymbolType initialSymbol = core::stringApi<SymbolType>::parse(input);

	token = grammar::GrammarFromStringLexer::next(input);
	if(token.type != grammar::GrammarFromStringLexer::TokenType::TUPLE_END) {
		throw exception::CommonException("Unrecognised Tuple end token.");
	}

	T grammar(nonterminals, terminals, initialSymbol);
	for(const auto& rule : rules) {
		for(const auto& ruleRHS : rule.second) {
			grammar.addRule(std::get<0>(rule.first), std::get<1>(rule.first), std::get<2>(rule.first), ruleRHS);
		}
	}
	return grammar;
}

} /* namespace grammar */

#endif /* _GRAMMAR_FROM_STRING_PARSER_COMMON_H_ */
