// vim: colorcolumn=80 ts=4 sw=4 linebreak breakindent breakindentopt=shift\:4

#include "gfi-list-item.h"
#include "gfi-application-window.h"

#define UI_RESOURCE RESOURCE_PREFIX "listitem.ui"

/* GLOBALS FOR PROPERTIES */

enum property_types
{
	PROP_ENTRY = 1,
	N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES];

/* GLOBALS FOR SIGNALS */

enum signal_types {
	ACTION_BUTTON_CLICKED,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

/* METHOD DECLARATIONS */

static void gfi_list_item_dispose (GObject *object);
static void gfi_list_item_finalize (GObject *object);

/* GOBJECT DEFINITION */

struct _GfiListItem
{
	GtkWidget parent_instance;

	GtkGesture *gesture_click;
	GfiListEntry *entry;

	/* From template: */

#if 0
:r!for i in `cat ../ui/listitem.ui |grep -i 'object.*id=' |sed -e 's,^\s*,,g' |sed -e 's,.*id=",,' |sed -e 's,".*,,'`; do echo 'GtkWidget *'${i}';'; done
#endif

	GtkWidget *menu;
	GtkWidget *icon;
	GtkWidget *app_label;
	GtkWidget *app_desc_label;
	GtkWidget *action_button;
};

G_DEFINE_TYPE (GfiListItem, gfi_list_item, GTK_TYPE_WIDGET)

/* ----------------- */

/* PRIVATE INTERNAL FUNCTIONS */

/* Helper macro for run_acn */
#define HANDLE_ANY_ERRORS \
	G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
	if (local_error) \
	{ \
		GtkWidget *dialog; \
		dialog = gtk_message_dialog_new (PARENT_WINDOW (self), \
				GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, \
				GTK_MESSAGE_ERROR, \
				GTK_BUTTONS_CLOSE, \
				"Could not launch %s", g_app_info_get_display_name (appinfo)); \
		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog), \
				"%s", local_error->message); \
		g_clear_error (&local_error); \
		gtk_widget_set_visible (dialog, TRUE); \
	} \
	G_GNUC_END_IGNORE_DEPRECATIONS

static void
run_acn (GtkWidget *widget,
		const char *action_name,
		GVariant *parameter)
{
	GfiListItem *self = GFI_LIST_ITEM(widget);
	const char *app_xdgname = NULL;
	g_autofree char *cmdline = NULL;
	GAppInfo *appinfo;
	gboolean startup_notify;
	gboolean needs_terminal;
	GAppInfoCreateFlags flags = G_APP_INFO_CREATE_NONE;
	GdkAppLaunchContext *context;
	GError *local_error = NULL;

	g_return_if_fail (GFI_IS_LIST_ENTRY (self->entry));

	app_xdgname = gfi_list_entry_get_app_xdg_name (self->entry);

	cmdline = exec_cmdline_from_xdg_id (app_xdgname);
	startup_notify = startup_notify_from_xdg_id (app_xdgname);
	needs_terminal = needs_terminal_from_xdg_id (app_xdgname);
	context = gdk_display_get_app_launch_context (
			gtk_widget_get_display (GTK_WIDGET(self)));

	if (startup_notify)
		flags |= G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION;

	if (needs_terminal)
		flags |= G_APP_INFO_CREATE_NEEDS_TERMINAL;

	appinfo = g_app_info_create_from_commandline (cmdline,
			app_xdgname,
			flags,
			&local_error);
	HANDLE_ANY_ERRORS

	g_app_info_launch (appinfo,
			NULL,	/* GList* files, */
			G_APP_LAUNCH_CONTEXT(context),
			&local_error);
	HANDLE_ANY_ERRORS
}
#undef HANDLE_ANY_ERRORS

static void
info_acn (GtkWidget *widget,
		const char *action_name,
		GVariant *parameter)
{
	GfiListItem *self = GFI_LIST_ITEM(widget);

	gfi_list_entry_create_app_info_dialog (self->entry, PARENT_WINDOW (self));
}

static void
emit_action_button_clicked (GfiListItem *self)
{
	g_signal_emit (self, signals[ACTION_BUTTON_CLICKED], 0, gfi_list_entry_get_list_type (self->entry));
}

static void
right_click_cb (GtkGestureClick *gesture,
		guint n_press,
		double x,
		double y,
		gpointer user_data)
{
	GfiListItem *self = GFI_LIST_ITEM(user_data);

	/* We are placing our menu at the point where
	 * the click happened, before popping it up.
	 */
	gtk_popover_set_pointing_to (GTK_POPOVER(self->menu),
			&(const GdkRectangle){ x, y, 1, 1 });

	gtk_popover_popup (GTK_POPOVER(self->menu));
}

static void
action_button_clicked_cb (GfiListItem *self, gpointer user_data)
{
	GfiListWidgetType type = gfi_list_entry_get_list_type (self->entry);
	GtkWidget *gfi_list_widget = gfi_application_window_get_list_widget (GFI_APPLICATION_WINDOW (PARENT_WINDOW (self)), type);

	g_return_if_fail (GFI_IS_LIST_WIDGET (gfi_list_widget));

	switch (type)
	{
		case GFI_LIST_WIDGET_TYPE_REMOVE:
			g_signal_emit_by_name (gfi_list_widget, "remove-requested",
					FLATPAK_REF(gfi_list_entry_get_inst_ref (self->entry)));
			break;

		case GFI_LIST_WIDGET_TYPE_UPGRADE:
			g_signal_emit_by_name (gfi_list_widget, "upgrade-requested",
					FLATPAK_REF(gfi_list_entry_get_inst_ref (self->entry)));
			break;

		default:
			g_assert_not_reached ();
	}
}

static void
refresh_listitem_text (GfiListItem *self)
{
	const char *app_prettyname = NULL;
	const char *app_xdgname = NULL;
	g_autofree char *app_desc = NULL;

	if (! self->entry)
	{
		gfi_list_item_set_label (self, "...");
		return;
	}

	app_xdgname = gfi_list_entry_get_app_xdg_name (self->entry);
	g_return_if_fail (app_xdgname);

	app_prettyname = gfi_list_entry_get_app_pretty_name (self->entry);

	switch (gfi_list_entry_get_list_type (self->entry))
	{
		case GFI_LIST_WIDGET_TYPE_REMOVE:
			app_desc = g_strdup (gfi_list_entry_get_app_desc (self->entry));
			gtk_button_set_label (GTK_BUTTON(self->action_button), "Remove");
			break;

		case GFI_LIST_WIDGET_TYPE_UPGRADE:
		{
			const char *app_latest_version = NULL;

			gtk_button_set_label (GTK_BUTTON(self->action_button), "Upgrade");

			app_latest_version = gfi_list_entry_get_app_latest_version (self->entry);

			if (app_latest_version)
				app_desc = g_strdup_printf ("Upgrade available: %s",
						app_latest_version);
			else
				app_desc = g_strdup ("Upgrade available");
		}
			break;

		default:
			g_error ("%s: Programmer error - invalid or unhandled "
					"GfiListWidgetType. Abort.", __func__);
			break;
	}
			
	gfi_list_item_set_label (self,
			app_prettyname ? app_prettyname : app_xdgname);

	if (app_xdgname)
		gfi_list_item_set_icon_name (self, app_xdgname);
	
	if (app_desc)
		gfi_list_item_set_app_desc (self, app_desc);
}

void
gfi_list_item_set_entry (GfiListItem *self, GfiListEntry *entry)
{
	g_return_if_fail (GFI_IS_LIST_ITEM (self));

	g_clear_object (&self->entry);

	if (entry)
	{
		self->entry = g_object_ref (entry);
		g_signal_connect_swapped (self->entry, "notify::app-latest-version",
				G_CALLBACK(refresh_listitem_text), self);
	}

	refresh_listitem_text (self);

	g_object_notify_by_pspec (G_OBJECT(self), properties[PROP_ENTRY]);
}

/* transfer none */
GfiListEntry *
gfi_list_item_get_entry (GfiListItem *self)
{
	if (self->entry)
		return self->entry;
	else
		return NULL;
}

static void
gfi_list_item_get_property (GObject *object,
		guint property_id,
		GValue *value,
		GParamSpec *pspec)
{
	GfiListItem *self = GFI_LIST_ITEM(object);

	switch (property_id)
	{
		case PROP_ENTRY:
			g_value_set_object (value, gfi_list_item_get_entry (self));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

static void
gfi_list_item_set_property (GObject *object,
		guint property_id,
		const GValue *value,
		GParamSpec *pspec)
{
	GfiListItem *self = GFI_LIST_ITEM(object);

	switch (property_id)
	{
		case PROP_ENTRY:
			gfi_list_item_set_entry (self, g_value_get_object (value));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

/* METHOD DEFINITIONS */

static void
gfi_list_item_init (GfiListItem *self)
{
	GtkWidget *widget = GTK_WIDGET(self);
	GtkCssProvider *provider;

	/* Good to run this first thing in _init, acc. to TFM. */
	gtk_widget_init_template (widget);

	/* Gesture for popover */

	self->gesture_click = gtk_gesture_click_new ();

	gtk_gesture_single_set_button (GTK_GESTURE_SINGLE(self->gesture_click),
			3);	/* handle right-click only */

	g_signal_connect (self->gesture_click, "pressed",
			G_CALLBACK(right_click_cb), self);

	gtk_widget_add_controller (widget,
			GTK_EVENT_CONTROLLER(self->gesture_click));

	g_signal_connect_swapped (self->action_button, "clicked",
			G_CALLBACK (emit_action_button_clicked), self);
}

static void
gfi_list_item_dispose (GObject *object)
{
	GfiListItem *self = GFI_LIST_ITEM(object);

	g_clear_pointer (&self->icon, gtk_widget_unparent);
	g_clear_pointer (&self->app_label, gtk_widget_unparent);
	g_clear_pointer (&self->app_desc_label, gtk_widget_unparent);
	g_clear_pointer (&self->action_button, gtk_widget_unparent);
	g_clear_pointer (&self->menu, gtk_widget_unparent);

	/* Chain up */
	G_OBJECT_CLASS(gfi_list_item_parent_class)->dispose (object);
}

static void
gfi_list_item_finalize (GObject *object)
{
	/* here, you would free stuff. I've got nuthin' for ya. */

	/* --- */

	/* Chain up */
	G_OBJECT_CLASS(gfi_list_item_parent_class)->finalize(object);
}

static void
gfi_list_item_class_init (GfiListItemClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
	GParamFlags default_flags = G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY;

	object_class->set_property = gfi_list_item_set_property;
	object_class->get_property = gfi_list_item_get_property;
	object_class->dispose = gfi_list_item_dispose;
	object_class->finalize = gfi_list_item_finalize;

	gtk_widget_class_set_template_from_resource (widget_class, UI_RESOURCE);

	/* autogenerate from template: */
#if 0
:r!for i in `cat ../ui/listitem.ui |grep -i 'object.*id=' |sed -e 's,^\s*,,g' |sed -e 's,.*id=",,' |sed -e 's,".*,,'`; do echo "gtk_widget_class_bind_template_child (widget_class, GfiListItem, ${i});"; done
#endif

	gtk_widget_class_bind_template_child (widget_class, GfiListItem, menu);
	gtk_widget_class_bind_template_child (widget_class, GfiListItem, icon);
	gtk_widget_class_bind_template_child (widget_class, GfiListItem, app_label);
	gtk_widget_class_bind_template_child (widget_class, GfiListItem, app_desc_label);
	gtk_widget_class_bind_template_child (widget_class, GfiListItem, action_button);

	/* !autogenerate */

	/* Properties */

	properties[PROP_ENTRY] = g_param_spec_object ("entry", NULL, NULL,
			GFI_TYPE_LIST_ENTRY,
			default_flags | G_PARAM_READWRITE | G_PARAM_CONSTRUCT);

	g_object_class_install_properties (object_class, N_PROPERTIES, properties);

	/* Signals */

	signals[ACTION_BUTTON_CLICKED] = g_signal_new_class_handler (
			"action-button-clicked",
			G_OBJECT_CLASS_TYPE (object_class),
			G_SIGNAL_RUN_LAST,
			G_CALLBACK(action_button_clicked_cb),
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type or params */
			G_TYPE_NONE,
			0);

	/* ACTIONS */

	gtk_widget_class_install_action (widget_class, "win.info",
			NULL,
			info_acn);

	gtk_widget_class_install_action (widget_class, "win.run",
			NULL,
			run_acn);

}

/* PUBLIC METHOD DEFINITIONS */

GtkWidget *
gfi_list_item_new (void)
{
	return g_object_new (GFI_TYPE_LIST_ITEM, NULL);
}

void
gfi_list_item_set_label (GfiListItem *self, const char *text)
{
	g_return_if_fail (GFI_IS_LIST_ITEM (self));

	gtk_label_set_markup (GTK_LABEL(self->app_label), text);
}

void
gfi_list_item_set_icon_name (GfiListItem *self, const char *icon_name)
{
	g_return_if_fail (GFI_IS_LIST_ITEM (self));

	gtk_image_set_from_icon_name (GTK_IMAGE(self->icon), icon_name);
}

void
gfi_list_item_set_app_desc (GfiListItem *self, const char *text)
{
	g_return_if_fail (GFI_IS_LIST_ITEM (self));

	gtk_label_set_markup (GTK_LABEL(self->app_desc_label), text);
}
