extends Control

enum ProgressStatus {
	CONNECTING,
	DOWNLOADING,
	STARTING,
	ERROR
}

class DownloadItem:
	var body_size: int
	var downloaded_bytes: int

@export var gate_events: GateEvents
@export var progress_bar_background: Control
@export var progress_bar_error: Control
@export var progress_bar: Control
@export var label: Label

const TWEEN_DURATION_S = 0.2
const SPEED_DELAY_MS = 300
const ZERO_SPEED_DELAY_MS = 2000

var download_items: Dictionary

# For calculating speed
var last_bytes: int
var last_ticks: int
var last_speed: String
var last_is_zero: bool
var first_zero_ticks: int


func _ready() -> void:
	gate_events.gate_info_loaded.connect(on_gate_info_loaded)
	gate_events.gate_entered.connect(on_gate_entered)
	gate_events.gate_error.connect(on_gate_error)
	set_progress("Connecting...", ProgressStatus.CONNECTING)


func on_gate_info_loaded(_gate: Gate) -> void:
	gate_events.download_progress.connect(show_progress)
	last_ticks = Time.get_ticks_msec()


func show_progress(url: String, body_size: int, downloaded_bytes: int) -> void:
	if body_size < 0: return
	
	var item = download_items.get_or_add(url, DownloadItem.new()) as DownloadItem
	item.body_size = body_size
	item.downloaded_bytes = downloaded_bytes
	
	var sum_downloaded_bytes = get_sum(&"downloaded_bytes")
	var sum_body_size = get_sum(&"body_size")
	
	var downloaded = StringTools.bytes_to_string(sum_downloaded_bytes)
	var body = StringTools.bytes_to_string(sum_body_size)
	var speed = get_speed(sum_downloaded_bytes)
	
	var text = "Downloading resources  —  %s of %s (%s/sec)" % [downloaded, body, speed]
	var progress = float(sum_downloaded_bytes) / sum_body_size
	set_progress(text, ProgressStatus.DOWNLOADING, progress)


func get_sum(property: StringName) -> int:
	var result: int = 0
	for item in download_items.values():
		result += item.get(property)
	return result


func get_speed(bytes: int) -> String:
	var delta_bytes = bytes - last_bytes
	var delta_ticks = Time.get_ticks_msec() - last_ticks
	
	if delta_ticks < SPEED_DELAY_MS and not last_speed.is_empty() and not last_is_zero:
		return last_speed
	
	var bytes_sec = 0
	if delta_bytes != 0 and delta_ticks != 0:
		bytes_sec = int(delta_bytes / (delta_ticks / 1000.0))
	
	if should_write_current_speed(bytes_sec):
		last_bytes = bytes
		last_ticks = Time.get_ticks_msec()
		last_speed = StringTools.bytes_to_string(bytes_sec)
	
	return last_speed


func should_write_current_speed(bytes_sec: int) -> bool:
	if last_speed.is_empty():
		last_is_zero = bytes_sec == 0
		return true
	
	if bytes_sec != 0:
		last_is_zero = false
		return true
	
	if not last_is_zero:
		first_zero_ticks = Time.get_ticks_msec()
		last_is_zero = true
		return false
	
	var delta_zero_tiks = Time.get_ticks_msec() - first_zero_ticks
	if delta_zero_tiks < ZERO_SPEED_DELAY_MS:
		return false
	
	return true


func on_gate_entered() -> void:
	gate_events.download_progress.disconnect(show_progress)
	set_progress("Starting the gate...", ProgressStatus.STARTING)


func on_gate_error(code: GateEvents.GateError) -> void:
	match code:
		GateEvents.GateError.NOT_FOUND:
			set_progress("Gate not found", ProgressStatus.ERROR)
		GateEvents.GateError.INVALID_CONFIG:
			set_progress("Invalid gate config", ProgressStatus.ERROR)
		GateEvents.GateError.MISSING_PACK, GateEvents.GateError.MISSING_LIBS:
			set_progress("Cannot load gate resources", ProgressStatus.ERROR)
		_:
			set_progress("Unknown error", ProgressStatus.ERROR)


func set_progress(text: String, status: ProgressStatus, progress: float = 0.0) -> void:
	label.text = text
	
	match status:
		ProgressStatus.CONNECTING:
			progress_bar.show()
			progress_bar_error.hide()
			move_progress_bar(0.0, 0.0)
			
		ProgressStatus.DOWNLOADING:
			progress_bar.show()
			progress_bar_error.hide()
			move_progress_bar(progress, FileDownloader.PROGRESS_DELAY)
			
		ProgressStatus.STARTING:
			progress_bar.show()
			progress_bar_error.hide()
			move_progress_bar(1.0, TWEEN_DURATION_S)
			
		ProgressStatus.ERROR:
			progress_bar.hide()
			progress_bar_error.show()
		
		_:
			progress_bar.hide()
			progress_bar_error.hide()


func move_progress_bar(progress: float, tween_duration: float) -> void:
	var full_size = progress_bar_background.size
	var current_size = Vector2(lerp(0.0, full_size.x, progress), full_size.y)
	
	var tween = get_tree().create_tween()
	tween.tween_property(progress_bar, "size", current_size, tween_duration)
