/* Copyright (c) 2013-2021 Jeffrey Pfau
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/script/types.h>

#include <mgba/script/context.h>
#include <mgba/script/macros.h>
#include <mgba-util/hash.h>
#include <mgba-util/string.h>
#include <mgba-util/table.h>

static void _allocList(struct mScriptValue*);
static void _freeList(struct mScriptValue*);

static void _allocTable(struct mScriptValue*);
static void _freeTable(struct mScriptValue*);
static void _deinitTableValue(void*);

static void _allocString(struct mScriptValue*);
static void _freeString(struct mScriptValue*);
static uint32_t _hashString(const struct mScriptValue*);
static bool _stringCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*);

static bool _castScalar(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*);
static uint32_t _hashScalar(const struct mScriptValue*);

static uint32_t _valHash(const void* val, size_t len, uint32_t seed);
static bool _valEqual(const void* a, const void* b);
static void* _valRef(void*);
static void _valDeref(void*);

static bool _typeEqual(const struct mScriptValue*, const struct mScriptValue*);
static bool _s32Equal(const struct mScriptValue*, const struct mScriptValue*);
static bool _u32Equal(const struct mScriptValue*, const struct mScriptValue*);
static bool _f32Equal(const struct mScriptValue*, const struct mScriptValue*);
static bool _s64Equal(const struct mScriptValue*, const struct mScriptValue*);
static bool _u64Equal(const struct mScriptValue*, const struct mScriptValue*);
static bool _f64Equal(const struct mScriptValue*, const struct mScriptValue*);
static bool _boolEqual(const struct mScriptValue*, const struct mScriptValue*);
static bool _charpEqual(const struct mScriptValue*, const struct mScriptValue*);
static bool _stringEqual(const struct mScriptValue*, const struct mScriptValue*);

const struct mScriptType mSTVoid = {
	.base = mSCRIPT_TYPE_VOID,
	.size = 0,
	.name = "void",
	.alloc = NULL,
	.free = NULL,
	.hash = NULL,
	.equal = _typeEqual,
	.cast = NULL,
};

const struct mScriptType mSTSInt8 = {
	.base = mSCRIPT_TYPE_SINT,
	.size = 1,
	.name = "s8",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _s32Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTUInt8 = {
	.base = mSCRIPT_TYPE_UINT,
	.size = 1,
	.name = "u8",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _u32Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTSInt16 = {
	.base = mSCRIPT_TYPE_SINT,
	.size = 2,
	.name = "s16",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _s32Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTUInt16 = {
	.base = mSCRIPT_TYPE_UINT,
	.size = 2,
	.name = "u16",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _u32Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTSInt32 = {
	.base = mSCRIPT_TYPE_SINT,
	.size = 4,
	.name = "s32",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _s32Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTUInt32 = {
	.base = mSCRIPT_TYPE_UINT,
	.size = 4,
	.name = "u32",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _u32Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTFloat32 = {
	.base = mSCRIPT_TYPE_FLOAT,
	.size = 4,
	.name = "f32",
	.alloc = NULL,
	.free = NULL,
	.hash = NULL,
	.equal = _f32Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTSInt64 = {
	.base = mSCRIPT_TYPE_SINT,
	.size = 8,
	.name = "s64",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _s64Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTUInt64 = {
	.base = mSCRIPT_TYPE_UINT,
	.size = 8,
	.name = "u64",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _u64Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTFloat64 = {
	.base = mSCRIPT_TYPE_FLOAT,
	.size = 8,
	.name = "f64",
	.alloc = NULL,
	.free = NULL,
	.hash = NULL,
	.equal = _f64Equal,
	.cast = _castScalar,
};

const struct mScriptType mSTBool = {
	.base = mSCRIPT_TYPE_UINT,
	.size = 1,
	.name = "bool",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashScalar,
	.equal = _boolEqual,
	.cast = _castScalar,
};

const struct mScriptType mSTString = {
	.base = mSCRIPT_TYPE_STRING,
	.size = sizeof(struct mScriptString),
	.name = "string",
	.alloc = _allocString,
	.free = _freeString,
	.hash = _hashString,
	.equal = _stringEqual,
	.cast = _stringCast,
};

const struct mScriptType mSTCharPtr = {
	.base = mSCRIPT_TYPE_STRING,
	.size = sizeof(char*),
	.name = "charptr",
	.alloc = NULL,
	.free = NULL,
	.hash = _hashString,
	.equal = _charpEqual,
	.cast = _stringCast,
	.isConst = true,
};

const struct mScriptType mSTList = {
	.base = mSCRIPT_TYPE_LIST,
	.size = sizeof(struct mScriptList),
	.name = "list",
	.alloc = _allocList,
	.free = _freeList,
	.hash = NULL,
};

const struct mScriptType mSTTable = {
	.base = mSCRIPT_TYPE_TABLE,
	.size = sizeof(struct Table),
	.name = "table",
	.alloc = _allocTable,
	.free = _freeTable,
	.hash = NULL,
};

const struct mScriptType mSTWrapper = {
	.base = mSCRIPT_TYPE_WRAPPER,
	.size = sizeof(struct mScriptValue),
	.name = "wrapper",
	.alloc = NULL,
	.free = NULL,
	.hash = NULL,
};

const struct mScriptType mSTStringWrapper = {
	.base = mSCRIPT_TYPE_WRAPPER,
	.size = sizeof(struct mScriptValue),
	.name = "wrapper string",
	.alloc = NULL,
	.free = NULL,
	.hash = NULL,
};

const struct mScriptType mSTListWrapper = {
	.base = mSCRIPT_TYPE_WRAPPER,
	.size = sizeof(struct mScriptValue),
	.name = "wrapper list",
	.alloc = NULL,
	.free = NULL,
	.hash = NULL,
};

const struct mScriptType mSTWeakref = {
	.base = mSCRIPT_TYPE_WEAKREF,
	.size = sizeof(uint32_t),
	.name = "weakref",
	.alloc = NULL,
	.free = NULL,
	.hash = NULL,
};

struct mScriptValue mScriptValueNull = {
	.type = &mSTVoid,
	.refs = mSCRIPT_VALUE_UNREF
};

DEFINE_VECTOR(mScriptList, struct mScriptValue)

void _allocList(struct mScriptValue* val) {
	val->value.list = malloc(sizeof(struct mScriptList));
	mScriptListInit(val->value.list, 0);
}

void _freeList(struct mScriptValue* val) {
	size_t i;
	for (i = 0; i < mScriptListSize(val->value.list); ++i) {
		struct mScriptValue* value = mScriptListGetPointer(val->value.list, i);
		if (!value->type) {
			continue;
		}
		struct mScriptValue* unwrapped = mScriptValueUnwrap(value);
		if (unwrapped) {
			mScriptValueDeref(unwrapped);
		}
	}
	mScriptListDeinit(val->value.list);
	free(val->value.list);
}

void _allocTable(struct mScriptValue* val) {
	val->value.table = malloc(sizeof(struct Table));
	struct TableFunctions funcs = {
		.deinitializer = _deinitTableValue,
		.hash = _valHash,
		.equal = _valEqual,
		.ref = _valRef,
		.deref = _valDeref
	};
	HashTableInitCustom(val->value.table, 0, &funcs);
}

void _freeTable(struct mScriptValue* val) {
	HashTableDeinit(val->value.table);
	free(val->value.table);
}

void _deinitTableValue(void* val) {
	mScriptValueDeref(val);
}

static void _allocString(struct mScriptValue* val) {
	struct mScriptString* string = calloc(1, sizeof(*string));
	string->size = 0;
	string->buffer = NULL;
	val->value.string = string;
}

static void _freeString(struct mScriptValue* val) {
	struct mScriptString* string = val->value.string;
	if (string->buffer) {
		free(string->buffer);
	}
	free(string);
}

static bool _stringCast(const struct mScriptValue* in, const struct mScriptType* type, struct mScriptValue* out) {
	if (in->type == type) {
		out->type = type;
		out->refs = mSCRIPT_VALUE_UNREF;
		out->flags = 0;
		out->value.opaque = in->value.opaque;
		return true;
	}
	if (in->type == mSCRIPT_TYPE_MS_STR && type == mSCRIPT_TYPE_MS_CHARP) {
		out->type = type;
		out->refs = mSCRIPT_VALUE_UNREF;
		out->flags = 0;
		out->value.opaque = in->value.string->buffer;
		return true;
	}
	return false;
}

static uint32_t _hashString(const struct mScriptValue* val) {
	const char* buffer = 0;
	size_t size = 0;
	if (val->type == &mSTString) {
		struct mScriptString* string = val->value.string;
		buffer = string->buffer;
		size = string->size;
	} else if (val->type == &mSTCharPtr) {
		buffer = val->value.opaque;
		size = strlen(buffer);
	}
	return hash32(buffer, size, 0);
}

#define AS(NAME, TYPE) \
	bool _as ## NAME(const struct mScriptValue* input, mSCRIPT_TYPE_C_ ## TYPE * T) { \
		switch (input->type->base) { \
		case mSCRIPT_TYPE_SINT: \
			if (input->type->size <= 4) { \
				*T = input->value.s32; \
			} else if (input->type->size == 8) { \
				*T = input->value.s64; \
			} else { \
				return false; \
			}\
			break; \
		case mSCRIPT_TYPE_UINT: \
			if (input->type->size <= 4) { \
				*T = input->value.u32; \
			} else if (input->type->size == 8) { \
				*T = input->value.u64; \
			} else { \
				return false; \
			} \
			break; \
		case mSCRIPT_TYPE_FLOAT: \
			if (input->type->size == 4) { \
				*T = input->value.f32; \
			} else if (input->type->size == 8) { \
				*T = input->value.f64; \
			} else { \
				return false; \
			} \
			break; \
		default: \
			return false; \
		} \
		return true; \
	}

AS(SInt32, S32);
AS(UInt32, U32);
AS(Float32, F32);
AS(SInt64, S64);
AS(UInt64, U64);
AS(Float64, F64);
AS(Bool, BOOL);

bool _castScalar(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) {
	switch (type->base) {
	case mSCRIPT_TYPE_SINT:
		if (type->size <= 4) {
			if (!_asSInt32(input, &output->value.s32)) {
				return false;
			}
		} else if (type->size == 8) {
			if (!_asSInt64(input, &output->value.s64)) {
				return false;
			}
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_UINT:
		if (type == mSCRIPT_TYPE_MS_BOOL) {
			bool b;
			if (!_asBool(input, &b)) {
				return false;
			}
			output->value.u32 = b;
		} else if (type->size <= 4) {
			if (!_asUInt32(input, &output->value.u32)) {
				return false;
			}
		} else if (type->size == 8) {
			if (!_asUInt64(input, &output->value.u64)) {
				return false;
			}
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_FLOAT:
		if (type->size == 4) {
			if (!_asFloat32(input, &output->value.f32)) {
				return false;
			}
		} else if (type->size == 8) {
			if (!_asFloat64(input, &output->value.f64)) {
				return false;
			}
		} else {
			return false;
		}
		break;
	default:
		return false;
	}
	output->type = type;
	return true;
}

uint32_t _hashScalar(const struct mScriptValue* val) {
	// From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
	uint32_t x = 0;
	_asUInt32(val, &x);
	x = ((x >> 16) ^ x) * 0x45D9F3B;
	x = ((x >> 16) ^ x) * 0x45D9F3B;
	x = (x >> 16) ^ x;
	return x;
}

uint32_t _valHash(const void* val, size_t len, uint32_t seed) {
	UNUSED(len);
	const struct mScriptValue* value = val;
	uint32_t hash = value->type->hash(value);
	return hash ^ seed;
}

bool _valEqual(const void* a, const void* b) {
	const struct mScriptValue* valueA = a;
	const struct mScriptValue* valueB = b;
	return valueA->type->equal(valueA, valueB);
}

void* _valRef(void* val) {
	mScriptValueRef(val);
	return val;
}

void _valDeref(void* val) {
	mScriptValueDeref(val);
}

bool _typeEqual(const struct mScriptValue* a, const struct mScriptValue* b) {
	return a->type == b->type;
}

bool _s32Equal(const struct mScriptValue* a, const struct mScriptValue* b) {
	int32_t val;
	switch (b->type->base) {
	case mSCRIPT_TYPE_SINT:
		if (b->type->size <= 4) {
			val = b->value.s32;
		} else if (b->type->size == 8) {
			if (b->value.s64 > INT_MAX || b->value.s64 < INT_MIN) {
				return false;
			}
			val = b->value.s64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_UINT:
		if (b->type == mSCRIPT_TYPE_MS_BOOL) {
			return !!a->value.s32 == b->value.u32;
		}
		if (a->value.s32 < 0) {
			return false;
		}
		if (b->type->size <= 4) {
			if (b->value.u32 > (uint32_t) INT_MAX) {
				return false;
			}
			val = b->value.u32;
		} else if (b->type->size == 8) {
			if (b->value.u64 > (uint64_t) INT_MAX) {
				return false;
			}
			val = b->value.u64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_VOID:
		return false;
	default:
		return b->type->equal && b->type->equal(b, a);
	}
	return a->value.s32 == val;
}

bool _u32Equal(const struct mScriptValue* a, const struct mScriptValue* b) {
	uint32_t val;
	switch (b->type->base) {
	case mSCRIPT_TYPE_SINT:
		if (b->type->size <= 4) {
			if (a->value.u32 > (uint32_t) INT_MAX) {
				return false;
			}
			if (b->value.s32 < 0) {
				return false;
			}
			val = b->value.s32;
		} else if (b->type->size == 8) {
			if (b->value.s64 > (int64_t) UINT_MAX || b->value.s64 < 0) {
				return false;
			}
			val = b->value.s64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_UINT:
		if (b->type == mSCRIPT_TYPE_MS_BOOL) {
			return !!a->value.u32 == b->value.u32;
		}
		if (b->type->size <= 4) {
			val = b->value.u32;
		} else if (b->type->size == 8) {
			if (b->value.u64 > UINT_MAX) {
				return false;
			}
			val = b->value.u64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_VOID:
		return false;
	default:
		return b->type->equal && b->type->equal(b, a);
	}
	return a->value.u32 == val;
}

bool _f32Equal(const struct mScriptValue* a, const struct mScriptValue* b) {
	float val;
	switch (b->type->base) {
	case mSCRIPT_TYPE_UINT:
		if (b->type == mSCRIPT_TYPE_MS_BOOL) {
			return (!(uint32_t) !a->value.f32)== b->value.u32;
		}
		// Fall through
	case mSCRIPT_TYPE_SINT:
	case mSCRIPT_TYPE_FLOAT:
		if (!_asFloat32(b, &val)) {
			return false;
		}
		break;
	case mSCRIPT_TYPE_VOID:
		return false;
	default:
		return b->type->equal && b->type->equal(b, a);
	}
	return a->value.f32 == val;
}

bool _s64Equal(const struct mScriptValue* a, const struct mScriptValue* b) {
	int64_t val;
	switch (b->type->base) {
	case mSCRIPT_TYPE_SINT:
		if (b->type->size <= 4) {
			val = b->value.s32;
		} else if (b->type->size == 8) {
			val = b->value.s64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_UINT:
		if (b->type == mSCRIPT_TYPE_MS_BOOL) {
			return !!a->value.s64 == b->value.u32;
		}
		if (a->value.s64 < 0) {
			return false;
		}
		if (b->type->size <= 4) {
			val = b->value.u32;
		} else if (b->type->size == 8) {
			if (b->value.u64 > (uint64_t) INT64_MAX) {
				return false;
			}
			val = b->value.u64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_VOID:
		return false;
	default:
		return b->type->equal && b->type->equal(b, a);
	}
	return a->value.s64 == val;
}

bool _u64Equal(const struct mScriptValue* a, const struct mScriptValue* b) {
	uint64_t val;
	switch (b->type->base) {
	case mSCRIPT_TYPE_SINT:
		if (b->type->size <= 4) {
			if (a->value.u64 > (uint64_t) INT_MAX) {
				return false;
			}
			if (b->value.s32 < 0) {
				return false;
			}
			val = b->value.s32;
		} else if (b->type->size == 8) {
			if (a->value.u64 > (uint64_t) INT64_MAX) {
				return false;
			}
			if (b->value.s64 < 0) {
				return false;
			}
			val = b->value.s64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_UINT:
		if (b->type == mSCRIPT_TYPE_MS_BOOL) {
			return !!a->value.u64 == b->value.u32;
		}
		if (b->type->size <= 4) {
			val = b->value.u32;
		} else if (b->type->size == 8) {
			val = b->value.u64;
		} else {
			return false;
		}
		break;
	case mSCRIPT_TYPE_VOID:
		return false;
	default:
		return b->type->equal && b->type->equal(b, a);
	}
	return a->value.u64 == val;
}

bool _f64Equal(const struct mScriptValue* a, const struct mScriptValue* b) {
	double val;
	switch (b->type->base) {
	case mSCRIPT_TYPE_UINT:
		if (b->type == mSCRIPT_TYPE_MS_BOOL) {
			return (!(uint32_t) !a->value.f64)== b->value.u32;
		}
		// Fall through
	case mSCRIPT_TYPE_SINT:
	case mSCRIPT_TYPE_FLOAT:
		if (!_asFloat64(b, &val)) {
			return false;
		}
		break;
	case mSCRIPT_TYPE_VOID:
		return false;
	default:
		return b->type->equal && b->type->equal(b, a);
	}
	return a->value.f64 == val;
}

bool _boolEqual(const struct mScriptValue* a, const struct mScriptValue* b) {
	switch (b->type->base) {
	case mSCRIPT_TYPE_SINT:
		if (b->type->size <= 4) {
			return a->value.u32 == !!b->value.s32;
		} else if (b->type->size == 8) {
			return a->value.u32 == !!b->value.s64;
		}
		return false;
	case mSCRIPT_TYPE_UINT:
		if (b->type->size <= 4) {
			return a->value.u32 == !!b->value.u32;
		} else if (b->type->size == 8) {
			return a->value.u32 == !!b->value.u64;
		}
		return false;
	case mSCRIPT_TYPE_VOID:
		return false;
	default:
		return b->type->equal && b->type->equal(b, a);
	}
}

bool _charpEqual(const struct mScriptValue* a, const struct mScriptValue* b) {
	const char* valA = a->value.opaque;
	const char* valB;
	size_t lenA;
	size_t lenB;
	if (b->type->base != mSCRIPT_TYPE_STRING) {
		return false;
	}
	if (b->type == &mSTCharPtr) {
		valB = b->value.opaque;
		lenB = strlen(valB);
	} else if (b->type == &mSTString) {
		struct mScriptString* stringB = b->value.string;
		valB = stringB->buffer;
		lenB = stringB->size;
	} else {
		// TODO: Allow casting
		return false;
	}
	lenA = strlen(valA);
	if (lenA != lenB) {
		return false;
	}
	return strncmp(valA, valB, lenA) == 0;
}

bool _stringEqual(const struct mScriptValue* a, const struct mScriptValue* b) {
	struct mScriptString* stringA = a->value.string;
	const char* valA = stringA->buffer;
	const char* valB;
	size_t lenA = stringA->size;
	size_t lenB;
	if (b->type->base != mSCRIPT_TYPE_STRING) {
		return false;
	}
	if (b->type == &mSTCharPtr) {
		valB = b->value.opaque;
		lenB = strlen(valB);
	} else if (b->type == &mSTString) {
		struct mScriptString* stringB = b->value.string;
		valB = stringB->buffer;
		lenB = stringB->size;
	} else {
		// TODO: Allow casting
		return false;
	}
	if (lenA != lenB) {
		return false;
	}
	return strncmp(valA, valB, lenA) == 0;
}

struct mScriptValue* mScriptValueAlloc(const struct mScriptType* type) {
	// TODO: Use an arena instead of just the generic heap
	struct mScriptValue* val = malloc(sizeof(*val));
	val->refs = 1;
	val->type = type;
	val->flags = 0;
	if (type->alloc) {
		type->alloc(val);
	} else {
		val->value.opaque = NULL;
	}
	return val;
}

void mScriptValueRef(struct mScriptValue* val) {
	if (val->refs == INT_MAX) {
		abort();
	} else if (val->refs == mSCRIPT_VALUE_UNREF) {
		return;
	}
	++val->refs;
}

void mScriptValueDeref(struct mScriptValue* val) {
	if (val->refs > 1) {
		--val->refs;
		return;
	} else if (val->refs <= 0) {
		return;
	}
	if (val->type->free) {
		val->type->free(val);
	} else if (val->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) {
		free(val->value.opaque);
	}
	free(val);
}

void mScriptValueWrap(struct mScriptValue* value, struct mScriptValue* out) {
	if (value->refs == mSCRIPT_VALUE_UNREF) {
		memcpy(out, value, sizeof(*out));
		return;
	}
	out->refs = mSCRIPT_VALUE_UNREF;
	switch (value->type->base) {
	case mSCRIPT_TYPE_SINT:
	case mSCRIPT_TYPE_UINT:
	case mSCRIPT_TYPE_FLOAT:
	case mSCRIPT_TYPE_WRAPPER:
		out->type = value->type;
		memcpy(&out->value, &value->value, sizeof(out->value));
		return;
	default:
		break;
	}

	out->type = mSCRIPT_TYPE_MS_WRAPPER;
	out->value.opaque = value;
	mScriptValueRef(value);
}

struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* value) {
	if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
		return value->value.opaque;
	}
	return NULL;
}

const struct mScriptValue* mScriptValueUnwrapConst(const struct mScriptValue* value) {
	if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
		return value->value.copaque;
	}
	return NULL;
}

struct mScriptValue* mScriptStringCreateEmpty(size_t size) {
	struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_STR);
	struct mScriptString* internal = val->value.opaque;
	internal->size = size;
	internal->length = 0;
	internal->buffer = malloc(size + 1);
	memset(internal->buffer, 0, size + 1);
	return val;
}

struct mScriptValue* mScriptStringCreateFromBytes(const void* string, size_t size) {
	struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_STR);
	struct mScriptString* internal = val->value.opaque;
	internal->size = size;
	internal->length = 0;
	internal->buffer = malloc(size + 1);
	memcpy(internal->buffer, string, size);
	internal->buffer[size] = '\0';
	return val;
}

struct mScriptValue* mScriptStringCreateFromUTF8(const char* string) {
	struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_STR);
	struct mScriptString* internal = val->value.string;
	internal->size = strlen(string);
	internal->length = utf8strlen(string);
	internal->buffer = strdup(string);
	return val;
}

struct mScriptValue* mScriptStringCreateFromASCII(const char* string) {
	struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_STR);
	struct mScriptString* internal = val->value.string;
	internal->size = strlen(string);
	internal->length = strlen(string);
	internal->buffer = latin1ToUtf8(string, internal->size + 1);
	return val;
}

struct mScriptValue* mScriptValueCreateFromSInt(int32_t value) {
	struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
	val->value.s32 = value;
	return val;
}

struct mScriptValue* mScriptValueCreateFromUInt(uint32_t value) {
	struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_U32);
	val->value.u32 = value;
	return val;
}

bool mScriptTableInsert(struct mScriptValue* table, struct mScriptValue* key, struct mScriptValue* value) {
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return false;
	}
	if (!key->type->hash) {
		return false;
	}
	mScriptValueRef(value);
	HashTableInsertCustom(table->value.table, key, value);
	return true;
}

bool mScriptTableRemove(struct mScriptValue* table, struct mScriptValue* key) {
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return false;
	}
	if (!key->type->hash) {
		return false;
	}
	HashTableRemoveCustom(table->value.table, key);
	return true;
}

struct mScriptValue* mScriptTableLookup(struct mScriptValue* table, struct mScriptValue* key) {
	if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
		table = mScriptValueUnwrap(table);
	}
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return false;
	}
	if (!key->type->hash) {
		return false;
	}
	return HashTableLookupCustom(table->value.table, key);
}

bool mScriptTableClear(struct mScriptValue* table) {
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return false;
	}
	HashTableClear(table->value.table);
	return true;
}

size_t mScriptTableSize(struct mScriptValue* table) {
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return 0;
	}
	return HashTableSize(table->value.table);
}

bool mScriptTableIteratorStart(struct mScriptValue* table, struct TableIterator* iter) {
	if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
		table = mScriptValueUnwrap(table);
	}
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return false;
	}
	return HashTableIteratorStart(table->value.table, iter);
}

bool mScriptTableIteratorNext(struct mScriptValue* table, struct TableIterator* iter) {
	if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
		table = mScriptValueUnwrap(table);
	}
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return false;
	}
	return HashTableIteratorNext(table->value.table, iter);
}

struct mScriptValue* mScriptTableIteratorGetKey(struct mScriptValue* table, struct TableIterator* iter) {
	if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
		table = mScriptValueUnwrap(table);
	}
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return NULL;
	}
	return HashTableIteratorGetCustomKey(table->value.table, iter);
}

struct mScriptValue* mScriptTableIteratorGetValue(struct mScriptValue* table, struct TableIterator* iter) {
	if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
		table = mScriptValueUnwrap(table);
	}
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return NULL;
	}
	return HashTableIteratorGetValue(table->value.table, iter);
}

bool mScriptTableIteratorLookup(struct mScriptValue* table, struct TableIterator* iter, struct mScriptValue* key) {
	if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
		table = mScriptValueUnwrap(table);
	}
	if (table->type != mSCRIPT_TYPE_MS_TABLE) {
		return false;
	}
	return HashTableIteratorLookupCustom(table->value.table, iter, key);
}

void mScriptFrameInit(struct mScriptFrame* frame) {
	mScriptListInit(&frame->arguments, 4);
	mScriptListInit(&frame->returnValues, 1);
}

void mScriptFrameDeinit(struct mScriptFrame* frame) {
	mScriptListDeinit(&frame->returnValues);
	mScriptListDeinit(&frame->arguments);
}

static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScriptClassInitDetails* details, bool child) {
	const char* docstring = NULL;

	size_t i;
	for (i = 0; details[i].type != mSCRIPT_CLASS_INIT_END; ++i) {
		const struct mScriptClassInitDetails* detail = &details[i];
		struct mScriptClassMember* member;

		switch (detail->type) {
		case mSCRIPT_CLASS_INIT_END:
			break;
		case mSCRIPT_CLASS_INIT_CLASS_DOCSTRING:
			if (!child) {
				cls->docstring = detail->info.comment;
			}
			break;
		case mSCRIPT_CLASS_INIT_DOCSTRING:
			docstring = detail->info.comment;
			break;
		case mSCRIPT_CLASS_INIT_INHERIT:
			member = calloc(1, sizeof(*member));
			member->name = "_super";
			member->type = detail->info.parent;
			if (!child) {
				cls->parent = detail->info.parent;
			}
			HashTableInsert(&cls->instanceMembers, member->name, member);
			_mScriptClassInit(cls, detail->info.parent->details.cls->details, true);
			break;
		case mSCRIPT_CLASS_INIT_INSTANCE_MEMBER:
			member = calloc(1, sizeof(*member));
			memcpy(member, &detail->info.member, sizeof(*member));
			if (docstring) {
				member->docstring = docstring;
				docstring = NULL;
			}
			HashTableInsert(&cls->instanceMembers, member->name, member);
			break;
		case mSCRIPT_CLASS_INIT_CAST_TO_MEMBER:
			HashTableInsert(&cls->castToMembers, detail->info.castMember.type->name, (char*) detail->info.castMember.member);
			break;
		case mSCRIPT_CLASS_INIT_INIT:
			cls->alloc = calloc(1, sizeof(*member));
			memcpy(cls->alloc, &detail->info.member, sizeof(*member));
			if (docstring) {
				cls->alloc->docstring = docstring;
				docstring = NULL;
			}
			break;
		case mSCRIPT_CLASS_INIT_DEINIT:
			cls->free = calloc(1, sizeof(*member));
			memcpy(cls->free, &detail->info.member, sizeof(*member));
			if (docstring) {
				cls->free->docstring = docstring;
				docstring = NULL;
			}
			break;
		case mSCRIPT_CLASS_INIT_GET:
			cls->get = calloc(1, sizeof(*member));
			memcpy(cls->get, &detail->info.member, sizeof(*member));
			if (docstring) {
				cls->get->docstring = docstring;
				docstring = NULL;
			}
			break;
		case mSCRIPT_CLASS_INIT_SET:
			cls->set = calloc(1, sizeof(*member));
			memcpy(cls->set, &detail->info.member, sizeof(*member));
			if (docstring) {
				cls->set->docstring = docstring;
				docstring = NULL;
			}
			break;
		case mSCRIPT_CLASS_INIT_INTERNAL:
			cls->internal = true;
			break;
		}
	}
}

void mScriptClassInit(struct mScriptTypeClass* cls) {
	if (cls->init) {
		return;
	}
	HashTableInit(&cls->instanceMembers, 0, free);
	HashTableInit(&cls->castToMembers, 0, NULL);

	cls->alloc = NULL;
	cls->free = NULL;
	cls->get = NULL;
	cls->set = NULL;
	_mScriptClassInit(cls, cls->details, false);

	cls->init = true;
}

void mScriptClassDeinit(struct mScriptTypeClass* cls) {
	if (!cls->init) {
		return;
	}
	HashTableDeinit(&cls->instanceMembers);
	HashTableDeinit(&cls->castToMembers);
	cls->init = false;
}


static bool _accessRawMember(struct mScriptClassMember* member, void* raw, bool isConst, struct mScriptValue* val) {
	raw = (void*) ((uintptr_t) raw + member->offset);
	switch (member->type->base) {
	case mSCRIPT_TYPE_SINT:
		switch (member->type->size) {
		case 1:
			*val = mSCRIPT_MAKE_S32(*(int8_t *) raw);
			break;
		case 2:
			*val = mSCRIPT_MAKE_S32(*(int16_t *) raw);
			break;
		case 4:
			*val = mSCRIPT_MAKE_S32(*(int32_t *) raw);
			break;
		case 8:
			*val = mSCRIPT_MAKE_S64(*(int64_t *) raw);
			break;
		default:
			return false;
		}
		break;
	case mSCRIPT_TYPE_UINT:
		switch (member->type->size) {
		case 1:
			*val = mSCRIPT_MAKE_U32(*(uint8_t *) raw);
			break;
		case 2:
			*val = mSCRIPT_MAKE_U32(*(uint16_t *) raw);
			break;
		case 4:
			*val = mSCRIPT_MAKE_U32(*(uint32_t *) raw);
			break;
		case 8:
			*val = mSCRIPT_MAKE_U64(*(uint64_t *) raw);
			break;
		default:
			return false;
		}
		break;
	case mSCRIPT_TYPE_FLOAT:
		switch (member->type->size) {
		case 4:
			*val = mSCRIPT_MAKE_F32(*(mSCRIPT_TYPE_C_F32 *) raw);
			break;
		case 8:
			*val = mSCRIPT_MAKE_F64(*(mSCRIPT_TYPE_C_F64 *) raw);
			break;
		default:
			return false;
		}
		break;
	case mSCRIPT_TYPE_TABLE:
		val->refs = mSCRIPT_VALUE_UNREF;
		val->flags = 0;
		val->type = mSCRIPT_TYPE_MS_WRAPPER;
		val->value.table = raw;
		break;
	case mSCRIPT_TYPE_FUNCTION:
		val->refs = mSCRIPT_VALUE_UNREF;
		val->flags = 0;
		val->type = member->type;
		member->type->alloc(val);
		break;
	case mSCRIPT_TYPE_OBJECT:
		val->refs = mSCRIPT_VALUE_UNREF;
		val->flags = 0;
		val->value.opaque = raw;
		if (isConst && !member->type->isConst) {
			val->type = member->type->constType;
		} else {
			val->type = member->type;
		}
		break;
	case mSCRIPT_TYPE_OPAQUE:
		val->refs = mSCRIPT_VALUE_UNREF;
		val->flags = 0;
		val->value.opaque = raw;
		val->type = member->type;
		break;
	default:
		return false;
	}
	return true;
}

bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScriptValue* val) {
	if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
		obj = mScriptValueUnwrap(obj);
	}
	if (obj->type->base != mSCRIPT_TYPE_OBJECT) {
		return false;
	}

	struct mScriptTypeClass* cls = obj->type->details.cls;
	if (!cls) {
		return false;
	}

	mScriptClassInit(cls);

	struct mScriptClassMember* m = HashTableLookup(&cls->instanceMembers, member);
	if (!m) {
		struct mScriptValue getMember;
		m = cls->get;
		if (!m || !_accessRawMember(m, obj->value.opaque, obj->type->isConst, &getMember)) {
			return false;
		}
		struct mScriptFrame frame;
		mScriptFrameInit(&frame);
		struct mScriptValue* this = mScriptListAppend(&frame.arguments);
		this->type = obj->type;
		this->refs = mSCRIPT_VALUE_UNREF;
		this->flags = 0;
		this->value.opaque = obj;
		mSCRIPT_PUSH(&frame.arguments, CHARP, member);
		if (!mScriptInvoke(&getMember, &frame) || mScriptListSize(&frame.returnValues) != 1) {
			mScriptFrameDeinit(&frame);
			return false;
		}
		memcpy(val, mScriptListGetPointer(&frame.returnValues, 0), sizeof(*val));
		mScriptFrameDeinit(&frame);
		return true;
	}

	return _accessRawMember(m, obj->value.opaque, obj->type->isConst, val);
}

bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, struct mScriptValue* val) {
	if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
		obj = mScriptValueUnwrapConst(obj);
	}
	if (obj->type->base != mSCRIPT_TYPE_OBJECT) {
		return false;
	}

	const struct mScriptTypeClass* cls = obj->type->details.cls;
	if (!cls) {
		return false;
	}

	struct mScriptClassMember* m = HashTableLookup(&cls->instanceMembers, member);
	if (!m) {
		return false;
	}

	return _accessRawMember(m, obj->value.opaque, true, val);
}

bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue* val) {
	if (obj->type->base != mSCRIPT_TYPE_OBJECT || obj->type->isConst) {
		return false;
	}

	struct mScriptTypeClass* cls = obj->type->details.cls;
	if (!cls) {
		return false;
	}

	mScriptClassInit(cls);

	struct mScriptClassMember* m = HashTableLookup(&cls->instanceMembers, member);
	if (!m) {
		return false;
	}

	void* rawMember = (void *)((uintptr_t) obj->value.opaque + m->offset);
	if (m->type != val->type) {
		if (!mScriptCast(m->type, val, val)) {
			return false;
		}
	}

	switch (m->type->base) {
	case mSCRIPT_TYPE_SINT:
		switch (m->type->size) {
		case 1:
			*(int8_t *) rawMember = val->value.s32;
			break;
		case 2:
			*(int16_t *) rawMember = val->value.s32;
			break;
		case 4:
			*(int32_t *) rawMember = val->value.s32;
			break;
		case 8:
			*(int64_t *) rawMember = val->value.s64;
			break;
		default:
			return false;
		}
		break;
	case mSCRIPT_TYPE_UINT:
		switch (m->type->size) {
		case 1:
			*(uint8_t *) rawMember = val->value.u32;
			break;
		case 2:
			*(uint16_t *) rawMember = val->value.u32;
			break;
		case 4:
			*(uint32_t *) rawMember = val->value.u32;
			break;
		case 8:
			*(uint64_t *) rawMember = val->value.u64;
			break;
		default:
			return false;
		}
		break;
	case mSCRIPT_TYPE_FLOAT:
		switch (m->type->size) {
		case 4:
			*(mSCRIPT_TYPE_C_F32 *) rawMember = val->value.f32;
			break;
		case 8:
			*(mSCRIPT_TYPE_C_F64 *) rawMember = val->value.f64;
			break;
		default:
			return false;
		}
		break;
	default:
		return false;
	}
	return true;
}

bool mScriptObjectCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) {
	if (input->type == type || (input->type->constType == type)) {
		output->type = type;
		output->value.opaque = input->value.opaque;
		output->refs = mSCRIPT_VALUE_UNREF;
		output->flags = 0;
		return true;
	}
	if (input->type->base != mSCRIPT_TYPE_OBJECT) {
		return false;
	}
	const char* member = HashTableLookup(&input->type->details.cls->castToMembers, type->name);
	if (member) {
		struct mScriptValue cast;
		if (!mScriptObjectGetConst(input, member, &cast)) {
			return false;
		}
		if (cast.type == type) {
			memcpy(output, &cast, sizeof(*output));
			return true;
		}
		return mScriptCast(type, &cast, output);
	}
	return false;
}

void mScriptObjectFree(struct mScriptValue* value) {
	if (value->type->base != mSCRIPT_TYPE_OBJECT) {
		return;
	}
	if (value->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) {
		mScriptClassInit(value->type->details.cls);
		if (value->type->details.cls->free) {
			struct mScriptValue deinitMember;
			if (_accessRawMember(value->type->details.cls->free, value->value.opaque, value->type->isConst, &deinitMember)) {
				struct mScriptFrame frame;
				mScriptFrameInit(&frame);
				struct mScriptValue* this = mScriptListAppend(&frame.arguments);
				this->type = mSCRIPT_TYPE_MS_WRAPPER;
				this->refs = mSCRIPT_VALUE_UNREF;
				this->flags = 0;
				this->value.opaque = value;
				mScriptInvoke(&deinitMember, &frame);
				mScriptFrameDeinit(&frame);
			}
		}
		free(value->value.opaque);
	}
}

bool mScriptPopS32(struct mScriptList* list, int32_t* out) {
	mSCRIPT_POP(list, S32, val);
	*out = val;
	return true;
}

bool mScriptPopS64(struct mScriptList* list, int64_t* out) {
	mSCRIPT_POP(list, S64, val);
	*out = val;
	return true;
}

bool mScriptPopU32(struct mScriptList* list, uint32_t* out) {
	mSCRIPT_POP(list, U32, val);
	*out = val;
	return true;
}

bool mScriptPopU64(struct mScriptList* list, uint64_t* out) {
	mSCRIPT_POP(list, U64, val);
	*out = val;
	return true;
}

bool mScriptPopF32(struct mScriptList* list, float* out) {
	mSCRIPT_POP(list, F32, val);
	*out = val;
	return true;
}

bool mScriptPopF64(struct mScriptList* list, double* out) {
	mSCRIPT_POP(list, F64, val);
	*out = val;
	return true;
}

bool mScriptPopBool(struct mScriptList* list, bool* out) {
	mSCRIPT_POP(list, BOOL, val);
	*out = val;
	return true;
}

bool mScriptPopPointer(struct mScriptList* list, void** out) {
	mSCRIPT_POP(list, PTR, val);
	*out = val;
	return true;
}

bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output) {
	if (input->type->base == mSCRIPT_TYPE_WRAPPER) {
		input = mScriptValueUnwrapConst(input);
	}
	if (type->cast && type->cast(input, type, output)) {
		return true;
	}
	if (input->type->cast && input->type->cast(input, type, output)) {
		return true;
	}
	return false;
}

bool mScriptCoerceFrame(const struct mScriptTypeTuple* types, struct mScriptList* frame) {
	if (types->count < mScriptListSize(frame) && !types->variable) {
		return false;
	}
	if (types->count > mScriptListSize(frame) && !types->variable && !types->defaults) {
		return false;
	}
	size_t i;
	for (i = 0; i < mScriptListSize(frame) && i < types->count; ++i) {
		if (types->entries[i] == mScriptListGetPointer(frame, i)->type) {
			continue;
		}
		struct mScriptValue* unwrapped = NULL;
		if (mScriptListGetPointer(frame, i)->type->base == mSCRIPT_TYPE_WRAPPER) {
			unwrapped = mScriptValueUnwrap(mScriptListGetPointer(frame, i));
			if (types->entries[i] == unwrapped->type) {
				continue;
			}
		}
		if (!mScriptCast(types->entries[i], mScriptListGetPointer(frame, i), mScriptListGetPointer(frame, i))) {
			return false;
		}
	}
	if (types->variable) {
		return true;
	}

	for (; i < types->count; ++i) {
		if (!types->defaults[i].type) {
			return false;
		}
		memcpy(mScriptListAppend(frame), &types->defaults[i], sizeof(struct mScriptValue));
	}
	return true;
}
