tool
extends HSplitContainer

var editor_reference
var timeline_name: String = ''
var timeline_file: String = ''
var current_timeline: Dictionary = {}
var TimelineUndoRedo := UndoRedo.new()

onready var master_tree = get_node('../MasterTreeContainer/MasterTree')
onready var timeline = $TimelineArea/TimeLine
onready var events_warning = $ScrollContainer/EventContainer/EventsWarning
onready var custom_events_container = $ScrollContainer/EventContainer/CustomEventsContainer

var hovered_item = null
var selected_style : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres")
var selected_style_text : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat_text_event.tres")
var selected_style_template : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat_template.tres")
var saved_style : StyleBoxFlat
var selected_items : Array = []

var event_scenes : Dictionary = {}

var currently_draged_event_type = null
var move_start_position = null
var moving_piece = null
var piece_was_dragged = false

var custom_events = {}

var id_to_scene_name = {
	#Main events
	'dialogic_001':'TextEvent',
	'dialogic_002':'CharacterJoin',
	'dialogic_003':'CharacterLeave',
	#Logic
	'dialogic_010':'Question',
	'dialogic_011':'Choice',
	'dialogic_012':'Condition',
	'dialogic_013':'EndBranch',
	'dialogic_014':'SetValue',
	#Timeline
	'dialogic_020':'ChangeTimeline',
	'dialogic_021':'ChangeBackground',
	'dialogic_022':'CloseDialog',
	'dialogic_023':'WaitSeconds',
	'dialogic_024':'SetTheme',
	'dialogic_025':'SetGlossary',
	'dialogic_026':'SaveEvent',
	#Audio
	'dialogic_030':'AudioEvent',
	'dialogic_031':'BackgroundMusic',
	#Godot
	'dialogic_040':'EmitSignal',
	'dialogic_041':'ChangeScene',
	'dialogic_042':'CallNode',
	}

var batches = []
var building_timeline = true
signal selection_updated
signal batch_loaded

func _ready():
	editor_reference = find_parent('EditorView')
	connect("batch_loaded", self, '_on_batch_loaded')
	var modifier = ''
	var _scale = get_constant("inspector_margin", "Editor")
	_scale = _scale * 0.125
	$ScrollContainer.rect_min_size.x = 180
	if _scale == 1.25:
		modifier = '-1.25'
		$ScrollContainer.rect_min_size.x = 200
	if _scale == 1.5:
		modifier = '-1.25'
		$ScrollContainer.rect_min_size.x = 200
	if _scale == 1.75:
		modifier = '-1.25'
		$ScrollContainer.rect_min_size.x = 390
	if _scale == 2:
		modifier = '-2'
		$ScrollContainer.rect_min_size.x = 390
	
	# We connect all the event buttons to the event creation functions
	
	for c in range(0, 5):
		for button in get_node("ScrollContainer/EventContainer/Grid"+str(c+1)).get_children():
			# Question
			if button.event_id == 'dialogic_010':
				button.connect('pressed', self, "_on_ButtonQuestion_pressed", [])
			# Condition
			elif button.event_id == 'dialogic_012':
				button.connect('pressed', self, "_on_ButtonCondition_pressed", [])
			else:
				button.connect('pressed', self, "_create_event_button_pressed", [button.event_id])
	
	var style = $TimelineArea.get('custom_styles/bg')
	style.set('bg_color', get_color("dark_color_1", "Editor"))
	
	update_custom_events()
	$TimelineArea.connect('resized', self, 'add_extra_scroll_area_to_timeline', [])
	

# handles dragging/moving of events
func _process(delta):
	if moving_piece != null:
		var current_position = get_global_mouse_position()
		var node_position = moving_piece.rect_global_position.y
		var height = get_block_height(moving_piece)
		var up_offset = get_block_height(get_block_above(moving_piece))
		var down_offset = get_block_height(get_block_below(moving_piece))
		if up_offset != null:
			up_offset = (up_offset / 2) + 5
			if current_position.y < node_position - up_offset:
				move_block(moving_piece, 'up')
				piece_was_dragged = true
		if down_offset != null:
			down_offset = height + (down_offset / 2) + 5
			if current_position.y > node_position + down_offset:
				move_block(moving_piece, 'down')
				piece_was_dragged = true


# SIGNAL handles input on the events mainly for selection and moving events
func _on_event_block_gui_input(event, item: Node):
	if event is InputEventMouseButton and event.button_index == 1:
		if (not event.is_pressed()):
			if (piece_was_dragged and moving_piece != null and move_start_position):
				var to_position = moving_piece.get_index()
				if move_start_position != to_position:
					# move it back so the DO action works. (Kinda stupid but whatever)
					move_block_to_index(to_position, move_start_position)
					TimelineUndoRedo.create_action("[D] Moved event (type '"+moving_piece.event_data.event_id+"').")
					TimelineUndoRedo.add_do_method(self, "move_block_to_index", move_start_position, to_position)
					TimelineUndoRedo.add_undo_method(self, "move_block_to_index", to_position, move_start_position)
					TimelineUndoRedo.commit_action()
				move_start_position = null
			if (moving_piece != null):
				
				indent_events()
			moving_piece = null
		elif event.is_pressed():
			moving_piece = item
			move_start_position = moving_piece.get_index()
			if not _is_item_selected(item):
				pass#piece_was_dragged = true
			else:
				piece_was_dragged = false
			select_item(item)


## *****************************************************************************
##					 	SHORTCUTS
## *****************************************************************************

func _input(event):
	# some shortcuts need to get handled in the common input event
	# especially CTRL-based
	# because certain godot controls swallow events (like textedit)
	# we protect this with is_visible_in_tree to not 
	# invoke a shortcut by accident
	if get_focus_owner() is TextEdit:
		return
	if (event is InputEventKey and event is InputEventWithModifiers and is_visible_in_tree()):
		# CTRL Z # UNDO
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_Z
			and event.echo == false
		):
			TimelineUndoRedo.undo()
			indent_events()
			get_tree().set_input_as_handled()
	if (event is InputEventKey and event is InputEventWithModifiers and is_visible_in_tree()):
		# CTRL +SHIFT+ Z # REDO
		if (event.pressed
			and event.alt == false
			and event.shift == true
			and event.control == true
			and event.scancode == KEY_Z
			and event.echo == false
		) or (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_Y
			and event.echo == false):
			TimelineUndoRedo.redo()
			indent_events()
			get_tree().set_input_as_handled()
	if (event is InputEventKey and event is InputEventWithModifiers and is_visible_in_tree()):
		# CTRL UP
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == false
			and event.scancode == KEY_UP
			and event.echo == false
		):
			# select previous
			if (len(selected_items) == 1):
				var prev = max(0, selected_items[0].get_index() - 1)
				var prev_node = timeline.get_child(prev)
				if (prev_node != selected_items[0]):
					selected_items = []
					select_item(prev_node)
				get_tree().set_input_as_handled()

			
		# CTRL DOWN
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == false
			and event.scancode == KEY_DOWN
			and event.echo == false
		):
			# select next
			if (len(selected_items) == 1):
				var next = min(timeline.get_child_count() - 1, selected_items[0].get_index() + 1)
				var next_node = timeline.get_child(next)
				if (next_node != selected_items[0]):
					selected_items = []
					select_item(next_node)
				get_tree().set_input_as_handled()
			
		# CTRL DELETE
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == false
			and event.scancode == KEY_DELETE
			and event.echo == false
		):
			if (len(selected_items) != 0):
				var events_indexed = get_events_indexed(selected_items)
				TimelineUndoRedo.create_action("[D] Deleting "+str(len(selected_items))+" event(s).")
				TimelineUndoRedo.add_do_method(self, "delete_events_indexed", events_indexed)
				TimelineUndoRedo.add_undo_method(self, "add_events_indexed", events_indexed)
				TimelineUndoRedo.commit_action()
				get_tree().set_input_as_handled()
			
		# CTRL T
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_T
			and event.echo == false
		):
			var at_index = -1
			if selected_items:
				at_index = selected_items[-1].get_index()+1
			else:
				at_index = timeline.get_child_count()
			TimelineUndoRedo.create_action("[D] Add Text event.")
			TimelineUndoRedo.add_do_method(self, "create_event", "dialogic_000", {'no-data': true}, true, at_index, true)
			TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 1)
			TimelineUndoRedo.commit_action()
			get_tree().set_input_as_handled()
			
		# CTRL A
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_A
			and event.echo == false
		):
			if (len(selected_items) != 0):
				select_all_items()
			get_tree().set_input_as_handled()
		
		# CTRL SHIFT A
		if (event.pressed
			and event.alt == false
			and event.shift == true
			and event.control == true
			and event.scancode == KEY_A
			and event.echo == false
		):
			if (len(selected_items) != 0):
				deselect_all_items()
			get_tree().set_input_as_handled()
		
		# CTRL C
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_C
			and event.echo == false
		):
			copy_selected_events()
			get_tree().set_input_as_handled()
		
		# CTRL V
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_V
			and event.echo == false
		):
			var events_list = paste_check()
			var paste_position = -1
			if selected_items:
				paste_position = selected_items[-1].get_index()
			else:
				paste_position = timeline.get_child_count()-1
			TimelineUndoRedo.create_action("[D] Pasting "+str(len(events_list))+" event(s).")
			TimelineUndoRedo.add_do_method(self, "add_events_at_index", events_list, paste_position)
			TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", paste_position+1, len(events_list))
			TimelineUndoRedo.commit_action()
			get_tree().set_input_as_handled()
		
		# CTRL X
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_X
			and event.echo == false
		):
			var events_indexed = get_events_indexed(selected_items)
			TimelineUndoRedo.create_action("[D] Cut "+str(len(selected_items))+" event(s).")
			TimelineUndoRedo.add_do_method(self, "cut_events_indexed", events_indexed)
			TimelineUndoRedo.add_undo_method(self, "add_events_indexed", events_indexed)
			TimelineUndoRedo.commit_action()
			get_tree().set_input_as_handled()

		# CTRL D
		if (event.pressed
			and event.alt == false
			and event.shift == false
			and event.control == true
			and event.scancode == KEY_D
			and event.echo == false
		):
			
			if len(selected_items) > 0:
				var events = get_events_indexed(selected_items).values()
				var at_index = selected_items[-1].get_index()
				TimelineUndoRedo.create_action("[D] Duplicate "+str(len(events))+" event(s).")
				TimelineUndoRedo.add_do_method(self, "add_events_at_index", events, at_index)
				TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, len(events))
				TimelineUndoRedo.commit_action()
			get_tree().set_input_as_handled()

func _unhandled_key_input(event):
	if (event is InputEventWithModifiers):
		# ALT UP
		if (event.pressed
			and event.alt == true 
			and event.shift == false 
			and event.control == false 
			and event.scancode == KEY_UP
			and event.echo == false
		):
			# move selected up
			if (len(selected_items) == 1):
				move_block(selected_items[0], "up")
				indent_events()
				get_tree().set_input_as_handled()
			
		# ALT DOWN
		if (event.pressed
			and event.alt == true 
			and event.shift == false 
			and event.control == false 
			and event.scancode == KEY_DOWN
			and event.echo == false
		):
			# move selected down
			if (len(selected_items) == 1):
				move_block(selected_items[0], "down")
				indent_events()
				get_tree().set_input_as_handled()

## *****************************************************************************
##					 	DELETING, COPY, PASTE
## *****************************************************************************

func get_events_indexed(events:Array) -> Dictionary:
	var indexed_dict = {}
	for event in events:
		indexed_dict[event.get_index()] = event.event_data.duplicate()
	return indexed_dict

func select_indexed_events(indexed_events:Dictionary) -> void:
	selected_items = []
	for event_index in indexed_events.keys():
		selected_items.append(timeline.get_child(event_index))

func add_events_indexed(indexed_events:Dictionary) -> void:
	var indexes = indexed_events.keys()
	indexes.sort()
	var events = []
	for event_idx in indexes:
		deselect_all_items()
		events.append(create_event(indexed_events[event_idx].event_id, indexed_events[event_idx]))
		timeline.move_child(events[-1], event_idx)
		
	selected_items = events
	visual_update_selection()

func delete_events_indexed(indexed_events:Dictionary) -> void:
	select_indexed_events(indexed_events)
	delete_selected_events()

func delete_selected_events():
	if len(selected_items) == 0:
		return
	
	# get next element
	var next = min(timeline.get_child_count() - 1, selected_items[-1].get_index() + 1)
	var next_node = timeline.get_child(next)
	if _is_item_selected(next_node):
		next_node = null
	
	for event in selected_items:
		event.get_parent().remove_child(event)
		event.queue_free()
	
	# select next
	if (next_node != null):
		select_item(next_node, false)
	else:
		if (timeline.get_child_count() > 0):
			next_node = timeline.get_child(max(0, timeline.get_child_count() - 1))
			if (next_node != null):
				select_item(next_node, false)
		else:
			deselect_all_items()
	
	indent_events()


func cut_selected_events():
	copy_selected_events()
	delete_selected_events()


func cut_events_indexed(indexed_events:Dictionary) -> void:
	select_indexed_events(indexed_events)
	cut_selected_events()


func copy_selected_events():
	if len(selected_items) == 0:
		return
	var event_copy_array = []
	for item in selected_items:
		event_copy_array.append(item.event_data)
	
	OS.clipboard = JSON.print(
		{
			"events":event_copy_array,
			"dialogic_version": editor_reference.version_string,
			"project_name": ProjectSettings.get_setting("application/config/name")
		})

func paste_check():
	var clipboard_parse = JSON.parse(OS.clipboard).result
	
	if typeof(clipboard_parse) == TYPE_DICTIONARY:
		if clipboard_parse.has("dialogic_version"):
			if clipboard_parse['dialogic_version'] != editor_reference.version_string:
				print("[D] Be careful when copying from older versions!")
		if clipboard_parse.has("project_name"):
			if clipboard_parse['project_name'] != ProjectSettings.get_setting("application/config/name"):
				print("[D] Be careful when copying from another project!")
		if clipboard_parse.has('events'):
			return clipboard_parse['events']

func remove_events_at_index(at_index:int, amount:int = 1)-> void:
	selected_items = []
	for i in range(0, amount):
		selected_items.append(timeline.get_child(at_index + i))
	delete_selected_events()

func add_events_at_index(event_list:Array, at_index:int) -> void:
	if at_index != -1:
		event_list.invert()
		selected_items = [timeline.get_child(at_index)]
	else:
		selected_items = []
	
	var new_items = []
	for item in event_list:
		if typeof(item) == TYPE_DICTIONARY and item.has('event_id'):
			new_items.append(create_event(item['event_id'], item))
	selected_items = new_items
	sort_selection()
	visual_update_selection()
	indent_events()

func paste_events_indexed(indexed_events):
	pass

func duplicate_events_indexed(indexed_events):
	pass

## *****************************************************************************
##					 	BLOCK SELECTION
## *****************************************************************************

func _is_item_selected(item: Node):
	return item in selected_items


func select_item(item: Node, multi_possible:bool = true):
	if item == null:
		return

	if Input.is_key_pressed(KEY_CONTROL) and multi_possible:
		# deselect the item if it is selected
		if _is_item_selected(item):
			selected_items.erase(item)
		else:
			selected_items.append(item)
	elif Input.is_key_pressed(KEY_SHIFT) and multi_possible:
		
		if len(selected_items) == 0:
			selected_items = [item]
		else:
			var index = selected_items[-1].get_index()
			var goal_idx = item.get_index()
			while true:
				if index < goal_idx: index += 1
				else: index -= 1
				if not timeline.get_child(index) in selected_items:
					selected_items.append(timeline.get_child(index))
				
				if index == goal_idx:
					break
	else:
		if len(selected_items) == 1:
			if _is_item_selected(item):
				selected_items.erase(item)
			else:
				selected_items = [item]
		else:
			selected_items = [item]
	
	sort_selection()
	
	visual_update_selection()


# checks all the events and sets their styles (selected/deselected)
func visual_update_selection():
	for item in timeline.get_children():
		item.visual_deselect()
	for item in selected_items:
		item.visual_select()


## Sorts the selection using 'custom_sort_selection'
func sort_selection():
	selected_items.sort_custom(self, 'custom_sort_selection')


## Compares two event blocks based on their position in the timeline
func custom_sort_selection(item1, item2):
	return item1.get_index() < item2.get_index()


## Helpers
func select_all_items():
	selected_items = []
	for event in timeline.get_children():
		selected_items.append(event)
	visual_update_selection()


func deselect_all_items():
	selected_items = []
	visual_update_selection()

## *****************************************************************************
##				SPECIAL BLOCK OPERATIONS
## *****************************************************************************

# SIGNAL handles the actions of the small menu on the right
func _on_event_options_action(action: String, item: Node):
	### WORK TODO
	if action == "remove":
		if len(selected_items) != 1 or (len(selected_items) == 1 and selected_items[0] != item):
			select_item(item, false)
		delete_selected_events()
	else:
		move_block(item, action)
	indent_events()


func delete_event(event):
	event.get_parent().remove_child(event)
	event.queue_free()


## *****************************************************************************
##				CREATING NEW EVENTS USING THE BUTTONS
## *****************************************************************************

# Event Creation signal for buttons
func _create_event_button_pressed(event_id):
	var at_index = -1
	if selected_items:
		at_index = selected_items[-1].get_index()+1
	else:
		at_index = timeline.get_child_count()
	TimelineUndoRedo.create_action("[D] Add event.")
	TimelineUndoRedo.add_do_method(self, "create_event", event_id, {'no-data': true}, true, at_index, true)
	TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 1)
	TimelineUndoRedo.commit_action()
	scroll_to_piece(at_index)
	indent_events()


# the Question button adds multiple blocks 
func _on_ButtonQuestion_pressed() -> void:
	var at_index = -1
	if selected_items:
		at_index = selected_items[-1].get_index()+1
	else:
		at_index = timeline.get_child_count()
	TimelineUndoRedo.create_action("[D] Add question events.")
	TimelineUndoRedo.add_do_method(self, "create_question", at_index)
	TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 4)
	TimelineUndoRedo.commit_action()

func create_question(at_position):
	if at_position == 0: selected_items = []
	else: selected_items = [timeline.get_child(at_position-1)]
	if len(selected_items) != 0:
		# Events are added bellow the selected node
		# So we must reverse the adding order
		create_event("dialogic_013", {'no-data': true}, true)
		create_event("dialogic_011", {'no-data': true}, true)
		create_event("dialogic_011", {'no-data': true}, true)
		create_event("dialogic_010", {'no-data': true}, true)
	else:
		create_event("dialogic_010", {'no-data': true}, true)
		create_event("dialogic_011", {'no-data': true}, true)
		create_event("dialogic_011", {'no-data': true}, true)
		create_event("dialogic_013", {'no-data': true}, true)


# the Condition button adds multiple blocks 
func _on_ButtonCondition_pressed() -> void:
	var at_index = -1
	if selected_items:
		at_index = selected_items[-1].get_index()+1
	else:
		at_index = timeline.get_child_count()
	TimelineUndoRedo.create_action("[D] Add condition events.")
	TimelineUndoRedo.add_do_method(self, "create_condition", at_index)
	TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 2)
	TimelineUndoRedo.commit_action()
	
func create_condition(at_position):
	if at_position == 0: selected_items = []
	else: selected_items = [timeline.get_child(at_position-1)]
	if len(selected_items) != 0:
		# Events are added bellow the selected node
		# So we must reverse the adding order
		create_event("dialogic_013", {'no-data': true}, true)
		create_event("dialogic_012", {'no-data': true}, true)
	else:
		create_event("dialogic_012", {'no-data': true}, true)
		create_event("dialogic_013", {'no-data': true}, true)


func update_custom_events() -> void:
	## CLEANUP
	custom_events = {}
	
	# cleaning the 'old' buttons
	for child in custom_events_container.get_children():
		child.queue_free()
	
	var path:String = "res://dialogic/custom-events"
	
	var dir = Directory.new()
	if dir.open(path) == OK:
		dir.list_dir_begin()
		var file_name = dir.get_next()
		# goes through all the folders in the custom events folder
		while file_name != "":
			# if it found a folder
			if dir.current_is_dir() and not file_name in ['.', '..']:
				# look through that folder
				#print("Found custom event folder: " + file_name)
				var event = load(path.plus_file(file_name).plus_file('EventBlock.tscn')).instance()
				if event:
					custom_events[event.event_data['event_id']] = {
						'event_block_scene' :path.plus_file(file_name).plus_file('EventBlock.tscn'),
						'event_name' : event.event_name,
						'event_icon' : event.event_icon
					}
					event.queue_free()
				else:
					print("[D] An error occurred when trying to access a custom event.")
				
				
			else:
				pass # files in the directory are ignored
			file_name = dir.get_next()
			
		# After we finishing checking, if any events exist, show the panel
		if custom_events.size() == 0:
			custom_events_container.hide()
			$ScrollContainer/EventContainer/CustomEventsHeadline.hide()
		else:
			custom_events_container.show()
			$ScrollContainer/EventContainer/CustomEventsHeadline.show()
	else:
		print("[D] An error occurred when trying to access the custom events folder.")
	
	## VISUAL UPDATE
	
	
	# adding new ones
	for custom_event_id in custom_events.keys():
		var button = load('res://addons/dialogic/Editor/TimelineEditor/SmallEventButton.tscn').instance()
		#button.set_script(preload("EventButton.gd"))
		button.event_id = custom_event_id
		button.visible_name = custom_events[custom_event_id]['event_name']
		button.self_modulate = Color('#494d58')
		button.hint_tooltip = custom_events[custom_event_id]['event_name']
		if custom_events[custom_event_id]['event_icon']:
			button.event_icon = custom_events[custom_event_id]['event_icon']
		button.connect("pressed", self, "_create_event_button_pressed", [custom_event_id])
		custom_events_container.add_child(button)

## *****************************************************************************
##					 	DRAG AND DROP
## *****************************************************************************

# Creates a ghost event for drag and drop
func create_drag_and_drop_event(event_id: String):
	var index = get_index_under_cursor()
	var piece = create_event(event_id)
	currently_draged_event_type = event_id
	timeline.move_child(piece, index)
	moving_piece = piece
	piece_was_dragged = true
	set_event_ignore_save(piece, true)
	select_item(piece)
	return piece


func drop_event():
	if moving_piece != null:
		var at_index = moving_piece.get_index()
		moving_piece.queue_free()
		TimelineUndoRedo.create_action("[D] Add event.")
		TimelineUndoRedo.add_do_method(self, "create_event", currently_draged_event_type, {'no-data': true}, true, at_index, true)
		TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 1)
		TimelineUndoRedo.commit_action()
		moving_piece = null
		piece_was_dragged = false
		indent_events()
		add_extra_scroll_area_to_timeline()


func cancel_drop_event():
	if moving_piece != null:
		moving_piece = null
		piece_was_dragged = false
		delete_selected_events()
		deselect_all_items()


## *****************************************************************************
##					 	CREATING THE TIMELINE
## *****************************************************************************

# Adding an event to the timeline
func create_event(event_id: String, data: Dictionary = {'no-data': true} , indent: bool = false, at_index: int = -1, auto_select: bool = false):
	var piece = null
	
	# check if it's a custom event
	if event_id in custom_events.keys():
		piece = load(custom_events[event_id]['event_block_scene']).instance()
	# check if it's a builtin event
	elif event_id in id_to_scene_name.keys():
		piece = load("res://addons/dialogic/Editor/Events/" + id_to_scene_name[event_id] + ".tscn").instance()
	# else use dummy event
	else:
		piece = load("res://addons/dialogic/Editor/Events/DummyEvent.tscn").instance()
	
	# load the piece with data
	piece.editor_reference = editor_reference
	
	if data.has('no-data') == false:
		piece.event_data = data
	
	if at_index == -1:
		if len(selected_items) != 0:
			timeline.add_child_below_node(selected_items[0], piece)
		else:
			timeline.add_child(piece)
	else:
		timeline.add_child(piece)
		timeline.move_child(piece, at_index)

	piece.connect("option_action", self, '_on_event_options_action', [piece])
	piece.connect("gui_input", self, '_on_event_block_gui_input', [piece])
	
	events_warning.visible = false
	if auto_select:
		select_item(piece, false)
	# Spacing
	add_extra_scroll_area_to_timeline()
	# Indent on create
	if indent:
		indent_events()
	return piece


func load_timeline(filename: String):
	clear_timeline()
	update_custom_events()
	if timeline_file != filename:
		TimelineUndoRedo.clear_history()
	building_timeline = true
	timeline_file = filename
	
	var data = DialogicResources.get_timeline_json(filename)
	if data['metadata'].has('name'):
		timeline_name = data['metadata']['name']
	else:
		timeline_name = data['metadata']['file']
	data = data['events']
	
	var page = 1
	var batch_size = 12
	while batch_events(data, batch_size, page).size() != 0:
		batches.append(batch_events(data, batch_size, page))
		page += 1
	load_batch(batches)
	# Reset the scroll position
	$TimelineArea.scroll_vertical = 0


func batch_events(array, size, batch_number):
	return array.slice((batch_number - 1) * size, batch_number * size - 1)


func load_batch(data):
	#print('[D] Loading batch')
	var current_batch = batches.pop_front()
	if current_batch:
		for i in current_batch:
			create_event(i['event_id'], i)
	emit_signal("batch_loaded")


func _on_batch_loaded():
	if batches.size() > 0:
		yield(get_tree().create_timer(0.01), "timeout")
		load_batch(batches)
	else:
		events_warning.visible = false
		indent_events()
		building_timeline = false
	add_extra_scroll_area_to_timeline()


func clear_timeline():
	deselect_all_items()
	for event in timeline.get_children():
		event.free()


## *****************************************************************************
##					 	BLOCK GETTERS
## *****************************************************************************

func get_block_above(block):
	var block_index = block.get_index()
	var item = null
	if block_index > 0:
		item = timeline.get_child(block_index - 1)
	return item


func get_block_below(block):
	var block_index = block.get_index()
	var item = null
	if block_index < timeline.get_child_count() - 1:
		item = timeline.get_child(block_index + 1)
	return item


func get_block_height(block):
	if block != null:
		return block.rect_size.y
	else:
		return null


func get_index_under_cursor():
	var current_position = get_global_mouse_position()
	var top_pos = 0
	for i in range(timeline.get_child_count()):
		var c = timeline.get_child(i)
		if c.rect_global_position.y < current_position.y:
			top_pos = i
	return top_pos


# ordering blocks in timeline
func move_block(block, direction):
	var block_index = block.get_index()
	if direction == 'up':
		if block_index > 0:
			timeline.move_child(block, block_index - 1)
			return true
	if direction == 'down':
		timeline.move_child(block, block_index + 1)
		return true
	return false

func move_block_to_index(block_index, index):
	timeline.move_child(timeline.get_child(block_index), index)

## *****************************************************************************
##					 TIMELINE CREATION AND SAVING
## *****************************************************************************


func create_timeline():
	timeline_file = 'timeline-' + str(OS.get_unix_time()) + '.json'
	var timeline = {
		"events": [],
		"metadata":{
			"dialogic-version": editor_reference.version_string,
			"file": timeline_file
		}
	}
	DialogicResources.set_timeline(timeline)
	return timeline

# Saving
func generate_save_data():
	var info_to_save = {
		'metadata': {
			'dialogic-version': editor_reference.version_string,
			'name': timeline_name,
			'file': timeline_file
		},
		'events': []
	}
	for event in timeline.get_children():
		# Checking that the event is not waiting to be removed
		# or that it is not a drag and drop placeholder
		if not get_event_ignore_save(event) and event.is_queued_for_deletion() == false:
			info_to_save['events'].append(event.event_data)
	return info_to_save


func set_event_ignore_save(event: Node, ignore: bool):
	event.ignore_save = ignore
	

func get_event_ignore_save(event: Node) -> bool:
	return event.ignore_save


func save_timeline() -> void:
	if timeline_file != '' and building_timeline == false:
		var info_to_save = generate_save_data()
		DialogicResources.set_timeline(info_to_save)
		#print('[+] Saving: ' , timeline_file)


## *****************************************************************************
##					 UTILITIES/HELPERS
## *****************************************************************************

# Scrolling
func scroll_to_piece(piece_index) -> void:
	var height = 0
	for i in range(0, piece_index):
		height += $TimelineArea/TimeLine.get_child(i).rect_size.y
	$TimelineArea.scroll_vertical = height

# Event Indenting
func indent_events() -> void:
	# Now indenting
	var indent: int = 0
	var starter: bool = false
	var event_list: Array = timeline.get_children()
	var question_index: int = 0
	var question_indent = {}
	if event_list.size() < 2:
		return
	# Resetting all the indents
	for event in event_list:
		var indent_node
		
		event.set_indent(0)
		
	# Adding new indents
	for event in event_list:
		# since there are indicators now, not all elements
		# in this list have an event_data property
		if (not "event_data" in event):
			continue
		
		
		if event.event_data['event_id'] == 'dialogic_011':
			if question_index > 0:
				indent = question_indent[question_index] + 1
				starter = true
		elif event.event_data['event_id'] == 'dialogic_010' or event.event_data['event_id'] == 'dialogic_012':
			indent += 1
			starter = true
			question_index += 1
			question_indent[question_index] = indent
		elif event.event_data['event_id'] == 'dialogic_013':
			if question_indent.has(question_index):
				indent = question_indent[question_index]
				indent -= 1
				question_index -= 1
				if indent < 0:
					indent = 0

		if indent > 0:
			# Keep old behavior for items without template
			if starter:
				event.set_indent(indent - 1)
			else:
				event.set_indent(indent)
		starter = false


# called from the toolbar
func fold_all_nodes():
	for event in timeline.get_children():
		event.set_expanded(false)
	add_extra_scroll_area_to_timeline()


# called from the toolbar
func unfold_all_nodes():
	for event in timeline.get_children():
		event.set_expanded(true)
	add_extra_scroll_area_to_timeline()


func add_extra_scroll_area_to_timeline():
	if timeline.get_children().size() > 4:
		timeline.rect_min_size.y = 0
		timeline.rect_size.y = 0
		if timeline.rect_size.y + 200 > $TimelineArea.rect_size.y:
			timeline.rect_min_size = Vector2(0, timeline.rect_size.y + 200)
