/* [ʞ] src/util-gd.h
 *  ~ lexi hale <lexi@hale.su>
 *  🄯 AGPLv3
 *  ? encapsulate annoying godot operations (read: pitiful,
 *    fragile blast shield over the most indefensibly psychotic
 *    pieces of the godot "type" "system")
 */

#pragma once
#include "gdjn.h"
#include <string.h>
#include "type.h"

static inline gd_string
gdu_string_of_stringName(const gd_stringName* const s) {
	gd_string r;
	_t(string).fromStringName(&r, (void*)&s);
	return r;
}

static inline gd_stringName
gdu_stringName_of_string(const gd_stringName* const s) {
	gd_stringName r;
	_t(stringName).fromString(&r, (void*)&s);
	return r;
}

static inline gd_stringName
gdu_intern_sz (const char* str, const size_t sz) {
	gd_stringName r = {};
	if (sz == 0) _t(stringName).newWithUtf8Chars(&r, str);
	        else _t(stringName).newWithUtf8CharsAndLen(&r, str, sz);
	return r;
}

static inline gd_stringName
gdu_intern (const char* str) {
	return gdu_intern_sz(str, 0);
}

static inline gd_stringName
gdu_sym_null(void) {
	gd_stringName n;
	_t(stringName).empty(&n, nullptr);
	return n;
}

static inline gd_string
gdu_str_null(void) {
	gd_string n;
	_t(string).empty(&n, nullptr);
	return n;
}

static inline gd_string
gdu_str_sz (const char* str, const size_t sz) {
	gd_string r = {};
	if (sz == 0) _t(string).newWithUtf8Chars(&r, str);
	        else _t(string).newWithUtf8CharsAndLen(&r, str, sz);
	return r;
}

static inline gd_string
gdu_str (const char* str) {
	return gdu_str_sz(str, 0);
}

#define _gdu_intern(x) (gdu_intern_sz((x), sizeof(x)-1))
#define  _litSz(x) (x), (sizeof (x)-1)
#define    _ref(x) typeof(typeof(x) const* const)
#define _refMut(x) typeof(typeof(x)      * const)
#define _with(T, k, v, ...) ({\
	typeof(gd_##T) k = v; \
	do { __VA_ARGS__; } while (0); \
	_t(T).dtor(&k); \
})

#define _withSym0(k, ...) \
	_with(stringName, k, {}, __VA_ARGS__)
#define _withSymSz(k, v, sz, ...) \
	_with(stringName, k, gdu_intern_sz(v, sz), __VA_ARGS__)
#define _withSym(k, v, ...) \
	_withSymSz(k, _strDynMem(v), _strDynSz(v), __VA_ARGS__)
#define _withSymLit(k, v, ...) \
	_withSymSz(k, _litSz(v), __VA_ARGS__)

#define _withStr0(k, ...) \
	_with(string, k, {}, __VA_ARGS__)
#define _withStrSz(k, v, sz, ...) \
	_with(string, k, gdu_str_sz(v, sz), __VA_ARGS__)
#define _withStr(k, v, ...) \
	_withStrSz(k, _strDynMem(v), _strDynSz(v), __VA_ARGS__)
#define _withStrLit(k, v, ...) \
	_withStrSz(k, _litSz(v), __VA_ARGS__)

static inline bool
gdu_symIs
(	gd_stringName const* const a,
	gd_stringName const* const b
) {
	bool res;
	_t(stringName).equal(a, b, &res);
	return res;
}

static inline bool
gdu_symEq_sz
(	_ref(gd_stringName) a,
	_ref(char)          b,
	size_t const        sz
) {
	auto bSym = gdu_intern_sz(b,sz);
	bool res  = gdu_symIs(a, &bSym);
	_t(stringName).dtor(&bSym);
	return res;
}

static inline bool
gdu_symEq
(	_ref(gd_stringName) a,
	_ref(char)          b
) { return gdu_symEq_sz(a, b, 0); }

#define _gdu_symEq(a,b) (gdu_symEq_sz(a, _litSz(b)))

static inline bool
gdu_objIs
(	GDExtensionObjectPtr obj,
	_ref(char)           id,
	size_t const         sz
) {
	bool res = false;
	_withSym0(name, ({
		if (!_t(object).getClassName(obj, gdjn_ctx -> gd.lib, &name))
			break;
		res = gdu_symEq_sz(&name, id, sz);
	}));
	return res;
}

static inline bool
gdu_strIs
(	gd_string const* const a,
	gd_string const* const b
) {
	bool res;
	_t(string).equal(a, b, &res);
	return res;
}

static inline bool
gdu_strEq_sz
(	_ref(gd_string) a,
	_ref(char)      b,
	size_t const    sz
) {
	auto bSym = gdu_str_sz(b,sz);
	bool res  = gdu_strIs(a, &bSym);
	_t(string).dtor(&bSym);
	return res;
}

static inline bool
gdu_strEq
(	_ref(gd_string) a,
	_ref(char)      b
) { return gdu_strEq_sz(a, b, 0); }

#define _gdu_symEq(a,b) (gdu_symEq_sz(a, _litSz(b)))
#define _gdu_strEq(a,b) (gdu_strEq_sz(a, _litSz(b)))
#define _gdu_objIs(obj, id) \
	(gdu_objIs(obj, _litSz(#id)))

#define _gdu_string_emit(s, tgt) \
	(gdu_string_emit(&(s), (tgt), sizeof (tgt)-1))

static inline size_t
gdu_string_emit
(	size_t sz;

	const gd_string* const s,
	char                   target[static sz],
	size_t                 sz
) {
	/* docs lie btw, this returns a count of bytes,
	 * not "characters" (??)
	 * (thank the gods for small mercies) */
	size_t len = _t(string).toUtf8Chars(s, target, sz);
	target[len] = 0;
	return len;
}

#define _gdu_stringName_emit(s, tgt) \
	(gdu_stringName_emit(&(s), (tgt), sizeof (tgt) - 1))

static inline pstr
gdu_string_pdup (_ref(gd_string) s) {
	size_t len = gd_string_length(s) + 1;
	char* buf = _alloc(char, len);
	gdu_string_emit(s, buf, len - 1);
	return (pstr){buf,len};
}

static inline gd_string
gdu_string_dup (_ref(gd_string) s) {
	/* the godot copy method seems to be broken somehow,
	 * probably for reasons that have to do with the
	 * hypercomplicated CoW implementation that i think
	 * is meant to be handled on this end somehow? we
	 * can seemingly avoid crashes and memory corruption
	 * if we copy the string through C, forcing a clean
	 * new string to be generated on the godot side. */
	// _t(string).copy(&cp, (void const*[]) {s});
	auto cstr = gdu_string_pdup(s);
	auto copied = gdu_str_sz(cstr.v, cstr.sz);
	_drop(cstr);
	return copied;
}

#define _cat(x,y) x##y
#define __cat(x,y) _cat(x,y)
#define _gensym __cat(_sym_,__COUNTER__)

#define __gdu_string_auto(szid, name, str) \
	size_t szid = gd_string_length(str) + 1; \
	char[szid] name; \
	gdu_string_emit(str, name, szid-1); 

#define _gdu_string_auto(...) __gdu_string_auto(_gensym, __VA_ARGS__)

#if __has_builtin(__builtin_alloca_with_align)
#	define _stalloc(ty, n) \
		(__builtin_alloca_with_align(sizeof(ty)*(n), alignof(ty)*8))
#else
#	define _stalloc(ty, n) \
		(__builtin_alloca(sizeof(ty)*(n)))
#endif

#define _gdu_gstr_stack(ty, str) ({ \
	size_t sz = gd_##ty##_length(str) + 1; \
	char* buf = _stalloc(char, sz);        \
	gdu_##ty##_emit(str, buf, sz - 1);     \
	buf; \
})
#define _gdu_gstr_stackp(ty, str) ({ \
	size_t sz = gd_##ty##_length(str) + 1; \
	char* buf = _stalloc(char, sz);        \
	gdu_##ty##_emit(str, buf, sz - 1);     \
	(pstr) {.v = buf, .sz = sz}; \
})

#define _gdu_string_stack(str)      _gdu_gstr_stack(string, str)
#define _gdu_stringName_stack(str)  _gdu_gstr_stack(stringName, str)
#define _gdu_string_stackp(str)     _gdu_gstr_stackp(string, str)
#define _gdu_stringName_stackp(str) _gdu_gstr_stackp(stringName, str)


static inline size_t
gdu_stringName_emit
(	size_t sz;

	_ref(gd_stringName) s,
	char                target[static sz],
	size_t              sz
) {
	gd_string r;
	_t(string).fromStringName(&r, (void*)&s);
	const auto len = gdu_string_emit(&r, target, sz);
	_t(string).dtor(&r);
	return len;
}

#define _gdu_packedArray_push_def(name, T, input, fn) \
	static inline bool \
	gdu_array_##name   \
	(	gd_packed##T##Array*         self,\
		const typeof(input)* const   arg  \
	) {\
		bool ret;\
		auto c = &gdjn_ctx -> gd.t; \
		(c -> gd_packed##T##Array.fn) ( \
			self,\
			(GDExtensionConstTypePtr[]) { \
				arg,\
			}, &ret, 1\
		);\
		return ret;\
	}

#define _gdu_packedArrayTypes \
	_(Byte,    byte,    uint8_t   ) \
	_(Int32,   int32,   int32_t   ) \
	_(Int64,   int64,   int64_t   ) \
	_(Float32, float32, int32_t   ) \
	_(Float64, float64, int64_t   ) \
	_(String,  string,  gd_string ) \
	_(Vector2, vector2, gd_vector2) \
	_(Vector3, vector3, gd_vector3) \
	_(Vector4, vector4, gd_vector4) \

#define _gdu_packedArray_defs(maj, min, input) \
	_gdu_packedArray_push_def(min##_##push,   maj, input, append) \
	_gdu_packedArray_push_def(min##_##concat, maj, gd_packed##maj##Array, append_array)
/* bool gdu_array_(type)_push(self, type)
 * bool gdu_array_(type)_concat(self, packedArray[type])
 */

#define _(...) _gdu_packedArray_defs(__VA_ARGS__)
	_gdu_packedArrayTypes
#undef _

/* obnoxious special case */
static inline bool
gdu_array_string_pushPtr
(	gd_packedStringArray* self,
	const char* const     str,
	size_t                sz
) {
	gd_string tmp;
	if (sz == 0) _t(string).newWithUtf8Chars      (&tmp, str);
	else         _t(string).newWithUtf8CharsAndLen(&tmp, str, sz);
	bool ret = gdu_array_string_push(self, &tmp);
	_t(string).dtor(&tmp);
	return ret;
}
#define _gdu_array_string_pushLit(self, str) \
	(gdu_array_string_pushPtr((self), (str), sizeof (str) - 1))

static inline bool
gdu_string_suffix
(	_ref(gd_string) self,
	_ref(char)      affix,
	size_t          affsz
) {
	auto ch = _gdu_string_stackp(self);
	if (affsz == 0) affsz = strlen(affix);
	if (ch.sz < affsz) return false;
	for (size_t i = 0; i < affsz; ++i) {
		auto a =  ch.v[ch.sz - 2 - i];
		auto b = affix[affsz - 1 - i];
		if (a != b) return false;
	}
	return true;
}

static inline void*
gdu_classTag(_ref(char) name) {
	void* tag = nullptr;
	_withSym(sName, name,
		tag = _t(classdb).getClassTag(&sName);
	);
	return tag;
}
static inline GDExtensionObjectPtr
gdu_cast
(	GDExtensionConstObjectPtr what,
	_ref(char)                to
) {
	return _t(object).castTo(what, gdu_classTag(to));
}

static inline void
gdu_setKey
(	gd_dictionary* const dict,
	pstr           const key,
	_ref(gd_variant)     val
) {
	gd_variant v = gd_variant_of_dictionary(*dict);
	_withSym(keyName, key, {
		uint8_t ok = false;
		_t(variant).setNamed(&v, &keyName, val, &ok);
	});
}

