#include "lib_runner.h"

#include "toy_memory.h"
#include "toy_interpreter.h"

#include "repl_tools.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Toy_Runner {
	Toy_Interpreter interpreter;
	const unsigned char* bytecode;
	size_t size;

	bool dirty;
} Toy_Runner;

//Toy native functions
static int nativeLoadScript(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//arguments
	if (arguments->count != 1) {
		interpreter->errorOutput("Incorrect number of arguments to loadScript\n");
		return -1;
	}

	//get the file path literal with a handle
	Toy_Literal drivePathLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal drivePathLiteralIdn = drivePathLiteral;
	if (TOY_IS_IDENTIFIER(drivePathLiteral) && Toy_parseIdentifierToValue(interpreter, &drivePathLiteral)) {
		Toy_freeLiteral(drivePathLiteralIdn);
	}

	if (TOY_IS_IDENTIFIER(drivePathLiteral)) {
		Toy_freeLiteral(drivePathLiteral);
		return -1;
	}

	Toy_Literal filePathLiteral = Toy_getFilePathLiteral(interpreter, &drivePathLiteral);

	if (TOY_IS_NULL(filePathLiteral)) {
		Toy_freeLiteral(filePathLiteral);
		Toy_freeLiteral(drivePathLiteral);
		return -1;
	}

	Toy_freeLiteral(drivePathLiteral);

	//use raw types - easier
	const char* filePath = Toy_toCString(TOY_AS_STRING(filePathLiteral));
	size_t filePathLength = Toy_lengthRefString(TOY_AS_STRING(filePathLiteral));

	//load and compile the bytecode
	size_t fileSize = 0;
	const char* source = (const char*)Toy_readFile(filePath, &fileSize);

	if (!source) {
		interpreter->errorOutput("Failed to load source file\n");
		Toy_freeLiteral(filePathLiteral);
		return -1;
	}

	const unsigned char* bytecode = Toy_compileString(source, &fileSize);
	free((void*)source);

	if (!bytecode) {
		interpreter->errorOutput("Failed to compile source file\n");
		Toy_freeLiteral(filePathLiteral);
		return -1;
	}

	//build the runner object
	Toy_Runner* runner = TOY_ALLOCATE(Toy_Runner, 1);
	Toy_setInterpreterPrint(&runner->interpreter, interpreter->printOutput);
	Toy_setInterpreterAssert(&runner->interpreter, interpreter->assertOutput);
	Toy_setInterpreterError(&runner->interpreter, interpreter->errorOutput);
	runner->interpreter.hooks = interpreter->hooks;
	runner->interpreter.scope = NULL;
	Toy_resetInterpreter(&runner->interpreter);
	runner->bytecode = bytecode;
	runner->size = fileSize;
	runner->dirty = false;

	//build the opaque object, and push it to the stack
	Toy_Literal runnerLiteral = TOY_TO_OPAQUE_LITERAL(runner, TOY_OPAQUE_TAG_RUNNER);
	Toy_pushLiteralArray(&interpreter->stack, runnerLiteral);

	//free the drive path
	Toy_freeLiteral(filePathLiteral);

	return 1;
}

static int nativeLoadScriptBytecode(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//arguments
	if (arguments->count != 1) {
		interpreter->errorOutput("Incorrect number of arguments to loadScriptBytecode\n");
		return -1;
	}

	//get the argument
	Toy_Literal drivePathLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal drivePathLiteralIdn = drivePathLiteral;
	if (TOY_IS_IDENTIFIER(drivePathLiteral) && Toy_parseIdentifierToValue(interpreter, &drivePathLiteral)) {
		Toy_freeLiteral(drivePathLiteralIdn);
	}

	if (TOY_IS_IDENTIFIER(drivePathLiteral)) {
		Toy_freeLiteral(drivePathLiteral);
		return -1;
	}

	Toy_RefString* drivePath = Toy_copyRefString(TOY_AS_STRING(drivePathLiteral));

	//get the drive and path as a string (can't trust that pesky strtok - custom split) TODO: move this to refstring library
	size_t driveLength = 0;
	while (Toy_toCString(drivePath)[driveLength] != ':') {
		if (driveLength >= Toy_lengthRefString(drivePath)) {
			interpreter->errorOutput("Incorrect drive path format given to loadScriptBytecode\n");
			Toy_deleteRefString(drivePath);
			Toy_freeLiteral(drivePathLiteral);
			return -1;
		}

		driveLength++;
	}

	Toy_RefString* drive = Toy_createRefStringLength(Toy_toCString(drivePath), driveLength);
	Toy_RefString* path = Toy_createRefStringLength( &Toy_toCString(drivePath)[driveLength + 1], Toy_lengthRefString(drivePath) - driveLength );

	//get the real drive file path
	Toy_Literal driveLiteral = TOY_TO_STRING_LITERAL(drive); //NOTE: driveLiteral takes ownership of the refString
	Toy_Literal realDriveLiteral = Toy_getLiteralDictionary(Toy_getDriveDictionary(), driveLiteral);

	if (!TOY_IS_STRING(realDriveLiteral)) {
		interpreter->errorOutput("Incorrect literal type found for drive: ");
		Toy_printLiteralCustom(realDriveLiteral, interpreter->errorOutput);
		interpreter->errorOutput("\n");
		Toy_freeLiteral(realDriveLiteral);
		Toy_freeLiteral(driveLiteral);
		Toy_deleteRefString(path);
		Toy_deleteRefString(drivePath);
		Toy_freeLiteral(drivePathLiteral);
		return -1;
	}

	//get the final real file path (concat) TODO: move this concat to refstring library
	Toy_RefString* realDrive = Toy_copyRefString(TOY_AS_STRING(realDriveLiteral));
	size_t realLength = Toy_lengthRefString(realDrive) + Toy_lengthRefString(path);

	char* filePath = TOY_ALLOCATE(char, realLength + 1); //+1 for null
	snprintf(filePath, realLength, "%s%s", Toy_toCString(realDrive), Toy_toCString(path));

	//clean up the drivepath stuff
	Toy_deleteRefString(realDrive);
	Toy_freeLiteral(realDriveLiteral);
	Toy_freeLiteral(driveLiteral);
	Toy_deleteRefString(path);
	Toy_deleteRefString(drivePath);
	Toy_freeLiteral(drivePathLiteral);

	//check for file extensions
	if (!(filePath[realLength - 4] == '.' && filePath[realLength - 3] == 't' && filePath[realLength - 2] == 'b')) {
		interpreter->errorOutput("Bad binary file extension (expected .tb)\n");
		TOY_FREE_ARRAY(char, filePath, realLength);
		return -1;
	}

	//check for break-out attempts
	for (size_t i = 0; i < realLength - 1; i++) {
		if (filePath[i] == '.' && filePath[i + 1] == '.') {
			interpreter->errorOutput("Parent directory access not allowed\n");
			TOY_FREE_ARRAY(char, filePath, realLength);
			return -1;
		}
	}

	//load the bytecode
	size_t fileSize = 0;
	unsigned char* bytecode = (unsigned char*)Toy_readFile(filePath, &fileSize);

	if (!bytecode) {
		interpreter->errorOutput("Failed to load bytecode file\n");
		return -1;
	}

	//build the runner object
	Toy_Runner* runner = TOY_ALLOCATE(Toy_Runner, 1);
	Toy_setInterpreterPrint(&runner->interpreter, interpreter->printOutput);
	Toy_setInterpreterAssert(&runner->interpreter, interpreter->assertOutput);
	Toy_setInterpreterError(&runner->interpreter, interpreter->errorOutput);
	runner->interpreter.hooks = interpreter->hooks;
	runner->interpreter.scope = NULL;
	Toy_resetInterpreter(&runner->interpreter);
	runner->bytecode = bytecode;
	runner->size = fileSize;
	runner->dirty = false;

	//build the opaque object, and push it to the stack
	Toy_Literal runnerLiteral = TOY_TO_OPAQUE_LITERAL(runner, TOY_OPAQUE_TAG_RUNNER);
	Toy_pushLiteralArray(&interpreter->stack, runnerLiteral);

	TOY_FREE_ARRAY(char, filePath, realLength);

	return 1;
}

static int nativeRunScript(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//no arguments
	if (arguments->count != 1) {
		interpreter->errorOutput("Incorrect number of arguments to runScript\n");
		return -1;
	}

	//get the runner object
	Toy_Literal runnerLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal runnerIdn = runnerLiteral;
	if (TOY_IS_IDENTIFIER(runnerLiteral) && Toy_parseIdentifierToValue(interpreter, &runnerLiteral)) {
		Toy_freeLiteral(runnerIdn);
	}

	if (TOY_IS_IDENTIFIER(runnerLiteral)) {
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	if (TOY_GET_OPAQUE_TAG(runnerLiteral) != TOY_OPAQUE_TAG_RUNNER) {
		interpreter->errorOutput("Unrecognized opaque literal in runScript\n");
		return -1;
	}

	Toy_Runner* runner = TOY_AS_OPAQUE(runnerLiteral);

	//run
	if (runner->dirty) {
		interpreter->errorOutput("Can't re-run a dirty script (try resetting it first)\n");
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	unsigned char* bytecodeCopy = TOY_ALLOCATE(unsigned char, runner->size);
	memcpy(bytecodeCopy, runner->bytecode, runner->size); //need a COPY of the bytecode, because the interpreter eats it

	Toy_runInterpreter(&runner->interpreter, bytecodeCopy, runner->size);
	runner->dirty = true;

	//cleanup
	Toy_freeLiteral(runnerLiteral);

	return 0;
}

static int nativeGetScriptVar(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//no arguments
	if (arguments->count != 2) {
		interpreter->errorOutput("Incorrect number of arguments to getScriptVar\n");
		return -1;
	}

	//get the runner object
	Toy_Literal varName = Toy_popLiteralArray(arguments);
	Toy_Literal runnerLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal varNameIdn = varName;
	if (TOY_IS_IDENTIFIER(varName) && Toy_parseIdentifierToValue(interpreter, &varName)) {
		Toy_freeLiteral(varNameIdn);
	}

	Toy_Literal runnerIdn = runnerLiteral;
	if (TOY_IS_IDENTIFIER(runnerLiteral) && Toy_parseIdentifierToValue(interpreter, &runnerLiteral)) {
		Toy_freeLiteral(runnerIdn);
	}

	if (TOY_IS_IDENTIFIER(varName) || TOY_IS_IDENTIFIER(runnerLiteral)) {
		Toy_freeLiteral(varName);
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	if (TOY_GET_OPAQUE_TAG(runnerLiteral) != TOY_OPAQUE_TAG_RUNNER) {
		interpreter->errorOutput("Unrecognized opaque literal in getScriptVar\n");
		return -1;
	}

	Toy_Runner* runner = TOY_AS_OPAQUE(runnerLiteral);

	//dirty check
	if (!runner->dirty) {
		interpreter->errorOutput("Can't access variable from a non-dirty script (try running it first)\n");
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	//get the desired variable
	Toy_Literal varIdn = TOY_TO_IDENTIFIER_LITERAL(Toy_copyRefString(TOY_AS_STRING(varName)));
	Toy_Literal result = TOY_TO_NULL_LITERAL;
	Toy_getScopeVariable(runner->interpreter.scope, varIdn, &result);

	Toy_pushLiteralArray(&interpreter->stack, result);

	//cleanup
	Toy_freeLiteral(result);
	Toy_freeLiteral(varIdn);
	Toy_freeLiteral(varName);
	Toy_freeLiteral(runnerLiteral);

	return 1;
}

static int nativeCallScriptFn(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//no arguments
	if (arguments->count < 2) {
		interpreter->errorOutput("Incorrect number of arguments to callScriptFn\n");
		return -1;
	}

	//get the rest args
	Toy_LiteralArray tmp;
	Toy_initLiteralArray(&tmp);

	while (arguments->count > 2) {
		Toy_Literal lit = Toy_popLiteralArray(arguments);
		Toy_pushLiteralArray(&tmp, lit);
		Toy_freeLiteral(lit);
	}

	Toy_LiteralArray rest;
	Toy_initLiteralArray(&rest);

	while (tmp.count > 0) { //correct the order of the rest args
		Toy_Literal lit = Toy_popLiteralArray(&tmp);
		Toy_pushLiteralArray(&rest, lit);
		Toy_freeLiteral(lit);
	}

	Toy_freeLiteralArray(&tmp);

	//get the runner object
	Toy_Literal varName = Toy_popLiteralArray(arguments);
	Toy_Literal runnerLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal varNameIdn = varName;
	if (TOY_IS_IDENTIFIER(varName) && Toy_parseIdentifierToValue(interpreter, &varName)) {
		Toy_freeLiteral(varNameIdn);
	}

	Toy_Literal runnerIdn = runnerLiteral;
	if (TOY_IS_IDENTIFIER(runnerLiteral) && Toy_parseIdentifierToValue(interpreter, &runnerLiteral)) {
		Toy_freeLiteral(runnerIdn);
	}

	if (TOY_IS_IDENTIFIER(varName) || TOY_IS_IDENTIFIER(runnerLiteral)) {
		Toy_freeLiteral(varName);
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	if (TOY_GET_OPAQUE_TAG(runnerLiteral) != TOY_OPAQUE_TAG_RUNNER) {
		interpreter->errorOutput("Unrecognized opaque literal in callScriptFn\n");
		return -1;
	}

	Toy_Runner* runner = TOY_AS_OPAQUE(runnerLiteral);

	//dirty check
	if (!runner->dirty) {
		interpreter->errorOutput("Can't access fn from a non-dirty script (try running it first)\n");
		Toy_freeLiteral(runnerLiteral);
		Toy_freeLiteralArray(&rest);
		return -1;
	}

	//get the desired variable
	Toy_Literal varIdn = TOY_TO_IDENTIFIER_LITERAL(Toy_copyRefString(TOY_AS_STRING(varName)));
	Toy_Literal fn = TOY_TO_NULL_LITERAL;
	Toy_getScopeVariable(runner->interpreter.scope, varIdn, &fn);

	if (!TOY_IS_FUNCTION(fn)) {
		interpreter->errorOutput("Can't run a non-function literal\n");
		Toy_freeLiteral(fn);
		Toy_freeLiteral(varIdn);
		Toy_freeLiteral(varName);
		Toy_freeLiteral(runnerLiteral);
		Toy_freeLiteralArray(&rest);
	}

	//call
	Toy_LiteralArray resultArray;
	Toy_initLiteralArray(&resultArray);

	Toy_callLiteralFn(interpreter, fn, &rest, &resultArray);

	Toy_Literal result = TOY_TO_NULL_LITERAL;
	if (resultArray.count > 0) {
		result = Toy_popLiteralArray(&resultArray);
	}

	Toy_pushLiteralArray(&interpreter->stack, result);

	//cleanup
	Toy_freeLiteralArray(&resultArray);
	Toy_freeLiteral(result);
	Toy_freeLiteral(fn);
	Toy_freeLiteral(varIdn);
	Toy_freeLiteral(varName);
	Toy_freeLiteral(runnerLiteral);
	Toy_freeLiteralArray(&rest);

	return 1;
}

static int nativeResetScript(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//no arguments
	if (arguments->count != 1) {
		interpreter->errorOutput("Incorrect number of arguments to resetScript\n");
		return -1;
	}

	//get the runner object
	Toy_Literal runnerLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal runnerIdn = runnerLiteral;
	if (TOY_IS_IDENTIFIER(runnerLiteral) && Toy_parseIdentifierToValue(interpreter, &runnerLiteral)) {
		Toy_freeLiteral(runnerIdn);
	}

	if (TOY_IS_IDENTIFIER(runnerLiteral)) {
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	if (TOY_GET_OPAQUE_TAG(runnerLiteral) != TOY_OPAQUE_TAG_RUNNER) {
		interpreter->errorOutput("Unrecognized opaque literal in resetScript\n");
		return -1;
	}

	Toy_Runner* runner = TOY_AS_OPAQUE(runnerLiteral);

	//reset
	if (!runner->dirty) {
		interpreter->errorOutput("Can't reset a non-dirty script (try running it first)\n");
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	Toy_resetInterpreter(&runner->interpreter);
	runner->dirty = false;
	Toy_freeLiteral(runnerLiteral);

	return 0;
}

static int nativeFreeScript(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//no arguments
	if (arguments->count != 1) {
		interpreter->errorOutput("Incorrect number of arguments to freeScript\n");
		return -1;
	}

	//get the runner object
	Toy_Literal runnerLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal runnerIdn = runnerLiteral;
	if (TOY_IS_IDENTIFIER(runnerLiteral) && Toy_parseIdentifierToValue(interpreter, &runnerLiteral)) {
		Toy_freeLiteral(runnerIdn);
	}

	if (TOY_IS_IDENTIFIER(runnerLiteral)) {
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	if (TOY_GET_OPAQUE_TAG(runnerLiteral) != TOY_OPAQUE_TAG_RUNNER) {
		interpreter->errorOutput("Unrecognized opaque literal in freeScript\n");
		return -1;
	}

	Toy_Runner* runner = TOY_AS_OPAQUE(runnerLiteral);

	//clear out the runner object
	runner->interpreter.hooks = NULL;
	Toy_freeInterpreter(&runner->interpreter);
	TOY_FREE_ARRAY(unsigned char, runner->bytecode, runner->size);

	TOY_FREE(Toy_Runner, runner);

	Toy_freeLiteral(runnerLiteral);

	return 0;
}

static int nativeCheckScriptDirty(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
	//no arguments
	if (arguments->count != 1) {
		interpreter->errorOutput("Incorrect number of arguments to checkScriptDirty\n");
		return -1;
	}

	//get the runner object
	Toy_Literal runnerLiteral = Toy_popLiteralArray(arguments);

	Toy_Literal runnerIdn = runnerLiteral;
	if (TOY_IS_IDENTIFIER(runnerLiteral) && Toy_parseIdentifierToValue(interpreter, &runnerLiteral)) {
		Toy_freeLiteral(runnerIdn);
	}

	if (TOY_IS_IDENTIFIER(runnerLiteral)) {
		Toy_freeLiteral(runnerLiteral);
		return -1;
	}

	if (TOY_GET_OPAQUE_TAG(runnerLiteral) != TOY_OPAQUE_TAG_RUNNER) {
		interpreter->errorOutput("Unrecognized opaque literal in checkScriptDirty\n");
		return -1;
	}

	Toy_Runner* runner = TOY_AS_OPAQUE(runnerLiteral);

	//run
	Toy_Literal result = TOY_TO_BOOLEAN_LITERAL(runner->dirty);

	Toy_pushLiteralArray(&interpreter->stack, result);

	//cleanup
	Toy_freeLiteral(result);
	Toy_freeLiteral(runnerLiteral);

	return 0;
}

//call the hook
typedef struct Natives {
	const char* name;
	Toy_NativeFn fn;
} Natives;

int Toy_hookRunner(Toy_Interpreter* interpreter, Toy_Literal identifier, Toy_Literal alias) {
	//build the natives list
	Natives natives[] = {
		{"loadScript", nativeLoadScript},
		{"loadScriptBytecode", nativeLoadScriptBytecode},
		{"runScript", nativeRunScript},
		{"getScriptVar", nativeGetScriptVar},
		{"callScriptFn", nativeCallScriptFn},
		{"resetScript", nativeResetScript},
		{"freeScript", nativeFreeScript},
		{"checkScriptDirty", nativeCheckScriptDirty},
		{NULL, NULL}
	};

	//store the library in an aliased dictionary
	if (!TOY_IS_NULL(alias)) {
		//make sure the name isn't taken
		if (Toy_isDelcaredScopeVariable(interpreter->scope, alias)) {
			interpreter->errorOutput("Can't override an existing variable\n");
			Toy_freeLiteral(alias);
			return -1;
		}

		//create the dictionary to load up with functions
		Toy_LiteralDictionary* dictionary = TOY_ALLOCATE(Toy_LiteralDictionary, 1);
		Toy_initLiteralDictionary(dictionary);

		//load the dict with functions
		for (int i = 0; natives[i].name; i++) {
			Toy_Literal name = TOY_TO_STRING_LITERAL(Toy_createRefString(natives[i].name));
			Toy_Literal func = TOY_TO_FUNCTION_NATIVE_LITERAL(natives[i].fn);

			Toy_setLiteralDictionary(dictionary, name, func);

			Toy_freeLiteral(name);
			Toy_freeLiteral(func);
		}

		//build the type
		Toy_Literal type = TOY_TO_TYPE_LITERAL(TOY_LITERAL_DICTIONARY, true);
		Toy_Literal strType = TOY_TO_TYPE_LITERAL(TOY_LITERAL_STRING, true);
		Toy_Literal fnType = TOY_TO_TYPE_LITERAL(TOY_LITERAL_FUNCTION_NATIVE, true);
		TOY_TYPE_PUSH_SUBTYPE(&type, strType);
		TOY_TYPE_PUSH_SUBTYPE(&type, fnType);

		//set scope
		Toy_Literal dict = TOY_TO_DICTIONARY_LITERAL(dictionary);
		Toy_declareScopeVariable(interpreter->scope, alias, type);
		Toy_setScopeVariable(interpreter->scope, alias, dict, false);

		//cleanup
		Toy_freeLiteral(dict);
		Toy_freeLiteral(type);
		return 0;
	}

	//default
	for (int i = 0; natives[i].name; i++) {
		Toy_injectNativeFn(interpreter, natives[i].name, natives[i].fn);
	}

	return 0;
}

//file system API
static Toy_LiteralDictionary Toy_driveDictionary;

void Toy_initDriveDictionary() {
	Toy_initLiteralDictionary(&Toy_driveDictionary);
}

void Toy_freeDriveDictionary() {
	Toy_freeLiteralDictionary(&Toy_driveDictionary);
}

Toy_LiteralDictionary* Toy_getDriveDictionary() {
	return &Toy_driveDictionary;
}

Toy_Literal Toy_getFilePathLiteral(Toy_Interpreter* interpreter, Toy_Literal* drivePathLiteral) {
	//check argument types
	if (!TOY_IS_STRING(*drivePathLiteral)) {
		interpreter->errorOutput("Incorrect argument type passed to Toy_getFilePathLiteral\n");
		return TOY_TO_NULL_LITERAL;
	}

	Toy_RefString* drivePath = Toy_copyRefString(TOY_AS_STRING(*drivePathLiteral));

	//get the drive and path as a string (can't trust that pesky strtok - custom split) TODO: move this to refstring library
	size_t driveLength = 0;
	while (Toy_toCString(drivePath)[driveLength] != ':') {
		if (driveLength >= Toy_lengthRefString(drivePath)) {
			interpreter->errorOutput("Incorrect drive path format given to Toy_getFilePathLiteral\n");

			return TOY_TO_NULL_LITERAL;
		}

		driveLength++;
	}

	Toy_RefString* drive = Toy_createRefStringLength(Toy_toCString(drivePath), driveLength);
	Toy_RefString* path = Toy_createRefStringLength( &Toy_toCString(drivePath)[driveLength + 1], Toy_lengthRefString(drivePath) - driveLength );

	//get the real drive file path
	Toy_Literal driveLiteral = TOY_TO_STRING_LITERAL(drive); //NOTE: driveLiteral takes ownership of the refString
	Toy_Literal realDriveLiteral = Toy_getLiteralDictionary(Toy_getDriveDictionary(), driveLiteral);

	if (!TOY_IS_STRING(realDriveLiteral)) {
		interpreter->errorOutput("Incorrect literal type found for drive: ");
		Toy_printLiteralCustom(realDriveLiteral, interpreter->errorOutput);
		interpreter->errorOutput("\n");
		Toy_freeLiteral(realDriveLiteral);
		Toy_freeLiteral(driveLiteral);
		Toy_deleteRefString(path);
		Toy_deleteRefString(drivePath);

		return TOY_TO_NULL_LITERAL;
	}

	//get the final real file path (concat) TODO: move this concat to refstring library
	Toy_RefString* realDrive = Toy_copyRefString(TOY_AS_STRING(realDriveLiteral));
	size_t realLength = Toy_lengthRefString(realDrive) + Toy_lengthRefString(path);

	char* filePath = TOY_ALLOCATE(char, realLength + 1); //+1 for null
	snprintf(filePath, realLength, "%s%s", Toy_toCString(realDrive), Toy_toCString(path));

	//clean up the drivepath stuff
	Toy_deleteRefString(realDrive);
	Toy_freeLiteral(realDriveLiteral);
	Toy_freeLiteral(driveLiteral);
	Toy_deleteRefString(path);
	Toy_deleteRefString(drivePath);

	//check for break-out attempts
	for (size_t i = 0; i < realLength - 1; i++) {
		if (filePath[i] == '.' && filePath[i + 1] == '.') {
			interpreter->errorOutput("Parent directory access not allowed\n");
			TOY_FREE_ARRAY(char, filePath, realLength + 1);
			return TOY_TO_NULL_LITERAL;
		}
	}

	Toy_Literal result = TOY_TO_STRING_LITERAL(Toy_createRefStringLength(filePath, realLength));

	TOY_FREE_ARRAY(char, filePath, realLength + 1);

	return result;
}