@tool
extends Node

const CACHE_DIR:String = "user://_guide_cache"

@onready var _sub_viewport:SubViewport = %SubViewport
@onready var _root:Node2D = %Root
@onready var _scene_holder = %SceneHolder

var _pending_requests:Array[Job] = []
var _current_request:Job = null
var _fetch_image:bool = false

func _ready():
	# keep working when game is paused
	process_mode = Node.PROCESS_MODE_ALWAYS
	# don't needlessly eat performance
	if _pending_requests.is_empty():
		set_process(false)

	
func clear_cache():
	var files = DirAccess.get_files_at(CACHE_DIR)
	for file in files:
		DirAccess.remove_absolute(CACHE_DIR + "/" + file)

## Makes an icon for the given input and returns a Texture2D with the icon. Icons
## are cached on disk so subsequent calls for the same input will be faster.
func make_icon(input:GUIDEInput, renderer:GUIDEIconRenderer, height_px:int) -> Texture2D:
	DirAccess.make_dir_recursive_absolute(CACHE_DIR)
	var cache_key = (str(height_px) + renderer.cache_key(input)).sha256_text()
	var cache_path = "user://_guide_cache/" + cache_key + ".res"
	if ResourceLoader.exists(cache_path):
		return ResourceLoader.load(cache_path, "Texture2D") 
	
	var job = Job.new()
	job.height = height_px
	job.input = input
	job.renderer = renderer
	_pending_requests.append(job)
	set_process(true)
	
	await job.done
	
	var image_texture = ImageTexture.create_from_image(job.result)
	ResourceSaver.save(image_texture, cache_path)
	image_texture.take_over_path(cache_path)
	
	return image_texture
		
	

func _process(delta):
	if _current_request == null and _pending_requests.is_empty():
		# nothing more to do..
		set_process(false)
		return 
		
	# nothing in progress, so pick the next request
	if _current_request == null:
		_current_request = _pending_requests.pop_front()
		var renderer = _current_request.renderer
		_root.add_child(renderer)
		
		renderer.render(_current_request.input)
		await get_tree().process_frame
		
		var actual_size = renderer.get_rect().size
		var scale =  float(_current_request.height) / float(actual_size.y)
		_root.scale = Vector2.ONE * scale
		_sub_viewport.size = actual_size  * scale
			
		_sub_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
			
		# give the renderer some time to update itself. 3 frames seem
		# to work nicely and keep things speedy.	
		await get_tree().process_frame
		await get_tree().process_frame
		await get_tree().process_frame
			
		_fetch_image = true
		return
		
	# fetch the image after the renderer is done	
	if _fetch_image:
		# we're done. make a copy of the viewport texture
		var image:Image = _scene_holder.texture.get_image()
		_current_request.result = image
		_current_request.done.emit()
		_current_request = null
		# remove the renderer
		_root.remove_child(_root.get_child(0))
		_sub_viewport.render_target_update_mode = SubViewport.UPDATE_DISABLED
		_fetch_image = false			
		
class Job:
	signal done()
	var height:int
	var input:GUIDEInput
	var renderer:GUIDEIconRenderer
	var result:Image
	
	
