#include "standard_library.h"
#include "toy_console_colors.h"

#include "toy_scope.h"
#include "toy_stack.h"

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

typedef struct CallbackPairs {
	const char* name;
	Toy_nativeCallback callback;
} CallbackPairs;

//example of how to write and use C bindings
static void std_min(Toy_VM* vm, Toy_FunctionNative* self) {
	(void)self;

	//return the lesser of two values, or null on error
	Toy_Value first = Toy_popStack(&vm->stack);
	Toy_Value second = Toy_popStack(&vm->stack);

	//check types
	if ((!TOY_VALUE_IS_INTEGER(first) && !TOY_VALUE_IS_FLOAT(first)) || (!TOY_VALUE_IS_INTEGER(second) && !TOY_VALUE_IS_FLOAT(second))) {
		char buffer[256];
		snprintf(buffer, 256, "Invalid types '%s' and '%s' found in 'min()'", Toy_getValueTypeAsCString(Toy_unwrapValue(first).type), Toy_getValueTypeAsCString(Toy_unwrapValue(second).type));
		Toy_error(buffer);

		Toy_freeValue(first);
		Toy_freeValue(second);
		Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
		return;
	}

	//compare ints, or coerce ints into floats if needed
	if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_INTEGER(second)) {
		Toy_Value result = TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_INTEGER(first) < TOY_VALUE_AS_INTEGER(second) ? TOY_VALUE_AS_INTEGER(first) : TOY_VALUE_AS_INTEGER(second));
		Toy_pushStack(&vm->stack, result);
		return;
	}
	else if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_FLOAT(second)) {
		first = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(first) );
	}
	else if (TOY_VALUE_IS_FLOAT(first) && TOY_VALUE_IS_INTEGER(second)) {
		second = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(second) );
	}

	//finally, do the comparison on floats
	Toy_Value result = TOY_VALUE_FROM_FLOAT(TOY_VALUE_AS_FLOAT(first) < TOY_VALUE_AS_FLOAT(second) ? TOY_VALUE_AS_FLOAT(first) : TOY_VALUE_AS_FLOAT(second));
	Toy_pushStack(&vm->stack, result);
	//NOTE: not freeing scalar values does work, but only in narrow cases
}

static void std_max(Toy_VM* vm, Toy_FunctionNative* self) {
	(void)self;

	//return the lesser of two values, or null on error
	Toy_Value first = Toy_popStack(&vm->stack);
	Toy_Value second = Toy_popStack(&vm->stack);

	//check types
	if ((!TOY_VALUE_IS_INTEGER(first) && !TOY_VALUE_IS_FLOAT(first)) || (!TOY_VALUE_IS_INTEGER(second) && !TOY_VALUE_IS_FLOAT(second))) {
		char buffer[256];
		snprintf(buffer, 256, "Invalid types '%s' and '%s' found in 'max()'", Toy_getValueTypeAsCString(Toy_unwrapValue(first).type), Toy_getValueTypeAsCString(Toy_unwrapValue(second).type));
		Toy_error(buffer);

		Toy_freeValue(first);
		Toy_freeValue(second);
		Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
		return;
	}

	//compare ints, or coerce ints into floats if needed
	if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_INTEGER(second)) {
		Toy_Value result = TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_INTEGER(first) > TOY_VALUE_AS_INTEGER(second) ? TOY_VALUE_AS_INTEGER(first) : TOY_VALUE_AS_INTEGER(second));
		Toy_pushStack(&vm->stack, result);
		return;
	}
	else if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_FLOAT(second)) {
		first = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(first) );
	}
	else if (TOY_VALUE_IS_FLOAT(first) && TOY_VALUE_IS_INTEGER(second)) {
		second = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(second) );
	}

	//finally, do the comparison on floats
	Toy_Value result = TOY_VALUE_FROM_FLOAT(TOY_VALUE_AS_FLOAT(first) > TOY_VALUE_AS_FLOAT(second) ? TOY_VALUE_AS_FLOAT(first) : TOY_VALUE_AS_FLOAT(second));
	Toy_pushStack(&vm->stack, result);
	//NOTE: not freeing scalar values does work, but only in narrow cases
}

static void std_floor(Toy_VM* vm, Toy_FunctionNative* self) {
	(void)self;

	Toy_Value value = Toy_popStack(&vm->stack);

	if (!TOY_VALUE_IS_INTEGER(value) && !TOY_VALUE_IS_FLOAT(value)) {
		char buffer[256];
		snprintf(buffer, 256, "Invalid type '%s' found in 'floor()'", Toy_getValueTypeAsCString(Toy_unwrapValue(value).type));
		Toy_error(buffer);

		Toy_freeValue(value);
		Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
		return;
	}

	//this only runs on floats, but converts them to integers
	if (TOY_VALUE_IS_FLOAT(value)) {
		double d = (double)TOY_VALUE_AS_FLOAT(value);
		d = floor(d);
		value = TOY_VALUE_FROM_INTEGER((int)d);
	}

	Toy_pushStack(&vm->stack, value);
}

static void std_ceil(Toy_VM* vm, Toy_FunctionNative* self) {
	(void)self;

	Toy_Value value = Toy_popStack(&vm->stack);

	if (!TOY_VALUE_IS_INTEGER(value) && !TOY_VALUE_IS_FLOAT(value)) {
		char buffer[256];
		snprintf(buffer, 256, "Invalid type '%s' found in 'ceil()'", Toy_getValueTypeAsCString(Toy_unwrapValue(value).type));
		Toy_error(buffer);

		Toy_freeValue(value);
		Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
		return;
	}

	//this only runs on floats, but converts them to integers
	if (TOY_VALUE_IS_FLOAT(value)) {
		double d = (double)TOY_VALUE_AS_FLOAT(value);
		d = ceil(d);
		value = TOY_VALUE_FROM_INTEGER((int)d);
	}

	Toy_pushStack(&vm->stack, value);
}

static void std_sqrt(Toy_VM* vm, Toy_FunctionNative* self) {
	(void)self;

	Toy_Value value = Toy_popStack(&vm->stack);

	if (!TOY_VALUE_IS_INTEGER(value) && !TOY_VALUE_IS_FLOAT(value)) {
		char buffer[256];
		snprintf(buffer, 256, "Invalid type '%s' found in 'sqrt()'", Toy_getValueTypeAsCString(Toy_unwrapValue(value).type));
		Toy_error(buffer);

		Toy_freeValue(value);
		Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
		return;
	}

	double d = 0;

	if (TOY_VALUE_IS_INTEGER(value)) d = (double)TOY_VALUE_AS_INTEGER(value);
	else if (TOY_VALUE_IS_FLOAT(value)) d = (double)TOY_VALUE_AS_FLOAT(value);

	//only works on non-negative values
	if (d < 0) {
		char buffer[256];
		snprintf(buffer, 256, "Can't get the sqrt of a negative number (found '%g')", d);
		Toy_error(buffer);

		Toy_freeValue(value);
		Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
		return;
	}

	//do the thing and push the result
	d = sqrt(d);

	Toy_pushStack(&vm->stack, TOY_VALUE_FROM_FLOAT((float)d));
}

static void next(Toy_VM* vm, Toy_FunctionNative* self) {
	//used by 'std_range'
	if (self->meta2 < self->meta1) {
		Toy_Value result = TOY_VALUE_FROM_INTEGER(self->meta2);
		Toy_pushStack(&vm->stack, result);
		self->meta2++;
	}
	else {
		Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
	}
}

static void std_range(Toy_VM* vm, Toy_FunctionNative* self) {
	(void)self;

	//one arg to represent the number of iterations
	Toy_Value value = Toy_popStack(&vm->stack);

	//check types
	if (!TOY_VALUE_IS_INTEGER(value)) {
		char buffer[256];
		snprintf(buffer, 256, "Expected Integer argument in 'range', found '%s'", Toy_getValueTypeAsCString(value.type));
		Toy_error(buffer);
		Toy_freeValue(value);
		return;
	}

	//make the callback
	Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, next);
	fn->native.meta1 = TOY_VALUE_AS_INTEGER(value); //fake a closure
	fn->native.meta2 = 0; //counter

	Toy_Value result = TOY_VALUE_FROM_FUNCTION(fn);

	Toy_pushStack(&vm->stack, result);
}

CallbackPairs callbackPairs[] = {
	{"min", std_min},
	{"max", std_max},
	{"floor", std_floor},
	{"ceil", std_ceil},
	{"sqrt", std_sqrt},
	{"range", std_range},

	{NULL, NULL},
};

//exposed functions
void initStandardLibrary(Toy_VM* vm) {
	if (vm == NULL || vm->scope == NULL || vm->memoryBucket == NULL) {
		fprintf(stderr, TOY_CC_ERROR "ERROR: Can't initialize standard library, exiting\n" TOY_CC_RESET);
		exit(-1);
	}

	//declare each pair
	for (int i = 0; callbackPairs[i].name; i++) {
		Toy_String* key = Toy_createStringLength(&vm->memoryBucket, callbackPairs[i].name, strlen(callbackPairs[i].name));
		Toy_Function* fn = Toy_createFunctionFromCallback(&(vm->memoryBucket), callbackPairs[i].callback);

		Toy_declareScope(vm->scope, key, TOY_VALUE_FUNCTION, TOY_VALUE_FROM_FUNCTION(fn), true);
	}
}
