#ifndef __CLI_LEXER_H_
#define __CLI_LEXER_H_

#include <memory>

#include <alib/string>
#include <alib/iostream>
#include <sstream>

#include <exception/CommonException.h>

#include <lexer/CharSequence.h>

namespace cli {

class Lexer {
public:
	enum class Hint {
		NONE,
		TYPE,
		FILE
	};

private:
	CharSequence m_source;
	Hint m_hint;

public:
	enum class TokenType : unsigned {
		IDENTIFIER,
		UNSIGNED,
		STRING,
		LESS_SIGN,
		LESS_OR_EQUAL_OPERATOR,
		MORE_SIGN,
		MORE_OR_EQUAL_OPERATOR,
		LEFT_PAREN,
		RIGHT_PAREN,
		LEFT_BRACE,
		RIGHT_BRACE,
		LEFT_BRACKET,
		RIGHT_BRACKET,
		DOLAR_SIGN,
		AT_SIGN,
		AMPERSAND_SIGN,
		AND_OPERATOR,
		PIPE_SIGN,
		OR_OPERATOR,
		CARET_SIGN,
		COLON_SIGN,
		SEMICOLON_SIGN,
		MINUS_SIGN,
		DEC_OPERATOR,
		PLUS_SIGN,
		INC_OPERATOR,
		SLASH_SIGN,
		STAR_SIGN,
		TYLDE_SIGN,
		EXCLAMATION_SIGN,
		PERCENTAGE_SIGN,
		ASSIGN_OPERATOR,
		EQUAL_OPERATOR,
		NOT_EQUAL_OPERATOR,
		HASH_SIGN,
		COMMA,
		DOT,
		FILE,
		TYPE,
		ERROR,
		EOT,
		EOS
	};

	friend bool operator < ( TokenType first, TokenType second ) {
		return static_cast < unsigned > ( first ) < static_cast < unsigned > ( second );
	}

	static std::string tokenTypeToString ( TokenType type ) {
		switch ( type ) {
		case TokenType::IDENTIFIER :
			return "identifier";
		case TokenType::UNSIGNED :
			return "unsigned";
		case TokenType::STRING :
			return "string";
		case TokenType::LESS_SIGN :
			return "less_sign";
		case TokenType::LESS_OR_EQUAL_OPERATOR :
			return "less_or_equal_operator";
		case TokenType::MORE_SIGN :
			return "more_sign";
		case TokenType::MORE_OR_EQUAL_OPERATOR :
			return "more_or_equal_operator";
		case TokenType::LEFT_PAREN :
			return "left_paren";
		case TokenType::RIGHT_PAREN :
			return "right_paren";
		case TokenType::LEFT_BRACE :
			return "left_brace";
		case TokenType::RIGHT_BRACE :
			return "right_brace";
		case TokenType::LEFT_BRACKET :
			return "left_bracket";
		case TokenType::RIGHT_BRACKET :
			return "right_bracket";
		case TokenType::AT_SIGN :
			return "at_sign";
		case TokenType::DOLAR_SIGN :
			return "dolar_sign";
		case TokenType::AMPERSAND_SIGN :
			return "ampersand_sign";
		case TokenType::AND_OPERATOR :
			return "and_operator";
		case TokenType::PIPE_SIGN :
			return "pipe_sign";
		case TokenType::OR_OPERATOR :
			return "or_operator";
		case TokenType::CARET_SIGN :
			return "caret_sign";
		case TokenType::COLON_SIGN :
			return "colon_sign";
		case TokenType::SEMICOLON_SIGN :
			return "semicolon_sign";
		case TokenType::MINUS_SIGN :
			return "minus_sign";
		case TokenType::DEC_OPERATOR :
			return "dec_operator";
		case TokenType::PLUS_SIGN :
			return "plus_sign";
		case TokenType::INC_OPERATOR :
			return "inc_operator";
		case TokenType::SLASH_SIGN :
			return "slash_sign";
		case TokenType::STAR_SIGN :
			return "star_sign";
		case TokenType::TYLDE_SIGN :
			return "tylde_sign";
		case TokenType::EXCLAMATION_SIGN :
			return "exclemation_sign";
		case TokenType::PERCENTAGE_SIGN :
			return "percentage_sign";
		case TokenType::ASSIGN_OPERATOR :
			return "assign_operator";
		case TokenType::EQUAL_OPERATOR :
			return "equal_operator";
		case TokenType::NOT_EQUAL_OPERATOR :
			return "not_equal_operator";
		case TokenType::HASH_SIGN :
			return "hash_sign";
		case TokenType::COMMA :
			return "comma";
		case TokenType::DOT :
			return "dot";
		case TokenType::FILE :
			return "file";
		case TokenType::TYPE :
			return "type";
		case TokenType::ERROR :
			return "error";
		case TokenType::EOT :
			return "eot";
		case TokenType::EOS :
			return "eos";
		default:
			throw exception::CommonException ( "Unhandled case in Lexer::tokenTypeToString" );
		}
	}

	static TokenType is_kw ( const std::string & /* identifier */ ) {
		return TokenType::IDENTIFIER;
	}

	struct Token {
		std::string m_value;
		std::string m_raw;
		TokenType m_type;

		operator std::string ( ) const {
			std::string res = Lexer::tokenTypeToString ( m_type );

			if ( m_value.empty ( ) )
				return res;

			switch ( m_type ) {
			case TokenType::IDENTIFIER :
				return res + ": " + m_value;
			case TokenType::UNSIGNED :
				return res + ": " + ext::to_string ( m_value );
			case TokenType::STRING :
				return res + ": \"" + ext::to_string ( m_value ) + "\"";
			default:
				return res;
			}
		}

		friend std::ostream & operator << ( std::ostream & out, const Token & token ) {
			return out << ( std::string ) token;
		}

		bool operator < ( const Token & token ) const {
			return std::tie ( m_type, m_value ) < std::tie ( token.m_type, token.m_value ); // m_raw omitted intentionally
		}

	};

	explicit Lexer ( CharSequence source ) : m_source ( std::move ( source ) ), m_hint ( Hint::NONE ) {
		m_source.advance ( true );
	}

	Token nextToken ( bool readNextLine = false );

	void putback ( Token && token ) {
		m_source.putback ( std::move ( token.m_raw ) );
	}

	void setHint ( Hint hint ) {
		m_hint = hint;
	}
};

} /* namespace cli */

#endif /* __CLI_LEXER_H_ */
