var _utils = load("res://addons/gut/utils.gd").get_instance()
# Hash containing all the built in types in Godot.  This provides an English
# name for the types that corosponds with the type constants defined in the
# engine.
var types = {}
var NativeScriptClass = null


func _init_types_dictionary():
	types[TYPE_NIL] = "TYPE_NIL"
	types[TYPE_BOOL] = "Bool"
	types[TYPE_INT] = "Int"
	types[TYPE_REAL] = "Float/Real"
	types[TYPE_STRING] = "String"
	types[TYPE_VECTOR2] = "Vector2"
	types[TYPE_RECT2] = "Rect2"
	types[TYPE_VECTOR3] = "Vector3"
	#types[8] = 'Matrix32'
	types[TYPE_PLANE] = "Plane"
	types[TYPE_QUAT] = "QUAT"
	types[TYPE_AABB] = "AABB"
	#types[12] = 'Matrix3'
	types[TYPE_TRANSFORM] = "Transform"
	types[TYPE_COLOR] = "Color"
	#types[15] = 'Image'
	types[TYPE_NODE_PATH] = "Node Path"
	types[TYPE_RID] = "RID"
	types[TYPE_OBJECT] = "TYPE_OBJECT"
	#types[19] = 'TYPE_INPUT_EVENT'
	types[TYPE_DICTIONARY] = "Dictionary"
	types[TYPE_ARRAY] = "Array"
	types[TYPE_RAW_ARRAY] = "TYPE_RAW_ARRAY"
	types[TYPE_INT_ARRAY] = "TYPE_INT_ARRAY"
	types[TYPE_REAL_ARRAY] = "TYPE_REAL_ARRAY"
	types[TYPE_STRING_ARRAY] = "TYPE_STRING_ARRAY"
	types[TYPE_VECTOR2_ARRAY] = "TYPE_VECTOR2_ARRAY"
	types[TYPE_VECTOR3_ARRAY] = "TYPE_VECTOR3_ARRAY"
	types[TYPE_COLOR_ARRAY] = "TYPE_COLOR_ARRAY"
	types[TYPE_MAX] = "TYPE_MAX"


# Types to not be formatted when using _str
var _str_ignore_types = [TYPE_INT, TYPE_REAL, TYPE_STRING, TYPE_NIL, TYPE_BOOL]


func _init():
	_init_types_dictionary()
	# NativeScript does not exist when GDNative is not included in the build
	if type_exists("NativeScript"):
		var getter = load("res://addons/gut/get_native_script.gd")
		NativeScriptClass = getter.get_it()


# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func _get_filename(path):
	return path.split("/")[-1]


# ------------------------------------------------------------------------------
# Gets the filename of an object passed in.  This does not return the
# full path to the object, just the filename.
# ------------------------------------------------------------------------------
func _get_obj_filename(thing):
	var filename = null

	if (
		thing == null
		or !is_instance_valid(thing)
		or str(thing) == "[Object:null]"
		or typeof(thing) != TYPE_OBJECT
		or thing.has_method("__gut_instance_from_id")
	):
		return

	if thing.get_script() == null:
		if thing is PackedScene:
			filename = _get_filename(thing.resource_path)
		else:
			# If it isn't a packed scene and it doesn't have a script then
			# we do nothing.  This just read better.
			pass
	elif NativeScriptClass != null and thing.get_script() is NativeScriptClass:
		# Work with GDNative scripts:
		# inst2dict fails with "Not a script with an instance" on GDNative script instances
		filename = _get_filename(thing.get_script().resource_path)
	elif !_utils.is_native_class(thing):
		var dict = inst2dict(thing)
		filename = _get_filename(dict["@path"])
		if dict["@subpath"] != "":
			filename += str("/", dict["@subpath"])

	return filename


# ------------------------------------------------------------------------------
# Better object/thing to string conversion.  Includes extra details about
# whatever is passed in when it can/should.
# ------------------------------------------------------------------------------
func type2str(thing):
	var filename = _get_obj_filename(thing)
	var str_thing = str(thing)

	if thing == null:
		# According to str there is a difference between null and an Object
		# that is somehow null.  To avoid getting '[Object:null]' as output
		# always set it to str(null) instead of str(thing).  A null object
		# will pass typeof(thing) == TYPE_OBJECT check so this has to be
		# before that.
		str_thing = str(null)
	elif typeof(thing) == TYPE_REAL:
		if !"." in str_thing:
			str_thing += ".0"
	elif typeof(thing) == TYPE_STRING:
		str_thing = str('"', thing, '"')
	elif typeof(thing) in _str_ignore_types:
		# do nothing b/c we already have str(thing) in
		# to_return.  I think this just reads a little
		# better this way.
		pass
	elif typeof(thing) == TYPE_OBJECT:
		if _utils.is_native_class(thing):
			str_thing = _utils.get_native_class_name(thing)
		elif _utils.is_double(thing):
			var double_path = _get_filename(thing.__gut_metadata_.path)
			if thing.__gut_metadata_.subpath != "":
				double_path += str("/", thing.__gut_metadata_.subpath)
			elif thing.__gut_metadata_.from_singleton != "":
				double_path = thing.__gut_metadata_.from_singleton + " Singleton"

			var double_type = "double"
			if thing.__gut_metadata_.is_partial:
				double_type = "partial-double"

			str_thing += str("(", double_type, " of ", double_path, ")")

			filename = null
	elif types.has(typeof(thing)):
		if !str_thing.begins_with("("):
			str_thing = "(" + str_thing + ")"
		str_thing = str(types[typeof(thing)], str_thing)

	if filename != null:
		str_thing += str("(", filename, ")")
	return str_thing


# ------------------------------------------------------------------------------
# Returns the string truncated with an '...' in it.  Shows the start and last
# 10 chars.  If the string is  smaller than max_size the entire string is
# returned.  If max_size is -1 then truncation is skipped.
# ------------------------------------------------------------------------------
func truncate_string(src, max_size):
	var to_return = src
	if src.length() > max_size - 10 and max_size != -1:
		to_return = str(
			src.substr(0, max_size - 10), "...", src.substr(src.length() - 10, src.length())
		)
	return to_return


func _get_indent_text(times, pad):
	var to_return = ""
	for i in range(times):
		to_return += pad

	return to_return


func indent_text(text, times, pad):
	if times == 0:
		return text

	var to_return = text
	var ending_newline = ""

	if text.ends_with("\n"):
		ending_newline = "\n"
		to_return = to_return.left(to_return.length() - 1)

	var padding = _get_indent_text(times, pad)
	to_return = to_return.replace("\n", "\n" + padding)
	to_return += ending_newline

	return padding + to_return