@tool
class_name DialogicVariableEvent
extends DialogicEvent

## Event that allows changing a dialogic variable or a property of an autoload.


enum Operations {SET, ADD, SUBSTRACT, MULTIPLY, DIVIDE}
enum VarValueType {
	STRING = 0,
	NUMBER = 1,
	VARIABLE = 2,
	BOOL = 3,
	EXPRESSION = 4,
	RANDOM_NUMBER = 5,
}

## Settings

## Name/Path of the variable that should be changed.
var name := "":
	set(_value):
		name = _value
		if Engine.is_editor_hint() and not value:
			match DialogicUtil.get_variable_type(name):
				DialogicUtil.VarTypes.ANY, DialogicUtil.VarTypes.STRING:
					_value_type = VarValueType.STRING
				DialogicUtil.VarTypes.FLOAT, DialogicUtil.VarTypes.INT:
					_value_type = VarValueType.NUMBER
				DialogicUtil.VarTypes.BOOL:
					_value_type = VarValueType.BOOL
			ui_update_needed.emit()
		update_editor_warning()

## The operation to perform.
var operation := Operations.SET:
	set(value):
		operation = value
		if operation != Operations.SET and _value_type == VarValueType.STRING:
			_value_type = VarValueType.NUMBER
			ui_update_needed.emit()
		update_editor_warning()

## The value that is used. Can be a variable as well.
var value: Variant = ""
var _value_type := 0 :
	set(_value):
		_value_type = _value
		if not _suppress_default_value:
			match _value_type:
				VarValueType.STRING, VarValueType.VARIABLE, VarValueType.EXPRESSION:
					value = ""
				VarValueType.NUMBER:
					value = 0
				VarValueType.BOOL:
					value = false
				VarValueType.RANDOM_NUMBER:
					value = null
			ui_update_needed.emit()
		update_editor_warning()

## If true, a random number between [random_min] and [random_max] is used instead of [value].
var random_min: int = 0
var random_max: int = 100

## Used to suppress _value_type from overwriting value with a default value when the type changes
## This is only used when initializing the event_variable.
var _suppress_default_value := false


################################################################################
## 						EXECUTE
################################################################################

func _execute() -> void:
	if name:
		var original_value: Variant = dialogic.VAR.get_variable(name, null, operation == Operations.SET and "[" in name)

		if value != null and (original_value != null or (operation == Operations.SET and "[" in name)):

			var interpreted_value: Variant
			var result: Variant

			match _value_type:
				VarValueType.STRING:
					interpreted_value = dialogic.VAR.get_variable('"' + value + '"')
				VarValueType.VARIABLE:
					interpreted_value = dialogic.VAR.get_variable('{' + value + '}')
				VarValueType.NUMBER, VarValueType.BOOL, VarValueType.EXPRESSION, VarValueType.RANDOM_NUMBER:
					interpreted_value = dialogic.VAR.get_variable(str(value))

			if operation != Operations.SET and (not str(original_value).is_valid_float() or not str(interpreted_value).is_valid_float()):
				printerr("[Dialogic] Set Variable event failed because one value wasn't a float! [", original_value, ", ",interpreted_value,"]")
				finish()
				return

			if operation == Operations.SET:
				result = interpreted_value

			else:
				original_value = float(original_value)
				interpreted_value = float(interpreted_value)

				match operation:
					Operations.ADD:
						result = original_value + interpreted_value
					Operations.SUBSTRACT:
						result = original_value - interpreted_value
					Operations.MULTIPLY:
						result = original_value * interpreted_value
					Operations.DIVIDE:
						result = original_value / interpreted_value

			dialogic.VAR.set_variable(name, result)
			dialogic.VAR.variable_was_set.emit(
				{
					'variable' : name,
					'value' : interpreted_value,
					'value_str' : value,
					'orig_value' : original_value,
					'new_value' : result,
				})

		else:
			printerr("[Dialogic] Set Variable event failed because one value wasn't set!")

	finish()


################################################################################
## 						INITIALIZE
################################################################################

func _init() -> void:
	event_name = "Set Variable"
	set_default_color('Color6')
	event_category = "Logic"
	event_sorting_index = 0
	help_page_path = "https://docs.dialogic.pro/variables.html#23-set-variable-event"


################################################################################
## 						SAVING/LOADING
################################################################################

func to_text() -> String:
	var string := "set "
	if name:
		string += "{" + name.trim_prefix('{').trim_suffix('}') + "}"
		match operation:
			Operations.SET:
				string+= " = "
			Operations.ADD:
				string+= " += "
			Operations.SUBSTRACT:
				string+= " -= "
			Operations.MULTIPLY:
				string+= " *= "
			Operations.DIVIDE:
				string+= " /= "

		value = str(value)
		match _value_type:
			VarValueType.STRING: # String
				string += '"'+value.replace('"', '\\"')+'"'
			VarValueType.NUMBER,VarValueType.BOOL,VarValueType.EXPRESSION: # Float Bool, or Expression
				string += str(value)
			VarValueType.VARIABLE: # Variable
				string += '{'+value+'}'
			VarValueType.RANDOM_NUMBER:
				string += 'range('+str(random_min)+','+str(random_max)+').pick_random()'

	return string


func from_text(string:String) -> void:
	var reg := RegEx.new()
	reg.compile("set(?<name>[^=+\\-*\\/]*)?(?<operation>=|\\+=|-=|\\*=|\\/=)?(?<value>.*)")
	var result := reg.search(string)
	if !result:
		return
	name = result.get_string('name').strip_edges().replace("{", "").replace("}", "")
	match result.get_string('operation').strip_edges():
		'=':
			operation = Operations.SET
		'-=':
			operation = Operations.SUBSTRACT
		'+=':
			operation = Operations.ADD
		'*=':
			operation = Operations.MULTIPLY
		'/=':
			operation = Operations.DIVIDE

	_suppress_default_value = true
	value = result.get_string('value').strip_edges()
	if not value.is_empty():
		if value.begins_with('"') and value.ends_with('"') and value.count('"')-value.count('\\"') == 2:
			value = result.get_string('value').strip_edges().replace('"', '')
			_value_type = VarValueType.STRING
		elif value.begins_with('{') and value.ends_with('}') and value.count('{') == 1:
			value = result.get_string('value').strip_edges().trim_suffix('}').trim_prefix('{')
			_value_type = VarValueType.VARIABLE
		elif value in ["true", "false"]:
			value = value == "true"
			_value_type = VarValueType.BOOL
		elif value.begins_with('range(') and value.ends_with(').pick_random()'):
			_value_type = VarValueType.RANDOM_NUMBER
			var randinf := str(value).trim_prefix('range(').trim_suffix(').pick_random()').split(',')
			random_min = int(randinf[0])
			random_max = int(randinf[1])
		else:
			value = result.get_string('value').strip_edges()
			if value.is_valid_float():
				_value_type = VarValueType.NUMBER
			else:
				_value_type = VarValueType.EXPRESSION
	else:
		value = null
	_suppress_default_value = false


func is_valid_event(string:String) -> bool:
	return string.begins_with('set')


################################################################################
## 						EDITOR REPRESENTATION
################################################################################

func build_event_editor() -> void:
	add_header_edit('name', ValueType.DYNAMIC_OPTIONS, {
			'left_text'		: 'Set',
			'suggestions_func' 	: get_var_suggestions,
			'icon' 					: load("res://addons/dialogic/Editor/Images/Pieces/variable.svg"),
			'placeholder'			:'Select Variable'}
			)
	add_header_edit('operation', ValueType.FIXED_OPTIONS, {
		'options': [
			{
				'label': 'to be',
				'icon': load("res://addons/dialogic/Editor/Images/Dropdown/set.svg"),
				'value': Operations.SET
			},{
				'label': 'to itself plus',
				'icon': load("res://addons/dialogic/Editor/Images/Dropdown/plus.svg"),
				'value': Operations.ADD
			},{
				'label': 'to itself minus',
				'icon': load("res://addons/dialogic/Editor/Images/Dropdown/minus.svg"),
				'value': Operations.SUBSTRACT
			},{
				'label': 'to itself multiplied by',
				'icon': load("res://addons/dialogic/Editor/Images/Dropdown/multiply.svg"),
				'value': Operations.MULTIPLY
			},{
				'label': 'to itself divided by',
				'icon': load("res://addons/dialogic/Editor/Images/Dropdown/divide.svg"),
				'value': Operations.DIVIDE
			}
		]
	}, '!name.is_empty()')
	add_header_edit('_value_type', ValueType.FIXED_OPTIONS, {
		'options': [
			{
				'label': 'String',
				'icon': ["String", "EditorIcons"],
				'value': VarValueType.STRING
			},{
				'label': 'Number',
				'icon': ["float", "EditorIcons"],
				'value': VarValueType.NUMBER
			},{
				'label': 'Variable',
				'icon': load("res://addons/dialogic/Editor/Images/Pieces/variable.svg"),
				'value': VarValueType.VARIABLE
			},{
				'label': 'Bool',
				'icon': ["bool", "EditorIcons"],
				'value': VarValueType.BOOL
			},{
				'label': 'Expression',
				'icon': ["Variant", "EditorIcons"],
				'value': VarValueType.EXPRESSION
			},{
				'label': 'Random Number',
				'icon': ["RandomNumberGenerator", "EditorIcons"],
				'value': VarValueType.RANDOM_NUMBER
			}],
		'symbol_only':true},
		'!name.is_empty()')
	add_header_edit('value', ValueType.SINGLELINE_TEXT, {}, '!name.is_empty() and (_value_type == VarValueType.STRING or _value_type == VarValueType.EXPRESSION) ')
	add_header_edit('value', ValueType.BOOL, {}, '!name.is_empty() and (_value_type == VarValueType.BOOL) ')
	add_header_edit('value', ValueType.NUMBER, {}, '!name.is_empty()  and _value_type == VarValueType.NUMBER')
	add_header_edit('value', ValueType.DYNAMIC_OPTIONS,
			{'suggestions_func' : get_value_suggestions, 'placeholder':'Select Variable'},
			'!name.is_empty() and _value_type == VarValueType.VARIABLE')
	add_header_label('a number between', '_value_type == VarValueType.RANDOM_NUMBER')
	add_header_edit('random_min', ValueType.NUMBER, {'right_text':'and', 'mode':1}, '!name.is_empty() and  _value_type == VarValueType.RANDOM_NUMBER')
	add_header_edit('random_max', ValueType.NUMBER, {'mode':1}, '!name.is_empty() and _value_type == VarValueType.RANDOM_NUMBER')
	add_header_button('', _on_variable_editor_pressed, 'Variable Editor', ["ExternalLink", "EditorIcons"])


func get_var_suggestions(filter:String) -> Dictionary:
	var suggestions := {}
	if filter:
		suggestions[filter] = {'value':filter, 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
	for var_path in DialogicUtil.list_variables(DialogicUtil.get_default_variables()):
		suggestions[var_path] = {'value':var_path, 'icon':load("res://addons/dialogic/Editor/Images/Pieces/variable.svg")}
	return suggestions


func get_value_suggestions(_filter:String) -> Dictionary:
	var suggestions := {}

	for var_path in DialogicUtil.list_variables(DialogicUtil.get_default_variables()):
		suggestions[var_path] = {'value':var_path, 'icon':load("res://addons/dialogic/Editor/Images/Pieces/variable.svg")}
	return suggestions


func _on_variable_editor_pressed() -> void:
	var editor_manager := editor_node.find_parent('EditorsManager')
	if editor_manager:
		editor_manager.open_editor(editor_manager.editors['VariablesEditor']['node'], true)


func update_editor_warning() -> void:
	if _value_type == VarValueType.STRING and operation != Operations.SET:
		ui_update_warning.emit('You cannot do this operation with a string!')
	elif operation != Operations.SET:
		var type := DialogicUtil.get_variable_type(name)
		if not type in [DialogicUtil.VarTypes.INT, DialogicUtil.VarTypes.FLOAT, DialogicUtil.VarTypes.ANY]:
			ui_update_warning.emit('The selected variable is not a number!')
		else:
			ui_update_warning.emit('')
	else:
		ui_update_warning.emit('')



####################### CODE COMPLETION ########################################
################################################################################

func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
	if CodeCompletionHelper.get_line_untill_caret(line) == 'set ':
		TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, '{', '{', TextNode.syntax_highlighter.variable_color)
	if symbol == '{':
		CodeCompletionHelper.suggest_variables(TextNode)


func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
	TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'set', 'set ', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5))


#################### SYNTAX HIGHLIGHTING #######################################
################################################################################

func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
	dict[line.find('set')] = {"color":event_color.lerp(Highlighter.normal_color, 0.5)}
	dict[line.find('set')+3] = {"color":Highlighter.normal_color}
	dict = Highlighter.color_region(dict, Highlighter.string_color, line, '"', '"', line.find('set'))
	dict = Highlighter.color_region(dict, Highlighter.variable_color, line, '{', '}', line.find('set'))
	return dict
