/*
 * Copyright (c) 2012-2025 Daniele Bartolini et al.
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

namespace Crown
{
public class MeshResource
{
	public static void create_components(Database db, Guid parent_unit_id, Guid unit_id, string material_name, string resource_name, string node_name, Hashtable node)
	{
		Unit unit = new Unit(db, unit_id);

		Matrix4x4 matrix_local = Matrix4x4.from_array((Gee.ArrayList<Value?>)node["matrix_local"]);
		Vector3 position = matrix_local.t.to_vector3();
		Quaternion rotation = matrix_local.rotation();
		Vector3 scale = matrix_local.scale();

		// Create transform
		{
			Guid component_id;
			if (!unit.has_component(out component_id, OBJECT_TYPE_TRANSFORM)) {
				component_id = Guid.new_guid();
				db.create(component_id, OBJECT_TYPE_TRANSFORM);
				db.add_to_set(unit_id, "components", component_id);
			}

			unit.set_component_property_vector3   (component_id, "data.position", position);
			unit.set_component_property_quaternion(component_id, "data.rotation", rotation);
			unit.set_component_property_vector3   (component_id, "data.scale", scale);
		}

		// Create mesh_renderer
		{
			Guid component_id;
			if (!unit.has_component(out component_id, OBJECT_TYPE_MESH_RENDERER)) {
				component_id = Guid.new_guid();
				db.create(component_id, OBJECT_TYPE_MESH_RENDERER);
				db.add_to_set(unit_id, "components", component_id);
			}

			unit.set_component_property_string(component_id, "data.geometry_name", node_name);
			unit.set_component_property_string(component_id, "data.material", material_name);
			unit.set_component_property_string(component_id, "data.mesh_resource", resource_name + ".mesh");
			unit.set_component_property_bool  (component_id, "data.visible", true);
		}

		// Create collider
		{
			Guid component_id;
			if (!unit.has_component(out component_id, OBJECT_TYPE_COLLIDER)) {
				component_id = Guid.new_guid();
				db.create(component_id, OBJECT_TYPE_COLLIDER);
				db.add_to_set(unit_id, "components", component_id);
			}

			unit.set_component_property_string(component_id, "data.shape", "mesh");
			unit.set_component_property_string(component_id, "data.scene", resource_name);
			unit.set_component_property_string(component_id, "data.name", node_name);
		}

		// Create actor
		{
			Guid component_id;
			if (!unit.has_component(out component_id, OBJECT_TYPE_ACTOR)) {
				component_id = Guid.new_guid();
				db.create(component_id, OBJECT_TYPE_ACTOR);
				db.add_to_set(unit_id, "components", component_id);
			}

			unit.set_component_property_string(component_id, "data.class", "static");
			unit.set_component_property_string(component_id, "data.collision_filter", "default");
			unit.set_component_property_double(component_id, "data.mass", 10);
			unit.set_component_property_string(component_id, "data.material", "default");
		}

		if (parent_unit_id != unit_id)
			db.add_to_set(parent_unit_id, "children", unit_id);

		if (node.has_key("children")) {
			Hashtable children = (Hashtable)node["children"];
			foreach (var child in children.entries) {
				Guid new_unit_id = Guid.new_guid();
				db.create(new_unit_id, OBJECT_TYPE_UNIT);
				create_components(db, unit_id, new_unit_id, material_name, resource_name, child.key, (Hashtable)child.value);
			}
		}
	}

	public static ImportResult import(Project project, string destination_dir, SList<string> filenames)
	{
		foreach (unowned string filename_i in filenames) {
			GLib.File file_src = File.new_for_path(filename_i);

			GLib.File file_dst       = File.new_for_path(Path.build_filename(destination_dir, file_src.get_basename()));
			string resource_filename = project.resource_filename(file_dst.get_path());
			string resource_path     = ResourceId.normalize(resource_filename);
			string resource_name     = ResourceId.name(resource_path);

			// Choose material or create new one
			Gtk.FileChooserDialog mtl = new Gtk.FileChooserDialog("Select material... (Cancel to create a new one)"
				, null
				, Gtk.FileChooserAction.OPEN
				, "Cancel"
				, Gtk.ResponseType.CANCEL
				, "Select"
				, Gtk.ResponseType.ACCEPT
				);
			mtl.set_current_folder(project.source_dir());

			Gtk.FileFilter fltr = new Gtk.FileFilter();
			fltr.set_filter_name("Material (*.material)");
			fltr.add_pattern("*.material");
			mtl.add_filter(fltr);

			string material_name = resource_name;
			if (mtl.run() == (int)Gtk.ResponseType.ACCEPT) {
				string material_filename = project.resource_filename(mtl.get_filename());
				string material_path     = ResourceId.normalize(material_filename);
				material_name            = ResourceId.name(material_path);
			} else {
				Hashtable material = new Hashtable();
				material["shader"]   = "mesh+DIFFUSE_MAP";
				material["textures"] = new Hashtable();
				material["uniforms"] = new Hashtable();
				SJSON.save(material, project.absolute_path(material_name) + ".material");
			}
			mtl.destroy();

			try {
				file_src.copy(file_dst, FileCopyFlags.OVERWRITE);
			} catch (Error e) {
				loge(e.message);
				return ImportResult.ERROR;
			}

			Database db = new Database(project);

			// Generate or modify existing .unit.
			Guid unit_id;
			if (db.add_from_resource_path(out unit_id, resource_name + ".unit") != 0) {
				unit_id = Guid.new_guid();
				db.create(unit_id, OBJECT_TYPE_UNIT);
			}

			Hashtable mesh = SJSON.load_from_path(filename_i);
			Hashtable mesh_nodes = (Hashtable)mesh["nodes"];

			if (mesh_nodes.size > 1) {
				// Create an extra "root" unit to accommodate multiple root objects. This
				// "root" unit will only have a transform centered at origin to allow other
				// objects to be linked to it via the SceneGraph.
				Unit unit = new Unit(db, unit_id);

				Guid component_id;
				if (!unit.has_component(out component_id, OBJECT_TYPE_TRANSFORM)) {
					component_id = Guid.new_guid();
					db.create(component_id, OBJECT_TYPE_TRANSFORM);
					db.add_to_set(unit_id, "components", component_id);
				}

				unit.set_component_property_vector3   (component_id, "data.position", VECTOR3_ZERO);
				unit.set_component_property_quaternion(component_id, "data.rotation", QUATERNION_IDENTITY);
				unit.set_component_property_vector3   (component_id, "data.scale", VECTOR3_ONE);
			}

			Guid new_unit_id = unit_id;
			foreach (var entry in mesh_nodes.entries) {
				if (mesh_nodes.size > 1) {
					// If the mesh contains multiple root objects, create a new unit for each
					// one of those, otherwise put the components inside the base unit.
					new_unit_id = Guid.new_guid();
					db.create(new_unit_id, OBJECT_TYPE_UNIT);
				}
				create_components(db
					, unit_id
					, new_unit_id
					, material_name
					, resource_name
					, entry.key
					, (Hashtable)entry.value
					);
			}

			db.save(project.absolute_path(resource_name) + ".unit", unit_id);
		}

		return ImportResult.SUCCESS;
	}
}

} /* namespace Crown */
