#ifndef _CLI_PARSER_H_
#define _CLI_PARSER_H_

#include <ast/Statement.h>
#include <ast/Option.h>
#include <ast/Arg.h>
#include <ast/Expression.h>

#include <ast/options/TypeOption.h>
#include <ast/options/CategoryOption.h>

#include <ast/command/CommandList.h>

#include <ast/statements/StatementList.h>

#include <lexer/Lexer.h>

#include <exception/CommonException.h>

#include <alib/algorithm>

namespace cli {

class Parser {
	cli::Lexer m_lexer;
	cli::Lexer::Token m_current;
	unsigned m_forceLexerReadNext = { 0 };

	ext::set < cli::Lexer::Token > m_checkedOptions;

	void clearCheckOptions ( ) {
		m_checkedOptions.clear ( );
	}

	const ext::set < cli::Lexer::Token > & getCheckOptions ( ) {
		return m_checkedOptions;
	}

	void restoreCheckOptions ( ext::set < cli::Lexer::Token > checkOptions ) {
		m_checkedOptions = std::move ( checkOptions );
	}

public:
	Parser ( cli::Lexer lexer ) : m_lexer ( std::move ( lexer ) ), m_current ( m_lexer.nextToken ( true ) ) {
	}

	void setHint ( Lexer::Hint hint ) {
		m_lexer.setHint ( hint );
		m_lexer.putback ( std::move ( m_current ) );
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		m_lexer.setHint ( Lexer::Hint::NONE );
	}

	template < class ... TokenTypes >
	bool check ( TokenTypes ... tokens ) {
		m_checkedOptions.merge ( std::set < cli::Lexer::Token > { cli::Lexer::Token { "", "", tokens } ... } );
		return ( ... || ( m_current.m_type == tokens ) );
	}

	template < class ... NonreservedTokens >
	bool check_nonreserved_kw ( const NonreservedTokens & ... kw ) {
		m_checkedOptions.merge ( std::set < cli::Lexer::Token > { cli::Lexer::Token { kw, "", cli::Lexer::TokenType::IDENTIFIER } ... } );
		return m_current.m_type == Lexer::TokenType::IDENTIFIER && ( ... || ( m_current.m_value == kw ) );
	}

	template < class ... TokenTypes >
	bool match ( cli::Lexer::TokenType token, TokenTypes ... tokens ) {
		if ( ! check ( token, tokens ... ) )
			throw exception::CommonException ( std::string ( "Mismatched token while matching a token " ) + ( Lexer::tokenTypeToString ( token ) + ... + ( ", " + Lexer::tokenTypeToString ( tokens ) ) ) + ". Actual was " + Lexer::tokenTypeToString ( m_current.m_type ) + ". Tokens in active set " + ext::to_string ( getCheckOptions ( ) ) + "." );
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		return true;
	}

	template < class ... NonreservedTokens >
	bool match_nonreserved_kw ( const std::string & kw, const NonreservedTokens & ... kws ) {
		if ( ! check_nonreserved_kw ( kw, kws ... ) )
			throw exception::CommonException ( "Mismatched token while matching a non reseved keyword: " + kw + ". Tokens in active set " + ext::to_string ( getCheckOptions ( ) ) + "." );
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		return true;
	}

	template < class ... TokenTypes >
	bool check_then_match ( cli::Lexer::TokenType token, TokenTypes ... tokens ) {
		if ( ! check ( token, tokens ... ) )
			return false;

		match ( token, tokens ... );
		return true;
	}

	template < class ... NonreservedTokens >
	bool check_then_match_nonreserved_kw ( const std::string & kw, const NonreservedTokens & ... kws ) {
		if ( ! check ( kw, kws ... ) )
			return false;

		match ( kw, kws ... );
		return true;
	}

	std::string matchIdentifier ( ) {
		if ( ! check ( Lexer::TokenType::IDENTIFIER ) )
			throw exception::CommonException ( "Mismatched token while matching an identifier. Tokens in active set " + ext::to_string ( getCheckOptions ( ) ) + "." );
		std::string res = m_current.m_value;
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		return res;
	}

	std::string matchString ( ) {
		if ( ! check ( Lexer::TokenType::STRING ) )
			throw exception::CommonException ( "Mismatched token while matching a string. Tokens in active set " + ext::to_string ( getCheckOptions ( ) ) + "." );
		std::string res = m_current.m_value;
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		return res;
	}

	std::string matchType ( ) {
		if ( ! check ( Lexer::TokenType::TYPE ) )
			throw exception::CommonException ( "Mismatched token while matching a type. Tokens in active set " + ext::to_string ( getCheckOptions ( ) ) + "." );
		std::string res = m_current.m_value;
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		return res;
	}

	std::string matchFile ( ) {
		if ( ! check ( Lexer::TokenType::FILE ) )
			throw exception::CommonException ( "Mismatched token while matching a file. Tokens in active set " + ext::to_string ( getCheckOptions ( ) ) + "." );
		std::string res = m_current.m_value;
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		return res;
	}

	int matchInteger ( ) {
		if ( ! check ( Lexer::TokenType::UNSIGNED ) )
			throw exception::CommonException ( "Mismatched token while matching an integer. Tokens in active set " + ext::to_string ( getCheckOptions ( ) ) + "."  );
		int res = ext::from_string < int > ( m_current.m_value );
		m_current = m_lexer.nextToken ( m_forceLexerReadNext > 0 );
		return res;
	}

	std::string getTokenValue ( ) {
		return m_current.m_value;
	}

	void incNestedLevel ( ) {
		++ m_forceLexerReadNext;
	}

	void decNestedLevel ( ) {
		-- m_forceLexerReadNext;
	}

	bool globalScope ( ) {
		return m_forceLexerReadNext == 0;
	}

	std::unique_ptr < Expression > assign_expression ( );

	std::unique_ptr < Expression > or_expression ( );

	std::unique_ptr < Expression > and_expression ( );

	std::unique_ptr < Expression > bitwise_or_expression ( );

	std::unique_ptr < Expression > bitwise_and_expression ( );

	std::unique_ptr < Expression > bitwise_xor_expression ( );

	std::unique_ptr < Expression > equality_expression ( );

	std::unique_ptr < Expression > relational_expression ( );

	std::unique_ptr < Expression > add_expression ( );

	std::unique_ptr < Expression > mul_expression ( );

	std::unique_ptr < Expression > cast_expression ( );

	std::unique_ptr < Expression > prefix_expression ( );

	std::unique_ptr < Expression > suffix_expression ( );

	std::unique_ptr < Expression > atom ( );

	std::vector < std::unique_ptr < Expression > > bracketed_expression_list ( );

	std::unique_ptr < CategoryOption > category_option ( );

	std::unique_ptr < TypeOption > type_option ( );

	std::unique_ptr < TypeOption > optional_type_option ( );

	std::unique_ptr < Arg > file ( );

	std::unique_ptr < Arg > type ( );

	std::unique_ptr < Arg > arg ( );

	std::unique_ptr < Arg > optional_arg ( );

	std::unique_ptr < Arg > template_arg ( );

	std::unique_ptr < Arg > optional_variable ( );

	std::unique_ptr < Arg > optional_binding ( );

	std::shared_ptr < Statement > in_redirect ( );

	std::shared_ptr < Statement > common ( );

	std::shared_ptr < Statement > param ( );

	std::shared_ptr < Statement > statement ( );

	std::shared_ptr < StatementList > statement_list ( );

	std::unique_ptr < Statement > out_redirect ( );

	std::pair < bool, bool > introspect_cast_from_to ( );

	std::unique_ptr < Command > introspect_command ( );

	std::unique_ptr < CommandList > block ( );

	std::unique_ptr < Expression > expression ( );

	std::unique_ptr < Expression > batch ( );

	std::unique_ptr < Expression > expression_or_batch ( );

	std::unique_ptr < Expression > batch_or_expression ( );

	std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > qualifiedType ( );

	std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > runnableParam ( );

	std::unique_ptr < Command > command ( );

	std::unique_ptr < Command > semicolon_command ( );

	std::unique_ptr < CommandList > parse ( );

};

} /* namespace cli */

#endif /* _CLI_PARSER_H_ */
