/*************************************************************************/
/*                       This file is part of:                           */
/*                           BLENDOT ENGINE                              */
/*                      https://blendot.org                              */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

import "blendot_object.gen.h"
import "class_db.h"
import "core_string_names.h"
import "message_queue.h"
import "print_string.h"
import "resource.h"
#ifdef BLENDOT
  import "core/os/os.h"
  import "core/translation.h"
#endif

import "script_language.h"

#ifdef DEBUG_ENABLED
struct _ObjectDebugLock:
	Object ⮞obj;
	def __init__(Object ⮞p_obj):
		obj = p_obj
		obj->_lock_index.ref()
	~_ObjectDebugLock() { obj->_lock_index.unref(); }

define OBJ_DEBUG_LOCK:
	_ObjectDebugLock _debug_lock(this);

#else

define(OBJ_DEBUG_LOCK= )

#endif

@const
def PropertyInfo::operator Dictionary():
	Dictionary d
	d["name"] = name
	d["class_name"] = class_name
	d["type"] = type
	d["hint"] = hint
	d["hint_string"] = hint_string
	d["usage"] = usage
	return d


def PropertyInfo::from_dict(const Dictionary ⤴p_dict) ->PropertyInfo:
	PropertyInfo pi
	if p_dict.has("type"):
		pi.type = Variant::Type(int(p_dict["type"]))
	if p_dict.has("name"):
		pi.name = p_dict["name"]
	if p_dict.has("class_name"):
		pi.class_name = p_dict["class_name"]
	if p_dict.has("hint"):
		pi.hint = PropertyHint(int(p_dict["hint"]))
	if p_dict.has("hint_string"):
		pi.hint_string = p_dict["hint_string"]
	if p_dict.has("usage"):
		pi.usage = p_dict["usage"]
	return pi


def convert_property_list(const List<PropertyInfo> ⮞p_list) ->Array:
	Array va
	for (const List<PropertyInfo>::Element ⮞E = p_list->front(); E; E = E->next()):
		va.push_back(Dictionary(E->get()))
	return va

@const
def MethodInfo::operator Dictionary():
	Dictionary d
	d["name"] = name
	d["args"] = convert_property_list(&arguments)
	Array da
	for (int i = 0; i < default_arguments.size(); i++):
		da.push_back(default_arguments[i])
	d["default_args"] = da
	d["flags"] = flags
	d["id"] = id
	Dictionary r = return_val
	d["return"] = r
	return d


def MethodInfo::MethodInfo():
	init_list:
		flags(METHOD_FLAG_NORMAL)
		id(0)


def MethodInfo::from_dict(const Dictionary ⤴p_dict) ->MethodInfo:
	MethodInfo mi
	if p_dict.has("name"):
		mi.name = p_dict["name"]
	Array args
	if p_dict.has("args"):
		args = p_dict["args"]
	for (int i = 0; i < args.size(); i++):
		Dictionary d = args[i]
		mi.arguments.push_back(PropertyInfo::from_dict(d))
	Array defargs
	if p_dict.has("default_args"):
		defargs = p_dict["default_args"]
	for (int i = 0; i < defargs.size(); i++):
		mi.default_arguments.push_back(defargs[i])
	if p_dict.has("return"):
		mi.return_val = PropertyInfo::from_dict(p_dict["return"])
	if p_dict.has("flags"):
		mi.flags = p_dict["flags"]
	return mi


def MethodInfo::MethodInfo(const String ⤴p_name):
	init_list:
		name(p_name)
		flags(METHOD_FLAG_NORMAL)
		id(0)

def MethodInfo::MethodInfo(const String ⤴p_name, const PropertyInfo ⤴p_param1):
	init_list:
		name(p_name)
		flags(METHOD_FLAG_NORMAL)
		id(0)
	arguments.push_back(p_param1)


def MethodInfo::MethodInfo(const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2):
	init_list:
		name(p_name)
		flags(METHOD_FLAG_NORMAL)
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)


def MethodInfo::MethodInfo(const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2, const PropertyInfo ⤴p_param3):
	init_list:
		name(p_name)
		flags(METHOD_FLAG_NORMAL)
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)


def MethodInfo::MethodInfo(const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2, const PropertyInfo ⤴p_param3, const PropertyInfo ⤴p_param4):
	init_list:
		name(p_name)
		flags(METHOD_FLAG_NORMAL)
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)
	arguments.push_back(p_param4)


def MethodInfo::MethodInfo(const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2, const PropertyInfo ⤴p_param3, const PropertyInfo &p_param4, const PropertyInfo ⤴p_param5) :
	init_list:
		name(p_name),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)
	arguments.push_back(p_param4)
	arguments.push_back(p_param5)


def MethodInfo::MethodInfo(Variant::Type ret) :
	init_list:
		flags(METHOD_FLAG_NORMAL),
		id(0)
	return_val.type = ret


def MethodInfo::MethodInfo(Variant::Type ret, const String ⤴p_name) :
	init_list:
		name(p_name),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	return_val.type = ret


def MethodInfo::MethodInfo(Variant::Type ret, const String ⤴p_name, const PropertyInfo ⤴p_param1) :
	init_list:
		name(p_name),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	return_val.type = ret
	arguments.push_back(p_param1)


def MethodInfo::MethodInfo(Variant::Type ret, const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2) :
	init_list:
		name(p_name),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	return_val.type = ret
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)


def MethodInfo::MethodInfo(Variant::Type ret, const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2, const PropertyInfo ⤴p_param3) :
	init_list:
		name(p_name),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	return_val.type = ret
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)


def MethodInfo::MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4) :
	init_list:
		name(p_name),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	return_val.type = ret
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)
	arguments.push_back(p_param4)


def MethodInfo::MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5) :
	init_list:
		name(p_name),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	return_val.type = ret
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)
	arguments.push_back(p_param4)
	arguments.push_back(p_param5)


def MethodInfo::MethodInfo(const PropertyInfo ⤴p_ret, const String ⤴p_name) :
	init_list:
		name(p_name),
		return_val(p_ret),
		flags(METHOD_FLAG_NORMAL),
		id(0)


def MethodInfo::MethodInfo(const PropertyInfo ⤴p_ret, const String ⤴p_name, const PropertyInfo ⤴p_param1) :
	init_list:
		name(p_name),
		return_val(p_ret),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	arguments.push_back(p_param1)


def MethodInfo::MethodInfo(const PropertyInfo ⤴p_ret, const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2) :
	init_list:
		name(p_name),
		return_val(p_ret),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)


def MethodInfo::MethodInfo(const PropertyInfo ⤴p_ret, const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2, const PropertyInfo ⤴p_param3) :
	init_list:
		name(p_name),
		return_val(p_ret),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)


def MethodInfo::MethodInfo(const PropertyInfo ⤴p_ret, const String ⤴p_name, const PropertyInfo ⤴p_param1, const PropertyInfo ⤴p_param2, const PropertyInfo ⤴p_param3, const PropertyInfo ⤴p_param4) :
	init_list:
		name(p_name),
		return_val(p_ret),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)
	arguments.push_back(p_param4)


def MethodInfo::MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5) :
	init_list:
		name(p_name),
		return_val(p_ret),
		flags(METHOD_FLAG_NORMAL),
		id(0)
	arguments.push_back(p_param1)
	arguments.push_back(p_param2)
	arguments.push_back(p_param3)
	arguments.push_back(p_param4)
	arguments.push_back(p_param5)

@const
def Object::Connection::operator Variant():
	Dictionary d
	d["source"] = source
	d["signal"] = signal
	d["target"] = target
	d["method"] = method
	d["flags"] = flags
	d["binds"] = binds
	return d

@const
def Object::Connection::operator<(const Connection ⤴p_conn) ->bool:
	if source == p_conn.source:
		if signal == p_conn.signal:
			if target == p_conn.target:
				return method < p_conn.method
			else:
				return target < p_conn.target
		else:
			return signal < p_conn.signal
	else:
		return source < p_conn.source


def Object::Connection::Connection(const Variant &p_variant) -> :
	Dictionary d = p_variant
	if d.has("source"):
		source = d["source"]
	if d.has("signal"):
		signal = d["signal"]
	if d.has("target"):
		target = d["target"]
	if d.has("method"):
		method = d["method"]
	if d.has("flags"):
		flags = d["flags"]
	if d.has("binds"):
		binds = d["binds"]


def Object::_predelete() ->bool:
	_predelete_ok = 1
	notification(NOTIFICATION_PREDELETE, true)
	if _predelete_ok:
		_class_ptr = NULL; //must restore so destructors can access class ptr correctly
	return _predelete_ok


def Object::_postinitialize():
	_class_ptr = _get_class_namev()
	_initialize_classv()
	notification(NOTIFICATION_POSTINITIALIZE)


def Object::get_valid_parents_static(List<String> *p_parents):
	## empty

def Object::_get_valid_parents_static(List<String> *p_parents):
	## empty


def Object::set(const StringName ⤴p_name, const Variant ⤴p_value, bool ⮞r_valid):
	#ifdef TOOLS_ENABLED
	  _edited = true;
	#endif
	if p_name == CoreStringNames::get_singleton()->_meta:
		##std::cout << "Object::set - setting metadata...." << std::endl;
		##set_meta(p_name,p_value);
		metadata = p_value
		if r_valid:
			*r_valid = true
		return
	## Blendot - get/set of props on the script instance is DEPRECATED
	##if script_instance:
	##	if script_instance->set(p_name, p_value):
	##		if r_valid:
	##			*r_valid = true
	##		return
	//try built-in setgetter
	with scope:
		if ClassDB::set_property(this, p_name, p_value, r_valid):
			//if r_valid:
			//	*r_valid=true;
			return
	if p_name == CoreStringNames::get_singleton()->_script:
		set_script(p_value)
		if r_valid:
			*r_valid = true
		return
	//something inside the object... :|
	bool success = _setv(p_name, p_value)
	if success:
		if r_valid:
			*r_valid = true
		return
	with scope:
		bool valid
		setvar(p_name, p_value, &valid)
		if valid:
			if r_valid:
				*r_valid = true
			return
	#ifdef TOOLS_ENABLED
	if script_instance:
		bool valid
		script_instance->property_set_fallback(p_name, p_value, &valid)
		if valid:
			if r_valid:
				*r_valid = true
			return
	#endif
	if r_valid:
		*r_valid = false


@const
def Object::get(const StringName ⤴p_name, bool ⮞r_valid) -> Variant:
	Variant ret
	## __meta__
	if p_name == CoreStringNames::get_singleton()->_meta:
		##std::cout << "Object::get - metadata..."
		ret = metadata
		if r_valid:
			*r_valid = true
		return ret
	## Blendot: get/set of props on the script instance is DEPRECATED
	## this was a design pattern for GDScript, which is removed in Blendot
	## this probably breaks VisualScripting, but VS will probably also be removed in Blendot.
	##if script_instance:
	##	if script_instance->get(p_name, ret):
	##		if r_valid:
	##			*r_valid = true
	##		return ret
	//try built-in setgetter
	with scope:
		if ClassDB::get_property(const_cast<Object⮞>(this), p_name, ret):
			if r_valid:
				*r_valid = true
			return ret
	if p_name == CoreStringNames::get_singleton()->_script:
		ret = get_script()
		if r_valid:
			*r_valid = true
		return ret
	else:
		//something inside the object... :|
		bool success = _getv(p_name, ret)
		if success:
			if r_valid:
				*r_valid = true
			return ret
		//if nothing else, use getvar
		with scope:
			bool valid
			ret = getvar(p_name, &valid)
			if valid:
				if r_valid:
					*r_valid = true
				return ret
		#ifdef TOOLS_ENABLED
		if script_instance:
			bool valid
			ret = script_instance->property_get_fallback(p_name, &valid)
			if valid:
				if r_valid:
					*r_valid = true
				return ret
		#endif
		if r_valid:
			*r_valid = false
		return Variant()


def Object::set_indexed(const Vector<StringName> ⤴p_names, const Variant ⤴p_value, bool ⮞r_valid):
	if p_names.empty():
		if r_valid:
			*r_valid = false
		return
	if p_names.size() == 1:
		set(p_names[0], p_value, r_valid)
		return
	bool valid = false
	if not r_valid:
		r_valid = &valid
	List<Variant> value_stack
	value_stack.push_back(get(p_names[0], r_valid))
	if not *r_valid:
		value_stack.clear()
		return
	for (int i = 1; i < p_names.size() - 1; i++):
		value_stack.push_back(value_stack.back()->get().get_named(p_names[i], r_valid))
		if not *r_valid:
			value_stack.clear()
			return
	value_stack.push_back(p_value); // p_names[p_names.size() - 1]
	for (int i = p_names.size() - 1; i > 0; i--):
		value_stack.back()->prev()->get().set_named(p_names[i], value_stack.back()->get(), r_valid)
		value_stack.pop_back()
		if not *r_valid:
			value_stack.clear()
			return
	set(p_names[0], value_stack.back()->get(), r_valid)
	value_stack.pop_back()
	ERR_FAIL_COND(!value_stack.empty())

@const
def Object::get_indexed(const Vector<StringName> ⤴p_names, bool ⮞r_valid) ->Variant:
	if p_names.empty():
		if r_valid:
			*r_valid = false
		return Variant()
	bool valid = false
	Variant current_value = get(p_names[0], &valid)
	for (int i = 1; i < p_names.size(); i++):
		current_value = current_value.get_named(p_names[i], &valid)
		if not valid:
			break
	if r_valid:
		*r_valid = valid
	return current_value

@const
def Object::get_property_list(List<PropertyInfo> ⮞p_list, bool p_reversed):
	if script_instance && p_reversed:
		p_list->push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY))
		script_instance->get_property_list(p_list)
	_get_property_listv(p_list, p_reversed)
	if not is_class("Script"):
		// can still be set, but this is for userfriendlyness
		#ifdef TOOLS_ENABLED
		p_list->push_back(PropertyInfo(Variant::NIL, "Script", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP))
		#endif
		p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT))
	// https://github.com/godotengine/godot/pull/30765/files/58fecfb0fb686a1cf4e42a49763fb235679d1333#diff-21145ad4eb02c796f1e240892c937e41
	//	if (!metadata.empty()) {
	//		p_list->push_back(PropertyInfo(Variant::DICTIONARY, "__meta__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
	//	}
	// TODO support custom props from blender
	#ifdef TOOLS_ENABLED
	p_list->push_back(PropertyInfo(Variant::NIL, "Metadata", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP))
	#endif
	p_list->push_back(PropertyInfo(Variant::DICTIONARY, "__meta__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT))
	// https://github.com/godotengine/godot/pull/30765
	if script_instance && !p_reversed:
		p_list->push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY))
		script_instance->get_property_list(p_list)

@const
def Object::_validate_property(PropertyInfo ⤴property):
	## empty

@const
def Object::get_method_list(List<MethodInfo> ⮞p_list):
	ClassDB::get_method_list(get_class_name(), p_list)
	if script_instance:
		script_instance->get_method_list(p_list)

def Object::_call_bind(const Variant ⮞⮞p_args, int p_argcount, Variant::CallError ⤴r_error) ->Variant:
	if p_argcount < 1:
		r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS
		r_error.argument = 0
		return Variant()
	if p_args[0]->get_type() != Variant::STRING:
		r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT
		r_error.argument = 0
		r_error.expected = Variant::STRING
		return Variant()
	StringName method = *p_args[0]
	return call(method, &p_args[1], p_argcount - 1, r_error)


def Object::_call_deferred_bind(const Variant ⮞⮞p_args, int p_argcount, Variant::CallError ⤴r_error) ->Variant:
	if p_argcount < 1:
		r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS
		r_error.argument = 0
		return Variant()
	if p_args[0]->get_type() != Variant::STRING:
		r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT
		r_error.argument = 0
		r_error.expected = Variant::STRING
		return Variant()
	r_error.error = Variant::CallError::CALL_OK
	StringName method = *p_args[0]
	MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1)
	return Variant()


#ifdef DEBUG_ENABLED
@static
def _test_call_error(const StringName ⤴p_func, const Variant::CallError ⤴error):
	switch error.error:
		case Variant::CallError::CALL_OK:
			## fall thru
		case Variant::CallError::CALL_ERROR_INVALID_METHOD:
			break;
		case Variant::CallError::CALL_ERROR_INVALID_ARGUMENT:
			ERR_FAIL_MSG("Error calling function: " + String(p_func) + " - Invalid type for argument " + itos(error.argument) + ", expected " + Variant::get_type_name(error.expected) + ".");
			break
		case Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
			ERR_FAIL_MSG("Error calling function: " + String(p_func) + " - Too many arguments, expected " + itos(error.argument) + ".");
			break
		case Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
			ERR_FAIL_MSG("Error calling function: " + String(p_func) + " - Too few arguments, expected " + itos(error.argument) + ".");
			break
		case Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL:
			break
#else

define(_test_call_error(m_str, m_err)= )

#endif

def Object::call_multilevel(const StringName ⤴p_method, const Variant ⮞⮞p_args, int p_argcount):
	if p_method == CoreStringNames::get_singleton()->_free:
		#ifdef DEBUG_ENABLED
		ERR_FAIL_COND_MSG(Object::cast_to<Reference>(this), "Can't 'free' a reference.")
		ERR_FAIL_COND_MSG(_lock_index.get() > 1, "Object is locked and can't be freed.")
		#endif
		//must be here, must be before everything,
		memdelete(this)
		return
	//Variant ret;
	OBJ_DEBUG_LOCK
	Variant::CallError error
	#ifdef BLENDOT
	if script_instance:
		script_instance->call_multilevel(p_method, p_args, p_argcount)
		//_test_call_error(p_method,error);
	#endif
	MethodBind *method = ClassDB::get_method(get_class_name(), p_method)
	if method:
		method->call(this, p_args, p_argcount, error)
		_test_call_error(p_method, error)


def Object::call_multilevel_reversed(const StringName &p_method, const Variant ⮞⮞p_args, int p_argcount):
	MethodBind ⮞method = ClassDB::get_method(get_class_name(), p_method)
	Variant::CallError error
	OBJ_DEBUG_LOCK
	if method:
		method->call(this, p_args, p_argcount, error)
		_test_call_error(p_method, error)
	//Variant ret;
	#ifdef BLENDOT
	if script_instance:
		script_instance->call_multilevel_reversed(p_method, p_args, p_argcount)
		//_test_call_error(p_method,error);
	#endif

@const
def Object::has_method(const StringName ⤴p_method) ->bool:
	if p_method == CoreStringNames::get_singleton()->_free:
		return true
	if script_instance && script_instance->has_method(p_method):
		return true
	MethodBind ⮞method = ClassDB::get_method(get_class_name(), p_method)
	return method != NULL

@const
def Object::getvar(const Variant ⤴p_key, bool ⮞r_valid) ->Variant:
	if r_valid:
		*r_valid = false
	return Variant()

def Object::setvar(const Variant ⤴p_key, const Variant ⤴p_value, bool ⮞r_valid):
	if r_valid:
		*r_valid = false

def Object::callv(const StringName ⤴p_method, const Array ⤴p_args) ->Variant:
	const Variant **argptrs = NULL
	if p_args.size() > 0:
		argptrs = (const Variant⮞⮞)alloca( sizeof(Variant⮞) * p_args.size() )
		for (int i = 0; i < p_args.size(); i++):
			argptrs[i] = &p_args[i]
	Variant::CallError ce
	Variant ret = call(p_method, argptrs, p_args.size(), ce)
	if ce.error != Variant::CallError::CALL_OK:
		ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + ".")
	return ret


def Object::call(const StringName ⤴p_name, VARIANT_ARG_DECLARE) ->Variant:
	VARIANT_ARGPTRS
	int argc = 0
	for (int i = 0; i < VARIANT_ARG_MAX; i++):
		if argptr[i]->get_type() == Variant::NIL:
			break
		argc++
	Variant::CallError error
	Variant ret = call(p_name, argptr, argc, error)
	return ret


def Object::call_multilevel(const StringName ⤴p_name, VARIANT_ARG_DECLARE):
	VARIANT_ARGPTRS
	int argc = 0
	for (int i = 0; i < VARIANT_ARG_MAX; i++):
		if argptr[i]->get_type() == Variant::NIL:
			break
		argc++
	//Variant::CallError error;
	call_multilevel(p_name, argptr, argc)


def Object::call(const StringName ⤴p_method, const Variant ⮞⮞p_args, int p_argcount, Variant::CallError ⤴r_error) -> Variant:
	r_error.error = Variant::CallError::CALL_OK
	if p_method == CoreStringNames::get_singleton()->_free:
		//free must be here, before anything, always ready
		#ifdef DEBUG_ENABLED
		if p_argcount != 0:
			r_error.argument = 0
			r_error.error = Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS
			return Variant()
		if Object::cast_to<Reference>(this):
			r_error.argument = 0
			r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD
			ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference.")
		if _lock_index.get() > 1:
			r_error.argument = 0
			r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD
			ERR_FAIL_V_MSG(Variant(), "Object is locked and can't be freed.")
		#endif
		//must be here, must be before everything,
		memdelete(this)
		r_error.error = Variant::CallError::CALL_OK
		return Variant()
	Variant ret;
	OBJ_DEBUG_LOCK
	#ifdef BLENDOT
	if script_instance:
		ret = script_instance->call(p_method, p_args, p_argcount, r_error)
		//force jumptable
		switch r_error.error:
			case Variant::CallError::CALL_OK:
				return ret
			case Variant::CallError::CALL_ERROR_INVALID_METHOD:
				break
			case Variant::CallError::CALL_ERROR_INVALID_ARGUMENT:
				##fallthru
			case Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
				##fallthru
			case Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
				return ret
			case Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL:
				##fallthru
	#endif
	MethodBind *method = ClassDB::get_method(get_class_name(), p_method)
	if method:
		ret = method->call(this, p_args, p_argcount, r_error)
	else:
		r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD
	return ret


def Object::notification(int p_notification, bool p_reversed):
	_notificationv(p_notification, p_reversed)
	#ifdef BLENDOT
	if script_instance:
		script_instance->notification(p_notification)
	#endif


def Object::to_string() ->String:
	#ifdef BLENDOT
	if script_instance:
		bool valid
		String ret = script_instance->to_string(&valid)
		if valid:
			return ret
	#endif
	auto classname = get_class()
	if classname == "TPYScript":
		##crashes##return "<" + classname + ":" + itos(get_instance_id()) + ">(" + Ref<Script>(this)->get_source_code() + ")"
		return "<" + classname + ":" + itos(get_instance_id()) + ">"
	else:
		return "[" + classname + ":" + itos(get_instance_id()) + "]"


def Object::_changed_callback(Object ⮞p_changed, const char ⮞p_prop):
	## empty

def Object::add_change_receptor(Object ⮞p_receptor):
	change_receptors.insert(p_receptor)


def Object::remove_change_receptor(Object ⮞p_receptor):
	change_receptors.erase(p_receptor)


def Object::property_list_changed_notify():
	_change_notify()


def Object::cancel_delete():
	_predelete_ok = true


def Object::set_script_and_instance(const RefPtr ⤴p_script, ScriptInstance ⮞p_instance):
	//this function is not meant to be used in any of these ways
	ERR_FAIL_COND(p_script.is_null())
	ERR_FAIL_COND(!p_instance)
	ERR_FAIL_COND(script_instance != NULL || !script.is_null())
	script = p_script
	script_instance = p_instance


def Object::set_script(const RefPtr ⤴p_script):
	#ifdef BLENDOT
	if script == p_script:
		return
	if script_instance:
		memdelete(script_instance)
		script_instance = NULL
	script = p_script
	Ref<Script> s(script)
	if not s.is_null():
		if s->can_instance():
			OBJ_DEBUG_LOCK
			script_instance = s->instance_create(this)
		elif Engine::get_singleton()->is_editor_hint():
			OBJ_DEBUG_LOCK
			script_instance = s->placeholder_instance_create(this)
	_change_notify(); //scripts may add variables, so refresh is desired
	emit_signal(CoreStringNames::get_singleton()->script_changed)
	#endif


def Object::set_script_instance(ScriptInstance ⮞p_instance):
	#ifdef BLENDOT
	if script_instance == p_instance:
		return
	if script_instance:
		memdelete(script_instance)
	script_instance = p_instance
	if p_instance:
		script = p_instance->get_script().get_ref_ptr()
	else:
		script = RefPtr()
	#endif

@const
def Object::get_script() -> RefPtr:
	return script

@const
def Object::has_meta(const String ⤴p_name) ->bool:
	return metadata.has(p_name)


def Object::set_meta(const String ⤴p_name, const Variant ⤴p_value):
	if p_value.get_type() == Variant::NIL:
		metadata.erase(p_name)
		return
	metadata[p_name] = p_value

@const
def Object::get_meta(const String ⤴p_name) -> Variant:
	ERR_FAIL_COND_V(!metadata.has(p_name), Variant())
	return metadata[p_name]


def Object::remove_meta(const String ⤴p_name):
	metadata.erase(p_name)

@const
def Object::_get_property_list_bind() ->Array:
	List<PropertyInfo> lpi
	get_property_list(&lpi)
	return convert_property_list(&lpi)

@const
def Object::_get_method_list_bind() -> Array:
	List<MethodInfo> ml
	get_method_list(&ml)
	Array ret
	for (List<MethodInfo>::Element ⮞E = ml.front(); E; E = E->next()):
		Dictionary d = E->get()
		//va.push_back(d);
		ret.push_back(d)
	return ret

@const
def Object::_get_meta_list_bind() -> PoolVector<String>:
	PoolVector<String> _metaret
	List<Variant> keys
	metadata.get_key_list(&keys)
	for (List<Variant>::Element ⮞E = keys.front(); E; E = E->next()):
		_metaret.push_back(E->get())
	return _metaret

@const
def Object::get_meta_list(List<String> ⮞p_list):
	List<Variant> keys
	metadata.get_key_list(&keys)
	for (List<Variant>::Element ⮞E = keys.front(); E; E = E->next()):
		p_list->push_back(E->get())

def Object::add_user_signal(const MethodInfo ⤴p_signal):
	ERR_FAIL_COND(p_signal.name == "")
	ERR_FAIL_COND(ClassDB::has_signal(get_class_name(), p_signal.name))
	ERR_FAIL_COND(signal_map.has(p_signal.name))
	Signal s
	s.user = p_signal
	signal_map[p_signal.name] = s

@const
def Object::_has_user_signal(const StringName ⤴p_name) ->bool:
	if not signal_map.has(p_name):
		return false
	return signal_map[p_name].user.name.length() > 0


struct _ObjectSignalDisconnectData:
	StringName signal
	Object *target
	StringName method


def Object::_emit_signal(const Variant ⮞⮞p_args, int p_argcount, Variant::CallError ⤴r_error) -> Variant:
	r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS
	ERR_FAIL_COND_V(p_argcount < 1, Variant())
	if p_args[0]->get_type() != Variant::STRING:
		r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT
		r_error.argument = 0
		r_error.expected = Variant::STRING
		ERR_FAIL_COND_V(p_args[0]->get_type() != Variant::STRING, Variant())
	r_error.error = Variant::CallError::CALL_OK
	StringName signal = *p_args[0]
	const Variant ⮞⮞args = NULL
	int argc = p_argcount - 1
	if argc:
		args = &p_args[1]
	emit_signal(signal, args, argc)
	return Variant()

// Blendot notes:
// a button click will trigger these signals
//Object::emit_signal - gui_input
//Object::emit_signal - pressed
//Object::emit_signal - button_up
//Object::emit_signal - frame_pre_draw
//Object::emit_signal - frame_post_draw

def Object::emit_signal(const StringName ⤴p_name, const Variant ⮞⮞p_args, int p_argcount) ->Error:
	if _block_signals:
		return ERR_CANT_ACQUIRE_RESOURCE; //no emit, signals blocked
	Signal ⮞s = signal_map.getptr(p_name)
	String sname = String(p_name)
	## note: audio_update_callback and audio_mix_callback is heavily called, much more than draw
	#ifdef DEBUG_SIGNALS
	if sname != "tree_exiting" and sname != "tree_exited" && sname != "resized" && sname != "item_rect_changed" && sname != "ready" && sname != "audio_mix_callback":
		if sname != "physics_frame" && sname != "idle_frame" && sname != "audio_update_callback" && sname != "timeout":
			if sname != "node_removed" && sname != "tree_changed":
				if sname != "draw" and sname != "gui_input" and sname != "frame_pre_draw" and sname != "frame_post_draw":
					std::cout << "Object::emit_signal - " << sname << std::endl;
	#endif
	if not s:
		#ifdef DEBUG_ENABLED
		bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_name)
		//check in script
		ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, "Can't emit non-existing signal " + String("\"") + p_name + "\".")
		#endif
		//not connected? just return
		return ERR_UNAVAILABLE
	elif s->is_lambda:
		## c++11 lambda callback support
		s->callback()
		return OK
	List<_ObjectSignalDisconnectData> disconnect_data
	//copy on write will ensure that disconnecting the signal or even deleting the object will not affect the signal calling.
	//this happens automatically and will not change the performance of calling.
	//awesome, isn't it?
	VMap<Signal::Target, Signal::Slot> slot_map = s->slot_map
	int ssize = slot_map.size()
	OBJ_DEBUG_LOCK
	Vector<const Variant *> bind_mem
	Error err = OK
	for (int i = 0; i < ssize; i++):
		const Connection &c = slot_map.getv(i).conn
		Object ⮞target
		#ifdef DEBUG_ENABLED
		target = ObjectDB::get_instance(slot_map.getk(i)._id)
		ERR_CONTINUE(!target)
		#else
		target = c.target
		#endif
		const Variant ⮞⮞args = p_args
		int argc = p_argcount
		if c.binds.size():
			//handle binds
			bind_mem.resize(p_argcount + c.binds.size())
			for (int j = 0; j < p_argcount; j++):
				bind_mem.write[j] = p_args[j]
			for (int j = 0; j < c.binds.size(); j++):
				bind_mem.write[p_argcount + j] = &c.binds[j]
			args = (const Variant **)bind_mem.ptr()
			argc = bind_mem.size()
		if c.flags & CONNECT_DEFERRED:
			MessageQueue::get_singleton()->push_call(target->get_instance_id(), c.method, args, argc, true)
		else:
			Variant::CallError ce
			target->call(c.method, args, argc, ce)
			if ce.error != Variant::CallError::CALL_OK:
				#ifdef DEBUG_ENABLED
				if c.flags & CONNECT_PERSIST && Engine::get_singleton()->is_editor_hint() && (script.is_null() || !Ref<Script>(script)->is_tool()):
					continue
				#endif
				if ce.error == Variant::CallError::CALL_ERROR_INVALID_METHOD && !ClassDB::class_exists(target->get_class_name()):
					//most likely object is not initialized yet, do not throw error.
				else:
					ERR_PRINTS("Error calling method from signal '" + String(p_name) + "': " + Variant::get_call_error_text(target, c.method, args, argc, ce) + ".")
					err = ERR_METHOD_NOT_FOUND
		bool disconnect = c.flags & CONNECT_ONESHOT
		#ifdef TOOLS_ENABLED
		if disconnect && (c.flags & CONNECT_PERSIST) && Engine::get_singleton()->is_editor_hint():
			//this signal was connected from the editor, and is being edited. just don't disconnect for now
			disconnect = false;
		#endif
		if disconnect:
			_ObjectSignalDisconnectData dd
			dd.signal = p_name
			dd.target = target
			dd.method = c.method
			disconnect_data.push_back(dd)
	while !disconnect_data.empty():
		const _ObjectSignalDisconnectData &dd = disconnect_data.front()->get()
		disconnect(dd.signal, dd.target, dd.method)
		disconnect_data.pop_front()
	return err


def Object::emit_signal(const StringName ⤴p_name, VARIANT_ARG_DECLARE) ->Error:
	VARIANT_ARGPTRS
	int argc = 0
	for (int i = 0; i < VARIANT_ARG_MAX; i++):
		if argptr[i]->get_type() == Variant::NIL:
			break
		argc++
	return emit_signal(p_name, argptr, argc)


def Object::_add_user_signal(const String ⤴p_name, const Array ⤴p_args):
	// this version of add_user_signal is meant to be used from scripts or external apis
	// without access to ADD_SIGNAL in bind_methods
	// added events are per instance, as opposed to the other ones, which are global
	MethodInfo mi
	mi.name = p_name
	for (int i = 0; i < p_args.size(); i++):
		Dictionary d = p_args[i]
		PropertyInfo param
		if d.has("name"):
			param.name = d["name"]
		if d.has("type"):
			param.type = (Variant::Type)(int)d["type"]
		mi.arguments.push_back(param)
	add_user_signal(mi)

@const
def Object::_get_signal_list() ->Array:
	List<MethodInfo> signal_list
	get_signal_list(&signal_list)
	Array ret
	for (List<MethodInfo>::Element ⮞E = signal_list.front(); E; E = E->next()):
		ret.push_back(Dictionary(E->get()))
	return ret

@const
def Object::_get_signal_connection_list(const String ⤴p_signal) ->Array:
	List<Connection> conns
	get_all_signal_connections(&conns)
	Array ret
	for (List<Connection>::Element ⮞E = conns.front(); E; E = E->next()):
		Connection ⤴c = E->get()
		if c.signal == p_signal:
			Dictionary rc
			rc["signal"] = c.signal
			rc["method"] = c.method
			rc["source"] = c.source
			rc["target"] = c.target
			rc["binds"] = c.binds
			rc["flags"] = c.flags
			ret.push_back(rc)
	return ret

@const
def Object::_get_incoming_connections() ->Array:
	Array ret
	int connections_amount = connections.size()
	for (int idx_conn = 0; idx_conn < connections_amount; idx_conn++):
		Dictionary conn_data
		conn_data["source"] = connections[idx_conn].source
		conn_data["signal_name"] = connections[idx_conn].signal
		conn_data["method_name"] = connections[idx_conn].method
		ret.push_back(conn_data)
	return ret

@const
def Object::get_signal_list(List<MethodInfo> ⮞p_signals):
	#ifdef BLENDOT
	if not script.is_null():
		Ref<Script> scr = script
		if scr.is_valid():
			scr->get_script_signal_list(p_signals)
	#endif
	ClassDB::get_signal_list(get_class_name(), p_signals)
	//find maybe usersignals?
	const StringName ⮞S = NULL
	while (S = signal_map.next(S)):
		if signal_map[*S].user.name != "":
			//user signal
			p_signals->push_back(signal_map[*S].user)

@const
def Object::get_all_signal_connections(List<Connection> ⮞p_connections):
	const StringName *S = NULL
	while (S = signal_map.next(S)):
		const Signal *s = &signal_map[*S]
		for (int i = 0; i < s->slot_map.size(); i++):
			p_connections->push_back(s->slot_map.getv(i).conn)

@const
def Object::get_signal_connection_list(const StringName ⤴p_signal, List<Connection> ⮞p_connections):
	const Signal ⮞s = signal_map.getptr(p_signal)
	if not s:
		return; //nothing
	for (int i = 0; i < s->slot_map.size(); i++):
		p_connections->push_back(s->slot_map.getv(i).conn)

@const
def Object::get_persistent_signal_connection_count() ->int:
	int count = 0
	const StringName ⮞S = NULL
	while (S = signal_map.next(S)):
		const Signal *s = &signal_map[*S]
		for (int i = 0; i < s->slot_map.size(); i++):
			if s->slot_map.getv(i).conn.flags & CONNECT_PERSIST:
				count += 1
	return count

@const
def Object::get_signals_connected_to_this(List<Connection> ⮞p_connections):
	for (const List<Connection>::Element *E = connections.front(); E; E = E->next()):
		p_connections->push_back(E->get())

## c++11 lambda callback support
def Object::connect_lambda(const StringName &p_signal, std::function<void(void)> callback) ->Error:
	std::cout << "Object::connect_lambda " << String(p_signal) << std::endl
	Signal *s = signal_map.getptr(p_signal)
	if not s:
		bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal)
		if not signal_is_valid:
			throw "invalid signal"
		signal_map[p_signal] = Signal()
		s = &signal_map[p_signal]
	s->is_lambda = true
	s->callback = callback
	return OK

def Object::connect(const StringName ⤴p_signal, Object ⮞p_to_object, const StringName ⤴p_to_method, const Vector<Variant> ⤴p_binds, uint32_t p_flags) ->Error:
	##std::cout << "p_signal addr: " << p_signal << std::endl
	##std::cout << "p_signal value: " << String(p_signal).utf8() << std::endl
	ERR_FAIL_NULL_V(p_to_object, ERR_INVALID_PARAMETER)
	Signal *s = signal_map.getptr(p_signal)
	if not s:
		bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal)
		##std::cout << "signal_is_valid1 = " << signal_is_valid << std::endl;
		#ifdef BLENDOT
		//check in script
		if not signal_is_valid && !script.is_null():
			##std::cout << "signal_is_valid is false and script is not null" << std::endl;
			if Ref<Script>(script)->has_script_signal(p_signal):
				##std::cout << "script has signal" << std::endl
				signal_is_valid = true
			#ifdef TOOLS_ENABLED
			else:
				##std::cout << "editor fallback checking if script is valid" << std::endl;
				//allow connecting signals anyway if script is invalid, see issue #17070
				if not Ref<Script>(script)->is_valid():
					std::cout << "	script is valid" << std::endl;
					signal_is_valid = true
			#endif
		#endif
		ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, "In Object of type '" + String(get_class()) + "': Attempt to connect nonexistent signal '" + p_signal + "' to method '" + p_to_object->get_class() + "." + p_to_method + "'.")
		signal_map[p_signal] = Signal()
		s = &signal_map[p_signal]
	Signal::Target target(p_to_object->get_instance_id(), p_to_method)
	if s->slot_map.has(target):
		if p_flags & CONNECT_REFERENCE_COUNTED:
			s->slot_map[target].reference_count++
			return OK
		else:
			ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Signal '" + p_signal + "' is already connected to given method '" + p_to_method + "' in that object.")
	Signal::Slot slot
	Connection conn
	conn.source = this
	conn.target = p_to_object
	conn.method = p_to_method
	conn.signal = p_signal
	conn.flags = p_flags
	conn.binds = p_binds
	slot.conn = conn
	slot.cE = p_to_object->connections.push_back(conn)
	if p_flags & CONNECT_REFERENCE_COUNTED:
		slot.reference_count = 1
	s->slot_map[target] = slot
	return OK

@const
def Object::is_connected(const StringName ⤴p_signal, Object ⮞p_to_object, const StringName ⤴p_to_method) ->bool:
	ERR_FAIL_NULL_V(p_to_object, false)
	const Signal *s = signal_map.getptr(p_signal)
	if not s:
		bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal)
		if signal_is_valid:
			return false
		#ifdef BLENDOT
		if not script.is_null() && Ref<Script>(script)->has_script_signal(p_signal):
			return false
		#endif
		ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + ".")
	Signal::Target target(p_to_object->get_instance_id(), p_to_method)
	return s->slot_map.has(target)
	//const Map<Signal::Target,Signal::Slot>::Element *E = s->slot_map.find(target);
	//return (E!=NULL);


def Object::disconnect(const StringName ⤴p_signal, Object ⮞p_to_object, const StringName ⤴p_to_method):
	_disconnect(p_signal, p_to_object, p_to_method)


def Object::_disconnect(const StringName ⤴p_signal, Object ⮞p_to_object, const StringName ⤴p_to_method, bool p_force):
	ERR_FAIL_NULL(p_to_object)
	Signal ⮞s = signal_map.getptr(p_signal)
	ERR_FAIL_COND_MSG(!s, "Nonexistent signal: " + p_signal + ".")
	ERR_FAIL_COND_MSG(s->lock > 0, "Attempt to disconnect signal '" + p_signal + "' while emitting (locks: " + itos(s->lock) + ").")
	Signal::Target target(p_to_object->get_instance_id(), p_to_method)
	ERR_FAIL_COND_MSG(!s->slot_map.has(target), "Disconnecting nonexistent signal '" + p_signal + "', slot: " + itos(target._id) + ":" + target.method + ".")
	Signal::Slot *slot = &s->slot_map[target]
	if not p_force:
		slot->reference_count--; // by default is zero, if it was not referenced it will go below it
		if slot->reference_count >= 0:
			return
	p_to_object->connections.erase(slot->cE)
	s->slot_map.erase(target)
	if s->slot_map.empty() && ClassDB::has_signal(get_class_name(), p_signal):
		//not user signal, delete
		signal_map.erase(p_signal)


def Object::_set_bind(const String ⤴p_set, const Variant ⤴p_value):
	set(p_set, p_value)

@const
def Object::_get_bind(const String ⤴p_name) ->Variant:
	return get(p_name)

def Object::_set_indexed_bind(const NodePath ⤴p_name, const Variant ⤴p_value):
	set_indexed(p_name.get_as_property_path().get_subnames(), p_value)

@const
def Object::_get_indexed_bind(const NodePath ⤴p_name) ->Variant:
	return get_indexed(p_name.get_as_property_path().get_subnames())


def Object::initialize_class():
	static bool initialized = false
	if initialized:
		return
	ClassDB::_add_class<Object>()
	_bind_methods()
	initialized = true

@const
def Object::tr(const StringName ⤴p_message) -> StringName:
	return p_message;
	//if (!_can_translate || !TranslationServer::get_singleton())
	//	return p_message;
	//return TranslationServer::get_singleton()->translate(p_message);


def Object::_clear_internal_resource_paths(const Variant ⤴p_var):
	switch p_var.get_type():
		case Variant::OBJECT:
			RES r = p_var
			if not r.is_valid():
				return
			if not r->get_path().begins_with("res://") || r->get_path().find("::") == -1:
				return; //not an internal resource
			Object *object = p_var
			if not object:
				return
			r->set_path("")
			r->clear_internal_resource_paths()
			break
		case Variant::ARRAY:
			Array a = p_var
			for (int i = 0; i < a.size(); i++):
				_clear_internal_resource_paths(a[i])
			break
		case Variant::DICTIONARY:
			Dictionary d = p_var
			List<Variant> keys
			d.get_key_list(&keys)
			for (List<Variant>::Element *E = keys.front(); E; E = E->next()):
				_clear_internal_resource_paths(E->get())
				_clear_internal_resource_paths(d[E->get()])
			break


#ifdef TOOLS_ENABLED
def Object::editor_set_section_unfold(const String ⤴p_section, bool p_unfolded):
	set_edited(true)
	if p_unfolded:
		editor_section_folding.insert(p_section)
	else:
		editor_section_folding.erase(p_section)

def Object::editor_is_section_unfolded(const String ⤴p_section) ->bool:
	return editor_section_folding.has(p_section)

#endif

def Object::clear_internal_resource_paths():
	List<PropertyInfo> pinfo
	get_property_list(&pinfo)
	for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()):
		_clear_internal_resource_paths(get(E->get().name))

def Object::_bind_methods():
	ClassDB::bind_method(D_METHOD("get_class"), &Object::get_class)
	ClassDB::bind_method(D_METHOD("is_class", "class"), &Object::is_class)
	ClassDB::bind_method(D_METHOD("set", "property", "value"), &Object::_set_bind)
	ClassDB::bind_method(D_METHOD("get", "property"), &Object::_get_bind)
	ClassDB::bind_method(D_METHOD("set_indexed", "property", "value"), &Object::_set_indexed_bind)
	ClassDB::bind_method(D_METHOD("get_indexed", "property"), &Object::_get_indexed_bind)
	ClassDB::bind_method(D_METHOD("get_property_list"), &Object::_get_property_list_bind)
	ClassDB::bind_method(D_METHOD("get_method_list"), &Object::_get_method_list_bind)
	ClassDB::bind_method(D_METHOD("notification", "what", "reversed"), &Object::notification, DEFVAL(false))
	ClassDB::bind_method(D_METHOD("to_string"), &Object::to_string)
	ClassDB::bind_method(D_METHOD("get_instance_id"), &Object::get_instance_id)
	ClassDB::bind_method(D_METHOD("set_script", "script"), &Object::set_script)
	ClassDB::bind_method(D_METHOD("get_script"), &Object::get_script)
	ClassDB::bind_method(D_METHOD("set_meta", "name", "value"), &Object::set_meta)
	ClassDB::bind_method(D_METHOD("remove_meta", "name"), &Object::remove_meta)
	ClassDB::bind_method(D_METHOD("get_meta", "name"), &Object::get_meta)
	ClassDB::bind_method(D_METHOD("has_meta", "name"), &Object::has_meta)
	ClassDB::bind_method(D_METHOD("get_meta_list"), &Object::_get_meta_list_bind)
	ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array()))
	ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal)
	with scope:
		MethodInfo mi
		mi.name = "emit_signal"
		mi.arguments.push_back(PropertyInfo(Variant::STRING, "signal"))
		ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "emit_signal", &Object::_emit_signal, mi)
	with scope:
		MethodInfo mi
		mi.name = "call"
		mi.arguments.push_back(PropertyInfo(Variant::STRING, "method"))
		ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call", &Object::_call_bind, mi)
	with scope:
		MethodInfo mi
		mi.name = "call_deferred"
		mi.arguments.push_back(PropertyInfo(Variant::STRING, "method"))
		ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_deferred", &Object::_call_deferred_bind, mi)
	ClassDB::bind_method(D_METHOD("set_deferred", "property", "value"), &Object::set_deferred)
	ClassDB::bind_method(D_METHOD("callv", "method", "arg_array"), &Object::callv)
	ClassDB::bind_method(D_METHOD("has_method", "method"), &Object::has_method)
	ClassDB::bind_method(D_METHOD("get_signal_list"), &Object::_get_signal_list)
	ClassDB::bind_method(D_METHOD("get_signal_connection_list", "signal"), &Object::_get_signal_connection_list)
	ClassDB::bind_method(D_METHOD("get_incoming_connections"), &Object::_get_incoming_connections)
	ClassDB::bind_method(D_METHOD("connect", "signal", "target", "method", "binds", "flags"), &Object::connect, DEFVAL(Array()), DEFVAL(0))
	ClassDB::bind_method(D_METHOD("disconnect", "signal", "target", "method"), &Object::disconnect)
	ClassDB::bind_method(D_METHOD("is_connected", "signal", "target", "method"), &Object::is_connected)
	ClassDB::bind_method(D_METHOD("set_block_signals", "enable"), &Object::set_block_signals)
	ClassDB::bind_method(D_METHOD("is_blocking_signals"), &Object::is_blocking_signals)
	ClassDB::bind_method(D_METHOD("property_list_changed_notify"), &Object::property_list_changed_notify)
	ClassDB::bind_method(D_METHOD("set_message_translation", "enable"), &Object::set_message_translation)
	ClassDB::bind_method(D_METHOD("can_translate_messages"), &Object::can_translate_messages)
	ClassDB::bind_method(D_METHOD("tr", "message"), &Object::tr)
	ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion)
	ClassDB::add_virtual_method("Object", MethodInfo("free"), false)
	ADD_SIGNAL(MethodInfo("script_changed"))
	BIND_VMETHOD(MethodInfo("_notification", PropertyInfo(Variant::INT, "what")))
	BIND_VMETHOD(MethodInfo(Variant::BOOL, "_set", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value")))
	#ifdef TOOLS_ENABLED
	MethodInfo miget("_get", PropertyInfo(Variant::STRING, "property"))
	miget.return_val.name = "Variant"
	miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT
	BIND_VMETHOD(miget)
	MethodInfo plget("_get_property_list")
	plget.return_val.type = Variant::ARRAY
	BIND_VMETHOD(plget)
	#endif
	BIND_VMETHOD(MethodInfo("_init"))
	BIND_VMETHOD(MethodInfo(Variant::STRING, "_to_string"))
	BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE)
	BIND_CONSTANT(NOTIFICATION_PREDELETE)
	BIND_ENUM_CONSTANT(CONNECT_DEFERRED)
	BIND_ENUM_CONSTANT(CONNECT_PERSIST)
	BIND_ENUM_CONSTANT(CONNECT_ONESHOT)
	BIND_ENUM_CONSTANT(CONNECT_REFERENCE_COUNTED)


def Object::call_deferred(const StringName ⤴p_method, VARIANT_ARG_DECLARE):
	MessageQueue::get_singleton()->push_call(this, p_method, VARIANT_ARG_PASS)


def Object::set_deferred(const StringName ⤴p_property, const Variant ⤴p_value):
	MessageQueue::get_singleton()->push_set(this, p_property, p_value)


def Object::set_block_signals(bool p_block):
	_block_signals = p_block

@const
def Object::is_blocking_signals() ->bool:
	return _block_signals

@const
def Object::get_translatable_strings(List<String> ⮞p_strings):
	List<PropertyInfo> plist
	get_property_list(&plist)
	for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()):
		if not (E->get().usage & PROPERTY_USAGE_INTERNATIONALIZED):
			continue
		String text = get(E->get().name)
		if text == "":
			continue
		p_strings->push_back(text)

@const
def Object::get_static_property_type(const StringName ⤴p_property, bool ⮞r_valid) ->Variant::Type:
	bool valid
	Variant::Type t = ClassDB::get_property_type(get_class_name(), p_property, &valid)
	if valid:
		if r_valid:
			*r_valid = true
		return t
	#ifdef BLENDOT
	if get_script_instance():
		return get_script_instance()->get_property_type(p_property, r_valid)
	#endif
	if r_valid:
		*r_valid = false
	return Variant::NIL

@const
def Object::get_static_property_type_indexed(const Vector<StringName> ⤴p_path, bool ⮞r_valid) -> Variant::Type:
	#ifdef BLENDOT
	if p_path.size() == 0:
		if r_valid:
			*r_valid = false
		return Variant::NIL
	bool valid = false
	Variant::Type t = get_static_property_type(p_path[0], &valid)
	if not valid:
		if r_valid:
			*r_valid = false
		return Variant::NIL
	Variant::CallError ce
	Variant check = Variant::construct(t, NULL, 0, ce)
	for (int i = 1; i < p_path.size(); i++):
		if check.get_type() == Variant::OBJECT || check.get_type() == Variant::DICTIONARY || check.get_type() == Variant::ARRAY:
			// We cannot be sure about the type of properties this types can have
			if r_valid:
				*r_valid = false
			return Variant::NIL
		check = check.get_named(p_path[i], &valid)
		if not valid:
			if r_valid:
				*r_valid = false
			return Variant::NIL
	if r_valid:
		*r_valid = true
	return check.get_type()
	#else
	return Variant::NIL
	#endif

@const
def Object::is_queued_for_deletion() ->bool:
	return _is_queued_for_deletion


#ifdef TOOLS_ENABLED
def Object::set_edited(bool p_edited):
	_edited = p_edited
	_edited_version++

@const
def Object::is_edited() ->bool:
	return _edited

@const
def Object::get_edited_version() ->uint32_t:
	return _edited_version

#endif

def Object::get_script_instance_binding(int p_script_language_index) ->void*:
	#ifdef DEBUG_ENABLED
	ERR_FAIL_INDEX_V(p_script_language_index, MAX_SCRIPT_INSTANCE_BINDINGS, NULL)
	#endif
	//it's up to the script language to make this thread safe, if the function is called twice due to threads being out of syncro
	//just return the same pointer.
	//if you want to put a big lock in the entire function and keep allocated pointers in a map or something, feel free to do it
	//as it should not really affect performance much (won't be called too often), as in far most caes the condition below will be false afterwards
	#ifdef BLENDOT
	if not _script_instance_bindings[p_script_language_index]:
		void *script_data = ScriptServer::get_language(p_script_language_index)->alloc_instance_binding_data(this)
		if script_data:
			atomic_increment(&instance_binding_count)
			_script_instance_bindings[p_script_language_index] = script_data
	#endif
	return _script_instance_bindings[p_script_language_index]


def Object::has_script_instance_binding(int p_script_language_index) ->bool:
	return _script_instance_bindings[p_script_language_index] != NULL


def Object::set_script_instance_binding(int p_script_language_index, void *p_data):
	#ifdef DEBUG_ENABLED
	CRASH_COND(_script_instance_bindings[p_script_language_index] != NULL)
	#endif
	_script_instance_bindings[p_script_language_index] = p_data


def Object::Object():
	_class_ptr = NULL
	_block_signals = false
	_predelete_ok = 0
	_instance_id = 0
	_instance_id = ObjectDB::add_instance(this)
	_can_translate = true
	_is_queued_for_deletion = false
	instance_binding_count = 0
	memset(_script_instance_bindings, 0, sizeof(void⮞) * MAX_SCRIPT_INSTANCE_BINDINGS)
	script_instance = NULL
	#ifdef TOOLS_ENABLED
	_edited = false
	_edited_version = 0
	#endif
	#ifdef DEBUG_ENABLED
	_lock_index.init(1)
	#endif


def Object::~Object():
	if script_instance:
		memdelete(script_instance)
	script_instance = NULL
	const StringName *S = NULL
	while (S = signal_map.next(NULL)):
		Signal ⮞s = &signal_map[*S]
		ERR_CONTINUE_MSG(s->lock > 0, "Attempt to delete an object in the middle of a signal emission from it.")
		//brute force disconnect for performance
		int slot_count = s->slot_map.size()
		const VMap<Signal::Target, Signal::Slot>::Pair ⮞slot_list = s->slot_map.get_array()
		for (int i = 0; i < slot_count; i++):
			slot_list[i].value.conn.target->connections.erase(slot_list[i].value.cE)
		signal_map.erase(*S)
	//signals from nodes that connect to this node
	while connections.size():
		Connection c = connections.front()->get()
		c.source->_disconnect(c.signal, c.target, c.method, true)
	ObjectDB::remove_instance(this)
	_instance_id = 0
	_predelete_ok = 2
	#ifdef BLENDOT
	if not ScriptServer::are_languages_finished():
		for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++):
			if _script_instance_bindings[i]:
				ScriptServer::get_language(i)->free_instance_binding_data(_script_instance_bindings[i])
	#endif


def predelete_handler(Object ⮞p_object) ->bool:
	return p_object->_predelete()


def postinitialize_handler(Object ⮞p_object):
	p_object->_postinitialize()


HashMap<ObjectID, Object⮞> ObjectDB::instances;
ObjectID ObjectDB::instance_counter = 1;
HashMap<Object⮞, ObjectID, ObjectDB::ObjectPtrHash> ObjectDB::instance_checks;

def ObjectDB::add_instance(Object ⮞p_object) -> ObjectID:
	ERR_FAIL_COND_V(p_object->get_instance_id() != 0, 0)
	#ifdef BLENDOT
	rw_lock->write_lock()
	#endif
	ObjectID instance_id = ++instance_counter
	instances[instance_id] = p_object
	instance_checks[p_object] = instance_id
	#ifdef BLENDOT
	rw_lock->write_unlock()
	#endif
	return instance_id


def ObjectDB::remove_instance(Object ⮞p_object):
	rw_lock->write_lock()
	instances.erase(p_object->get_instance_id())
	instance_checks.erase(p_object)
	rw_lock->write_unlock()


def ObjectDB::get_instance(ObjectID p_instance_id) -> Object⮞:
	rw_lock->read_lock()
	Object **obj = instances.getptr(p_instance_id)
	rw_lock->read_unlock()
	if not obj:
		return NULL
	return *obj


def ObjectDB::debug_objects(DebugFunc p_func):
	rw_lock->read_lock()
	const ObjectID ⮞K = NULL
	while (K = instances.next(K)):
		p_func(instances[*K])
	rw_lock->read_unlock()

@const
def Object::get_argument_options(const StringName ⤴p_function, int p_idx, List<String> ⮞r_options):
	## empty


def ObjectDB::get_object_count() ->int:
	rw_lock->read_lock()
	int count = instances.size()
	rw_lock->read_unlock()
	return count


RWLock ⮞ObjectDB::rw_lock = NULL

def ObjectDB::setup():
	#ifdef BLENDOT
	rw_lock = RWLock::create()
	#endif


def ObjectDB::cleanup():
	rw_lock->write_lock()
	if instances.size():
		WARN_PRINT("ObjectDB Instances still exist!")
		#ifdef BLENDOT
		if OS::get_singleton()->is_stdout_verbose():
			const ObjectID ⮞K = NULL
			while (K = instances.next(K)):
				String node_name
				if instances[*K]->is_class("Node"):
					node_name = " - Node name: " + String(instances[*K]->call("get_name"))
				if instances[*K]->is_class("Resource"):
					node_name = " - Resource name: " + String(instances[*K]->call("get_name")) + " Path: " + String(instances[*K]->call("get_path"))
				print_line("Leaked instance: " + String(instances[*K]->get_class()) + ":" + itos(*K) + node_name)
		#endif
	instances.clear()
	instance_checks.clear()
	rw_lock->write_unlock()
	memdelete(rw_lock)



