tool
extends Control

var text_speed := 0.02 # Higher = lower speed
var theme_text_speed = text_speed
var theme_text_max_height = 0

#experimental database of current commands
var commands = []
#the regex matching object
var regex = RegEx.new()
var bbcoderemoverregex = RegEx.new()

onready var text_container = $TextContainer
onready var text_label = $TextContainer/RichTextLabel
onready var name_label = $NameLabel
onready var next_indicator = $NextIndicatorContainer/NextIndicator

var _finished := false
var _theme

signal text_completed()
signal letter_written(lastLetter)
signal signal_request(arg)

## *****************************************************************************
##								PUBLIC METHODS
## *****************************************************************************


func update_name(name: String, color: Color = Color.white, autocolor: bool=false) -> void:
	var name_is_hidden = _theme.get_value('name', 'is_hidden', false)
	if name_is_hidden:
		name_label.visible = false
		return
	
	if not name.empty():
		# Hack to reset the size
		name_label.rect_min_size = Vector2(0, 0)
		name_label.rect_size = Vector2(-1, 40)
		# Setting the color and text
		name_label.text = name
		# Alignment
		call_deferred('align_name_label')
		if autocolor:
			name_label.set('custom_colors/font_color', color)
		
		name_label.visible = true
	else:
		name_label.visible = false

func clear():
	text_label.bbcode_text = ""
	name_label.text = ""
	$WritingTimer.stop()

func update_text(text:String):
	
	var orig_text = text
	text_label.bbcode_text = text
	var text_bbcodefree = text_label.text
	
	#regex moved from func scope to class scope
	#regex compilation moved to _ready
	#  - KvaGram
	#var regex = RegEx.new()
	var result:RegExMatch = null
	text_speed = theme_text_speed # Resetting the speed to the default
	commands = []
	
	### remove commands from text, and store where and what they are
	#current regex: \[\s*(nw|(nw|speed|signal|play|pause)\s*=\s*(.+?)\s*)\](.*?)
	#Note: The version defined in _ready will have aditional escape characers.
	#      DO NOT JUST COPY/PASTE
	#remeber regex101.com is your friend. Do not shoot it. You may ask it to verify the code.
	#The capture groups, and what they do:
	# 0 everything ex [speed=5]
	# 1 the "nw" single command or one of the variable commands ex "nw" or "speed=5"
	# 2 the command, assuming it is an variable command ex "speed"
	# 3 the argument, again assuming a variable command ex "5"
	# 4 nothing (ignore it)
	#keep this up to date whenever the regex string is updated! - KvaGram
	
	result = regex.search(text_bbcodefree)
	#loops until all commands are cleared from the text
	while result:
		if result.get_string(1) == "nw" || result.get_string(2) == "nw":
			#The no wait command is handled elsewhere. Ignore it.
			pass
		else:
			#Store an assigned varible command as an array by 0 index in text, 1 command-name, 2 argument
			commands.append([result.get_start()-1, result.get_string(2).strip_edges(), result.get_string(3).strip_edges()])
		text_bbcodefree = text_bbcodefree.substr(0, result.get_start()) + text_bbcodefree.substr(result.get_end())
		text = text.replace(result.get_string(), "")
		
		result = regex.search(text_bbcodefree)

	text_label.bbcode_text = text
	text_label.visible_characters = 0

	## SIZING THE RICHTEXTLABEL
	# The sizing is done in a very terrible way because the RichtTextLabel has 
	# a hard time knowing what size it will be and how to display this.
	# for this reason the RichTextLabel ist first set to just go for the size it needs,
	# even if this might be more than available.
	text_label.size_flags_vertical = 0
	text_label.rect_clip_content = 0
	text_label.fit_content_height = true
	# a frame later, when the sizes have been updated, it will check if there 
	# is enough space or the scrollbar should be activated.
	call_deferred("update_sizing")
	
	
	# updating the size alignment stuff
	text_label.grab_focus()
	start_text_timer()
	return true

func update_sizing():
	# this will enable/disable the scrollbar based on the size of the text
	theme_text_max_height = text_container.rect_size.y

	if text_label.rect_size.y >= theme_text_max_height:
		text_label.fit_content_height = false
		text_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
	else:
		text_label.fit_content_height = true
		text_label.size_flags_vertical = 0


#handle an activated command.
func handle_command(command:Array):
	if(command[1] == "speed"):
		text_speed = float(command[2]) * 0.01
		$WritingTimer.stop()
		start_text_timer()
	elif(command[1] == "signal"):
		emit_signal("signal_request", command[2])
	elif(command[1] == "play"):
		var path = "res://dialogic/sounds/" + command[2]
		if ResourceLoader.exists(path, "AudioStream"):
			var audio:AudioStream = ResourceLoader.load(path, "AudioStream")
			$sounds.stream = audio
			$sounds.play()
	elif(command[1] == "pause"):
		$WritingTimer.stop()
		var x = text_label.visible_characters
		get_parent().get_node("DialogicTimer").start(float(command[2]))
		yield(get_parent().get_node("DialogicTimer"), "timeout")
		# only continue, if no skip was performed
		if text_label.visible_characters == x: 
			start_text_timer()
		

func skip():
	text_label.visible_characters = -1
	_handle_text_completed()


func reset():
	name_label.text = ''
	name_label.visible = false


func load_theme(theme: ConfigFile):
	# Text
	var theme_font = DialogicUtil.path_fixer_load(theme.get_value('text', 'font', 'res://addons/dialogic/Example Assets/Fonts/DefaultFont.tres'))
	text_label.set('custom_fonts/normal_font', theme_font)
	text_label.set('custom_fonts/bold_font', DialogicUtil.path_fixer_load(theme.get_value('text', 'bold_font', 'res://addons/dialogic/Example Assets/Fonts/DefaultBoldFont.tres')))
	text_label.set('custom_fonts/italics_font', DialogicUtil.path_fixer_load(theme.get_value('text', 'italic_font', 'res://addons/dialogic/Example Assets/Fonts/DefaultItalicFont.tres')))
	name_label.set('custom_fonts/font', DialogicUtil.path_fixer_load(theme.get_value('name', 'font', 'res://addons/dialogic/Example Assets/Fonts/NameFont.tres')))
	
	# setting the vertical alignment
	var alignment = theme.get_value('text', 'alignment',0)
	if alignment <= 2: # top
		text_container.alignment = BoxContainer.ALIGN_BEGIN
	elif alignment <= 5: # center
		text_container.alignment = BoxContainer.ALIGN_CENTER
	elif alignment <= 8: # bottom
		text_container.alignment = BoxContainer.ALIGN_END
	
	var text_color = Color(theme.get_value('text', 'color', '#ffffffff'))
	text_label.set('custom_colors/default_color', text_color)
	name_label.set('custom_colors/font_color', text_color)

	text_label.set('custom_colors/font_color_shadow', Color('#00ffffff'))
	name_label.set('custom_colors/font_color_shadow', Color('#00ffffff'))

	if theme.get_value('text', 'shadow', false):
		var text_shadow_color = Color(theme.get_value('text', 'shadow_color', '#9e000000'))
		text_label.set('custom_colors/font_color_shadow', text_shadow_color)

	var shadow_offset = theme.get_value('text', 'shadow_offset', Vector2(2,2))
	text_label.set('custom_constants/shadow_offset_x', shadow_offset.x)
	text_label.set('custom_constants/shadow_offset_y', shadow_offset.y)
	

	# Text speed
	text_speed = theme.get_value('text','speed', 2) * 0.01
	theme_text_speed = text_speed

	# Margin
	text_container.set('margin_left', theme.get_value('text', 'text_margin_left', 20))
	text_container.set('margin_right', theme.get_value('text', 'text_margin_right', -20))
	text_container.set('margin_top', theme.get_value('text', 'text_margin_top', 10))
	text_container.set('margin_bottom', theme.get_value('text', 'text_margin_bottom', -10))

	# Backgrounds
	$TextureRect.texture = DialogicUtil.path_fixer_load(theme.get_value('background','image', "res://addons/dialogic/Example Assets/backgrounds/background-2.png"))
	$ColorRect.color = Color(theme.get_value('background','color', "#ff000000"))

	if theme.get_value('background', 'modulation', false):
		$TextureRect.modulate = Color(theme.get_value('background', 'modulation_color', '#ffffffff'))
	else:
		$TextureRect.modulate = Color('#ffffffff')

	$ColorRect.visible = theme.get_value('background', 'use_color', false)
	$TextureRect.visible = theme.get_value('background', 'use_image', true)
	$TextureRect.visible = theme.get_value('background', 'use_image', true)
	$TextureRect.patch_margin_left = theme.get_value('ninepatch', 'ninepatch_margin_left', 0)
	$TextureRect.patch_margin_right = theme.get_value('ninepatch', 'ninepatch_margin_right', 0)
	$TextureRect.patch_margin_top = theme.get_value('ninepatch', 'ninepatch_margin_top', 0)
	$TextureRect.patch_margin_bottom = theme.get_value('ninepatch', 'ninepatch_margin_bottom', 0)

	# Next image
	$NextIndicatorContainer.rect_position = Vector2(0,0)
	next_indicator.texture = DialogicUtil.path_fixer_load(theme.get_value('next_indicator', 'image', 'res://addons/dialogic/Example Assets/next-indicator/next-indicator.png'))
	# Reset for up and down animation
	next_indicator.margin_top = 0 
	next_indicator.margin_bottom = 0 
	next_indicator.margin_left = 0 
	next_indicator.margin_right = 0 
	# Scale
	var indicator_scale = theme.get_value('next_indicator', 'scale', 0.4)
	next_indicator.rect_scale = Vector2(indicator_scale, indicator_scale)
	# Offset
	var offset = theme.get_value('next_indicator', 'offset', Vector2(13, 10))
	next_indicator.rect_position = theme.get_value('box', 'size', Vector2(910, 167)) - (next_indicator.texture.get_size() * indicator_scale)
	next_indicator.rect_position -= offset
	
	# Character Name
	$NameLabel/ColorRect.visible = theme.get_value('name', 'background_visible', false)
	$NameLabel/ColorRect.color = Color(theme.get_value('name', 'background', '#282828'))
	$NameLabel/TextureRect.visible = theme.get_value('name', 'image_visible', false)
	$NameLabel/TextureRect.texture = DialogicUtil.path_fixer_load(theme.get_value('name','image', "res://addons/dialogic/Example Assets/backgrounds/background-2.png"))
	
	var name_padding = theme.get_value('name', 'name_padding', Vector2( 10, 0 ))
	var name_style = name_label.get('custom_styles/normal')
	name_style.set('content_margin_left', name_padding.x)
	name_style.set('content_margin_right', name_padding.x)
	name_style.set('content_margin_bottom', name_padding.y)
	name_style.set('content_margin_top', name_padding.y)
	
	var name_shadow_offset = theme.get_value('name', 'shadow_offset', Vector2(2,2))
	if theme.get_value('name', 'shadow_visible', true):
		name_label.set('custom_colors/font_color_shadow', Color(theme.get_value('name', 'shadow', '#9e000000')))
		name_label.set('custom_constants/shadow_offset_x', name_shadow_offset.x)
		name_label.set('custom_constants/shadow_offset_y', name_shadow_offset.y)
	name_label.rect_position.y = theme.get_value('name', 'bottom_gap', 48) * -1 - (name_padding.y)
	if theme.get_value('name', 'modulation', false) == true:
		$NameLabel/TextureRect.modulate = Color(theme.get_value('name', 'modulation_color', '#ffffffff'))
	else:
		$NameLabel/TextureRect.modulate = Color('#ffffffff')
	
	
	# Setting next indicator animation
	next_indicator.self_modulate = Color('#ffffff')
	var animation = theme.get_value('next_indicator', 'animation', 'Up and down')
	next_indicator.get_node('AnimationPlayer').play(animation)
	
	# Saving reference to the current theme
	_theme = theme

## *****************************************************************************
##								PRIVATE METHODS
## *****************************************************************************


func _on_writing_timer_timeout():
	if _finished == false:
		text_label.visible_characters += 1
		if(commands.size()>0 && commands[0][0] <= text_label.visible_characters):
			handle_command(commands.pop_front()) #handles the command, and removes it from the queue
		if text_label.visible_characters > text_label.get_total_character_count():
			_handle_text_completed()
		elif (
			text_label.visible_characters > 0 #and 
			#text_label.text.length() > text_label.visible_characters-1 and 
			#text_label.text[text_label.visible_characters-1] != " "
		):
			emit_signal('letter_written', text_label.text[text_label.visible_characters-1] )
	else:
		$WritingTimer.stop()


func start_text_timer():
	if text_speed == 0:
		text_label.visible_characters = -1
		_handle_text_completed()
	else:
		$WritingTimer.start(text_speed)
		_finished = false


func _handle_text_completed():
	$WritingTimer.stop()
	_finished = true
	emit_signal("text_completed")


func align_name_label():
	var name_padding = _theme.get_value('name', 'name_padding', Vector2( 10, 0 ))
	var horizontal_offset = _theme.get_value('name', 'horizontal_offset', 0)
	var name_label_position = _theme.get_value('name', 'position', 0)
	var label_size = name_label.rect_size.x
	if name_label_position == 0:
		name_label.rect_global_position.x = rect_global_position.x + horizontal_offset
	elif name_label_position == 1: # Center
		name_label.rect_global_position.x = rect_global_position.x + (rect_size.x / 2) - (label_size / 2) + horizontal_offset
	elif name_label_position == 2: # Right
		name_label.rect_global_position.x = rect_global_position.x + rect_size.x - label_size + horizontal_offset

## *****************************************************************************
##								OVERRIDES
## *****************************************************************************


func _ready():
	reset()
	$WritingTimer.connect("timeout", self, "_on_writing_timer_timeout")
	text_label.meta_underlined = false
	regex.compile("\\[\\s*(nw|(nw|speed|signal|play|pause)\\s*=\\s*(.+?)\\s*)\\](.*?)")