extends Node

signal all_statuses_updated

# Time between updating the status of downloads, in seconds
const STATUS_POLL_INTERVAL: int = 5

# Permanent LBRY URLs as keys and their download statuses as values
var _download_statuses: Dictionary = {}
# Permanent LBRY URLs as keys and commands to open them as values
var _open_when_downloaded: Dictionary = {}
var _status_poll_timer := Timer.new()


func _ready() -> void:
	connect("all_statuses_updated", self, "_open_downloaded")
	_status_poll_timer.connect("timeout", self, "_all_status_update")
	_status_poll_timer.set_wait_time(STATUS_POLL_INTERVAL)
	add_child(_status_poll_timer)
	_status_poll_timer.start()


func download(url: String, path: String) -> bool:
	"""
	Downloads the media indicated by `url` to `path`
	
	Args:
		url: permanent or short LBRY URL
		path: where to download the media to
	
	Returns:
		`true` if the download started successfully (or had already started),
		`false` otherwise
	"""
	# Must have yielded on each code path
	yield(get_tree(), "idle_frame")
	
	if not LBRYActions.is_lbry_url(url):
		push_error("Not a LBRY URL: %s" % url)
		return false
	
	var get_response: Dictionary = {}
	var args: Dictionary = {
		"uri": url,
		"file_name": path.get_file(),
		"download_directory": path.get_base_dir(),
	}
	get_response = yield(SDK.gets(args), "completed")
	
	# Check the response for errors
	var error: bool = false
	var error_msg: String = ""
	if get_response.get("result", "").empty():
		error = true
		error_msg = "Failed to download media: %s" % url
	elif not get_response.get("result", {}).get("error", "").empty():
		error = true
		error_msg = get_response["result"]["error"]
	
	if not error:
		var claim_id: String = get_response \
				.get("result", {}) \
				.get("claim_id", "")
		if not claim_id.empty():
			yield(_status_update(claim_id), "completed")
		else:
			var warning_msg: String = "Could not retrieve the claim ID for URL"
			warning_msg += " %s" % url
			push_warning(warning_msg)
		
		if _status_poll_timer.is_stopped():
			_status_poll_timer.start()
		return true
	else:
		push_warning(error_msg)
		return false


func set_open_when_downloaded(url: String, should_open: bool,
		command: String="") -> void:
	"""
	Args:
		url: permanent or short LBRY URL
		should_open: if the media should be automatically opened once it has
			fully downloaded
		command: command to open the media with
	"""
	var permanent_url: String = ""
	var resolve_response: Dictionary = yield(SDK.resolve([url]), "completed")
	if len(resolve_response.get("result", {})) > 0:
		permanent_url = resolve_response["result"] \
				.get(resolve_response["result"].keys()[0]) \
				.get("permanent_url", "")
	if permanent_url.empty():
		return
	
	if not should_open:
		_open_when_downloaded.erase(permanent_url)
	else:
		_open_when_downloaded[permanent_url] = command


func get_status(url: String) -> Dictionary:
	"""
	Args:
		url: permanent or short LBRY URL
	
	Returns:
		The download status of the URL at the last status update. If there was
		an error retrieving the status or the download hadn't been started then
		returns an empty Dictionary
	"""
	var status: Dictionary = {}
	var resolve_response: Dictionary = yield(SDK.resolve([url]), "completed")
	if len(resolve_response.get("result", {})) > 0:
		var permanent_url: String = resolve_response["result"] \
				.get(resolve_response["result"].keys()[0]) \
				.get("permanent_url", "")
		status = _download_statuses.get(permanent_url, {}).duplicate(true)
	return status


func get_all_statuses() -> Dictionary:
	"""
	Returns:
		The status of all media that was fully or partially downloaded and
		stored by the SDK
	"""
	return _download_statuses.duplicate(true)


func get_downloading_statuses() -> Dictionary:
	"""
	Returns:
		The download status info of the permanent URLs that are currently being
		downloaded to a file
	"""
	var downloading: Dictionary = {}
	for permanent_url in _download_statuses:
		var status: Dictionary = _download_statuses[permanent_url]
		if (status.get("completed", true) == false
				and status.get("download_path", null) != null
				and status.get("stopped", true) == false):
			downloading[permanent_url] = status.duplicate(true)
	return downloading


func get_downloaded_statuses() -> Dictionary:
	"""
	Returns:
		The download status of the permanent URLs that are fully downloaded to
		a file
	"""
	var downloaded: Dictionary = {}
	for permanent_url in _download_statuses:
		var status: Dictionary = _download_statuses[permanent_url]
		if status.get("completed", false) == true:
			downloaded[permanent_url] = status.duplicate(true)
	return downloaded


func force_status_update(url: String) -> void:
	"""
	Updates the download status of the URL immediately
	
	Args:
		url: permanent or short LBRY URL
	"""
	var resolve_response: Dictionary = yield(SDK.resolve([url]), "completed")
	if len(resolve_response.get("result", {})) > 0:
		var claim_id: String = resolve_response["result"] \
				.get(resolve_response["result"].keys()[0]) \
				.get("claim_id", "")
		if not claim_id.empty():
			yield(_status_update(claim_id), "completed")


func _status_update(claim_id: String) -> void:
	"""
	Updates the download status of the given claim. If unable to retrieve the
	status then removes the entry from the `_download_statuses` cache
	"""
	var args: Dictionary = {"claim_id": claim_id}
	var file_list_response: Dictionary = yield(
			SDK.file_list(args), "completed")
	
	# Get the claim name
	var claim_name: String = ""
	if len(file_list_response.get("result", {}).get("items", [])) > 0:
		claim_name = file_list_response["result"]["items"][0].get(
				"claim_name", "")
	
	# Check the response for errors
	var error: bool = false
	var error_msg: String = ""
	if file_list_response.get("result", {}).empty():
		error = true
		error_msg = "Failed to get the download status of claim %s" % claim_id
	elif not file_list_response.get("result", {}).get("error", "").empty():
		error = true
		error_msg = file_list_response["result"]["error"]
	elif claim_name.empty():
		error = true
		error_msg = "Could not retrieve the claim name"
	
	if not error:
		var permanent_url: String = "lbry://{0}#{1}" \
				.format([claim_name, claim_id])
		_download_statuses[permanent_url] = \
				file_list_response["result"]["items"][0]
	else:
		push_warning(error_msg)
		# Erase the claim from the status cache
		for current_perm_url in _download_statuses.keys():
			if _download_statuses[current_perm_url].get("claim_id", "") == claim_id:
				_download_statuses.erase(current_perm_url)
				break


func _all_status_update() -> void:
	""" Cache the statuses of all downloads """
	var file_list_response: Dictionary = yield(
			SDK.file_list({"page": 1}), "completed")
	var files: Array = file_list_response.get("result", {}).get("items", [])
	var last_page: int = 1
	var total_pages: int = file_list_response \
			.get("result", {}) \
			.get("total_pages", 1)
	while last_page < total_pages:
		file_list_response = \
				yield(SDK.file_list({"page": last_page + 1}), "completed")
		files.append_array(
				file_list_response.get("result", {}).get("items", []))
		last_page += 1
	
	# Store the current statuses of the downloads
	_download_statuses.clear()
	var all_downloads_completed: bool = true
	for file in files:
		if (not file.get("claim_name", "").empty()
				and not file.get("claim_id", "").empty()):
			var permanent_url: String = "lbry://{0}#{1}" \
					.format([file["claim_name"], file["claim_id"]])
			_download_statuses[permanent_url] = file
		
		# Don't use the "completed" property here because files that were fully
		# downloaded but then deleted by the user will be marked as not
		# completed but may still be stored in blob storage by the SDK
		if file.get("blobs_remaining", 1) > 0:
			all_downloads_completed = false
	
	if all_downloads_completed:
		# All downloads completed so no need to poll the statuses
		_status_poll_timer.stop()
	
	emit_signal("all_statuses_updated")


func _open_downloaded() -> void:
	"""
	Open files that have finished downloading and have been marked as
	should be opened when fully downloaded
	"""
	for permanent_url in _open_when_downloaded.keys():
		if _download_statuses[permanent_url].get("completed", false) == true:
			var download_path: String = _download_statuses[permanent_url].get(
					"download_path", "")
			var command: String = _open_when_downloaded[permanent_url]
			if not download_path.empty():
				MediaHandler.open_with(download_path, command)
			_open_when_downloaded.erase(permanent_url)
		elif _download_statuses[permanent_url].get("blobs_remaining", 0) == 0:
			# If the download is not "completed" but there are no blobs
			# remaining, then the file has been deleted so can stop checking it
			_open_when_downloaded.erase(permanent_url)
