package internal

/*
#cgo CFLAGS: -Iluau/VM/include -I/usr/lib/gcc/x86_64-pc-linux-gnu/14.1.1/include
// #cgo LDFLAGS: -L${SRCDIR}/luau/cmake -lLuau.VM -lm -lstdc++
#include <lua.h>
#include <lualib.h>
#include <stdlib.h>
#include "clua.h"

// From https://golang-nuts.narkive.com/UsNENgyt/cgo-how-to-pass-string-to-char-array
static char** makeCharArray(int size) {
	return calloc(sizeof(char*), size);
}

static void setArrayString(char** a, char* s, int n) {
	a[n] = s;
}

static void freeCharArray(char** a, int size) {
	int i;
	for (i = 0; i < size; i++)
	free(a[i]);
	free(a);
}
*/
import "C"
import "unsafe"

type LuaLReg C.luaL_Reg

func LRegister(L *LuaState, libname string, l *LuaLReg) {
	clibname := C.CString(libname)
	defer C.free(unsafe.Pointer(clibname))

	C.luaL_register(L, clibname, (*C.luaL_Reg)(l))
}

func LGetMetaField(L *LuaState, obj int32, e string) int32 {
	ce := C.CString(e)
	defer C.free(unsafe.Pointer(ce))

	return int32(C.luaL_getmetafield(L, C.int(obj), ce))
}

func LCallMeta(L *LuaState, obj int32, e string) int32 {
	ce := C.CString(e)
	defer C.free(unsafe.Pointer(ce))

	return int32(C.luaL_callmeta(L, C.int(obj), ce))
}

func LTypeError(L *LuaState, narg int32, tname string) {
	ctname := C.CString(tname)
	defer C.free(unsafe.Pointer(ctname))

	C.luaL_typeerrorL(L, C.int(narg), ctname)
}

func LArgError(L *LuaState, narg int32, extramsg string) {
	cextramsg := C.CString(extramsg)
	defer C.free(unsafe.Pointer(cextramsg))

	C.luaL_argerrorL(L, C.int(narg), cextramsg)
}

func LCheckLString(L *LuaState, narg int32, l *uint64) string {
	p := C.luaL_checklstring(L, C.int(narg), (*C.size_t)(l))
	defer C.free(unsafe.Pointer(p))

	return C.GoString(p)
}

func LOptLString(L *LuaState, narg int32, def string, l *uint64) string {
	cdef := C.CString(def)
	defer C.free(unsafe.Pointer(cdef))

	p := C.luaL_optlstring(L, C.int(narg), cdef, (*C.ulong)(l))
	defer C.free(unsafe.Pointer(p))

	return C.GoString(p)
}

func LCheckNumber(L *LuaState, narg int32) LuaNumber {
	return LuaNumber(C.luaL_checknumber(L, C.int(narg)))
}

func LOptNumber(L *LuaState, narg int32, def LuaNumber) LuaNumber {
	return LuaNumber(C.luaL_optnumber(L, C.int(narg), C.lua_Number(def)))
}

func LCheckBoolean(L *LuaState, narg int32) bool {
	return C.luaL_checkboolean(L, C.int(narg)) != 0
}

func LOptBoolean(L *LuaState, narg int32, def bool) bool {
	cdef := C.int(0)
	if def {
		cdef = C.int(1)
	}

	return C.luaL_optboolean(L, C.int(narg), cdef) != 0
}

func LCheckInteger(L *LuaState, narg int32) LuaInteger {
	return LuaInteger(C.luaL_checkinteger(L, C.int(narg)))
}

func LOptInteger(L *LuaState, narg int32, def LuaInteger) LuaInteger {
	return LuaInteger(C.luaL_optinteger(L, C.int(narg), C.lua_Integer(def)))
}

func LCheckUnsigned(L *LuaState, narg int32) LuaUnsigned {
	return LuaUnsigned(C.luaL_checkunsigned(L, C.int(narg)))
}

func LOptUnsigned(L *LuaState, narg int32, def LuaUnsigned) LuaUnsigned {
	return LuaUnsigned(C.luaL_optunsigned(L, C.int(narg), C.lua_Unsigned(def)))
}

func LCheckVector(L *LuaState, narg int32) *float32 {
	return (*float32)(C.luaL_checkvector(L, C.int(narg)))
}

func LOptVector(L *LuaState, narg int32, def *float32) *float32 {
	return (*float32)(C.luaL_optvector(L, C.int(narg), (*C.float)(def)))
}

func LCheckStack(L *LuaState, sz int32, msg string) {
	cmsg := C.CString(msg)
	defer C.free(unsafe.Pointer(cmsg))

	C.luaL_checkstack(L, C.int(sz), cmsg)
}

func LCheckType(L *LuaState, narg int32, t int32) {
	C.luaL_checktype(L, C.int(narg), C.int(t))
}

func LCheckAny(L *LuaState, narg int32) {
	C.luaL_checkany(L, C.int(narg))
}

func LNewMetatable(L *LuaState, tname string) bool {
	ctname := C.CString(tname)
	defer C.free(unsafe.Pointer(ctname))

	return C.luaL_newmetatable(L, ctname) != 0
}

func LCheckUdata(L *LuaState, ud int32, tname string) unsafe.Pointer {
	ctname := C.CString(tname)
	defer C.free(unsafe.Pointer(ctname))

	return C.luaL_checkudata(L, C.int(ud), ctname)
}

func LCheckBuffer(L *LuaState, narg int32, len *uint64) unsafe.Pointer {
	return C.luaL_checkbuffer(L, C.int(narg), (*C.size_t)(len))
}

func LWhere(L *LuaState, lvl int32) {
	C.luaL_where(L, C.int(lvl))
}

// NOTE: It's not possible to pass varargs from Go->C via cgo, so instead we
// expect the user to format the message and hand it over to us, which we
// pass to luaL_errorL. This is an inconsistency with the actual C API, but
// there isn't really anything we can do.
func LErrorL(L *LuaState, msg string) {
	cmsg := C.CString(msg)
	defer C.free(unsafe.Pointer(cmsg))

	C.cluaL_errorL(L, cmsg)
}

func LCheckOption(L *LuaState, narg int32, def string, lst []string) int32 {
	cdef := C.CString(def)
	defer C.free(unsafe.Pointer(cdef))

	clst := C.makeCharArray(C.int(len(lst)))
	defer C.freeCharArray(clst, C.int(len(lst)))
	for i, s := range lst {
		C.setArrayString(clst, C.CString(s), C.int(i))
	}

	return int32(C.luaL_checkoption(L, C.int(narg), cdef, clst))
}

func LToLString(L *LuaState, idx int32, len *uint64) string {
	p := C.luaL_tolstring(L, C.int(idx), (*C.size_t)(len))
	defer C.free(unsafe.Pointer(p))

	return C.GoString(p)
}

func LNewState() *LuaState {
	return C.luaL_newstate()
}

func LTypeName(L *LuaState, idx int32) string {
	return C.GoString(C.luaL_typename(L, C.int(idx)))
}

func LSandbox(L *LuaState) {
	C.luaL_sandbox(L)
}

func LSandboxThread(L *LuaState) {
	C.luaL_sandboxthread(L)
}

//
// Some useful macros
//

func LArgCheck(L *LuaState, cond bool, arg int32, extramsg string) {
	if cond {
		LArgError(L, arg, extramsg)
	}
}

func LArgExpected(L *LuaState, cond bool, arg int32, tname string) {
	if cond {
		LTypeError(L, arg, tname)
	}
}

func LCheckString(L *LuaState, n int32) string {
	return LCheckLString(L, n, nil)
}

func LOptString(L *LuaState, n int32, d string) string {
	return LOptLString(L, n, d, nil)
}

// TODO: More utility functions, buffer bindings