# ############################################################################ #
# Copyright © 2015-2021 inkle Ltd.
# Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
# All Rights Reserved
#
# This file is part of inkgd.
# inkgd is licensed under the terms of the MIT license.
# ############################################################################ #

extends RefCounted

class_name InkUtils

# ############################################################################ #
# Imports
# ############################################################################ #

const ValueType = preload("res://addons/inkgd/runtime/values/value_type.gd").ValueType

# ############################################################################ #
# Exceptions
# ############################################################################ #

static func throw_exception(message: String) -> void:
	InkRuntime().handle_exception(message)

static func throw_story_exception(
		message: String,
		use_end_line_number = false,
		metadata = null
) -> void:
	InkRuntime().handle_story_exception(message, use_end_line_number, metadata)

static func throw_argument_exception(message: String) -> void:
	InkRuntime().handle_argument_exception(message)

# ############################################################################ #
# Assertions
# ############################################################################ #

static func __assert__(condition: bool, message = "") -> void:
	if !condition && message != "":
		printerr(message)

	assert(condition)

# ############################################################################ #
# Type Assertion
# ############################################################################ #

static func as_or_null(variant, name_of_class: String):
	if (
			is_ink_class(variant, name_of_class) ||
			(name_of_class == "Dictionary" && variant is Dictionary) ||
			(name_of_class == "Array" && variant is Array)
	):
		return variant
	else:
		return null

static func cast(variant, name_of_class: String):
	if is_ink_class(variant, name_of_class):
		return variant
	else:
		push_error(
				"Original implementation threw a RuntimeException here, because of a " +
				"cast issue. Undefined behaviors should be expected."
		)

		assert(false)
		return null

static func as_INamedContent_or_null(variant):
	var properties = variant.get_property_list()

	var has_has_valid_name = false
	var has_name = false

	for property in properties:
		if property["name"] == "has_valid_name":
			has_has_valid_name = true

			if has_has_valid_name && has_name:
				return variant
		elif property["name"] == "name":
			has_name = true

			if has_has_valid_name && has_name:
				return variant

	return null

static func is_ink_class(object: Variant, name_of_class: String) -> bool:
	return (object is Object) && object.is_ink_class(name_of_class)

static func are_of_same_type(object1: Variant, object2: Variant) -> bool:
	if (object1 is Object) && (object2 is Object):
		return object1.get_ink_class() == object2.get_ink_class()

	return typeof(object1) == typeof(object2)

static func value_type_name(value_type: int) -> String:
	match value_type:
		ValueType.BOOL: return "Boolean"

		ValueType.INT: return "Int"
		ValueType.FLOAT: return "Float"
		ValueType.LIST: return "List"
		ValueType.STRING: return "String"

		ValueType.DIVERT_TARGET: return "Divert Target"
		ValueType.VARIABLE_POINTER: return "Variable Pointer"

		_: return "unknown"

static func typename_of(variant) -> String:
	match typeof(variant):
		TYPE_NIL: return "null"
		TYPE_BOOL: return "bool"
		TYPE_INT: return "int"
		TYPE_FLOAT: return "float"
		TYPE_STRING: return "String"
		TYPE_VECTOR2: return "Vector2"
		TYPE_RECT2: return "Rect2"
		TYPE_VECTOR3: return "Vector3"
		TYPE_TRANSFORM2D: return "Transform2D"
		TYPE_PLANE: return "Plane"
		TYPE_QUATERNION: return "Quaternion"
		TYPE_AABB: return "AABB"
		TYPE_BASIS: return "Basis"
		TYPE_TRANSFORM3D: return "Transform3D"
		TYPE_COLOR: return "Color"
		TYPE_NODE_PATH: return "NodePath"
		TYPE_RID: return "RID"
		TYPE_OBJECT: return variant.get_ink_class()
		TYPE_DICTIONARY: return "Dictionary"
		TYPE_ARRAY: return "Array"
		TYPE_PACKED_BYTE_ARRAY: return "PackedByteArray"
		TYPE_PACKED_INT32_ARRAY: return "PackedInt32Array"
		TYPE_PACKED_FLOAT32_ARRAY: return "PackedFloat32Array"
		TYPE_PACKED_STRING_ARRAY: return "PackedStringArray"
		TYPE_PACKED_VECTOR2_ARRAY: return "PackedVector2Array"
		TYPE_PACKED_VECTOR3_ARRAY: return "PackedVector3Array"
		TYPE_PACKED_COLOR_ARRAY: return "PackedColorArray"
		_: return "unknown"

# ############################################################################ #
# String Utils
# ############################################################################ #

static func trim(string_to_trim: String, characters = []) -> String:
	if characters.is_empty():
		return string_to_trim.strip_edges()

	var length = string_to_trim.length()
	var beginning = 0
	var end = length

	var i = 0
	while i < string_to_trim.length():
		var character = string_to_trim[i]
		if characters.find(character) != -1:
			beginning += 1
		else:
			break

		i += 1

	i = string_to_trim.length() - 1
	while i >= 0:
		var character = string_to_trim[i]
		if characters.find(character) != -1:
			end -= 1
		else:
			break

		i -= 1

	if beginning == 0 && end == length:
		return string_to_trim

	return string_to_trim.substr(beginning, end - beginning)

# ############################################################################ #
# Array Utils
# ############################################################################ #

static func join(joiner: String, array: Array) -> String:
	var joined_string = ""

	var i = 0
	for element in array:
		var element_string
		if is_ink_class(element, "InkBase"):
			element_string = element._to_string()
		else:
			element_string = str(element)

		joined_string += element_string

		if i >= 0 && i < array.size() - 1:
			joined_string += joiner

		i += 1

	return joined_string

static func get_range(array: Array, index: int, count: int) -> Array:
	if !(index >= 0 && index < array.size()):
		printerr("get_range: index (%d) is out of bounds." % index)
		return array.duplicate()

	if index + count > array.size():
		printerr("get_range: [index (%d) + count (%d)] is out of bounds." % [index, count])
		return array.duplicate()

	var new_array = []
	var i = index
	var c = 0

	while (c < count):
		new_array.append(array[i + c])
		c += 1

	return new_array

static func remove_range(array: Array, index: int, count: int) -> void:
	if !(index >= 0 && index < array.size()):
		printerr("get_range: index (%d) is out of bounds." % index)
		return

	if index + count > array.size():
		printerr("get_range: [index (%d) + count (%d)] is out of bounds." % [index, count])
		return

	var i = index
	var c = 0

	while (c < count):
		array.remove_at(i)
		c += 1

static func array_equal(a1: Array, a2: Array, use_equals = false) -> bool:
	if a1.size() != a2.size():
		return false

	var i = 0
	while (i < a1.size()):
		var first_element = a1[i]
		var second_element = a2[i]

		if use_equals:
			if !first_element.equals(second_element):
				return false
			else:
				i += 1
				continue
		else:
			if first_element != second_element:
				return false
			else:
				i += 1
				continue

		i += 1

	return true

# ############################################################################ #

static func InkRuntime():
	return Engine.get_main_loop().root.get_node("__InkRuntime")
