/*
 * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <gplugin.h>
#include <gplugin-gtk/gplugin-gtk-plugin-info.h>

#include <glib/gi18n-lib.h>

/**
 * SECTION:gplugin-gtk-plugin-info
 * @title: Plugin Info Gtk Widgets
 * @short_description: Gtk Widgets for plugins
 *
 * #GPluginGtkPluginInfo is a Gtk widget that shows information about plugins.
 */

/**
 * GPLUGIN_GTK_TYPE_PLUGIN_INFO:
 *
 * The standard _get_type macro for #GPluginGtkPluginInfo.
 */

/**
 * GPluginGtkPluginInfo:
 *
 * A widget that displays a #GPluginPluginInfo in a user friendly way.
 */

/******************************************************************************
 * Structs
 *****************************************************************************/
struct _GPluginGtkPluginInfo {
	GtkBox parent;

	GPluginPlugin *plugin;
	gulong signal_id;

	GtkWidget *name;
	GtkWidget *version;
	GtkWidget *authors_box;
	GtkWidget *website;
	GtkWidget *summary;
	GtkWidget *description;
	GtkWidget *dependencies_box;
	GtkWidget *error;
	GtkWidget *expander;
	GtkWidget *id;
	GtkWidget *filename;
	GtkWidget *abi_version;
	GtkWidget *loader;
	GtkWidget *internal;
	GtkWidget *load_on_query;
};

/*****************************************************************************s
 * Enums
 *****************************************************************************/
enum {
	PROP_ZERO,
	PROP_PLUGIN,
	N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {
	NULL,
};

G_DEFINE_TYPE(GPluginGtkPluginInfo, gplugin_gtk_plugin_info, GTK_TYPE_BOX);

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
gplugin_gtk_plugin_info_expander_activate(
	GtkExpander *expander,
	G_GNUC_UNUSED gpointer data)
{
	if(gtk_expander_get_expanded(expander))
		gtk_expander_set_label(expander, "More");
	else
		gtk_expander_set_label(expander, "Less");
}

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
_gplugin_gtk_plugin_info_refresh(GPluginGtkPluginInfo *info)
{
	GtkWidget *widget = NULL;
	GError *error = NULL;
	GList *children = NULL, *iter = NULL;
	gchar *markup = NULL;
	gchar *name = NULL, *version = NULL, *website = NULL;
	gchar *summary = NULL, *description = NULL, *id = NULL, *abi_version = NULL;
	gchar *loader = NULL;
	gchar **authors = NULL;
	gchar **dependencies = NULL;
	guint32 abi_version_uint;
	gboolean loq = FALSE, internal = FALSE;
	const gchar *filename = NULL;

	/* remove all the children from the authors box */
	children = gtk_container_get_children(GTK_CONTAINER(info->authors_box));
	for(iter = children; iter; iter = iter->next)
		gtk_widget_destroy(GTK_WIDGET(iter->data));
	g_list_free(children);

	/* remove all the children from the dependencies box */
	children =
		gtk_container_get_children(GTK_CONTAINER(info->dependencies_box));
	for(iter = children; iter; iter = iter->next)
		gtk_widget_destroy(GTK_WIDGET(iter->data));
	g_list_free(children);

	/* now get the info if we can */
	if(GPLUGIN_IS_PLUGIN(info->plugin)) {
		GPluginPluginInfo *plugin_info = gplugin_plugin_get_info(info->plugin);
		GPluginLoader *plugin_loader = gplugin_plugin_get_loader(info->plugin);

		filename = gplugin_plugin_get_filename(info->plugin);
		error = gplugin_plugin_get_error(info->plugin);

		if(plugin_loader && GPLUGIN_IS_LOADER(plugin_loader)) {
			const char *loader_name = G_OBJECT_TYPE_NAME(plugin_loader);
			loader = g_strdup(loader_name);
			g_object_unref(G_OBJECT(plugin_loader));
		}

		/* clang-format off */
		g_object_get(
			G_OBJECT(plugin_info),
			"abi_version", &abi_version_uint,
			"authors", &authors,
			"summary", &summary,
			"description", &description,
			"dependencies", &dependencies,
			"id", &id,
			"internal", &internal,
			"load-on-query", &loq,
			"name", &name,
			"version", &version,
			"website", &website,
			NULL);
		/* clang-format on */

		/* fanagle the plugin name */
		markup = g_markup_printf_escaped(
			"<span font_size=\"large\" "
			"font_weight=\"bold\">%s</span>",
			(name) ? name : "Unnamed");
		g_free(name);
		name = markup;

		/* fanagle the website */
		if(website) {
			markup = g_markup_printf_escaped(
				"<a href=\"%s\">%s</a>",
				website,
				website);
			g_free(website);
			website = markup;
		}

		/* fanagle the abi_version */
		abi_version = g_strdup_printf("%08x", abi_version_uint);

		g_object_unref(G_OBJECT(plugin_info));
	}

	gtk_label_set_markup(GTK_LABEL(info->name), (name) ? name : "Unnamed");
	gtk_label_set_text(GTK_LABEL(info->version), (version) ? version : "");
	gtk_label_set_markup(GTK_LABEL(info->website), (website) ? website : "");
	gtk_label_set_text(GTK_LABEL(info->summary), (summary) ? summary : "");
	gtk_label_set_text(
		GTK_LABEL(info->description),
		(description) ? description : "");
	gtk_label_set_text(GTK_LABEL(info->id), (id) ? id : "");
	gtk_label_set_text(
		GTK_LABEL(info->error),
		(error) ? error->message : "(none)");
	gtk_label_set_text(GTK_LABEL(info->filename), (filename) ? filename : "");
	gtk_label_set_text(
		GTK_LABEL(info->abi_version),
		(abi_version) ? abi_version : "");
	gtk_label_set_text(GTK_LABEL(info->loader), (loader) ? loader : "Unknown");
	gtk_label_set_text(GTK_LABEL(info->internal), (internal) ? "Yes" : "No");
	gtk_label_set_text(GTK_LABEL(info->load_on_query), (loq) ? "Yes" : "No");

	g_free(description);
	g_free(id);
	g_free(name);
	g_free(version);
	g_free(website);
	g_free(loader);
	g_clear_error(&error);

	/* set the authors */
	if(authors) {
		gint i = 0;

		for(i = 0; authors[i]; i++) {
			widget = gtk_label_new(authors[i]);
			gtk_widget_set_halign(widget, GTK_ALIGN_START);
			gtk_widget_set_valign(widget, GTK_ALIGN_START);
			gtk_box_pack_start(
				GTK_BOX(info->authors_box),
				widget,
				TRUE,
				TRUE,
				0);
			gtk_widget_show(widget);
		}
	}
	g_strfreev(authors);

	/* set the dependencies */
	if(dependencies) {
		gint i = 0;

		for(i = 0; dependencies[i]; i++) {
			widget = gtk_label_new(dependencies[i]);
			gtk_widget_set_halign(widget, GTK_ALIGN_START);
			gtk_widget_set_valign(widget, GTK_ALIGN_START);
			gtk_box_pack_start(
				GTK_BOX(info->dependencies_box),
				widget,
				TRUE,
				TRUE,
				0);
			gtk_widget_show(widget);
		}
	} else {
		widget = gtk_label_new(_("(none)"));
		gtk_box_pack_start(
			GTK_BOX(info->dependencies_box),
			widget,
			TRUE,
			TRUE,
			0);
		gtk_widget_show(widget);
	}
	g_strfreev(dependencies);
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
gplugin_gtk_plugin_info_state_cb(GObject *obj, GParamSpec *pspec, gpointer data)
{
	_gplugin_gtk_plugin_info_refresh(GPLUGIN_GTK_PLUGIN_INFO(data));
}

/******************************************************************************
 * GObject Stuff
 *****************************************************************************/
static void
gplugin_gtk_plugin_info_set_property(
	GObject *obj,
	guint prop_id,
	const GValue *value,
	GParamSpec *pspec)
{
	GPluginGtkPluginInfo *info = GPLUGIN_GTK_PLUGIN_INFO(obj);

	switch(prop_id) {
		case PROP_PLUGIN:
			gplugin_gtk_plugin_info_set_plugin(info, g_value_get_object(value));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
			break;
	}
}

static void
gplugin_gtk_plugin_info_get_property(
	GObject *obj,
	guint prop_id,
	GValue *value,
	GParamSpec *pspec)
{
	GPluginGtkPluginInfo *info = GPLUGIN_GTK_PLUGIN_INFO(obj);

	switch(prop_id) {
		case PROP_PLUGIN:
			g_value_set_object(value, gplugin_gtk_plugin_info_get_plugin(info));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
			break;
	}
}

static void
gplugin_gtk_plugin_info_finalize(GObject *obj)
{
	GPluginGtkPluginInfo *info = GPLUGIN_GTK_PLUGIN_INFO(obj);

	if(info->signal_id != 0 && GPLUGIN_IS_PLUGIN(info->plugin)) {
		g_signal_handler_disconnect(G_OBJECT(info->plugin), info->signal_id);
	}

	g_clear_object(&info->plugin);

	G_OBJECT_CLASS(gplugin_gtk_plugin_info_parent_class)->finalize(obj);
}

static void
gplugin_gtk_plugin_info_init(GPluginGtkPluginInfo *info)
{
	gtk_widget_init_template(GTK_WIDGET(info));

	info->signal_id = 0;
}

static void
gplugin_gtk_plugin_info_class_init(GPluginGtkPluginInfoClass *klass)
{
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->get_property = gplugin_gtk_plugin_info_get_property;
	obj_class->set_property = gplugin_gtk_plugin_info_set_property;
	obj_class->finalize = gplugin_gtk_plugin_info_finalize;

	/* properties */
	properties[PROP_PLUGIN] = g_param_spec_object(
		"plugin",
		"plugin",
		"The GPluginPlugin who's info should be displayed",
		G_TYPE_OBJECT,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT);

	/* template stuff */
	gtk_widget_class_set_template_from_resource(
		widget_class,
		"/org/imfreedom/keep/gplugin/gplugin-gtk/plugin-info.ui");

	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		name);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		version);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		authors_box);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		website);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		summary);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		description);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		dependencies_box);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		error);

	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		expander);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		id);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		filename);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		abi_version);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		loader);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		internal);
	gtk_widget_class_bind_template_child(
		widget_class,
		GPluginGtkPluginInfo,
		load_on_query);

	gtk_widget_class_bind_template_callback(
		widget_class,
		gplugin_gtk_plugin_info_expander_activate);
}

/******************************************************************************
 * API
 *****************************************************************************/

/**
 * gplugin_gtk_plugin_info_new:
 *
 * Create a new GPluginGtkView which can be used to display info about a
 * #GPluginPlugin.
 *
 * Returns: (transfer full): The new #GPluginGtkView widget.
 */
GtkWidget *
gplugin_gtk_plugin_info_new(void)
{
	return GTK_WIDGET(g_object_new(GPLUGIN_GTK_TYPE_PLUGIN_INFO, NULL));
}

/**
 * gplugin_gtk_plugin_info_set_plugin:
 * @info: The #GPluginGtkPluginInfo instance.
 * @plugin: The #GPluginPlugin instance.
 *
 * Sets the #GPluginPlugin that should be displayed.
 *
 * A @plugin value of %NULL will clear the widget.
 */
void
gplugin_gtk_plugin_info_set_plugin(
	GPluginGtkPluginInfo *info,
	GPluginPlugin *plugin)
{
	g_return_if_fail(GPLUGIN_GTK_IS_PLUGIN_INFO(info));

	if(info->signal_id != 0 && GPLUGIN_IS_PLUGIN(info->plugin)) {
		g_signal_handler_disconnect(info->plugin, info->signal_id);
		info->signal_id = 0;
	}

	if(g_set_object(&info->plugin, plugin) && GPLUGIN_IS_PLUGIN(plugin)) {
		_gplugin_gtk_plugin_info_refresh(info);

		/* Connect a signal to refresh when the plugin's state changes.  We
		 * can't use g_signal_connect_object because the plugin object never
		 * gets destroyed, as the manager and the loader both keep a reference
		 * to it and the GPluginGtkPluginInfo widget is reused for all plugins
		 * so that all means that we just have to manage the callback
		 * ourselves.
		 */
		info->signal_id = g_signal_connect(
			G_OBJECT(plugin),
			"notify::state",
			G_CALLBACK(gplugin_gtk_plugin_info_state_cb),
			info);
	}
}

/**
 * gplugin_gtk_plugin_info_get_plugin:
 * @info: The #GPluginGtkPluginInfo instance.
 *
 * Returns the #GPluginPlugin that's being displayed.
 *
 * Return Value: (transfer full): The #GPluginPlugin that's being
 *                                displayed.
 */
GPluginPlugin *
gplugin_gtk_plugin_info_get_plugin(GPluginGtkPluginInfo *info)
{
	g_return_val_if_fail(GPLUGIN_GTK_IS_PLUGIN_INFO(info), NULL);

	return (info->plugin) ? GPLUGIN_PLUGIN(g_object_ref(G_OBJECT(info->plugin)))
						  : NULL;
}
