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

#include "gfi-list-widget.h"

#define UI_RESOURCE RESOURCE_PREFIX "gfi-list-widget.ui"

/* GLOBALS FOR PROPERTIES */

enum property_types
{
	PROP_LIST_TYPE = 1,
	N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES];

/* GLOBALS FOR SIGNALS */

enum signal_types
{
	REMOVE_REQUESTED,
	UPGRADE_REQUESTED,
	REFRESH_DONE,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

/* METHOD DECLARATIONS */

static void gfi_list_widget_dispose (GObject *object);
static void gfi_list_widget_finalize (GObject *object);

/* GOBJECT DEFINITION */

struct _GfiListWidget
{
	GtkWidget parent_instance;

	GfiListWidgetType type;
	GListStore *list_store;			/* of GfiListEntry */
	FlatpakInstallation *user_inst;
	GCancellable *cancellable;

	/* From template */
	GtkWidget *scrolled_window;
	GtkWidget *list_view;
};

G_DEFINE_TYPE (GfiListWidget, gfi_list_widget, GTK_TYPE_WIDGET)

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

/* PROPERTIES - GETTERS AND SETTERS */

static void
gfi_list_widget_set_property (GObject *object,
		guint property_id,
		const GValue *value,
		GParamSpec *pspec)
{
	GfiListWidget *self = GFI_LIST_WIDGET(object);

	switch (property_id)
	{
		case PROP_LIST_TYPE:
			self->type = g_value_get_enum (value);
			g_object_notify_by_pspec (G_OBJECT(self), properties[PROP_LIST_TYPE]);
			break;

		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

static void
gfi_list_widget_get_property (GObject *object,
		guint property_id,
		GValue *value,
		GParamSpec *pspec)
{
	GfiListWidget *self = GFI_LIST_WIDGET(object);

	switch (property_id)
	{
		case PROP_LIST_TYPE:
			g_value_set_enum (value, self->type);
			break;

		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}


/* INTERNAL FUNCTION DEFINITIONS */

static void
newest_version_async_ready_cb (GObject *source_object,
		GAsyncResult *res,
		gpointer user_data)
{
	GTask *task = G_TASK(res);
	FlatpakInstalledRef *inst_ref = FLATPAK_INSTALLED_REF(source_object);
	GCancellable *cancellable = g_task_get_cancellable (task);
	GfiListEntry *entry = GFI_LIST_ENTRY(user_data);
	g_autofree char *app_latest_version = NULL;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	app_latest_version = newest_version_of_app_from_installed_ref_finish (inst_ref, res);

	if (app_latest_version)
		gfi_list_entry_set_app_latest_version (entry, app_latest_version);
}

/* This function is called whenever an item in the list is activated (eg
 * clicked on).
 */
static void
activate_cb (GtkListView *list,
		guint position,
		gpointer user_data)
{
	GfiListWidget *self = GFI_LIST_WIDGET(user_data);
	GfiListEntry *entry = g_list_model_get_item (G_LIST_MODEL(gtk_list_view_get_model (list)), position);

	gfi_list_entry_create_app_info_dialog (entry, PARENT_WINDOW (self));
}

/* METHOD DEFINITIONS */

static void
gfi_list_widget_init (GfiListWidget *self)
{
	GtkWidget *widget = GTK_WIDGET(self);

	gtk_widget_init_template (widget);

	/* Setup list_store / selection model for list_view
	 */
	self->list_store = g_list_store_new (GFI_TYPE_LIST_ENTRY);
	gtk_list_view_set_model (GTK_LIST_VIEW(self->list_view), 
			GTK_SELECTION_MODEL(gtk_single_selection_new (G_LIST_MODEL(self->list_store))));

	self->cancellable = g_cancellable_new ();

	g_signal_connect (self->list_view, "activate", G_CALLBACK(activate_cb), self);
}

static void
gfi_list_widget_dispose (GObject *object)
{
	GfiListWidget *self = GFI_LIST_WIDGET(object);

	g_clear_pointer (&self->scrolled_window, gtk_widget_unparent);

	g_cancellable_cancel (self->cancellable);
	g_clear_object (&self->cancellable);
	g_clear_object (&self->user_inst);

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

static void
gfi_list_widget_finalize (GObject *object)
{
	/* Chain up */
	G_OBJECT_CLASS(gfi_list_widget_parent_class)->finalize (object);
}

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

	object_class->dispose = gfi_list_widget_dispose;
	object_class->finalize = gfi_list_widget_finalize;
	object_class->set_property = gfi_list_widget_set_property;
	object_class->get_property = gfi_list_widget_get_property;

	/* WIDGET TEMPLATE .UI */

	g_type_ensure (GFI_TYPE_LIST_ITEM);
	g_type_ensure (GFI_TYPE_LIST_WIDGET_TYPE);

	gtk_widget_class_set_template_from_resource (widget_class, UI_RESOURCE);

	gtk_widget_class_bind_template_child (widget_class, GfiListWidget, list_view);
	gtk_widget_class_bind_template_child (widget_class, GfiListWidget, scrolled_window);

	/* Properties */

	properties[PROP_LIST_TYPE] = g_param_spec_enum ("list-type", NULL, NULL,
			GFI_TYPE_LIST_WIDGET_TYPE,
			GFI_LIST_WIDGET_TYPE_REMOVE,
			default_flags | G_PARAM_CONSTRUCT);

	g_object_class_install_properties (object_class, N_PROPERTIES, properties);

	/* Signals */

	signals[REMOVE_REQUESTED] = g_signal_new_class_handler (
			"remove-requested",
			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: flatpakref for removal */
			1,
			FLATPAK_TYPE_REF);

	signals[UPGRADE_REQUESTED] = g_signal_new_class_handler (
			"upgrade-requested",
			G_OBJECT_CLASS_TYPE (object_class),
			G_SIGNAL_RUN_LAST,
			NULL,
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 1 parameter: flatpakref for upgrade */
			1,
			FLATPAK_TYPE_REF);

	signals[REFRESH_DONE] = g_signal_new_class_handler (
			"refresh-done",
			G_OBJECT_CLASS_TYPE (object_class),
			G_SIGNAL_RUN_LAST,
			NULL,
			NULL, NULL, NULL,	
		/* No return type or params. */
			G_TYPE_NONE, 0);

	/* Layout Manager */

	gtk_widget_class_set_layout_manager_type (GTK_WIDGET_CLASS(klass),
			GTK_TYPE_BIN_LAYOUT);
}

/* PUBLIC METHOD DEFINITIONS */

GtkWidget *
gfi_list_widget_new (GfiListWidgetType type)
{
	GfiListWidget *self = g_object_new (GFI_TYPE_LIST_WIDGET,
			"list-type", type,
			NULL);

	return GTK_WIDGET(self);
}

static void
list_installed_async_ready_common (GObject *source_object,
		GAsyncResult *res,
		gpointer user_data,
		gboolean update)
{
	GfiListWidget *self = GFI_LIST_WIDGET(user_data);
	FlatpakInstallation *installation = FLATPAK_INSTALLATION(source_object);
	g_autoptr(GPtrArray) user_refs = update ?
		list_installed_refs_for_update_finish (installation, res) : 
		list_installed_refs_finish (installation, res);

	if (user_refs)
	{
		for (guint i = 0; i < user_refs->len; ++i)
		{
			FlatpakInstalledRef *inst_ref = g_ptr_array_index (user_refs, i);
			g_autoptr(GfiListEntry) entry = gfi_list_entry_new (self->type, inst_ref);

			/* skip invalid entries */
			if (! gfi_list_entry_get_app_xdg_name (entry))
				continue;

			g_list_store_append (self->list_store, entry);

			if (update) {
				newest_version_of_app_from_installed_ref_async (inst_ref,
						self->cancellable,
						newest_version_async_ready_cb,
						entry);
			}
		}
	}
	else
		g_debug ("%s: couldn't get array of user_refs for: %s",
				__func__, update ? "UPDATE" : "LIST/REMOVE");

	g_signal_emit (self, signals[REFRESH_DONE], 0);
}

static void
list_installed_for_update_async_ready_cb (GObject *source_object,
		GAsyncResult *res,
		gpointer user_data)
{
	list_installed_async_ready_common (source_object, res, user_data, TRUE);
}

static void
list_installed_async_ready_cb (GObject *source_object,
		GAsyncResult *res,
		gpointer user_data)
{
	list_installed_async_ready_common (source_object, res, user_data, FALSE);
}

void
gfi_list_widget_refresh (GfiListWidget *self)
{
	g_return_if_fail (GFI_IS_LIST_WIDGET (self));

	g_list_store_remove_all (self->list_store);

	g_clear_object (&self->user_inst);
	self->user_inst = flatpak_installation_new_user (NULL, NULL);

	switch (self->type)
	{
		case GFI_LIST_WIDGET_TYPE_UPGRADE:
			list_installed_refs_for_update_async (self->user_inst,
					self->cancellable,
					list_installed_for_update_async_ready_cb,
					self);
			break;

		case GFI_LIST_WIDGET_TYPE_REMOVE:
			list_installed_refs_async (self->user_inst,
					self->cancellable,
					list_installed_async_ready_cb,
					self);
			break;

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