extends Node

onready var ConsoleService = $"/root/ConsoleService"

var package_dict = {}
var package_load_order = []

# Locate all packages that can be loaded.
func discover_packages():
	var dir = Directory.new()
	dir.open("res://packages")
	dir.list_dir_begin()
	
	while true:
		var package_name = dir.get_next()
		if package_name == "": break
		if not dir.current_is_dir() or package_name == "." or package_name == "..": continue
		
		var package_script = load("res://packages/"+package_name+"/main.gd");
		if !package_script: continue
		
		var package = {
			"name": package_script.package.name if package_script.package.has("name") else package_name,
			"version": package_script.package.version if package_script.package.has("version") else "1.0.0",
			"dependencies": package_script.package.dependencies if package_script.package.has("dependencies") else [],
			"_script": package_script
		}
		package_dict[package.name] = package

	dir.list_dir_end()
	_compute_dependency_graph()
	
func load_packages():
	for package_name in package_load_order:
		var package = package_dict[package_name]
		package._instance = package._script.new()
		if !package._instance:
			ConsoleService.log("[PackageService] Failed to load package '"+package.name+"'")
			continue

func init_packages():
	for package_name in package_load_order:
		var package = package_dict[package_name]
		if not package.has("_instance"):
			ConsoleService.log("[PackageService] Package '"+package.name+"' has not been loaded. Please load before initializing.")
			continue
		$"/root/Main/Packages".add_child(package._instance)
		if package._instance.has_method('init'):
			package._instance.init()

func _compute_dependency_graph():
	if package_dict.size() == 0:
		ConsoleService.error("[PackageService] packages have not been preloaded (Run preload_instances first)")
		return

	var resolved = []; # Properly ordered package list.
	for package in package_dict.values():
		_resolve_dependency(package, resolved, [])

	package_load_order = resolved

func _resolve_dependency(package: Dictionary, resolved: Array, unresolved: Array):
	if package.name in resolved:
		return
	# Unresolved is used to prevent recursive lookups.
	unresolved.append(package.name)

	for dependency_name in package.dependencies:
		if dependency_name in resolved: continue
		if dependency_name in unresolved:
			ConsoleService.error("[PackageService] Circular dependency between '"+package.name+"' <-> '"+dependency_name+"'")
			assert(false)
		if not package_dict.has(dependency_name):
			ConsoleService.error("[PackageService] package '"+package.name+"' requires package '"+dependency_name+"' to be installed, but it is not")
			assert(false)

		var dependency = package_dict[dependency_name]
		_resolve_dependency(dependency, resolved, unresolved)

	resolved.append(package.name)
	unresolved.erase(package.name)
