// vim: colorcolumn=80 ts=4 sw=4

#include "gfi-list-item.h"

#define UI_RESOURCE RESOURCE_PREFIX "listitem.ui"

/* 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;

	GtkListItem *parent_list_item;
	GfiListItemActionButtonType action_button_type;
	GtkGesture *gesture_click;

	/* 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 \
	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_show (dialog); \
	}

static void
run_acn (GtkWidget *widget,
		const char *action_name,
		GVariant *parameter)
{
	GfiListItem *self = GFI_LIST_ITEM(widget);
	GtkListItem *list_item = gfi_list_item_get_parent_list_item (self);
	FlatpakInstalledRef *inst_ref = gtk_list_item_get_item (list_item);
	const char *app_xdgname;
	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 (FLATPAK_IS_INSTALLED_REF (inst_ref));

	app_xdgname = g_object_get_data (G_OBJECT(inst_ref), "app_xdgname");
	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);
	GtkListItem *list_item = gfi_list_item_get_parent_list_item (self);
	FlatpakInstalledRef *inst_ref = gtk_list_item_get_item (list_item);

	g_return_if_fail (FLATPAK_IS_INSTALLED_REF (inst_ref));

	create_app_info_dialog (inst_ref, PARENT_WINDOW (self));
}

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

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

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

	/* Create default css and apply to all our children */

	provider = gtk_css_provider_new ();
	gtk_css_provider_load_from_data (provider,
			"label.subtitle {\n"
			"  font-weight: bold;\n"
			"}",
			-1);
	apply_css_provider_to_widget_and_all_children (widget, provider);

	/* setup action button - default to remove */

	gfi_list_item_set_action_button_text (self,
			GFI_LIST_ITEM_ACTION_BUTTON_TYPE_REMOVE,
			NULL);

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

	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 */

	/* Signals */

	signals[ACTION_BUTTON_CLICKED] = g_signal_new_class_handler (
			"action-button-clicked",
			G_OBJECT_CLASS_TYPE (object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 1 parameter: GfiListItemActionButtonType (as integer) */
			1,
			G_TYPE_INT);

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

void
gfi_list_item_set_action_button_text (GfiListItem *self,
		GfiListItemActionButtonType type,
		const char *text)
{
	g_return_if_fail (GFI_IS_LIST_ITEM (self));

	switch (type)
	{
		case GFI_LIST_ITEM_ACTION_BUTTON_TYPE_REMOVE:
			self->action_button_type = type;
			gtk_button_set_label (GTK_BUTTON(self->action_button),
					text ? text : "Remove");
			break;

		case GFI_LIST_ITEM_ACTION_BUTTON_TYPE_UPGRADE:
			self->action_button_type = type;
			gtk_button_set_label (GTK_BUTTON(self->action_button),
					text ? text : "Upgrade");
			break;

		case GFI_LIST_ITEM_ACTION_BUTTON_TYPE_CUSTOM:
			self->action_button_type = type;
			if (text)
			{
				gtk_button_set_label (GTK_BUTTON(self->action_button),
						text);
			}
			else
			{
				g_warning ("%s: Custom button type chosen, but no text provided. "
						"Setting to a dummy string.", __func__);
				gtk_button_set_label (GTK_BUTTON(self->action_button),
						"NULL");
			}
			break;

		default:
			g_error ("%s: Programmer error - "
					"invalid GfiListItemActionButtonType provided.",
					__func__);
			break;
	}
}

void
gfi_list_item_set_parent_list_item (GfiListItem *self, GtkListItem *item)
{
	g_return_if_fail (GFI_IS_LIST_ITEM (self));
	g_return_if_fail (GTK_IS_LIST_ITEM (item));

	self->parent_list_item = item;
}

GtkListItem *
gfi_list_item_get_parent_list_item (GfiListItem *self)
{
	g_return_val_if_fail (GFI_IS_LIST_ITEM (self), NULL);

	return self->parent_list_item;
}

GfiListItemActionButtonType
gfi_list_item_get_action_button_type (GfiListItem *self)
{
	g_assert (GFI_IS_LIST_ITEM (self));

	return self->action_button_type;
}
