class_name MediaWindow
extends VSplitContainer

# Minimum height of the media thumbnail as a percentage of screen height
const MEDIA_THUMBNAIL_MIN_HEIGHT_RATIO: float = 0.15

export(PackedScene) var MediaListItem_scene: PackedScene = load("res://scenes/custom_nodes/media_list_item/media_list_item.tscn")
export(PackedScene) var ChannelCard_scene: PackedScene = load("res://scenes/custom_nodes/channel_card/channel_card.tscn")
export(String, FILE) var default_thumbnail_path: String = "res://icon.png"

# URL to get the media through the LBRY protocol
var _lbry_url: String = ""
# URL of the channel on the LBRY blockchain
var _channel_url: String = ""
# All the resolved data for this `_lbry_url`
var _data: Dictionary = {}
# Right margin offset that should be applied so that the scroll bar hugs the
# edge of the window
var _main_scroll_container_offset: int = 0
var _comments_scroll_container_offset: int = 0
var _initialized: bool = false
var _async_helper := AsyncHelper.new(self)
onready var _default_thumbnail: Image = load(default_thumbnail_path).get_data()
onready var _title_node: Label = find_node("Title")
onready var _description_node: Label = find_node("Description")
onready var _channel_button: ButtonContainer = find_node("ChannelButton")
onready var _channel_card: ChannelCard = find_node("ChannelCard")
onready var _media_thumbnail_button: ButtonContainer = \
		find_node("MediaThumbnailButton")
onready var _media_thumbnail_node: TextureRect = find_node("MediaThumbnail")
onready var _media_download_button: ButtonContainer = \
		find_node("MediaDownloadButton")
onready var _main_margin_container: MarginContainer = \
		find_node("MainMarginContainer")
onready var _main_scroll_container: ScrollContainer = \
		find_node("MainScrollContainer")
onready var _comments_node: VBoxContainer = find_node("Comments")
onready var _comments_margin_container: MarginContainer = \
		find_node("CommentsMarginContainer")
onready var _comments_scroll_container: ScrollContainer = \
		find_node("CommentsScrollContainer")
onready var _suggestions_node: DynamicGridContainer = find_node("Suggestions")


func init(data: Dictionary) -> void:
	if _initialized:
		return
	
	_data = data
	_lbry_url = data.get("short_url", "")
	_channel_url = data.get("signing_channel", {}).get("short_url", "")
	_initialized = true


func _ready() -> void:
	_media_thumbnail_button.connect("pressed", self, "_open")
	_media_download_button.connect(
			"pressed", self, "_on_MediaDownloadButton_pressed")
	_channel_button.connect(
			"pressed", self, "_open_channel_window", [_channel_url])
	_comments_node.connect("channel_pressed", self, "_open_channel_window")
	_main_scroll_container.get_v_scrollbar().connect("visibility_changed",
			self, "_on_MainScrollContainer_v_scrollbar_visibility_changed")
	_comments_scroll_container.get_v_scrollbar().connect("visibility_changed",
			self, "_on_CommentsScrollContainer_v_scrollbar_visibility_changed")
	
	# Set the split offset to initially only show the first child
	set_split_offset(int(OS.get_window_size().y / 2))
	
	_suggestions_node.set_item_size(MediaListItem.SIZE)
	
	yield(_setup(), "completed")
	_comments_node.init(_data.get("claim_id", ""))
	_show_suggestions(yield(_get_suggestions(), "completed"))


func set_title(title: String) -> void:
	_title_node.set_text(title)


func set_description(description: String) -> void:
	_description_node.set_text(description)


func set_media_thumbnail(thumbnail: ImageTexture) -> void:
	_media_thumbnail_node.set_texture(thumbnail)


func _setup() -> void:
	if len(_data) == 0:
		var tracker: Dictionary = _async_helper.connect_wrapped(
				get_tree(), "idle_frame", false)
		yield(_async_helper.wait_until_finished([tracker]), "completed")
		return
	
	var title: String
	var description: String
	var media_thumbnail := ImageTexture.new()
	
	title = _data.get("value", {}).get("title", "")
	description = _data.get("value", {}).get("description", "")
	
	var media_thumbnail_url: String = _data \
			.get("value", {}) \
			.get("thumbnail", {}) \
			.get("url", "")
	var tracker: Dictionary = _async_helper.connect_wrapped(
			Requests.get_image(media_thumbnail_url), "completed")
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	var media_thumbnail_img: Image = tracker["result"]
	if media_thumbnail_img.is_empty():
		media_thumbnail_img = _default_thumbnail
	media_thumbnail.create_from_image(media_thumbnail_img)
	
	var channel_card: ChannelCard = ChannelCard_scene.instance()
	channel_card.init(_channel_url)
	
	set_title(title)
	set_description(description)
	set_media_thumbnail(media_thumbnail)
	_media_thumbnail_node.set_tooltip(_lbry_url)
	_media_thumbnail_node.set_custom_minimum_size(
			Vector2(
				_media_thumbnail_node.get_custom_minimum_size().x,
				OS.get_screen_size().y * MEDIA_THUMBNAIL_MIN_HEIGHT_RATIO
			))
	_channel_card.get_parent().add_child_below_node(
			_channel_card, channel_card)
	_channel_card.queue_free()
	_channel_card = channel_card
	_make_scroll_bars_hug_window_edge()


func _get_suggestions() -> Array:
	"""
	Finds media that is related to the current one by words in the title
	
	Must be called after `_setup()` has completed
	"""
	var media: Array = []
	var args: Dictionary = {
		"text": _title_node.get_text(),
		"any_tags": _data.get("value", {}).get("tags", []),
		"claim_type": "stream",
		"order_by": ["trending_mixed"],
		"no_totals": true,
		"remove_duplicates": true
	}
	var permanent_search_options: Dictionary = Data.get_search_options()
	Helpers.dictionary_merge_into(args, permanent_search_options, true, false)
	var tracker: Dictionary = _async_helper.connect_wrapped(
			SDK.claim_search(args), "completed")
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	var search_response: Dictionary = tracker["result"]
	media = search_response.get("result", {}).get("items", [])
	
	for i in range(len(media) - 1, -1, -1):
		if media[i].get("short_url", "") == _lbry_url:
			media.remove(i)
			break
	return media


func _show_suggestions(suggestions: Array) -> void:
	for item in suggestions:
		var item_node: MediaListItem = MediaListItem_scene.instance()
		var channel_url: String = item \
				.get("signing_channel", {}) \
				.get("short_url", "")
		item_node.init(item)
		item_node.connect_media_button(
				"pressed", self, "_open_media_window", [item])
		item_node.connect_channel_button(
				"pressed", self, "_open_channel_window", [channel_url])
		_suggestions_node.add_child(item_node)


func _make_scroll_bars_hug_window_edge() -> void:
	var main_scroll_container_right_edge: float = (
			_main_scroll_container.get_global_position().x
			+ _main_scroll_container.get_size().x
	)
	var main_diff := int(
			OS.get_window_size().x - main_scroll_container_right_edge)
	_main_scroll_container_offset = -main_diff
	if _main_scroll_container.get_v_scrollbar().is_visible_in_tree():
		_main_margin_container.add_constant_override(
				"margin_right", _main_scroll_container_offset)
	
	var comments_scroll_container_right_edge: float = (
			_comments_scroll_container.get_global_position().x
			+ _comments_scroll_container.get_size().x
	)
	var comments_diff := int(
			OS.get_window_size().x - comments_scroll_container_right_edge)
	_comments_scroll_container_offset = -comments_diff
	if _comments_scroll_container.get_v_scrollbar().is_visible_in_tree():
		_comments_margin_container.add_constant_override(
			"margin_right", _comments_scroll_container_offset)


func _open() -> void:
	""" Opens the media with the appropriate program """
	var mime_type: String = ""
	var streaming_url: String = ""
	var download_path: String = ""
	var download_completed: bool = false
	
	if _lbry_url != "":
		var args: Dictionary = {"uri": _lbry_url}
		var tracker: Dictionary = _async_helper.connect_wrapped(
				SDK.gets(args), "completed")
		yield(_async_helper.wait_until_finished([tracker]), "completed")
		var get_response: Dictionary = tracker["result"]
		if not get_response.empty():
			mime_type = \
					get_response.get("result", {}).get("mime_type", "")
			streaming_url = \
					get_response.get("result", {}).get("streaming_url", "")
			# If the media hasn't been downloaded then the "download_path"
			# result will be set to `null`
			if get_response.get("result", {}).get("download_path", "") != null:
				download_path = \
						get_response.get("result", {}).get("download_path", "")
			download_completed = \
					get_response.get("result", {}).get("completed", false)
		else:
			var popup := AcceptDialog.new()
			popup.set_text("Failed to retrieve media")
			add_child(popup)
			popup.popup_centered()
			yield(popup, "popup_hide")
			popup.queue_free()
		
		if download_completed and not download_path.empty():
			MediaHandler.open(download_path, mime_type)
		elif not streaming_url.empty():
			MediaHandler.open(streaming_url, mime_type)


func _download(path: String) -> void:
	"""
	Downloads the media to `path`. Displays a popup showing the download state
	
	Args:
		path: where to download the media to
	"""
	var get_response: Dictionary = {}
	if _lbry_url != "":
		var args: Dictionary = {
			"uri": _lbry_url,
			"file_name": path.get_file(),
			"download_directory": path.get_base_dir()
		}
		var tracker: Dictionary = _async_helper.connect_wrapped(
				SDK.gets(args), "completed")
		yield(_async_helper.wait_until_finished([tracker]), "completed")
		get_response = tracker["result"]
	
	if not get_response.empty():
		var result_download_path = \
				get_response.get("result", {}).get("download_path")
		var completed_download: bool = \
				get_response.get("result", {}).get("completed")
		# Download failed
		if result_download_path == null:
			var msg: String = "Failed to download the media.\n\n"
			msg += "Please make sure you entered a valid filepath\n"
			msg += "which you are able to write to."
			var accept_dialogue := AcceptDialog.new()
			accept_dialogue.set_text(msg)
			add_child(accept_dialogue)
			accept_dialogue.popup_centered()
			yield(accept_dialogue, "popup_hide")
			accept_dialogue.queue_free()
		# Download started
		elif result_download_path != null and not completed_download:
			var msg: String = "Downloading to:\n%s" % result_download_path
			var accept_dialogue := AcceptDialog.new()
			accept_dialogue.set_text(msg)
			add_child(accept_dialogue)
			accept_dialogue.popup_centered()
			yield(accept_dialogue, "popup_hide")
			accept_dialogue.queue_free()
		# Already downloaded
		elif result_download_path != null and completed_download:
			var msg: String = \
					"Already downloaded to:\n%s" % result_download_path
			var accept_dialogue := AcceptDialog.new()
			accept_dialogue.set_text(msg)
			add_child(accept_dialogue)
			accept_dialogue.popup_centered()
			yield(accept_dialogue, "popup_hide")
			accept_dialogue.queue_free()
	else:
		var popup := AcceptDialog.new()
		popup.set_text("Failed to retrieve media")
		add_child(popup)
		popup.popup_centered()
		yield(popup, "popup_hide")
		popup.queue_free()


func _open_media_window(data: Dictionary) -> void:
	# Can't specify the exact node type here because it causes a cyclic
	# reference error
	var media_window: Control = \
			get_parent().get_window_type("MediaWindow").instance()
	media_window.init(data)
	get_parent().set_window(media_window)


func _open_channel_window(channel_url: String) -> void:
	# Can't specify the exact node type here because it causes a cyclic
	# reference error
	var channel_window: Control = \
			get_parent().get_window_type("ChannelWindow").instance()
	channel_window.init(channel_url)
	get_parent().set_window(channel_window)


func _on_MediaDownloadButton_pressed() -> void:
	var file_dialog := FileDialog.new()
	file_dialog.connect("file_selected", self, "_download")
	file_dialog.set_access(FileDialog.ACCESS_FILESYSTEM)
	file_dialog.set_exclusive(true)
	file_dialog.set_resizable(true)
	add_child(file_dialog)
	file_dialog.popup_centered_minsize(Vector2(100, 200))
	yield(file_dialog, "popup_hide")
	file_dialog.queue_free()


func _on_MainScrollContainer_v_scrollbar_visibility_changed() -> void:
	""" Ensure the margin is maintained when the scroll bar is not visible """
	var right_margin: int = (
			_main_scroll_container_offset
			if _main_scroll_container.get_v_scrollbar().is_visible_in_tree()
			else 0
	)
	_main_margin_container.add_constant_override("margin_right", right_margin)


func _on_CommentsScrollContainer_v_scrollbar_visibility_changed() -> void:
	""" Ensure the margin is maintained when the scroll bar is not visible """
	var right_margin: int = (
			_comments_scroll_container_offset
			if _comments_scroll_container \
					.get_v_scrollbar() \
					.is_visible_in_tree()
			else 0
	)
	_comments_margin_container.add_constant_override(
			"margin_right", right_margin)
