extends Node

var ConsoleService

var data_path = "."
var mod_dict = {}
var mod_arr = []
var mod_instances = {}

func _ready():
	ConsoleService = $"/root/ConsoleService"

# Locate all mods that can be loaded for the given game.
func find_local_mods():
	var dir = Directory.new()

	var mod_dir_exists = dir.open(data_path + "/mods")
	if (mod_dir_exists != 0):
		dir.change_dir(data_path)
		dir.make_dir("mods")
		dir.change_dir(data_path + "/mods")

	dir.list_dir_begin()

	while true:
		var file = dir.get_next()
		var basename = file.get_basename()
		if file == "":
			break
		elif file.ends_with(".zip"):
			var mod_desc = {
				"name": basename,
				"path": data_path + "/mods/" + file,
				"md5": File.new().get_md5(data_path + "/mods/" + file),
				"type": "zip"
			}
			mod_arr.append(mod_desc)
			mod_dict[mod_desc.name] = mod_desc
		elif dir.file_exists(basename+"/main.gd"):
			var mod_desc = {
				"name": basename,
				"path": data_path + "/mods/" + file,
				"md5": null,
				"type": "folder"
			}
			mod_arr.append(mod_desc)
			mod_dict[mod_desc.name] = mod_desc

	dir.list_dir_end()

# Prepare initial instances of mods for preprocessing. Used to determine mod dependencies and configuration
# Prior to final loading and initialization.
func preload_instances():
	if mod_arr.size() == 0:
		ConsoleService.error("[ModService] No mods found (Run find_local_mods and download_server_mods first)")
		return

	for mod_desc in mod_arr:
		mod_instances[mod_desc.name] = _get_mod_instance(mod_desc)
		if !mod_instances[mod_desc.name]:
			ConsoleService.error("[ModService] Failed to load mod '"+mod_desc.name+"'")

func compute_dependency_graph():
	if mod_instances.size() == 0:
		ConsoleService.error("[ModService] Mods have not been preloaded (Run preload_instances first)")
		return

	var resolved = []; # Properly ordered mod list.
	for mod_desc in mod_arr:
		_resolve_dependency(mod_desc, resolved, [])

	return resolved

func _resolve_dependency(mod_desc: Dictionary, resolved: Array, unresolved: Array):
	if mod_desc in resolved:
		return
	# Seen is used to prevent
	unresolved.append(mod_desc)
	if not "dependencies" in mod_instances[mod_desc.name]:
		resolved.append(mod_desc)
		unresolved.erase(mod_desc)
		return

	var deps = mod_instances[mod_desc.name].dependencies
	for dep in deps:
		var dep_desc = mod_dict[dep]
		if not dep_desc in resolved:
			if dep_desc in unresolved:
				ConsoleService.error("[ModService] Circular dependency between '"+mod_desc.name+"' <-> '"+dep+"'")
				assert(false)
			if not dep in mod_instances:
				ConsoleService.error("[ModService] Mod '"+mod_desc.name+"' requires mod '"+dep+"' to be installed, but it is not")
				assert(false)
			_resolve_dependency(dep_desc, resolved, unresolved)
	resolved.append(mod_desc)
	unresolved.erase(mod_desc)
	
func load_mods():
	if mod_arr.size() == 0:
		print("[ModService] No mods found (Run find_mods first)")
		return

	for mod_desc in mod_arr:
		var mod_instance = _get_mod_instance(mod_desc)
		mod_instances[mod_desc.name] = mod_instance
		if !mod_instance:
			ConsoleService.log("[ModService] Failed to load mod '"+mod_desc.name+"'")
			continue
		
		$"/root/Main/Mods".add_child(mod_instance)
		if mod_instance.has_method('init'):
			mod_instance.init()

func _get_mod_instance(mod_desc: Dictionary):
	if mod_desc.type == 'zip':
		ProjectSettings.load_resource_pack(mod_desc.path, true)
	
	var mod_script = load("res://mods/"+mod_desc.name+"/main.gd");
	if !mod_script:
		return
	var mod_instance = mod_script.new()
	return mod_instance
