/**
 * limbo_task_db.cpp
 * =============================================================================
 * Copyright (c) 2023-present Serhii Snitsaruk and the LimboAI contributors.
 *
 * Use of this source code is governed by an MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT.
 * =============================================================================
 */

#include "limbo_task_db.h"

#include "limbo_compat.h"

#ifdef LIMBOAI_MODULE
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#endif // LIMBOAI_MODULE

#ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/dir_access.hpp>
#include <godot_cpp/classes/project_settings.hpp>
using namespace godot;
#endif // LIMBOAI_GDEXTENSION

HashMap<String, List<String>> LimboTaskDB::core_tasks;
HashMap<String, List<String>> LimboTaskDB::tasks_cache;

_FORCE_INLINE_ void _populate_scripted_tasks_from_dir(String p_path, List<String> *p_task_classes) {
	if (p_path.is_empty()) {
		return;
	}

	Ref<DirAccess> dir = DIR_ACCESS_CREATE();

	if (dir->change_dir(p_path) == OK) {
		dir->list_dir_begin();
		String fn = dir->get_next();
		while (!fn.is_empty()) {
			if (fn.ends_with(".gd") || fn.ends_with(".cs")) {
				String full_path = p_path.path_join(fn);
				p_task_classes->push_back(full_path);
			}
			fn = dir->get_next();
		}
		dir->list_dir_end();
	} else {
		ERR_FAIL_MSG(vformat("Failed to list \"%s\" directory.", p_path));
	}
}

_FORCE_INLINE_ void _populate_from_user_dir(String p_path, HashMap<String, List<String>> *p_categories) {
	if (p_path.is_empty()) {
		return;
	}

	Ref<DirAccess> dir = DIR_ACCESS_CREATE();
	if (dir->change_dir(p_path) == OK) {
		dir->list_dir_begin();
		String fn = dir->get_next();
		while (!fn.is_empty()) {
			if (dir->current_is_dir() && !fn.begins_with(".")) {
				String full_path;
				String category;
				if (fn == ".") {
					full_path = p_path;
					category = LimboTaskDB::get_misc_category();
				} else {
					full_path = p_path.path_join(fn);
					category = fn.capitalize();
				}

				if (!p_categories->has(category)) {
					p_categories->insert(category, List<String>());
				}

				_populate_scripted_tasks_from_dir(full_path, &p_categories->get(category));
			}
			fn = dir->get_next();
		}
		dir->list_dir_end();

		_populate_scripted_tasks_from_dir(p_path, &p_categories->get(LimboTaskDB::get_misc_category()));

	} else {
		ERR_FAIL_MSG(vformat("Failed to list \"%s\" directory.", p_path));
	}
}

void LimboTaskDB::scan_user_tasks() {
	tasks_cache = HashMap<String, List<String>>(core_tasks);

	if (!tasks_cache.has(LimboTaskDB::get_misc_category())) {
		tasks_cache[LimboTaskDB::get_misc_category()] = List<String>();
	}

	PackedStringArray user_task_directories = GLOBAL_GET("limbo_ai/behavior_tree/user_task_dirs");
	for (const String &user_task_dir : user_task_directories) {
		_populate_from_user_dir(user_task_dir, &tasks_cache);
	}

	for (KeyValue<String, List<String>> &E : tasks_cache) {
		E.value.sort_custom<ComparatorByTaskName>();
	}
}

List<String> LimboTaskDB::get_categories() {
	List<String> r_cat;
	for (const KeyValue<String, List<String>> &E : tasks_cache) {
		r_cat.push_back(E.key);
	}
	r_cat.sort();
	return r_cat;
}

List<String> LimboTaskDB::get_tasks_in_category(const String &p_category) {
	return List<String>(tasks_cache[p_category]);
}