/*
 * Copyright (c) 2016-2021, 2023-2024 gnome-mpv
 *
 * This file is part of Celluloid.
 *
 * Celluloid is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Celluloid is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Celluloid.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <glib/gi18n.h>
#include <adwaita.h>

#include "celluloid-plugins-manager.h"
#include "celluloid-plugins-manager-item.h"
#include "celluloid-file-dialog.h"
#include "celluloid-common.h"

enum
{
	PROP_0,
	PROP_PARENT,
	N_PROPERTIES
};

struct _CelluloidPluginsManager
{
	AdwPreferencesPage parent;
	GtkWindow *parent_window;
	AdwPreferencesGroup *pref_group;
	GtkWidget *placeholder;
	GFileMonitor *monitor;
	gchar *directory;
};

struct _CelluloidPluginsManagerClass
{
	AdwPreferencesPage parent_class;
};

G_DEFINE_TYPE(CelluloidPluginsManager, celluloid_plugins_manager, ADW_TYPE_PREFERENCES_PAGE)

static void
celluloid_plugins_manager_constructed(GObject *object);

static void
celluloid_plugins_manager_finalize(GObject *object);

static void
celluloid_plugins_manager_set_property(	GObject *object,
					guint property_id,
					const GValue *value,
					GParamSpec *pspec );

static void
celluloid_plugins_manager_get_property(	GObject *object,
					guint property_id,
					GValue *value,
					GParamSpec *pspec );

static void
add_dialog_callback(GObject *source_object, GAsyncResult *res, gpointer data);

static void
add_handler(GtkButton *button, gpointer data);

static gboolean
drop_handler(	GtkDropTarget *self,
		GValue *value,
		gdouble x,
		gdouble y,
		gpointer data );

static void
changed_handler(	GFileMonitor *monitor,
			GFile *file,
			GFile *other_file,
			GFileMonitorEvent event_type,
			gpointer data );

static void
copy_file_to_directory(CelluloidPluginsManager *pmgr, GFile *src);

static void
celluloid_plugins_manager_constructed(GObject *object)
{
	CelluloidPluginsManager *self = CELLULOID_PLUGINS_MANAGER(object);
	gchar *scripts_dir = get_scripts_dir_path();

	celluloid_plugins_manager_set_directory(self, scripts_dir);

	g_free(scripts_dir);

	G_OBJECT_CLASS(celluloid_plugins_manager_parent_class)
		->constructed(object);
}

static void
celluloid_plugins_manager_finalize(GObject *object)
{
	CelluloidPluginsManager *pmgr = CELLULOID_PLUGINS_MANAGER(object);

	g_clear_object(&pmgr->monitor);
	g_free(pmgr->directory);

	G_OBJECT_CLASS(celluloid_plugins_manager_parent_class)
		->finalize(object);
}

static void
celluloid_plugins_manager_set_property(	GObject *object,
					guint property_id,
					const GValue *value,
					GParamSpec *pspec )
{
	CelluloidPluginsManager *self = CELLULOID_PLUGINS_MANAGER(object);

	if(property_id == PROP_PARENT)
	{
		self->parent_window = g_value_get_pointer(value);
	}
	else
	{
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
	}
}

static void
celluloid_plugins_manager_get_property(	GObject *object,
					guint property_id,
					GValue *value,
					GParamSpec *pspec )
{
	CelluloidPluginsManager *self = CELLULOID_PLUGINS_MANAGER(object);

	if(property_id == PROP_PARENT)
	{
		g_value_set_pointer(value, self->parent_window);
	}
	else
	{
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
	}
}

static GtkWidget*
get_list_box_from_pref_group(AdwPreferencesGroup *pref_group)
{
	GtkWidget *result = NULL;

	result = gtk_widget_get_first_child(GTK_WIDGET(pref_group));
	result = gtk_widget_get_last_child(result);
	result = gtk_widget_get_first_child(result);

	return result;
}

static void
add_dialog_callback(GObject *source_object, GAsyncResult *res, gpointer data)
{
	CelluloidFileDialog *dialog =
		CELLULOID_FILE_DIALOG(source_object);
	CelluloidPluginsManager *pmgr =
		CELLULOID_PLUGINS_MANAGER(data);
	GListModel *files =
		celluloid_file_dialog_open_multiple_finish(dialog, res, NULL);

	if(files)
	{
		const guint n_items = g_list_model_get_n_items(files);

		for(guint i = 0; i < n_items; i++)
		{
			GFile *file = g_list_model_get_item(files, i);
			copy_file_to_directory(pmgr, file);
		}

		g_object_unref(files);
	}

	g_object_unref(dialog);
}

static void
add_handler(GtkButton *button, gpointer data)
{
	CelluloidPluginsManager *pmgr = CELLULOID_PLUGINS_MANAGER(data);
	CelluloidFileDialog *dialog = celluloid_file_dialog_new(TRUE);

	celluloid_file_dialog_set_title(dialog, _("Add Plugin"));

	GListStore *filters = g_list_store_new(GTK_TYPE_FILE_FILTER);

	GtkFileFilter *filter = NULL;

	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("All Files"));
	gtk_file_filter_add_pattern(filter, "*");
	g_list_store_append(filters, filter);

	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("Lua Plugins"));
	gtk_file_filter_add_mime_type(filter, "text/x-lua");
	g_list_store_append(filters, filter);
	celluloid_file_dialog_set_default_filter(dialog, filter);

	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("JavaScript Plugins"));
	gtk_file_filter_add_mime_type(filter, "application/javascript");
	g_list_store_append(filters, filter);

	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("C Plugins"));
	gtk_file_filter_add_mime_type(filter, "application/x-sharedlib");
	g_list_store_append(filters, filter);

	celluloid_file_dialog_set_filters
		(	dialog,
			G_LIST_MODEL(filters) );
	celluloid_file_dialog_open_multiple
		(	dialog,
			pmgr->parent_window,
			NULL,
			add_dialog_callback,
			pmgr );
}

static gboolean
drop_handler(	GtkDropTarget *self,
		GValue *value,
		gdouble x,
		gdouble y,
		gpointer data )
{
	g_assert(G_VALUE_HOLDS(value, G_TYPE_FILE));

	GFile *file = g_value_get_object(value);
	copy_file_to_directory(CELLULOID_PLUGINS_MANAGER(data), file);

	return TRUE;
}

static void
changed_handler(	GFileMonitor *monitor,
			GFile *file,
			GFile *other_file,
			GFileMonitorEvent event_type,
			gpointer data )
{
	CelluloidPluginsManager *pmgr = data;
	GDir *dir = NULL;
	const gchar *filename = NULL;
	gboolean empty = TRUE;
	const GFileMonitorEvent mask =	G_FILE_MONITOR_EVENT_CREATED|
					G_FILE_MONITOR_EVENT_DELETED|
					G_FILE_MONITOR_EVENT_UNMOUNTED;

	if(event_type&mask)
	{
		g_assert(pmgr->directory);

		dir = g_dir_open(pmgr->directory, 0, NULL);
	}

	if(dir)
	{
		AdwPreferencesGroup *pref_group;
		GtkWidget *first_child;
		GtkWidget *list_box;

		pref_group = ADW_PREFERENCES_GROUP(pmgr->pref_group);

		// It's not possible to cleanly delete PreferenceGroup's
		// children, hence this hack.
		list_box = get_list_box_from_pref_group(pref_group);

		first_child = gtk_widget_get_first_child(list_box);

		// Empty the list box
		while(first_child)
		{
			gtk_list_box_remove
				(GTK_LIST_BOX(list_box), first_child);

			first_child = gtk_widget_get_first_child(list_box);
		}

		// Populate the list box with files in the plugins directory
		filename = g_dir_read_name(dir);

		while(filename)
		{
			gchar *full_path =
				g_build_filename
				(pmgr->directory, filename, NULL);
			GtkWidget *item =
				celluloid_plugins_manager_item_new
				(	pmgr->parent_window,
					filename,
					full_path );

			adw_preferences_group_add
				(pmgr->pref_group, item);

			empty = FALSE;

			g_free(full_path);

			filename = g_dir_read_name(dir);
		}

		gtk_widget_set_visible(pmgr->placeholder, empty);

		g_dir_close(dir);
	}
}

static void
copy_file_to_directory(CelluloidPluginsManager *pmgr, GFile *src)
{
	gchar *dest_path = NULL;
	GFile *dest = NULL;
	GError *error = NULL;

	if(src)
	{
		g_assert(pmgr->directory);

		dest_path = g_build_filename(	pmgr->directory,
						g_file_get_basename(src),
						NULL );
		dest = g_file_new_for_path(dest_path);

		g_file_copy(	src,
				dest,
				G_FILE_COPY_NONE,
				NULL, NULL, NULL,
				&error );
	}

	if(error)
	{
		gchar *src_path =
			g_file_get_path(src)?
			NULL:
			g_file_get_uri(src);
		gchar *message =
			g_strdup_printf
			(	_("Failed to copy file from '%s' to '%s'. Reason: %s"),
				src_path,
				dest_path,
				error->message );

		g_signal_emit_by_name(pmgr, "error-raised", message, NULL);
		g_warning("%s", message);

		g_error_free(error);
		g_free(src_path);
		g_free(message);
	}

	g_clear_object(&dest);
	g_free(dest_path);
}

static void
celluloid_plugins_manager_class_init(CelluloidPluginsManagerClass *klass)
{
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
	GParamSpec *pspec = NULL;

	obj_class->constructed = celluloid_plugins_manager_constructed;
	obj_class->finalize = celluloid_plugins_manager_finalize;
	obj_class->set_property = celluloid_plugins_manager_set_property;
	obj_class->get_property = celluloid_plugins_manager_get_property;

	g_signal_new(	"error-raised",
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_FIRST,
			0,
			NULL,
			NULL,
			g_cclosure_marshal_VOID__STRING,
			G_TYPE_NONE,
			1,
			G_TYPE_STRING );

	pspec = g_param_spec_pointer
		(	"parent",
			"Parent",
			"Parent window for the dialogs",
			G_PARAM_CONSTRUCT_ONLY|G_PARAM_READWRITE );

	g_object_class_install_property(obj_class, PROP_PARENT, pspec);
}

static void
celluloid_plugins_manager_init(CelluloidPluginsManager *pmgr)
{
	GtkWidget *add_button = gtk_button_new();
	GtkWidget *add_button_child = adw_button_content_new();

	adw_preferences_page_set_title
		(ADW_PREFERENCES_PAGE(pmgr), _("Plugins"));
	adw_preferences_page_set_icon_name
		(ADW_PREFERENCES_PAGE(pmgr), "application-x-addon-symbolic");

	gtk_widget_add_css_class
		(add_button, "flat");
	adw_button_content_set_label
		(ADW_BUTTON_CONTENT(add_button_child), _("Add…"));
	adw_button_content_set_icon_name
		(ADW_BUTTON_CONTENT(add_button_child), "list-add-symbolic");
	gtk_button_set_child
		(GTK_BUTTON(add_button), add_button_child);

	pmgr->pref_group = ADW_PREFERENCES_GROUP(adw_preferences_group_new());
	pmgr->placeholder = adw_status_page_new();
	pmgr->parent_window = NULL;
	pmgr->monitor = NULL;
	pmgr->directory = NULL;

	adw_preferences_group_set_header_suffix(pmgr->pref_group, add_button);

	g_signal_connect(	add_button,
				"clicked",
				G_CALLBACK(add_handler),
				pmgr );

	GtkDropTarget *drop_target =
		gtk_drop_target_new(G_TYPE_FILE, GDK_ACTION_COPY);

	gtk_widget_add_controller
		(GTK_WIDGET(pmgr), GTK_EVENT_CONTROLLER(drop_target));

	g_signal_connect(	drop_target,
				"drop",
				G_CALLBACK(drop_handler),
				pmgr );

	gtk_widget_set_tooltip_text(add_button, _("Add Plugin"));

	adw_status_page_set_title
		(	ADW_STATUS_PAGE(pmgr->placeholder),
			_("No Plugins Found") );
	adw_status_page_set_description
		(	ADW_STATUS_PAGE(pmgr->placeholder),
			_("Click the <b>Add…</b> button to install a new plugin") );
	adw_status_page_set_icon_name
		(	ADW_STATUS_PAGE(pmgr->placeholder),
			"application-x-addon-symbolic" );

	gtk_widget_set_vexpand
		(pmgr->placeholder, true);
	gtk_widget_add_css_class
		(pmgr->placeholder,"card");
	adw_preferences_group_add
		(ADW_PREFERENCES_GROUP(pmgr->pref_group), pmgr->placeholder);

	adw_preferences_page_add(ADW_PREFERENCES_PAGE(pmgr), pmgr->pref_group);
}

AdwPreferencesPage *
celluloid_plugins_manager_new(GtkWindow *parent)
{
	return g_object_new(	celluloid_plugins_manager_get_type(),
				"parent", parent,
				NULL);
}

void
celluloid_plugins_manager_set_directory(	CelluloidPluginsManager *pmgr,
						const gchar *path )
{
	GFile *directory = g_file_new_for_path(path);

	g_clear_object(&pmgr->monitor);
	g_free(pmgr->directory);

	pmgr->monitor = g_file_monitor_directory(	directory,
							G_FILE_MONITOR_NONE,
							NULL,
							NULL );
	pmgr->directory = g_strdup(path);

	if(pmgr->monitor)
	{
		g_signal_connect(	pmgr->monitor,
					"changed",
					G_CALLBACK(changed_handler),
					pmgr );

		changed_handler(	pmgr->monitor, NULL, NULL,
					G_FILE_MONITOR_EVENT_CREATED,
					pmgr );
	}
	else
	{
		g_warning("Failed to monitor directory %s", path);
	}

	g_object_unref(directory);
}
