extends Panel

# Borrowed from ProtonGraph

# Courtesy of pycbouh, original code can be found there:
# https://gist.github.com/pycbouh/7a88b55697138b646f69da0de40f71ac

# Node references
var graph_edit : GraphEdit
var graph_hscroll : HScrollBar
var graph_vscroll : VScrollBar

var minimap_button : Button

# Public properties
export var toolbar_icon : Texture

# Private properties
var _graph_nodes : Array = [] # of Dictionary
var _graph_lines : Array = [] # of Dictionary
var _camera_position : Vector2 = Vector2(100, 50)
var _camera_size : Vector2 = Vector2(200, 200)
var _graph_proportions : Vector2 = Vector2(1, 1)
var _zoom_level : float = 1.0

var _map_padding : Vector2 = Vector2(5, 5)
var _graph_padding : Vector2 = Vector2(0, 0)

var _is_pressing : bool = false


func _enter_tree() -> void:
	_update_node_references()


func _ready() -> void:
	_update_theme()
	_update_map()

	if (graph_edit):
		minimap_button = ToolButton.new()
		minimap_button.icon = toolbar_icon
		minimap_button.hint_tooltip = "Toggle graph minimap."
		graph_edit.get_zoom_hbox().add_child(minimap_button)
		minimap_button.connect("pressed", self, "_on_minimap_button_pressed")

		graph_edit.connect("draw", self, "_on_graph_edit_changed")
		graph_edit.connect("update_minimap", self, "_on_graph_edit_changed")

	# Update size based on editor scale
	rect_min_size *= 1.0 # EditorUtil.get_editor_scale()

	update()


func _gui_input(event) -> void:
	if (event is InputEventMouseButton && event.button_index == BUTTON_LEFT):
		if (event.is_pressed()):
			_is_pressing = true
			var click_location = _convert_to_graph_position(event.position - _map_padding) - _graph_padding

			if (graph_edit):
				var scroll_offset = get_scroll_offset()
				graph_edit.scroll_offset = click_location + scroll_offset - _camera_size / 2
		elif (_is_pressing):
			_is_pressing = false

		accept_event()
	elif (event is InputEventMouseMotion && _is_pressing):
		var click_location = _convert_to_graph_position(event.position - _map_padding) - _graph_padding

		if (graph_edit):
			var scroll_offset = get_scroll_offset()
			graph_edit.scroll_offset = click_location + scroll_offset - _camera_size / 2

		accept_event()


func _draw() -> void:
	var centering_offset = _convert_from_graph_position(_graph_padding)
	var map_offset = _map_padding + centering_offset

	for node in _graph_nodes:
		var position = _convert_from_graph_position(node.position) + map_offset
		var size = _convert_from_graph_position(node.size)
		var node_shape = Rect2(position - size / 2, size)

		if (node.is_comment):
			var comment_background = node.node_color.linear_interpolate(Color.black, 0.65)
			draw_rect(node_shape, comment_background, true)
			draw_rect(node_shape, node.node_color, false)
		else:
			draw_rect(node_shape, node.node_color)

	for line in _graph_lines:
		var from_position = _convert_from_graph_position(line.from_position) + map_offset
		var to_position = _convert_from_graph_position(line.to_position) + map_offset
		draw_line(from_position, to_position, line.from_color)

	var camera_center = _convert_from_graph_position(_camera_position + _camera_size / 2) + map_offset
	var camera_viewport = _convert_from_graph_position(_camera_size)
	var camera_position = (camera_center - camera_viewport / 2)
	draw_rect(Rect2(camera_position, camera_viewport), Color(0.65, 0.65, 0.65, 0.2), true)
	draw_rect(Rect2(camera_position, camera_viewport), Color(0.65, 0.65, 0.65, 0.45), false)


func _update_theme() -> void:
	if (!Engine.editor_hint || !is_inside_tree()):
		return

	modulate.a = 0.85


func _update_map() -> void:
	if (!graph_edit):
		return

	# Update graph spatial information
	var scroll_offset = get_scroll_offset()
	var graph_size = get_graph_size()

	_zoom_level = graph_edit.zoom
	_camera_position = graph_edit.scroll_offset - scroll_offset
	_camera_size = graph_edit.rect_size

	var render_size = get_render_size()
	var target_ratio = render_size.x / render_size.y
	var graph_ratio = graph_size.x / graph_size.y

	_graph_proportions = graph_size
	_graph_padding = Vector2(0, 0)
	if (graph_ratio > target_ratio):
		_graph_proportions.x = graph_size.x
		_graph_proportions.y = graph_size.x / target_ratio
		_graph_padding.y = abs(graph_size.y - _graph_proportions.y) / 2
	else:
		_graph_proportions.x = graph_size.y * target_ratio
		_graph_proportions.y = graph_size.y
		_graph_padding.x = abs(graph_size.x - _graph_proportions.x) / 2

	# Update node information
	_graph_nodes = []
	_graph_lines = []

	for child in graph_edit.get_children():
		if !(child is GraphNode):
			continue

		var node_data := {
			"position": (child.offset + child.rect_size / 2) * _zoom_level - scroll_offset,
			"size": child.rect_size * _zoom_level,
			"node_color": Color.white,
			"is_comment": child.comment,
		}

		var child_color = child.get("minimap_color")
		if (child_color):
			node_data.node_color = child_color

		_graph_nodes.append(node_data)

	for connection in graph_edit.get_connection_list():
		var from_child = graph_edit.get_node_or_null(connection.from)
		var to_child = graph_edit.get_node_or_null(connection.to)
		if (!from_child || !to_child):
			continue # We got caught in between two resources switching, some data is outdated

		var from_slot_position = from_child.rect_size if from_child.comment else from_child.rect_size / 2
		var to_slot_position = 0 if from_child.comment else to_child.rect_size / 2

		var line_data := {
			"from_position": (from_child.offset + from_slot_position) * _zoom_level - scroll_offset,
			"to_position": (to_child.offset + to_slot_position) * _zoom_level - scroll_offset,
			"from_color": Color.white,
			"to_color": Color.white,
		}

		var from_child_color = from_child.get("minimap_color")
		if (from_child_color):
			line_data.from_color = from_child_color
		var to_child_color = to_child.get("minimap_color")
		if (to_child_color):
			line_data.to_color = to_child_color

		_graph_lines.append(line_data)

	update()


func _update_node_references() -> void:
	var parent_node = get_parent()
	if !(parent_node is GraphEdit):
		return

	graph_edit = parent_node
	var top_layer
	for c in graph_edit.get_children():
		if c.get_class() == "GraphEditFilter":
			top_layer = c
			break
	for c in top_layer.get_children():
		if c is HScrollBar:
			graph_hscroll = c
		elif c is VScrollBar:
			graph_vscroll = c


### Helpers
func _convert_from_graph_position(graph_position : Vector2) -> Vector2:
	var position = Vector2(0, 0)
	var render_size = get_render_size()

	position.x = graph_position.x * render_size.x / _graph_proportions.x
	position.y = graph_position.y * render_size.y / _graph_proportions.y

	return position


func _convert_to_graph_position(position : Vector2) -> Vector2:
	var graph_position = Vector2(0, 0)
	var render_size = get_render_size()

	graph_position.x = position.x * _graph_proportions.x / render_size.x
	graph_position.y = position.y * _graph_proportions.y / render_size.y

	return graph_position


func get_render_size() -> Vector2:
	if (!is_inside_tree()):
		return Vector2.ZERO

	return rect_size - 2 * _map_padding


func get_scroll_offset() -> Vector2:
	if (!graph_hscroll || !graph_vscroll):
		return Vector2(0, 0)

	return Vector2(graph_hscroll.min_value, graph_vscroll.min_value)


func get_graph_size() -> Vector2:
	if (!graph_hscroll || !graph_vscroll):
		return Vector2(1, 1)

	var scroll_offset = get_scroll_offset()
	var graph_size = Vector2(graph_hscroll.max_value, graph_vscroll.max_value) - scroll_offset

	if (graph_size.x == 0):
		graph_size.x = 1
	if (graph_size.y == 0):
		graph_size.y = 1

	return graph_size


### Event handlers
func _on_graph_edit_changed() -> void:
	_update_map()


func _on_minimap_button_pressed() -> void:
	if (visible):
		hide()
	else:
		show()
