(* [ʞ] src/janet-lang.gcd vi:ft=d
 *  ~ lexi hale <lexi@hale.su>
 *  🄯 AGPLv3
 *  ? implement the godot-janet interface
 *)

import "type.h";
import "util-gd.h";
use    "util-jn.h";
use    <janet.h>;

class JanetLang is ScriptLanguageExtension {
	(* use <stdio.h>; *)

	new {};

	(* var as array[ref Object]: placeholders; *)

	impl _get_name() -> string {
		gd_string j;
		_t(string).newWithUtf8Chars(&j, "Janet");
		return j;
	};

	impl _get_extension() -> string {
		gd_string j;
		_t(string).newWithUtf8Chars(&j, "janet");
		return j;
	};

	impl _supports_documentation() -> bool { return true; };
	impl _supports_builtin_mode()  -> bool { return true; };
	impl _is_using_templates()     -> bool { return false; };
	impl _can_inherit_from_file()  -> bool { return true; };

	impl _handles_global_class_type(string t) -> bool { return false; };
	impl _get_type() -> string {
		gd_string s = {};
		_t(string).newWithUtf8Chars(&s, "JanetScriptText");
		return s;
	};
	impl _get_public_functions() -> array[dictionary] {
		gd_array list;
		_t(array).empty(&list, nullptr);
		return list;
	};
	impl _get_public_constants() -> dictionary {
		gd_dictionary consts;
		_t(dictionary).empty(&consts, nullptr);
		return consts;
	};
	impl _get_public_annotations() -> array[dictionary] {
		gd_array list;
		_t(array).empty(&list, nullptr);
		return list;
	};
	impl _get_recognized_extensions() -> packed-string-array {
		gd_packedStringArray r = {};
		_t(packedStringArray).empty(&r, nullptr);
		_gdu_array_string_pushLit(&r, "janet");
		_gdu_array_string_pushLit(&r, "jimage");
		return r;
	};
	impl _get_comment_delimiters() -> packed-string-array {
		gd_packedStringArray r = {};
		_t(packedStringArray).empty(&r, nullptr);
		_gdu_array_string_pushLit(&r, "#");
		return r;
	};
	impl _get_string_delimiters() -> packed-string-array {
		gd_packedStringArray r = {};
		_t(packedStringArray).empty(&r, nullptr);
		_gdu_array_string_pushLit(&r, "\" \"");
		_gdu_array_string_pushLit(&r, "```` ````");
		_gdu_array_string_pushLit(&r, "``` ```");
		_gdu_array_string_pushLit(&r, "`` ``");
		_gdu_array_string_pushLit(&r, "` `");
		(* et cetera ad infinitum *)
		return r;
	};
	impl _is_control_flow_keyword(string k) -> bool {
		#define l(s) ((pstr){(s),sizeof(s)})
		const pstr words[] = {
			l("if"), l("cond"), l("when"), l("unless"),
			l("loop"), l("each"),
			l("for"), l("forv"), l("forever"),
			l("seq"), l("catseq"),
			(* et cetera ad nauseam *)
		};
		#undef l
		for (size_t i = 0; i < _sz(words); ++i) {
			if (gdu_strEq_sz(&k, words[i].v, words[i].sz))
				return true;
		}
		return false;
	};
	impl _get_reserved_words() -> packed-string-array {
		typedef struct {const char* w; size_t sz;} pstr;
		#define l(s) ((pstr){(s),sizeof(s)})
		const pstr words[] = {
			l("if"), l("cond"),
			l("def"), l("defn"), l("defmacro"),
			l("fn"),
			l("var"), l("let"),
			l("loop"), l("each"),
			l("for"), l("forv"), l("forever"),
			l("seq"), l("catseq"),
			l("map"), l("mapcat"),
			l("find"),
			l("array"), l("tuple"),
			l("string"), l("buffer"),
			l("table"), l("struct"),
			l("print"), l("prin"), l("pp"),
			(* gdjn macros *)
			l("defm"), l("defm-"),
			l("class"), l("defclass"), l("defclass-"),
			l("prop"),
			(* et cetera ad nauseam *)
		};
		gd_packedStringArray r = {};
		_t(packedStringArray).empty(&r, nullptr);
		for (size_t i = 0; i < _sz(words); ++i) {
			gdu_array_string_pushPtr(&r, words[i].w, words[i].sz);
		}
		return r;
		#undef l
	};

	impl _validate_path(string path) -> string {
		gd_string s = {};
		_t(string).empty(&s, nullptr);
		return s;
	};


	impl _make_template
	(	string tpl;
		string class;
		string base;
	) -> ref Script {
		auto janscr = gdjn_class_JanetScriptText_new();
		return janscr -> self;
	};
	impl _create_script() -> ref Object {
		auto janscr = gdjn_class_JanetScriptText_new();
		return janscr -> self;
	};

	use {
		#define _typeCode(x) gd_ScriptLanguageExtension_LookupResultType_lookupResult##x
	};
	impl _get_documentation() -> array[dictionary] {
		gd_array a = {};
		_t(array).ref(&a, &gdjn_ctx -> gd.dox);
		return gdjn_ctx -> gd.dox;
	};
	impl _lookup_code
	(	string code;
		string symbol;
		string path;
		ref Object owner;
	) -> dictionary {
		gd_dictionary d;
		_t(dictionary).empty(&d, nullptr);
		gd_variant v;

		v = gd_variant_of_int(gd_Error_failed);
		gdu_setKey(&d, _pstrLit("result"), &v);

		v = gd_variant_of_int(_typeCode(ScriptLocation));
		gdu_setKey(&d, _pstrLit("type"), &v);
		return d;
	};

	impl _init() { /* (* "deprecated" but i still have to impl it?? *) */ }; 
	impl _frame() {};
	impl _thread_enter() { janet_init(); };
	impl _thread_exit() { janet_deinit(); };
	impl _finish() {};

	impl _overrides_external_editor() -> bool { return false; };
	impl _get_global_class_name(string path) -> dictionary {
		gd_dictionary dict;
		_t(dictionary).empty(&dict,nullptr);
		return dict;
		(* FIXME *)
	};
	impl _validate(
		string script;
		string path;
		bool vFuncs;
		bool vErrs;
		bool vWarns;
		bool vSafe;
	) -> dictionary {
		gd_dictionary dict;
		_t(dictionary).empty(&dict,nullptr);
		return dict;
	};
};

class JanetScript is ScriptExtension {
	var as string: path;
	var JanetTable*: env;
	var pstr: id;
	var pstr: exportID;
	var pstr: uid;
	var pstr: doc;
	var pstr: inherit;
	
	var as array[dictionary]: methodBinds;

	use "vm.h";

	new {
		_t(string).empty(&me -> path, nullptr);
		me -> env = nullptr;

		_t(array).empty(&me -> methodBinds, nullptr);
		me -> id       = (pstr){};
		me -> exportID = (pstr){};
		me -> uid      = (pstr){};
		me -> doc      = (pstr){};
		me -> inherit  = (pstr){};
	};
	del {
		_t(string).dtor(&me -> path);
		_t(array).dtor(&me -> methodBinds);
		if (me -> env) janet_gcunroot(janet_wrap_table(me -> env));
		_drop(me -> id);
		_drop(me -> exportID);
		_drop(me -> uid);
		_drop(me -> doc);
		_drop(me -> inherit);

	};

	class BaseInstance is Object {
		var as ref JanetScript: script;
		del {
			gd_refCounted_unreference(me -> script);
			printf("del script instance\n");
		};
	};
	class Instance extends JanetScript_BaseInstance {
	};
	class Placeholder extends JanetScript_BaseInstance {
	};

	(* impl _create_instance(array[variant] argv, int argc, ref Object owner, bool refCounted, int error) -> ref Object { *)
	impl _can_instantiate() -> bool {
		if (me -> env == nullptr) return false;
		return true; // FIXME
	};
	impl _instance_create(ref Object obj) -> ref Object {
		auto ci = gdjn_class_JanetScript_Instance_new();
		printf("made new script instance\n");
		ci -> super.script = me;
		gd_refCounted_reference(me -> self);
		return ci;
	};
	impl _placeholder_instance_create(ref Object obj) -> ref Object {
		auto ci = gdjn_class_JanetScript_Placeholder_new();
		printf("made new script placeholder\n");
		ci -> super.script = me;
		gd_refCounted_reference(me -> self);
		return ci;
	};
	impl _get_language() -> ref ScriptLanguage {
		return gdjn_ctx -> gd.janetLang_inst;
	};
	impl _set_path(string path, bool takeOver) {
		_t(string).dtor(&me -> path);
		/* _t(string).copy(&me -> path, (void const*[]) {&path}); */
		me -> path = gdu_string_dup(&path);
	};
	impl _get_base_script() -> ref Script {
		return nullptr;
	};
	impl _has_static_method(string-name method) -> bool {
		return false;
	};
	impl _is_tool() -> bool { return false; }; (* FIXME *)
	impl _get_global_name() -> string-name {
		if (me -> exportID.v)
			return gdu_internp(me -> exportID);
		return gdu_sym_null();
	};
	impl _is_abstract() -> bool {
		return false; (* TODO abuse for non-class scripts? *)
	};
	impl _get_instance_base_type() -> string-name {
		return gdu_internp(me -> inherit);
	};
	impl _is_valid() -> bool {
		return true;
	};
	impl _get_documentation() -> array[dictionary] {
		gd_array dox;
		_t(array).empty(&dox, nullptr);
		auto empty = gdu_sym_null();
		_t(array).setTyped(&dox,
			GDEXTENSION_VARIANT_TYPE_DICTIONARY, &empty, &empty);
		_t(stringName).dtor(&empty);
		return dox;
	};

	use {
		static pstr
		classParam
		(	JanetTable* env,
			const char* sym,
			pstr        dflt
		) {
			Janet v = janet_table_rawget(env, janet_csymbolv(sym));
			if (janet_type(v) != JANET_TABLE) return dflt;

			Janet s = janet_table_rawget(janet_unwrap_table(v), 
				janet_ckeywordv("value"));
			if (janet_type(s) == JANET_NIL) return dflt;

			JanetString str = janet_unwrap_string(s);
			return (pstr) {
				.v = (char*)str,
				.sz = janet_string_length(str),
			};
		}
	};
	impl _reload(bool keepState) -> int {
		gd_array_clear(&me -> methodBinds);
		/* parse the class environment */
		JanetTable* const env = me -> env;
		printf("core reload\n");

		pstr id      = classParam(env, "@id", (pstr){});
		pstr gdid    = classParam(env, "@gd-id", (pstr){});
		pstr inherit = classParam(env, "@inherit", _pstrLit("RefCounted"));
		pstr doc     = classParam(env, "@doc", (pstr){});

		if (gdid.v) me -> exportID = pstrDup(gdid);
			else me -> exportID = pstrDup(id);
		if (id.v) me -> id = pstrDup(id);

		if (doc.v) me -> doc = pstrDup(doc);
		if (inherit.v) me -> inherit = pstrDup(inherit);

		Janet wenv = janet_wrap_table(env);
		/* scan for bindings */
		for (Janet key = janet_next(wenv, janet_wrap_nil());
					janet_type(key) != JANET_NIL;
					key = janet_next(wenv, key)) {

			Janet val = janet_table_rawget(env, key);

			if (!janet_checktypes(key, JANET_TFLAG_BYTES)) continue;
			printf(" * consider val id %s (%s)\n",
					janet_unwrap_string(key),
					janet_type_names[janet_type(val)]);
			if (!janet_checktypes(val, JANET_TFLAG_DICTIONARY)) continue;
			JanetTable* v = janet_unwrap_table(val);
			Janet ty;
			const bool priv = gdjn_vm_metaFlag(v, "private");
			JanetString jkey = janet_unwrap_string(key);

			if (gdjn_vm_metaKey(v, "method", &ty)) {
			/* &&  janet_type(ty) == JANET_TABLE) { */
				printf("   * is method %s\n",
					janet_type_names[janet_type(ty)]);
				JanetTable* tspec = janet_unwrap_table(ty);
				/* janet_unwrap_function() */
				gd_dictionary meta;
				_t(dictionary).empty(&meta, nullptr);
				gdu_setKey_str(&meta, _pstrLit("name"),
					(pstr) {
						.sz = janet_string_length(jkey),
						.v = (char*)jkey,
					});

				/* build arg array */
				gd_array argList;
				_t(array).empty(&argList, nullptr);
				for (size_t j = 0; j < tspec -> count; ++j) {
				/*
					gd_dictionary
					gdu_array_append(&argList_, )
					*/
				}
				
				{gd_variant tv = gd_variant_of_array(argList);
				 gdu_setKey(&meta, _pstrLit("args"), &tv);}
					

				{gd_variant tv = gd_variant_of_dictionary(meta);
				 gd_array_append(&me -> methodBinds, &tv);}

				_t(array).dtor(&argList);
				_t(dictionary).dtor(&meta);
			} else if (gdjn_vm_metaKey(v, "prop", &ty)) {
			} else if (gdjn_vm_metaKey(v, "const", &ty)) {
			} else if (gdjn_vm_metaFlag(v, "subclass")) {
			};
			/* else: the value is not meaningful to godot; skip over */
		}
		return gd_Error_ok;
	};
	
	(*
	impl _update_exports() {

	};
	impl _placeholder_instance_create
	(ref Object forObj) -> ref Object {
		
	};
	impl _placeholder_erased
	(ref Object ph) {
	};
	*)
		
};

class JanetScriptText extends JanetScript {
	var pstr: src;
	new {
		me -> src = (pstr){};
	};
	del {
		_drop(me -> src);
	};

	impl _has_source_code() -> bool   { return true; };
	impl _get_source_code() -> string {
		return gdu_str_sz(me -> src.v, me -> src.sz);
		/* auto d = gdu_string_dup(&me -> src); */
		/* return d; */
	};
	impl _set_source_code(string s) {
		_drop(me -> src);
		me -> src = gdu_string_pdup(&s);
		/* printf("janet: set script %d %s\n", (int)me -> src.sz, me -> src.v); */
		gdjn_class_JanetScriptText_method__reload(me, false);
		/* _t(string).dtor(&me -> src); */
		/* _t(string).copy(&me -> src, (void const*[]) {&s}); */
	};
	impl _reload(bool keepState) -> int (* Error *) {
		auto errorCode = gd_Error_ok;
		if (me -> super.env)
			janet_gcunroot(janet_wrap_table(me -> super.env));
		me -> super.env = nullptr;
		pstr path = _gdu_string_stackp(&me -> super.path);

		auto cls = jnu_table_extend(gdjn_ctx -> jn.api, 8);
		janet_gcroot(janet_wrap_table(cls));
		/* printf("janet: doing bytes %d %s\n", (int)me -> src.sz, me -> src.v); */
		int e = janet_dobytes(cls,
			(uint8_t const*)me -> src.v, me -> src.sz,
			path.v, nullptr);
		(* we discard the return value; the environment is what
		 * we're really interested in here *)
		/* printf("janet: bytes done, got %d\n",e); */
		if (e == 0) {
			me -> super.env = cls;
		} else {
			_err("janet module could not be loaded");
			errorCode = gd_Error_errParseError;
			janet_gcunroot(janet_wrap_table(cls));
		}
		if (errorCode != gd_Error_ok) return errorCode;

		/* invoke super by static dispatch */
		return gdjn_class_JanetScript_method__reload(&me -> super, keepState);
	};
};

import {
	typedef struct gdjn_janet_image {
		size_t   sz;
		uint8_t* buf;
	} gdjn_janet_image;
};

class JanetScriptImage extends JanetScript {
	impl _has_source_code() -> bool { return false; };
	var gdjn_janet_image: image;
	new {
		me -> image = (gdjn_janet_image) {};
	};
	del {
		if (me -> image.buf != nullptr) _free(me -> image.buf);
	};
	impl _reload(bool keepState) -> int (* Error *) {
		(* TODO *)
		return gd_Error_ok;
	};
};
