#include <lexer.hpp>

namespace TokenType = Enum::Token::Type;
using namespace TokenType;
using namespace std;

Annotator::Annotator(void)
{
}

#define ANNOTATE(method, data, info) do {		\
		method(ctx, data, tk, &info);			\
		if (info.type != Undefined) {			\
			tk->info = info;					\
			ctx->prev_type = info.type;			\
			return;								\
		}										\
	} while (0)

void Annotator::annotate(LexContext *ctx, Token *tk)
{
	if (tk->info.type != Undefined) {
		ctx->prev_type = tk->info.type;
		return;
	}
	TokenInfo info;
	info.type = Undefined;
	string data = string(tk->_data);
	ANNOTATE(annotateRegOpt, data, info);
	ANNOTATE(annotateNamespace, data, info);
	ANNOTATE(annotateMethod, data, info);
	ANNOTATE(annotateKey, data, info);
	ANNOTATE(annotateShortScalarDereference, data, info);
	ANNOTATE(annotateCallDecl, data, info);
	ANNOTATE(annotateReservedKeyword, data, info);
	ANNOTATE(annotateGlobOrMul, data, info);
	ANNOTATE(annotateNamelessFunction, data, info);
	ANNOTATE(annotateLocalVariable, data, info);
	ANNOTATE(annotateVariable, data, info);
	ANNOTATE(annotateGlobalVariable, data, info);
	ANNOTATE(annotateFunction, data, info);
	ANNOTATE(annotateCall, data, info);
	ANNOTATE(annotateClass, data, info);
	ANNOTATE(annotateModuleName, data, info);
	ANNOTATE(annotateBareWord, data, info);
}

void Annotator::annotateRegOpt(LexContext *ctx, const string &data, Token *tk, TokenInfo *info)
{
	if (ctx->prev_type == RegDelim && isalpha(tk->_data[0]) &&
		data != "if"      && data != "while" &&
		data != "foreach" && data != "for") {
		//(data == "g" || data == "m" || data == "s" || data == "x")) {
		*info = ctx->tmgr->getTokenInfo(RegOpt);
	}
}

void Annotator::annotateNamespace(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	Token *next_tk = ctx->tmgr->nextToken(tk);
	if (next_tk && next_tk->_data[0] == ':' && next_tk->_data[1] == ':' &&
		next_tk->info.type != String && next_tk->info.type != RawString) {
		*info = ctx->tmgr->getTokenInfo(Namespace);
	} else if (ctx->prev_type == NamespaceResolver) {
		*info = ctx->tmgr->getTokenInfo(Namespace);
	}
}

void Annotator::annotateMethod(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	if (ctx->prev_type == Pointer && isalpha(tk->_data[0])) {
		*info = ctx->tmgr->getTokenInfo(Method);
	}
}

void Annotator::annotateKey(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	Token *next_tk = ctx->tmgr->nextToken(tk);
	if (ctx->prev_type == LeftBrace && next_tk &&
		(isalpha(tk->_data[0]) || tk->_data[0] == '_') &&
		next_tk->_data[0] == '}') {
		*info = ctx->tmgr->getTokenInfo(Key);
	} else if (next_tk &&
			   (isalpha(tk->_data[0]) || tk->_data[0] == '_') &&
			   (next_tk->_data[0] == '=' && next_tk->_data[1] == '>')) {
		*info = ctx->tmgr->getTokenInfo(Key);
	}
}

void Annotator::annotateShortScalarDereference(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	Token *next_tk = ctx->tmgr->nextToken(tk);
	if (next_tk && (tk->_data[0] == '$' && tk->_data[1] == '$') &&
		(isalpha(next_tk->_data[0]) || next_tk->_data[0] == '_')) {
		*info = ctx->tmgr->getTokenInfo(ShortScalarDereference);
	}
}

void Annotator::annotateCallDecl(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	Token *prev_tk = ctx->tmgr->previousToken(tk);
	if (prev_tk && prev_tk->info.type == TokenType::Ref && tk->_data[0] == '&') {
		*info = ctx->tmgr->getTokenInfo(CallDecl);
	} else if (tk->_data[0] == '&') {
		*info = ctx->tmgr->getTokenInfo(BitAnd);
	}
}

void Annotator::annotateReservedKeyword(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	TokenInfo reserved_info = ctx->tmgr->getTokenInfo(tk->_data);
	if (reserved_info.type != TokenType::Undefined && ctx->prev_type != FunctionDecl) {
		*info = reserved_info;
	}
}

void Annotator::annotateGlobOrMul(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	if (tk->_data[0] == '*') {
		*info = ctx->tmgr->getTokenInfo(Mul);
	}
}

void Annotator::annotateNamelessFunction(LexContext *ctx, const string &, Token *tk, TokenInfo *info)
{
	if (ctx->prev_type == FunctionDecl && tk->_data[0] == '{') {
		*info = ctx->tmgr->getTokenInfo(tk->_data);
	}
}

void Annotator::annotateLocalVariable(LexContext *ctx, const string &data, Token *, TokenInfo *info)
{
	if (ctx->prev_type == VarDecl && data.find("$") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(LocalVar);
		vardecl_map.insert(StringMap::value_type(data, ""));
	} else if (ctx->prev_type == VarDecl && data.find("@") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(LocalArrayVar);
		vardecl_map.insert(StringMap::value_type(data, ""));
	} else if (ctx->prev_type == VarDecl && data.find("%") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(LocalHashVar);
		vardecl_map.insert(StringMap::value_type(data, ""));
	}
}

void Annotator::annotateVariable(LexContext *ctx, const string &data, Token *, TokenInfo *info)
{
	if (vardecl_map.find(data) == vardecl_map.end()) return;
	if (data.find("@") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(ArrayVar);
	} else if (data.find("%") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(HashVar);
	} else {
		*info = ctx->tmgr->getTokenInfo(Var);
	}
}

void Annotator::annotateGlobalVariable(LexContext *ctx, const string &data, Token *, TokenInfo *info)
{
	if (data.find("$") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(GlobalVar);
		vardecl_map.insert(StringMap::value_type(data, ""));
	} else if (data.find("@") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(GlobalArrayVar);
		vardecl_map.insert(StringMap::value_type(data, ""));
	} else if (data.find("%") != string::npos) {
		*info = ctx->tmgr->getTokenInfo(GlobalHashVar);
		vardecl_map.insert(StringMap::value_type(data, ""));
	}
}

void Annotator::annotateFunction(LexContext *ctx, const string &data, Token *, TokenInfo *info)
{
	if (ctx->prev_type == FunctionDecl) {
		*info = ctx->tmgr->getTokenInfo(Function);
		funcdecl_map.insert(StringMap::value_type(data, ""));
	}
}

void Annotator::annotateCall(LexContext *ctx, const string &data, Token *, TokenInfo *info)
{
	if (funcdecl_map.find(data) != funcdecl_map.end()) {
		*info = ctx->tmgr->getTokenInfo(Call);
	}
}

void Annotator::annotateClass(LexContext *ctx, const string &data, Token *, TokenInfo *info)
{
	if (ctx->prev_type == Package) {
		*info = ctx->tmgr->getTokenInfo(Class);
		pkgdecl_map.insert(StringMap::value_type(data, ""));
	} else if (pkgdecl_map.find(data) != pkgdecl_map.end()) {
		*info = ctx->tmgr->getTokenInfo(Class);
	}
}

void Annotator::annotateModuleName(LexContext *ctx, const string &, Token *, TokenInfo *info)
{
	if (ctx->prev_type == UseDecl) {
		*info = ctx->tmgr->getTokenInfo(UsedName);
	} else if (ctx->prev_type == RequireDecl) {
		*info = ctx->tmgr->getTokenInfo(RequiredName);
	}
}

void Annotator::annotateBareWord(LexContext *ctx, const string &, Token *, TokenInfo *info)
{
	*info = ctx->tmgr->getTokenInfo(Key);//BareWord);
	info->has_warnings = true;
}
