/*  Copyright 2008-2022 Carsten Elton Sorensen and contributors

	This file is part of ASMotor.

	ASMotor is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	ASMotor is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with ASMotor.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdint.h>
#include <stdbool.h>

#include "lexer.h"
#include "lexer_constants.h"
#include "lexer_context.h"
#include "parse.h"
#include "parse_block.h"
#include "parse_expression.h"
#include "parse_string.h"
#include "parse_symbol.h"
#include "errors.h"
#include "symbol.h"


static SSymbol*
g_rsSymbol = NULL;

static bool
isDotLocalName(const string* name) {
	return str_CharAt(name, 0) == '.';
}

static uint32_t
colonCount(void) {
	if (lex_Context->token.id == ':') {
		parse_GetToken();
		if (lex_Context->token.id == ':') {
			parse_GetToken();
			return 2;
		}
	}
	return 1;
}

static SSymbol*
getRsCounter(void) {
	if (g_rsSymbol == NULL) {
		string* rsName = str_Create("__RS");
		g_rsSymbol = sym_CreateSet(rsName, 0);
		str_Free(rsName);
	}
	return g_rsSymbol;
}

int32_t
parse_IncrementRs(int32_t size) {
	SSymbol* rsSymbol = getRsCounter();
	int32_t rsValue = rsSymbol->value.integer;
	rsSymbol->value.integer += size;

	return rsValue;
}

static void
createRsSymbol(string* symbolName, int32_t multiplier) {
	parse_GetToken();

	int value = multiplier * parse_ConstantExpression();
	if (isDotLocalName(symbolName)) {
		if (sym_CurrentScope != NULL && sym_CurrentScope->type == SYM_STRUCTURE) {
			sym_CreateSet(symbolName, parse_IncrementRs(value));
			sym_CurrentScope->value.integer += value;
		} else {
			err_Error(ERROR_NOT_IN_STRUCTURE_SCOPE);
		}
	} else {
		sym_CreateSet(symbolName, parse_IncrementRs(value));
	}
}

void
parse_SetRs(int32_t rsValue) {
	getRsCounter()->value.integer = rsValue;
}

static void
createRsSetSymbol(string* symbolName, int32_t value) {
	parse_SetRs(value);

	if (isDotLocalName(symbolName))
		sym_CreateSet(symbolName, value);
	else
		sym_CreateStructure(symbolName, value);
}

bool
parse_SymbolDefinition(void) {
	bool r = false;

	if (lex_Context->token.id == T_LABEL) {
		if (lex_ConstantsMatchTokenString() != NULL) {
			err_Warn(WARN_SYMBOL_WITH_RESERVED_NAME);
		}

		string* symbolName = lex_TokenString();

		parse_GetToken();

		uint32_t totalColons = colonCount();

		if (lex_Context->token.id == T_SYM_MACRO) {
			if (totalColons != 1) {
			    err_Error(ERROR_SYMBOL_EXPORT, str_String(lex_Context->buffer.name), lex_Context->lineNumber);
				return false;
			} else {
				uint32_t lineNumber = lex_Context->lineNumber;

				string* block = parse_CopyMacroBlock();
				if (block != NULL) {
					sym_CreateMacro(symbolName, block, lineNumber);
					str_Free(block);
					parse_GetToken();
					r = true;
				} else {
					err_Fail(ERROR_NEED_ENDM, str_String(lex_Context->buffer.name), lineNumber);
					return false;
				}
			}
		} else {
			switch (lex_Context->token.id) {
				default: {
					sym_CreateLabel(symbolName);
					break;
				}
				case T_DIRECTIVE_RSRESET: {
					createRsSetSymbol(symbolName, 0);
					break;
				}
				case T_DIRECTIVE_RSSET: {
					parse_GetToken();
					createRsSetSymbol(symbolName, parse_ConstantExpression());
					break;
				}
				case T_DIRECTIVE_RB: {
					createRsSymbol(symbolName, 1);
					break;
				}
				case T_DIRECTIVE_RW: {
					createRsSymbol(symbolName, 2);
					break;
				}
				case T_DIRECTIVE_RL: {
					createRsSymbol(symbolName, 4);
					break;
				}
				case T_SYM_EQU: {
					parse_GetToken();
					sym_CreateEqu(symbolName, parse_ConstantExpression());
					break;
				}
				case T_SYM_SET: {
					parse_GetToken();
					sym_CreateSet(symbolName, parse_ConstantExpression());
					break;
				}
				case T_SYM_EQUS: {
					parse_GetToken();

					string* value = parse_ExpectStringExpression();
					if (value != NULL) {
						sym_CreateEqus(symbolName, value);
						str_Free(value);
					}
					break;
				}
				case T_SYM_GROUP: {
					EGroupType groupType;

					parse_GetToken();
					switch (lex_Context->token.id) {
						case T_GROUP_TEXT:
							groupType = GROUP_TEXT;
							break;
						case T_GROUP_BSS:
							groupType = GROUP_BSS;
							break;
						default:
							err_Error(ERROR_EXPECT_TEXT_BSS);
							return false;
					}
					sym_CreateGroup(symbolName, groupType);
					parse_GetToken();
					break;
				}
			}

			if (totalColons == 2)
				sym_Export(symbolName);

			r = true;
		}
		str_Free(symbolName);
	}

	return r;
}
