#ifndef __CLI_LEXER_H_
#define __CLI_LEXER_H_

#include <memory>

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

#include <exception/CommonException.h>

#include "CharSequence.h"
#include <readline/StringLineReader.h>

namespace cli {

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

private:
	CharSequence & m_source;
	Hint m_hint;

public:
	enum class TokenType {
		IDENTIFIER,
		INTEGER,
		STRING,
		IN_REDIRECT,
		OUT_REDIRECT,
		LEFT_PAREN,
		RIGHT_PAREN,
		LEFT_BRACE,
		RIGHT_BRACE,
		LEFT_BRACKET,
		RIGHT_BRACKET,
		DOLAR_SIGN,
		AT_SIGN,
		AMPERSAND_SIGN,
		PIPE_SIGN,
		CARET_SIGN,
		COLON_SIGN,
		SEMICOLON_SIGN,
		DASH_SIGN,
		EQUAL_SIGN,
		HASH_SIGN,
		FILE,
		TYPE,
		ERROR,
		EOT,
		EOS
	};

	static std::string tokenTypeToString ( TokenType type ) {
		switch ( type ) {
		case TokenType::IDENTIFIER :
			return "identifier";
		case TokenType::INTEGER :
			return "number";
		case TokenType::STRING :
			return "string";
		case TokenType::IN_REDIRECT :
			return "in_redirect";
		case TokenType::OUT_REDIRECT :
			return "out_redirect";
		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::PIPE_SIGN :
			return "pipe_sign";
		case TokenType::CARET_SIGN :
			return "caret_sign";
		case TokenType::COLON_SIGN :
			return "colon_sign";
		case TokenType::SEMICOLON_SIGN :
			return "semicolon_sign";
		case TokenType::DASH_SIGN :
			return "dash_sign";
		case TokenType::EQUAL_SIGN :
			return "equal_sign";
		case TokenType::HASH_SIGN :
			return "hash_sign";
		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 );
			switch ( m_type ) {
			case TokenType::IDENTIFIER :
				return res + ": " + m_value;
			case TokenType::INTEGER :
				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;
		}

	};

	explicit Lexer ( CharSequence & source ) : m_source ( 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;
	}

	std::string getLine ( ) const {
		return m_source.getLine ( );
	}
};

} /* namespace cli */

#endif /* __CLI_LEXER_H_ */
