#include "model.h"
#include "compat.h"
#include "compat.h"
#include "parser.h"
#include <stdlib.h>
#include <string.h>

#include "intClass.h"
#include "StringClass.h"
#include "unsolvedArrayClass.h"
#include "functionClass.h"

#include "scriptError.h"
#include "standardLibrary.h"

#ifndef WIN32
#include "../tegraexplorer/tconf.h"
#include <storage/nx_sd.h>
#endif

static inline int isValidWord(char c) {
	char r = c | 0x20;
	return ((r >= 'a' && r <= 'z') || c == '_');
}

static inline int isValidNum(char c) {
	return (c >= '0' && c <= '9');
}

static inline int isValidVar(char c) {
	return (isValidWord(c) || isValidNum(c));
}

static inline int isValidHexNum(char c) {
	char r = c | 0x20;
	return (isValidNum(r) || (r >= 'a' && r <= 'f'));
}

char* getTokenText(u8 token) {
	for (u32 i = 0; i < tokenConvertionCount; i++) {
		if (tokenConvertions[i].token == token)
			return tokenConvertions[i].strToken;
	}

	return NULL;
}

char* utils_copyStringSize(const char* in, int size) {
	if (size > strlen(in) || size < 0)
		size = strlen(in);

	char* out = calloc(size + 1, 1);
	//strncpy(out, in, size);
	if (size)
		memcpy(out, in, size);
	return out;
}

#define ELIFC(c) else if (*in == c)

Vector_t script;
s64 lineNumber;

enum TokenType {
	Token_Variable = 0,
	Token_String,
	Token_Int,
	Token_Token,
	Token_Err,
	Token_Fatal_Err,
};

typedef enum {
	History_Function = 0,
	History_Bracket,
	History_Array,
} StackHistory_t;

char* end;

u8 nextToken(char** inPtr, void** val) {
	char* in = *inPtr;
	u8 ret = Token_Err;
	while (ret == Token_Err) {
		if (*in == '#') {
			if (!memcmp(in + 1, "REQUIRE ", 8)) {
				if (!memcmp(in + 9, "VER ", 4)) {
					char* verStart = in + 13;
					char* verEnd = verStart;

					while (isValidNum(*verEnd) || *verEnd == '.')
						verEnd++;

					u8 outdated = (verEnd - verStart != strlen(LP_VER));

					if (!outdated){
						outdated = (memcmp(LP_VER, verStart, verEnd - verStart) < 0);
					}

					if (outdated) {
						printScriptError(SCRIPT_LEXER_FATAL, "Script requires a newer TegraExplorer version!");
						return Token_Fatal_Err;
					}
				}
				else if (!memcmp(in + 9, "MINERVA", 7)) {
					#ifdef WIN32
					u8 minervaEnabled = 0; 
					#else
					u8 minervaEnabled = TConf.minervaEnabled;
					#endif
					if (!minervaEnabled) {
						printScriptError(SCRIPT_LEXER_FATAL, "Extended memory required.\nPut the bootloader folder from hekate on your sd!");
						return Token_Fatal_Err;
					}
				}
				else if (!memcmp(in + 9, "KEYS", 4)) {
					#ifdef WIN32
					u8 gotKeys = 0;
					#else
					u8 gotKeys = TConf.keysDumped;
					#endif
					if (!gotKeys){
						printScriptError(SCRIPT_LEXER_FATAL, "Keys required.\nMake sure you're on the latest version of TegraExplorer!");
						return Token_Fatal_Err;
					}
				}
				else if (!memcmp(in + 9, "SD", 2)) {
					#ifdef WIN32
					u8 gotSd = 0;
					#else
					u8 gotSd = sd_mount();
					#endif
					if (!gotSd){
						printScriptError(SCRIPT_LEXER_FATAL, "Sd required.");
						return Token_Fatal_Err;
					}
				}
			}

			while (*in && *in != '\n')
				in++;

			lineNumber++;
			scriptCurrentLine = lineNumber;
		}
		else if (isValidWord(*in)) {
			char* startWord = in;
			in++;
			while (isValidVar(*in))
				in++;

			char* str = utils_copyStringSize(startWord, in - startWord);

			//gfx_printf("Variable: '%s'\n", str);
			ret = Token_Variable;
			*val = str;
			break;
		}
		else if (isValidNum(*in) || (*in == '-' && isValidNum(in[1]))) {
			s64 parse = 0;
			u8 negative = (*in == '-');
			if (negative)
				in++;

			if (*in == '0' && (in[1] | 0x20) == 'x') {
				in += 2;
				while (isValidHexNum(*in)) {
					parse = parse * 16 + (*in & 0x0F) + (*in >= 'A' ? 9 : 0);
					in++;
				}
			}
			else while (isValidNum(*in)) {
				parse = parse * 10 + *in++ - '0';
			}

			if (negative)
				parse *= -1;

			//gfx_printf("Integer: '%d'\n", parse);
			ret = Token_Int;

			s64* parsePersistent = malloc(sizeof(s64));
			*parsePersistent = parse;

			*val = parsePersistent;
			break;
		}
		ELIFC('"') {
			char* startStr = ++in;
			int len = 0;
			while (*in != '"') {
				in++;
			}
			len = in - startStr;

			char* storage = malloc(len + 1);

			int pos = 0;
			for (int i = 0; i < len; i++) {
				if (startStr[i] == '\\') {
					if (startStr[i + 1] == 'n') {
						storage[pos++] = '\n';
						i++;
						continue;
					}

					if (startStr[i + 1] == 'r') {
						storage[pos++] = '\r';
						i++;
						continue;
					}
				}
				storage[pos++] = startStr[i];
			}
			storage[pos] = '\0';

			//gfx_printf("String: '%s'\n", storage);
			ret = Token_String;
			*val = storage;
		}
		else if (*in == '\0' || in > end) {
			*inPtr = in;
			return ret;
		}
		else if (*in == '\n'){
			lineNumber++;
			scriptCurrentLine = lineNumber;
		}
		else {
			for (u32 i = 0; i < tokenConvertionCount; i++) {
				TokenConvertion_t t = tokenConvertions[i];
				if (!memcmp(t.strToken, in, (t.strToken[1] == '\0') ? 1 : 2)) {
					//gfx_printf("Token: '%s'\n", t.strToken);
					ret = Token_Token;
					*val = (void*)t.token;

					if (t.strToken[1] != '\0')
						in++;

					break;
				}
			}
		}
		in++;
	}

	*inPtr = in;
	return ret;
}

#define CreateVariableReferenceStatic(var) VariableReference_t reference = { .staticVariable = var, .staticVariableSet = 1, .staticVariableRef = 1 }
#define CreateVariableReferenceStr(str) VariableReference_t reference = { .name = str }

void setNextActionOperator(Vector_t *opHolder, ActionType_t action, ActionExtraType_t actionExtra, void *extra) {
	Operator_t* ops = opHolder->data;
	Operator_t* lastOp = &ops[opHolder->count - 1];

	if (lastOp->token == CallArgs) {
		CallArgs_t* last = &lastOp->callArgs;
		for (; last->next != NULL; last = last->next);
		last->next = calloc(sizeof(CallArgs_t), 1);
		last->next->action = action;
		last->next->extra = extra;
		last->next->extraAction = actionExtra;
	}
	else {
		Operator_t newOp = { .token = CallArgs };
		newOp.callArgs.action = action;
		newOp.callArgs.extra = extra;
		newOp.callArgs.extraAction = actionExtra;
		vecAdd(opHolder, newOp);
	}
}

CallArgs_t* getLastRef(CallArgs_t* ref) {
	for (; ref->next != NULL; ref = ref->next);
	return ref;
}

int isLastVarSet(Operator_t* opHolder) {
	return (opHolder->token == CallArgs && getLastRef(&opHolder->callArgs)->action == ActionSet);
}

int isLastVarCall(Operator_t* opHolder) {
	return (opHolder->token == CallArgs && getLastRef(&opHolder->callArgs)->action == ActionCall);
}

ParserRet_t parseScript(char* in, u32 len) {
	Vector_t functionStack; // Function_t
	Vector_t stackHistoryHolder; // StaticHistory_t
	Vector_t staticVariableHolder; // Variable_t

	functionStack = newVec(sizeof(Function_t), 0);
	Function_t firstFunction = createEmptyFunction();
	vecAdd(&functionStack, firstFunction);

	staticVariableHolder = newVec(sizeof(Variable_t), 0);

	stackHistoryHolder = newVec(sizeof(StackHistory_t), 1);
	StackHistory_t firstHistory = History_Function;
	vecAdd(&stackHistoryHolder, firstHistory);
	u8 notNext = 0;
	lineNumber = 1;
	scriptCurrentLine = 1;
	end = in + len;

	while (*in && in <= end) {
		Function_t* lastFunc = getStackEntry(&functionStack);
		StackHistory_t* lastHistory = getStackEntry(&stackHistoryHolder);

		Operator_t* lastOp = NULL;

		if (lastFunc) {
			lastOp = getStackEntry(&lastFunc->operations);
		}

		void* var = NULL;
		u8 tokenType = nextToken(&in, &var);

		if (tokenType == Token_Err)
			break;

		if (tokenType == Token_Fatal_Err)
			return (ParserRet_t) { 0 };

		Operator_t op = { .token = Variable };

		if (tokenType >= Token_Variable && tokenType <= Token_Int && lastOp) {
			if (lastOp->token == Variable || lastOp->token == BetweenBrackets || (lastOp->token == CallArgs && !isLastVarSet(lastOp))) {
				op.token = EquationSeperator;
				op.lineNumber = lineNumber;
				vecAdd(&lastFunc->operations, op);
				op.token = Variable;
			}
		}

		if (tokenType == Token_Variable) {
			u8 stdLen = 0;
			ClassFunctionTableEntry_t* cfte = searchStdLib(var, &stdLen);

			if (cfte == NULL) {
				CreateVariableReferenceStr(var);
				op.variable = reference;
			}
			else {
				VariableReference_t reference = { .staticVariableType = 3, .staticFunction = cfte, .staticFunctionLen = stdLen };
				op.variable = reference;
			}
		}
		else if (tokenType == Token_Int) {
			/*
			Variable_t a = newIntVariable(*((s64*)var));
			a.gcDoNotFree = 1;
			free(var);
			vecAdd(&staticVariableHolder, a);
			CreateVariableReferenceStatic((Variable_t*)(staticVariableHolder.count - 1));
			*/

			VariableReference_t reference = { .staticVariableType = 1, .integerType = *((s64*)var) };
			op.variable = reference;
			free(var);
		}
		else if (tokenType == Token_String) {
			/*
			Variable_t a = newStringVariable(var, 1, 1);
			a.gcDoNotFree = 1;
			vecAdd(&staticVariableHolder, a);
			CreateVariableReferenceStatic((Variable_t*)(staticVariableHolder.count - 1));
			*/

			VariableReference_t reference = { .staticVariableType = 2, .stringType = var };
			op.variable = reference;
		}
		else if (tokenType == Token_Token) {
			u8 token = (u8)var;

			if (token == Equals && lastOp) {
				if (lastOp->token == Variable) {
					if (lastOp->variable.staticVariableSet) {
						SCRIPT_PARSER_ERR("Trying to assign to a static variable");
					}
					else {
						setNextActionOperator(&lastFunc->operations, ActionSet, ActionExtraNone, NULL);
						continue;
					}
				}
				else if (lastOp->token == CallArgs) {
					CallArgs_t* last = getLastRef(&lastOp->callArgs);
					last->action = ActionSet;
					continue;
				}
				else {
					SCRIPT_PARSER_ERR("Trying to assign to non-object");
				}
			}
			else if (token == LeftCurlyBracket) {
				Function_t templateFunction = createEmptyFunction();
				vecAdd(&functionStack, templateFunction);

				StackHistory_t functionHistory = History_Function;
				vecAdd(&stackHistoryHolder, functionHistory);
				continue;
			}
			else if (token == RightCurlyBracket) {
				if (stackHistoryHolder.count != 1 && *lastHistory == History_Function) {
					Function_t *popFunc = popStackEntry(&functionStack);
					popStackEntry(&stackHistoryHolder);

					lastFunc = getStackEntry(&functionStack);

					if (lastFunc) { // TODO: Add check for null deref
						lastOp = getStackEntry(&lastFunc->operations);
					}

					if (lastOp && (lastOp->token == Variable || (lastOp->token == CallArgs && !isLastVarSet(lastOp)))) {
						if (lastOp->token == Variable) {
							SCRIPT_PARSER_ERR("GET variable before {}");
							continue;
						}

						// Set last arg to {} 
						CallArgs_t* lastCall = getLastRef(&lastOp->callArgs);
						if (lastCall->extraAction == ActionExtraCallArgs) {
							Function_t* funcArgs = lastCall->extra;
							if (funcArgs->operations.count != 0) {
								op.token = EquationSeperator;
								op.lineNumber = lineNumber;
								vecAdd(&funcArgs->operations, op);
								op.token = Variable;
							}

							Variable_t a = newFunctionVariable(createFunctionClass(*popFunc, NULL));
							vecAdd(&staticVariableHolder, a);
							CreateVariableReferenceStatic((Variable_t*)(staticVariableHolder.count - 1));
							op.variable = reference;
							vecAdd(&funcArgs->operations, op);
							continue;
						}
					}



					Variable_t a = newFunctionVariable(createFunctionClass(*popFunc, NULL));
					vecAdd(&staticVariableHolder, a);
					CreateVariableReferenceStatic((Variable_t*)(staticVariableHolder.count - 1));
					op.variable = reference;
					vecAdd(&lastFunc->operations, op);

					op.token = EquationSeperator;
					op.lineNumber = lineNumber;
				}
				else {
					SCRIPT_PARSER_ERR("Stack count is 1 or state is not a function");
				}
			}
			else if (token == Dot) {
				if (lastOp && (lastOp->token == Variable || lastOp->token == BetweenBrackets || (lastOp->token == CallArgs && !isLastVarSet(lastOp)))) {
					tokenType = nextToken(&in, &var);
					if (tokenType != Token_Variable) {
						SCRIPT_PARSER_ERR("Acessing member with non-dynamic token");
					}
					else {
						setNextActionOperator(&lastFunc->operations, ActionGet, ActionExtraMemberName, var);
						continue;
					}
				}
				else {
					SCRIPT_PARSER_ERR("Member access on non-variable");
				}
			}
			else if (token == LeftBracket) {
				Function_t templateFunction = createEmptyFunction();
				vecAdd(&functionStack, templateFunction);

				StackHistory_t functionHistory = History_Bracket;
				vecAdd(&stackHistoryHolder, functionHistory);
				continue;
			}
			else if (token == RightBracket) { 
				if (*lastHistory == History_Bracket) {
					Function_t* bstack = popStackEntry(&functionStack);
					popStackEntry(&stackHistoryHolder);
					lastFunc = getStackEntry(&functionStack);

					if (lastFunc) { // TODO: Add check for null deref
						lastOp = getStackEntry(&lastFunc->operations);
					}

					if (lastOp && (lastOp->token == Variable || lastOp->token == BetweenBrackets || (lastOp->token == CallArgs && !isLastVarSet(lastOp) && !isLastVarCall(lastOp)))) {
						Function_t* newBStack = malloc(sizeof(Function_t));
						*newBStack = *bstack;
						setNextActionOperator(&lastFunc->operations, ActionCall, ActionExtraCallArgs, newBStack); // Maybe pass NULL if array is empty?
						continue;
					}
					else {
						if (lastOp && isLastVarCall(lastOp)) {
							op.token = EquationSeperator;
							vecAdd(&lastFunc->operations, op);
						}

						if (!countTokens(bstack, EquationSeperator)) {
							op.variable.betweenBrackets.data = bstack->operations.data;
							op.variable.betweenBrackets.len = bstack->operations.count;
							op.token = BetweenBrackets;
						}
						else {
							SCRIPT_PARSER_ERR("Priority brackets can only contain 1 argument");
 						}

					}
				}
				else {
					SCRIPT_PARSER_ERR(") without (");
				}
			}
			else if (token == LeftSquareBracket) {
				Function_t templateFunction = createEmptyFunction();
				vecAdd(&functionStack, templateFunction);

				StackHistory_t functionHistory = History_Array;
				vecAdd(&stackHistoryHolder, functionHistory);
				continue;
			}
			else if (token == RightSquareBracket) {
				if (*lastHistory == History_Array) {
					Function_t* astack = popStackEntry(&functionStack);
					popStackEntry(&stackHistoryHolder);
					lastFunc = getStackEntry(&functionStack);

					if (lastFunc) { // TODO: Add check for null deref
						lastOp = getStackEntry(&lastFunc->operations);
					}

					if (lastOp && (lastOp->token == Variable || (lastOp->token == CallArgs && !isLastVarSet(lastOp)))) {
						if (!countTokens(astack, EquationSeperator)) {
							Function_t* newAStack = malloc(sizeof(Function_t));
							*newAStack = *astack;
							setNextActionOperator(&lastFunc->operations, ActionGet, ActionExtraArrayIndex, newAStack);
							continue;
						}
						else {
							// We're just assuming that it's a new array lol

							op.token = EquationSeperator;
							vecAdd(&lastFunc->operations, op);
							op.token = Variable;


							Variable_t a = createUnsolvedArrayVariable(astack);
							vecAdd(&staticVariableHolder, a);
							CreateVariableReferenceStatic((Variable_t*)(staticVariableHolder.count - 1));
							op.variable = reference;

							//gfx_printf("[FATAL] indexes cannot contain mutiple arguments");
						}
					}
					else {
						// TODO: optimize output to a typed array, if possible
						Variable_t a = createUnsolvedArrayVariable(astack);
						vecAdd(&staticVariableHolder, a);
						CreateVariableReferenceStatic((Variable_t*)(staticVariableHolder.count - 1));
						op.variable = reference;
						/*
						vecAdd(&lastFunc->operations, op);
						op.token = EquationSeperator;
						op.lineNumber = lineNumber;
						*/
					}
				}
				else {
					SCRIPT_PARSER_ERR("] without [");
				}
			}
			else if (token == Not) {
				notNext = !notNext;
				continue;
			}
			else {
				op.token = token;

				for (u32 i = 0; i < tokenConvertionCount; i++) {
					if (token == tokenConvertions[i].token) {
						op.tokenStr = tokenConvertions[i].strToken;
						break;
					}
				}
			}
		}

		if (notNext) {
			op.not = 1;
			notNext = 0;
		}

		vecAdd(&lastFunc->operations, op);
	}

	if (functionStack.count != 1 || stackHistoryHolder.count != 1) {
		SCRIPT_PARSER_ERR("There seems to be an open bracket somewhere. EOF reached");
	}
		

	ParserRet_t parse = { .main = (*(Function_t*)getStackEntry(&functionStack)), .staticVarHolder = staticVariableHolder, .valid = 1 };

	vecFree(functionStack);
	vecFree(stackHistoryHolder);
	scriptCurrentLine = 1;
	return parse;
}


void exitFunction(Operator_t* start, u32 len) {
	for (u32 i = 0; i < len; i++) {
		if (start[i].token == Variable) {
			if (start[i].variable.staticVariableOptionsUnion == 0) {
				FREE(start[i].variable.name);
			}

			if (start[i].variable.staticVariableType == 2) {
				FREE(start[i].variable.stringType);
			}
		}
		else if (start[i].token == BetweenBrackets) {
			exitFunction(start[i].variable.betweenBrackets.data, start[i].variable.betweenBrackets.len);
			FREE(start[i].variable.betweenBrackets.data);
		}
		else if (start[i].token == CallArgs) {
			CallArgs_t* call = &start[i].callArgs;

			while (call != NULL) {
				if (call->extraAction == ActionExtraArrayIndex) {
					Function_t* f = call->extra;
					exitFunction(f->operations.data, f->operations.count);
					vecFree(f->operations);
					FREE(f);
				}
				else if (call->extraAction == ActionExtraMemberName) {
					FREE(call->extra);
				}
				else if (call->extraAction == ActionExtraCallArgs) {
					Function_t* f = call->extra;
					exitFunction(f->operations.data, f->operations.count);
					vecFree(f->operations);
					FREE(f);
				}

				CallArgs_t* nextCall = call->next;
				if (call != &start[i].callArgs) {
					FREE(call);
				}
					
				call = nextCall;
			}
		}
	}
}

void exitStaticVars(Vector_t* v) {
	vecForEach(Variable_t*, staticVar, v) {
		if (staticVar->variableType == FunctionClass) {
			if (!staticVar->function.builtIn) {
				exitFunction(staticVar->function.function.operations.data, staticVar->function.function.operations.count);
				vecFree(staticVar->function.function.operations);
			}
		}
		else {
			freeVariableInternal(staticVar);
		}
	}
}