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

namespace Crown
{
public Gdk.RGBA collider_color = { 1.0, 0.5, 0.0, 1.0 };

public enum Pivot
{
	TOP_LEFT,
	TOP_CENTER,
	TOP_RIGHT,
	LEFT,
	CENTER,
	RIGHT,
	BOTTOM_LEFT,
	BOTTOM_CENTER,
	BOTTOM_RIGHT
}

Vector2 sprite_cell_xy(int r, int c, int offset_x, int offset_y, int cell_w, int cell_h, int spacing_x, int spacing_y)
{
	int x0 = offset_x + c*cell_w + c*spacing_x;
	int y0 = offset_y + r*cell_h + r*spacing_y;
	return Vector2(x0, y0);
}

Vector2 sprite_cell_pivot_xy(int cell_w, int cell_h, int pivot)
{
	int pivot_x = 0;
	int pivot_y = 0;

	switch (pivot) {
	case Pivot.TOP_LEFT:
		pivot_x = 0;
		pivot_y = 0;
		break;

	case Pivot.TOP_CENTER:
		pivot_x = cell_w / 2;
		pivot_y = 0;
		break;

	case Pivot.TOP_RIGHT:
		pivot_x = cell_w;
		pivot_y = 0;
		break;

	case Pivot.BOTTOM_LEFT:
		pivot_x = 0;
		pivot_y = cell_h;
		break;

	case Pivot.BOTTOM_CENTER:
		pivot_x = cell_w / 2;
		pivot_y = cell_h;
		break;

	case Pivot.BOTTOM_RIGHT:
		pivot_x = cell_w;
		pivot_y = cell_h;
		break;

	case Pivot.LEFT:
		pivot_x = 0;
		pivot_y = cell_h / 2;
		break;

	case Pivot.CENTER:
		pivot_x = cell_w / 2;
		pivot_y = cell_h / 2;
		break;

	case Pivot.RIGHT:
		pivot_x = cell_w;
		pivot_y = cell_h / 2;
		break;

	default:
		assert(false);
		break;
	}

	return Vector2(pivot_x, pivot_y);
}

void sprite_cell_from_index(out int r, out int c, int num_cols, int index)
{
	r = (int)(index / num_cols);
	c = index - r * num_cols;
}

public class SpriteImportDialog : Gtk.Window
{
	public Project _project;
	public string _destination_dir;
	public GLib.SList<string> _filenames;
	public unowned Import _import_result;

	public string _image_type;

	public Gdk.Pixbuf _pixbuf;

	public InputResourceBasename _unit_name;
	public Gtk.Label resolution;
	public InputVector2 cells;
	public Gtk.CheckButton cell_wh_auto;
	public InputVector2 cell;
	public InputVector2 offset;
	public InputVector2 spacing;
	public Gtk.ComboBoxText pivot;
	public InputDouble layer;
	public InputDouble depth;

	public Gtk.CheckButton collision_enabled;
	public Gtk.CheckButton mirror_cell;
	public string shape_active_name;
	public Gtk.StackSwitcher shape_switcher;
	public Gtk.Stack shape;

	public InputVector2 circle_collision_center;
	public InputDouble circle_collision_radius;

	public InputVector2 capsule_collision_center;
	public InputDouble capsule_collision_height;
	public InputDouble capsule_collision_radius;

	public InputVector2 collision_xy;
	public InputVector2 collision_wh;
	public InputEnum actor_class;
	public InputDouble mass;
	public Gtk.CheckButton lock_rotation_z;

	public Gtk.Button _previous_frame;
	public Gtk.Button _next_frame;
	public InputDouble _current_frame;
	public Gtk.Box _frame_selector_box;
	public Gtk.Overlay _preview_overlay;
	public PixbufView _slices;
	public PixbufView _preview;
	public Gtk.ScrolledWindow _scrolled_window;

	public Gtk.Notebook _notebook;

	public Gtk.Box _box;
	public Gtk.Button _import;
	public Gtk.Button _cancel;
	public Gtk.HeaderBar _header_bar;

	public SpriteImportDialog(Database database, string destination_dir, GLib.SList<string> filenames, Import import_result)
	{
		this.set_icon_name(CROWN_EDITOR_ICON_NAME);

		_project = database._project;
		_destination_dir = destination_dir;
		_filenames = new GLib.SList<string>();
		foreach (var f in filenames)
			_filenames.append(f);
		_import_result = import_result;

		string settings_path;
		string image_path;
		string image_name;
		{
			GLib.File file_src = File.new_for_path(_filenames.nth_data(0));
			image_path = file_src.get_path();
			_image_type = image_path.substring(image_path.last_index_of_char('.') + 1
				, image_path.length - image_path.last_index_of_char('.') - 1
				);

			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);

			settings_path = _project.absolute_path(resource_name) + ".importer_settings";

			int last_slash = resource_name.last_index_of_char('/');
			if (last_slash == -1)
				image_name = resource_name;
			else
				image_name = resource_name.substring(last_slash + 1, resource_name.length - last_slash - 1);
		}

		try {
			_pixbuf = new Gdk.Pixbuf.from_file(image_path);
		} catch (GLib.Error e) {
			loge(e.message);
		}

		_unit_name = new InputResourceBasename(image_name);
		_unit_name.sensitive = _filenames.length() == 1;

		resolution = new Gtk.Label(_pixbuf.width.to_string() + " × " + _pixbuf.height.to_string());
		resolution.halign = Gtk.Align.START;

		cells = new InputVector2(Vector2(1.0, 1.0), Vector2(1.0, 1.0), Vector2(256.0, 256.0));
		cell_wh_auto = new Gtk.CheckButton();
		cell_wh_auto.active = true;
		cell = new InputVector2(Vector2(_pixbuf.width / cells.value.x, _pixbuf.height / cells.value.y), Vector2(1.0, 1.0), Vector2(double.MAX, double.MAX));
		cell.sensitive = !cell_wh_auto.active;
		offset = new InputVector2(Vector2(0.0, 0.0), Vector2(0.0, 0.0), Vector2(double.MAX, double.MAX));
		spacing = new InputVector2(Vector2(0.0, 0.0), Vector2(0.0, 0.0), Vector2(double.MAX, double.MAX));

		collision_enabled = new Gtk.CheckButton();
		collision_enabled.active = true;
		collision_enabled.toggled.connect(() => {
				_preview.queue_draw();
			});
		mirror_cell = new Gtk.CheckButton();
		mirror_cell.active = true;
		collision_xy = new InputVector2(Vector2(0.0, 0.0), Vector2(-double.MAX, -double.MAX), Vector2(double.MAX, double.MAX));
		collision_wh = new InputVector2(Vector2(32.0, 32.0), Vector2(-double.MAX, -double.MAX), Vector2(double.MAX, double.MAX));
		actor_class = new InputEnum();
		actor_class.append("static", "static");
		actor_class.append("dynamic", "dynamic");
		actor_class.append("keyframed", "keyframed");
		actor_class.append("trigger", "trigger");
		actor_class.value = "static";
		lock_rotation_z = new Gtk.CheckButton();
		lock_rotation_z.active = true;
		mass = new InputDouble(10.0, 0.0, double.MAX);

		circle_collision_center = new InputVector2(Vector2(cell.value.x/2.0, cell.value.y/2.0), Vector2(-double.MAX, -double.MAX), Vector2(double.MAX, double.MAX));
		circle_collision_radius = new InputDouble(32.0, 0.5, double.MAX);

		capsule_collision_center = new InputVector2(Vector2(cell.value.x/2.0, cell.value.y/2.0), Vector2(-double.MAX, -double.MAX), Vector2(double.MAX, double.MAX));
		capsule_collision_radius = new InputDouble(32.0, 0.5, double.MAX);
		capsule_collision_height = new InputDouble(64.0, 2.0*capsule_collision_radius.value, double.MAX);

		cells.value_changed.connect(() => {
				if (cell_wh_auto.active)
					cell.value = Vector2(_pixbuf.width / cells.value.x, _pixbuf.height / cells.value.y);

				calc_collider_shape();
				_current_frame.set_max(cells.value.x * cells.value.y - 1);

				_slices.queue_draw();
				set_preview_frame();
				_preview.queue_draw();
			});

		cell_wh_auto.toggled.connect(() => {
				cell.sensitive = !cell_wh_auto.active;
				cell.value = Vector2(_pixbuf.width / cells.value.x, _pixbuf.height / cells.value.y);
				_slices.queue_draw();
				_preview.queue_draw();
			});

		cell.value_changed.connect(() => {
				calc_collider_shape();
				_slices.queue_draw();
				set_preview_frame();
				_preview.queue_draw();
			});

		offset.value_changed.connect(() => {
				_slices.queue_draw();
				set_preview_frame();
				_preview.queue_draw();
			});

		spacing.value_changed.connect(() => {
				_slices.queue_draw();
				set_preview_frame();
				_preview.queue_draw();
			});

		collision_enabled.toggled.connect(() => {
				collision_xy.sensitive = !collision_xy.sensitive;
				collision_wh.sensitive = !collision_wh.sensitive;
				shape_switcher.sensitive = !shape_switcher.sensitive;
				mirror_cell.sensitive = !mirror_cell.sensitive;
				circle_collision_center.sensitive = !circle_collision_center.sensitive;
				circle_collision_radius.sensitive = !circle_collision_radius.sensitive;
				capsule_collision_center.sensitive = !capsule_collision_center.sensitive;
				capsule_collision_radius.sensitive = !capsule_collision_radius.sensitive;
				capsule_collision_height.sensitive = !capsule_collision_height.sensitive;
				actor_class.sensitive = !actor_class.sensitive;
				mass.sensitive = !mass.sensitive;
				lock_rotation_z.sensitive = !lock_rotation_z.sensitive;
			});

		collision_xy.value_changed.connect(() => {
				_preview.queue_draw();
			});

		collision_wh.value_changed.connect(() => {
				_preview.queue_draw();
			});

		circle_collision_center.value_changed.connect(() => {
				_preview.queue_draw();
			});

		circle_collision_radius.value_changed.connect(() => {
				_preview.queue_draw();
			});

		capsule_collision_center.value_changed.connect(() => {
				_preview.queue_draw();
			});

		capsule_collision_radius.value_changed.connect(() => {
				capsule_collision_height.set_min(2.0 * capsule_collision_radius.value);
				_preview.queue_draw();
			});

		capsule_collision_height.value_changed.connect(() => {
				_preview.queue_draw();
			});

		pivot = new Gtk.ComboBoxText();
		pivot.append_text("Top left");      // TOP_LEFT
		pivot.append_text("Top center");    // TOP_CENTER
		pivot.append_text("Top right");     // TOP_RIGHT
		pivot.append_text("Left");          // LEFT
		pivot.append_text("Center");        // CENTER
		pivot.append_text("Right");         // RIGHT
		pivot.append_text("Bottom left");   // BOTTOM_LEFT
		pivot.append_text("Bottom center"); // BOTTOM_CENTER
		pivot.append_text("Bottom right");  // BOTTOM_RIGHT
		pivot.active = Pivot.CENTER;

		pivot.changed.connect(() => {
				_slices.queue_draw();
				_preview.queue_draw();
			});

		layer = new InputDouble(0.0, 0.0, 7.0);
		depth = new InputDouble(0.0, 0.0, 9999.0);

		PropertyGridSet sprite_set = new PropertyGridSet();

		// Slices.
		PropertyGrid cv;
		cv = new PropertyGrid();
		cv.add_row("Name", _unit_name, "Name of the imported unit.");
		sprite_set.add_property_grid(cv, "Unit");

		cv = new PropertyGrid();
		cv.add_row("Resolution", resolution, "Resolution of the source image.");
		cv.add_row("Cells", cells, "Split the image into X columns and Y rows.");
		cv.add_row("Auto Size", cell_wh_auto, "Compute the cell size automatically.");
		cv.add_row("Cell", cell, "Size of each cell.");
		cv.add_row("Offset", offset, "Starting offset of the cells.");
		cv.add_row("Spacing", spacing, "Spacing between the cells.");
		cv.add_row("Pivot", pivot, "Origin of the sprite.");
		cv.add_row("Collision", collision_enabled, "Enable collision detection.");
		sprite_set.add_property_grid(cv, "Image");

		// Sprite Renderer.
		cv = new PropertyGrid();
		cv.add_row("Layer", layer, "Sorting layer. Higher values makes the sprite appear in front.");
		cv.add_row("Depth", depth, "Higher values make the sprite apper in front of other sprites in the same layer.");
		sprite_set.add_property_grid(cv, "Sprite Renderer");

		// Collider.
		shape = new Gtk.Stack();
		shape.sensitive = false;
		shape.vhomogeneous = false;
		shape.notify["visible-child"].connect(() => {
				calc_collider_shape();
				_preview.queue_draw();
			});

		cv = new PropertyGrid();
		cv.add_row("Origin", collision_xy);
		cv.add_row("Size", collision_wh);
		shape.add_titled(cv, "square_collider", "Square");

		cv = new PropertyGrid();
		cv.add_row("Origin", circle_collision_center);
		cv.add_row("Radius", circle_collision_radius);
		shape.add_titled(cv, "circle_collider", "Circle");

		cv = new PropertyGrid();
		cv.add_row("Origin", capsule_collision_center);
		cv.add_row("Radius", capsule_collision_radius);
		cv.add_row("Height", capsule_collision_height);
		shape.add_titled(cv, "capsule_collider", "Capsule");

		shape_switcher = new Gtk.StackSwitcher();
		shape_switcher.set_stack(shape);

		cv = new PropertyGrid();
		cv.row_homogeneous = false;
		cv.add_row("Shape Type", shape_switcher, "Shape to use as collider.");
		cv.add_row("Mirror Cell", mirror_cell, "Compute the shape size based on the cell size.");
		cv.add_row("Shape Data", shape, "Set the shape size manually.");
		sprite_set.add_property_grid(cv, "Collider");

		mirror_cell.toggled.connect(() => {
				shape.sensitive = !shape.sensitive;
				calc_collider_shape();
				_slices.queue_draw();
				_preview.queue_draw();
			});

		// Actor.
		cv = new PropertyGrid();
		cv.add_row("Class", actor_class, "Actor class.");
		cv.add_row("Mass", mass, "Actor physical mass.");
		cv.add_row("Lock Rotation", lock_rotation_z, "Prevent the actor from rotating around the Z axis.");
		sprite_set.add_property_grid(cv, "Actor");

		_previous_frame = new Gtk.Button.from_icon_name("go-previous-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
		_previous_frame.clicked.connect(() => {
				_current_frame.value -= 1;
				set_preview_frame();
				_preview.queue_draw();
			});
		_next_frame = new Gtk.Button.from_icon_name("go-next-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
		_next_frame.clicked.connect(() => {
				_current_frame.value += 1;
				set_preview_frame();
				_preview.queue_draw();
			});
		_current_frame = new InputDouble(0.0, 0.0, cells.value.x * cells.value.y - 1);
		_current_frame.value_changed.connect(() => {
				set_preview_frame();
				_preview.queue_draw();
			});
		_frame_selector_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
		_frame_selector_box.homogeneous = true;
		_frame_selector_box.halign = Gtk.Align.CENTER;
		_frame_selector_box.valign = Gtk.Align.END;
		_frame_selector_box.margin_bottom = 24;
		_frame_selector_box.pack_start(_previous_frame);
		_frame_selector_box.pack_start(_current_frame);
		_frame_selector_box.pack_end(_next_frame);

		_slices = new PixbufView();
		_slices.set_size_request(_pixbuf.width, _pixbuf.height);
		_slices.set_pixbuf(_pixbuf);

		_slices.draw.connect((cr) => {
				int allocated_width = _preview.get_allocated_width();
				int allocated_height = _preview.get_allocated_height();
				double original_line_width = cr.get_line_width();

				cr.translate(allocated_width * 0.5, allocated_height * 0.5);
				cr.scale(_slices._zoom, _slices._zoom);
				cr.set_line_width(original_line_width / _slices._zoom);
				cr.translate(-_pixbuf.width * 0.5, -_pixbuf.height * 0.5);

				int num_v = (int)cells.value.y;
				int num_h = (int)cells.value.x;

				for (int h = 0; h < num_v; ++h) {
					for (int w = 0; w < num_h; ++w) {
						Vector2 sc = sprite_cell_xy(h
							, w
							, (int)offset.value.x
							, (int)offset.value.y
							, (int)cell.value.x
							, (int)cell.value.y
							, (int)spacing.value.x
							, (int)spacing.value.y
							);

						int x0 = (int)sc.x;
						int y0 = (int)sc.y;
						int x1 = x0 + (int)cell.value.x;
						int y1 = y0;
						int x2 = x1;
						int y2 = y0 + (int)cell.value.y;
						int x3 = x0;
						int y3 = y2;
						// https://www.cairographics.org/FAQ/#sharp_lines
						cr.move_to((double)x0,       (double)y0 + 0.5);
						cr.line_to((double)x1,       (double)y1 + 0.5);
						cr.move_to((double)x1 + 0.5, (double)y1);
						cr.line_to((double)x2 + 0.5, (double)y2);
						cr.move_to((double)x2,       (double)y2 + 0.5);
						cr.line_to((double)x3,       (double)y3 + 0.5);
						cr.move_to((double)x3 + 0.5, (double)y3);
						cr.line_to((double)x0 + 0.5, (double)y0);

						cr.set_source_rgba(0.9, 0.1, 0.1, 0.9);
						cr.stroke();
					}
				}

				return Gdk.EVENT_STOP;
			});

		_preview = new PixbufView();
		_preview._zoom = 4.0;
		_preview.set_size_request(128, 128);
		set_preview_frame();

		_preview.draw.connect((cr) => {
				int allocated_width = _preview.get_allocated_width();
				int allocated_height = _preview.get_allocated_height();
				double original_line_width = cr.get_line_width();

				cr.translate(allocated_width * 0.5, allocated_height * 0.5);
				cr.scale(_preview._zoom, _preview._zoom);
				cr.set_line_width(original_line_width / _preview._zoom);
				cr.translate(-cell.value.x * 0.5, -cell.value.y * 0.5);

				// Draw collider.
				if (collision_enabled.active) {
					if (shape.visible_child_name == "square_collider") {
						cr.rectangle(collision_xy.value.x, collision_xy.value.y, collision_wh.value.x, collision_wh.value.y);
						cr.set_source_rgba(collider_color.red, collider_color.green, collider_color.blue, collider_color.alpha);
						cr.stroke();
					} else if (shape.visible_child_name == "circle_collider") {
						cr.arc(circle_collision_center.value.x, circle_collision_center.value.y, circle_collision_radius.value, 0, 2*Math.PI);
						cr.set_source_rgba(collider_color.red, collider_color.green, collider_color.blue, collider_color.alpha);
						cr.stroke();
					} else if (shape.visible_child_name == "capsule_collider") {
						double x = capsule_collision_center.value.x;
						double y = capsule_collision_center.value.y;
						double radius = capsule_collision_radius.value;
						double height = capsule_collision_height.value - 2*radius;
						cr.arc(x - height/2, y, radius, Math.PI/2, 3*Math.PI/2);
						cr.rectangle(x - height/2, y - radius, height, 2*radius);
						cr.arc(x + height/2, y, radius, 3*Math.PI/2, Math.PI/2);
						cr.set_source_rgba(collider_color.red, collider_color.green, collider_color.blue, collider_color.alpha);
						cr.stroke();
					}
				}

				// Draw pivot.
				// Pivot is relative to the top-left corner of the cell.
				Vector2 pivot = sprite_cell_pivot_xy((int)cell.value.x
					, (int)cell.value.y
					, (int)pivot.active
					);
				cr.arc(pivot.x, pivot.y, 5.0, 0, 2*Math.PI);
				cr.set_source_rgba(0.1, 0.1, 0.9, 0.6);
				cr.fill();

				return Gdk.EVENT_STOP;
			});

		_preview_overlay = new Gtk.Overlay();
		_preview_overlay.add(_preview);
		_preview_overlay.add_overlay(_frame_selector_box);

		_scrolled_window = new Gtk.ScrolledWindow(null, null);
		_scrolled_window.min_content_width = 640;
		_scrolled_window.min_content_height = 640;
		_scrolled_window.add(_slices);

		_notebook = new Gtk.Notebook();
		_notebook.append_page(_preview_overlay, new Gtk.Label("Preview"));
		_notebook.append_page(_scrolled_window, new Gtk.Label("Slices"));
		_notebook.show_border = false;

		Gtk.Paned pane;
		pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
		pane.pack1(_notebook, false, false);
		pane.pack2(sprite_set, true, false);

		_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
		_box.pack_start(pane, false, false);

		_cancel = new Gtk.Button.with_label("Cancel");
		_cancel.clicked.connect(() => {
				close();
			});
		_import = new Gtk.Button.with_label("Import");
		_import.get_style_context().add_class("suggested-action");
		_import.clicked.connect(on_import);

		_header_bar = new Gtk.HeaderBar();
		_header_bar.title = "Import Sprite...";
		_header_bar.show_close_button = true;
		_header_bar.pack_start(_cancel);
		_header_bar.pack_end(_import);

		this.set_titlebar(_header_bar);
		this.add(_box);
		this.map_event.connect(on_map_event);

		if (File.new_for_path(settings_path).query_exists()) {
			try {
				decode(SJSON.load_from_path(settings_path));
			} catch (JsonSyntaxError e) {
				// No-op.
			}
		}
	}

	public bool on_map_event(Gdk.EventAny ev)
	{
		_unit_name.grab_focus();
		return Gdk.EVENT_PROPAGATE;
	}

	public void set_preview_frame()
	{
		assert(cells != null);
		assert(_current_frame != null);
		assert(offset != null);
		assert(cell != null);
		assert(spacing != null);

		int r, c;
		sprite_cell_from_index(out r, out c, (int)cells.value.x, (int)_current_frame.value);

		Vector2 sc = sprite_cell_xy(r
			, c
			, (int)offset.value.x
			, (int)offset.value.y
			, (int)cell.value.x
			, (int)cell.value.y
			, (int)spacing.value.x
			, (int)spacing.value.y
			);

		Gdk.Pixbuf frame_pixbuf = new Gdk.Pixbuf.subpixbuf(_pixbuf
			, (int)sc.x
			, (int)sc.y
			, (int)cell.value.x
			, (int)cell.value.y
			);

		_preview.set_pixbuf(frame_pixbuf);
	}

	public void calc_collider_shape()
	{
		if (!mirror_cell.active)
			return;

		if (shape.visible_child_name == "square_collider") {
			collision_xy.value = Vector2(0, 0);
			collision_wh.value = cell.value;
		} else if (shape.visible_child_name == "circle_collider") {
			circle_collision_center.value = Vector2(cell.value.x * 0.5, cell.value.y * 0.5);
			circle_collision_radius.value = double.min(cell.value.x * 0.5, cell.value.y * 0.5);
		} else if (shape.visible_child_name == "capsule_collider") {
			capsule_collision_center.value = Vector2(cell.value.x * 0.5, cell.value.y * 0.5);
			capsule_collision_radius.value = double.min(cell.value.x * 0.5, cell.value.y * 0.5);
			capsule_collision_height.value = cell.value.y;
		}
	}

	public void decode(Hashtable obj)
	{
		// Load settings
		cells.value              = Vector2((double)obj["num_h"], (double)obj["num_v"]);
		cell.value               = Vector2((double)obj["cell_w"], (double)obj["cell_h"]);
		offset.value             = Vector2((double)obj["offset_x"], (double)obj["offset_y"]);
		spacing.value            = Vector2((double)obj["spacing_x"], (double)obj["spacing_y"]);
		layer.value              = (double)obj["layer"];
		depth.value              = (double)obj["depth"];
		pivot.active             = (int)(double)obj["pivot"];
		collision_enabled.active = (bool)obj["collision_enabled"];
		collision_xy.value       = Vector2((double)obj["collision_x"], (double)obj["collision_y"]);
		collision_wh.value       = Vector2((double)obj["collision_w"], (double)obj["collision_h"]);

		circle_collision_center.value = Vector2(obj.has_key("circle_collision_center_x") ? (double)obj["circle_collision_center_x"] : cell.value.x/2.0, obj.has_key("circle_collision_center_y") ? (double)obj["circle_collision_center_y"] : cell.value.y/2.0);
		circle_collision_radius.value = obj.has_key("circle_collision_radius") ? (double)obj["circle_collision_radius"] : 32;

		capsule_collision_center.value = Vector2(obj.has_key("capsule_collision_center_x") ? (double)obj["capsule_collision_center_x"] : cell.value.x/2.0, obj.has_key("capsule_collision_center_y") ? (double)obj["capsule_collision_center_y"] : cell.value.y/2.0);
		capsule_collision_radius.value = obj.has_key("capsule_collision_radius") ? (double)obj["capsule_collision_radius"] : 32;
		capsule_collision_height.value = obj.has_key("capsule_collision_height") ? (double)obj["capsule_collision_height"] : 64;

		shape.visible_child_name = obj.has_key("shape_active_name") ? (string)obj["shape_active_name"] : "square_collider";
		actor_class.value        = obj.has_key("actor_class") ? (string)obj["actor_class"] : "static";
		lock_rotation_z.active   = obj.has_key("lock_rotation_z") ? (bool)obj["lock_rotation_z"] : true;
		mass.value               = obj.has_key("mass") ? (double)obj["mass"] : 10.0;
	}

	public Hashtable encode()
	{
		Hashtable obj = new Hashtable();

		obj["num_h"]                      = cells.value.x;
		obj["num_v"]                      = cells.value.y;
		obj["cell_w"]                     = cell.value.x;
		obj["cell_h"]                     = cell.value.y;
		obj["offset_x"]                   = offset.value.x;
		obj["offset_y"]                   = offset.value.y;
		obj["spacing_x"]                  = spacing.value.x;
		obj["spacing_y"]                  = spacing.value.y;
		obj["layer"]                      = layer.value;
		obj["depth"]                      = depth.value;
		obj["pivot"]                      = pivot.active;
		obj["collision_enabled"]          = collision_enabled.active;
		obj["collision_x"]                = collision_xy.value.x;
		obj["collision_y"]                = collision_xy.value.y;
		obj["collision_w"]                = collision_wh.value.x;
		obj["collision_h"]                = collision_wh.value.y;
		obj["circle_collision_center_x"]  = circle_collision_center.value.x;
		obj["circle_collision_center_y"]  = circle_collision_center.value.y;
		obj["circle_collision_radius"]    = circle_collision_radius.value;
		obj["capsule_collision_center_x"] = capsule_collision_center.value.x;
		obj["capsule_collision_center_y"] = capsule_collision_center.value.y;
		obj["capsule_collision_radius"]   = capsule_collision_radius.value;
		obj["capsule_collision_height"]   = capsule_collision_height.value;
		obj["shape_active_name"]          = shape.visible_child_name;
		obj["actor_class"]                = actor_class.value;
		obj["lock_rotation_z"]            = lock_rotation_z.active;
		obj["mass"]                       = mass.value;

		return obj;
	}

	public void on_import()
	{
		_import_result(SpriteResource.do_import(this, _project, _destination_dir, _filenames));
		close();
	}
}

public class SpriteResource
{
	public static ImportResult do_import(SpriteImportDialog dlg, Project project, string destination_dir, GLib.SList<string> filenames)
	{
		int width     = (int)dlg._pixbuf.width;
		int height    = (int)dlg._pixbuf.height;
		int num_h     = (int)dlg.cells.value.x;
		int num_v     = (int)dlg.cells.value.y;
		int cell_w    = (int)dlg.cell.value.x;
		int cell_h    = (int)dlg.cell.value.y;
		int offset_x  = (int)dlg.offset.value.x;
		int offset_y  = (int)dlg.offset.value.y;
		int spacing_x = (int)dlg.spacing.value.x;
		int spacing_y = (int)dlg.spacing.value.y;
		double layer  = dlg.layer.value;
		double depth  = dlg.depth.value;

		Vector2 pivot_xy = sprite_cell_pivot_xy(cell_w, cell_h, dlg.pivot.active);

		bool collision_enabled         = dlg.collision_enabled.active;
		string shape_active_name       = (string)dlg.shape.visible_child_name;
		int circle_collision_center_x  = (int)dlg.circle_collision_center.value.x;
		int circle_collision_center_y  = (int)dlg.circle_collision_center.value.y;
		int circle_collision_radius    = (int)dlg.circle_collision_radius.value;
		int capsule_collision_center_x = (int)dlg.capsule_collision_center.value.x;
		int capsule_collision_center_y = (int)dlg.capsule_collision_center.value.y;
		int capsule_collision_radius   = (int)dlg.capsule_collision_radius.value;
		int capsule_collision_height   = (int)dlg.capsule_collision_height.value;
		int collision_x                = (int)dlg.collision_xy.value.x;
		int collision_y                = (int)dlg.collision_xy.value.y;
		int collision_w                = (int)dlg.collision_wh.value.x;
		int collision_h                = (int)dlg.collision_wh.value.y;
		string actor_class             = (string)dlg.actor_class.value;
		bool lock_rotation_z           = dlg.lock_rotation_z.active;
		double mass                    = (double)dlg.mass.value;

		foreach (unowned string filename_i in filenames) {
			GLib.File file_src = File.new_for_path(filename_i);
			string resource_basename;
			if (filenames.length() == 1)
				resource_basename = dlg._unit_name.value + "." + dlg._image_type;
			else
				resource_basename = file_src.get_basename();

			GLib.File file_dst       = File.new_for_path(Path.build_filename(destination_dir, resource_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);

			try {
				SJSON.save(dlg.encode(), project.absolute_path(resource_name) + ".importer_settings");
			} catch (JsonWriteError e) {
				loge(e.message);
				return ImportResult.ERROR;
			}

			Database db = new Database(project);

			MaterialResource material_resource = MaterialResource.sprite(db, Guid.new_guid(), resource_name);
			if (material_resource.save(project, resource_name) != 0)
				return ImportResult.ERROR;

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

			var texture_resource = TextureResource.sprite(db, Guid.new_guid(), resource_path);
			if (texture_resource.save(project, resource_name) != 0)
				return ImportResult.ERROR;

			db.reset();

			// Generate .sprite.
			Guid sprite_id = Guid.new_guid();
			db.create(sprite_id, OBJECT_TYPE_SPRITE);
			db.set_double(sprite_id, "width", width);
			db.set_double(sprite_id, "height", height);

			// Create 'animations' folder.
			string directory_name = "animations";
			string animations_path = destination_dir;
			{
				GLib.File animations_file = File.new_for_path(Path.build_filename(destination_dir, directory_name));
				try {
					animations_file.make_directory();
				} catch (GLib.IOError.EXISTS e) {
					// Ignore.
				} catch (GLib.Error e) {
					loge(e.message);
					return ImportResult.ERROR;
				}

				animations_path = animations_file.get_path();
			}

			// Generate .sprite_animation.
			Guid sprite_animation_id = Guid.new_guid();
			SpriteAnimation sa = SpriteAnimation(db, sprite_animation_id);

			double frame_index = 0.0;

			for (int r = 0; r < num_v; ++r) {
				for (int c = 0; c < num_h; ++c) {
					Vector2 cell_xy = sprite_cell_xy(r
						, c
						, offset_x
						, offset_y
						, cell_w
						, cell_h
						, spacing_x
						, spacing_y
						);

					// Pivot is relative to the top-left corner of the cell
					int x = (int)cell_xy.x;
					int y = (int)cell_xy.y;

					Guid frame_id = Guid.new_guid();
					db.create(frame_id, "sprite_frame");
					db.set_string    (frame_id, "name", "sprite_%d".printf(c + num_h*r));
					db.set_quaternion(frame_id, "region", Quaternion(x, y, cell_w, cell_h));
					db.set_vector3   (frame_id, "pivot", Vector3(x + pivot_xy.x, y + pivot_xy.y, 0.0));
					db.set_double    (frame_id, "index", frame_index);

					db.add_to_set(sprite_id, "frames", frame_id);

					Guid anim_frame = Guid.new_guid();
					AnimationFrame af = AnimationFrame(db
						, anim_frame
						, (int)frame_index
						, (int)frame_index
						);
					sa.add_frame(af);

					frame_index++;
				}
			}

			if (db.save(project.absolute_path(resource_name) + ".sprite", sprite_id) != 0)
				return ImportResult.ERROR;

			string anim_basename = GLib.File.new_for_path(resource_name).get_basename();
			string anim_filename = Path.build_filename(animations_path, anim_basename + "_default" + "." + OBJECT_TYPE_SPRITE_ANIMATION);
			GLib.File anim_file  = GLib.File.new_for_path(anim_filename);
			string anim_path     = anim_file.get_path();

			string anim_resource_filename = project.resource_filename(anim_path);
			string anim_resource_path     = ResourceId.normalize(anim_resource_filename);
			string anim_resource_name     = ResourceId.name(anim_resource_path);

			if (sa.save(project, anim_resource_name) != 0)
				return ImportResult.ERROR;

			// Generate .state_machine.
			Guid state_machine_id = Guid.new_guid();
			StateMachineResource smr = StateMachineResource.sprite(db, state_machine_id, anim_resource_name);
			if (smr.save(project, resource_name) != 0)
				return ImportResult.ERROR;

			db.reset();

			// 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);
			}

			Unit unit = Unit(db, unit_id);

			// 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_vector3   (component_id, "data.position", VECTOR3_ZERO);
				unit.set_component_quaternion(component_id, "data.rotation", QUATERNION_IDENTITY);
				unit.set_component_vector3   (component_id, "data.scale", VECTOR3_ONE);
			}

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

				unit.set_component_string(component_id, "data.material", resource_name);
				unit.set_component_string(component_id, "data.sprite_resource", resource_name);
				unit.set_component_double(component_id, "data.layer", layer);
				unit.set_component_double(component_id, "data.depth", depth);
				unit.set_component_bool  (component_id, "data.visible", true);
			}

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

				unit.set_component_string(component_id, "data.state_machine_resource", resource_name);
			}

			if (collision_enabled) {
				// Create collider
				double PIXELS_PER_METER = 32.0;
				{
					Quaternion rotation = QUATERNION_IDENTITY;

					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_string(component_id, "data.source", "inline");
					if (shape_active_name == "square_collider") {
						double pos_x =  (collision_x + collision_w/2.0 - pivot_xy.x) / PIXELS_PER_METER;
						double pos_y = -(collision_y + collision_h/2.0 - pivot_xy.y) / PIXELS_PER_METER;
						Vector3 position = Vector3(pos_x, pos_y, 0);
						Vector3 half_extents = Vector3(collision_w/2/PIXELS_PER_METER, collision_h/2/PIXELS_PER_METER, 0.5/PIXELS_PER_METER);
						unit.set_component_vector3   (component_id, "data.collider_data.position", position);
						unit.set_component_quaternion(component_id, "data.collider_data.rotation", rotation);
						unit.set_component_string    (component_id, "data.shape", "box");
						unit.set_component_vector3   (component_id, "data.collider_data.half_extents", half_extents);
					} else if (shape_active_name == "circle_collider") {
						double pos_x =  (circle_collision_center_x - pivot_xy.x) / PIXELS_PER_METER;
						double pos_y = -(circle_collision_center_y - pivot_xy.y) / PIXELS_PER_METER;
						Vector3 position = Vector3(pos_x, pos_y, 0);
						double radius = circle_collision_radius / PIXELS_PER_METER;
						unit.set_component_vector3   (component_id, "data.collider_data.position", position);
						unit.set_component_quaternion(component_id, "data.collider_data.rotation", rotation);
						unit.set_component_string    (component_id, "data.shape", "sphere");
						unit.set_component_double    (component_id, "data.collider_data.radius", radius);
					} else if (shape_active_name == "capsule_collider") {
						double pos_x =  (capsule_collision_center_x - pivot_xy.x) / PIXELS_PER_METER;
						double pos_y = -(capsule_collision_center_y - pivot_xy.y) / PIXELS_PER_METER;
						Vector3 position = Vector3(pos_x, pos_y, 0);
						double radius = capsule_collision_radius / PIXELS_PER_METER;
						double capsule_height = (capsule_collision_height - 2*capsule_collision_radius) / PIXELS_PER_METER;
						unit.set_component_vector3   (component_id, "data.collider_data.position", position);
						unit.set_component_quaternion(component_id, "data.collider_data.rotation", Quaternion.from_axis_angle(Vector3(0, 0, 1), (float)Math.PI/2));
						unit.set_component_string    (component_id, "data.shape", "capsule");
						unit.set_component_double    (component_id, "data.collider_data.radius", radius);
						unit.set_component_double    (component_id, "data.collider_data.height", capsule_height);
					}
				}

				// 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_string(component_id, "data.class", actor_class);
					unit.set_component_string(component_id, "data.collision_filter", "default");
					unit.set_component_bool  (component_id, "data.lock_rotation_x", true);
					unit.set_component_bool  (component_id, "data.lock_rotation_z", true);
					unit.set_component_bool  (component_id, "data.lock_rotation_z", lock_rotation_z);
					unit.set_component_bool  (component_id, "data.lock_translation_x", false);
					unit.set_component_bool  (component_id, "data.lock_translation_y", false);
					unit.set_component_bool  (component_id, "data.lock_translation_z", true);
					unit.set_component_double(component_id, "data.mass", mass);
					unit.set_component_string(component_id, "data.material", "default");
				}
			} else { /* if (collision_enabled) */
				// Destroy collider and actor if any
				Guid component_id;
				if (unit.has_component(out component_id, OBJECT_TYPE_COLLIDER)) {
					db.remove_from_set(unit_id, "components", component_id);
					db.destroy(component_id);
				}
				if (unit.has_component(out component_id, OBJECT_TYPE_ACTOR)) {
					db.remove_from_set(unit_id, "components", component_id);
					db.destroy(component_id);
				}
			}

			if (db.save(project.absolute_path(resource_name) + ".unit", unit_id) != 0)
				return ImportResult.ERROR;
		}

		return ImportResult.SUCCESS;
	}

	public static void import(Import import_result, Database database, string destination_dir, SList<string> filenames, Gtk.Window? parent_window)
	{
		SpriteImportDialog dlg = new SpriteImportDialog(database, destination_dir, filenames, import_result);
		dlg.set_transient_for(parent_window);
		dlg.set_modal(true);
		dlg.show_all();
	}
}

} /* namespace Crown */
