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

#include "common.h"

void
set_heading_text (GtkLabel *label, const char *text)
{
	g_autofree char *tmp = NULL;

	g_return_if_fail (GTK_IS_LABEL (label));

	tmp = g_strdup_printf ("<big><b>%s</b></big>", text);
	gtk_label_set_markup (GTK_LABEL(label), tmp);
}

char *
get_name_from_keyfile (GKeyFile *keyfile, KeyfileNameType type)
{
	char *name = NULL;

	switch (type)
	{
		case KEYFILE_NAME_APPLICATION:
			name = g_key_file_get_string (keyfile,
					"Application",
					"name",
					NULL);
			break;

		case KEYFILE_NAME_REFERENCE:
			name = g_key_file_get_string (keyfile,
					"Reference",
					"name",
					NULL);
			break;

		case KEYFILE_NAME_FLATPAKREF:
			name =  g_key_file_get_string (keyfile,
					"Flatpak Ref",
					"Name",
					NULL);
			break;

		default:
			g_error ("%s: Programmer error. Invalid KeyfileNameType.", __func__);
			break;
	}
	return name;
}

/* Helper function for the _from_xdg_id functions */
static GKeyFile *
desktop_keyfile_from_xdg_id (const char *xdg_id)
{
	GKeyFile *keyfile = NULL;
	g_autofree char *desktop_path = NULL;
	gboolean load_retval = FALSE;

	/* No point in using portable path separators as this is XDG/UNIX only. */
	desktop_path = g_strdup_printf ("applications/%s.desktop", xdg_id);

	keyfile = g_key_file_new ();
	load_retval = g_key_file_load_from_data_dirs (keyfile,
			desktop_path,
			NULL,	/* gchar** full_path, */
			G_KEY_FILE_NONE,
			NULL);
	if (! load_retval)
	{
		g_debug ("%s: No .desktop file found for: %s", __func__, xdg_id);
		return NULL;
	}
	return keyfile;
}

char *
app_desc_from_xdg_id (const char *xdg_id)
{
	char *desc = NULL;
	g_autoptr(GKeyFile) keyfile = NULL;

	keyfile = desktop_keyfile_from_xdg_id (xdg_id);

	g_return_val_if_fail (keyfile, NULL);
			
	/* Grab description from 'Comment' key of .desktop file as it tends to be
	 * most descriptive. If that fails, try 'GenericName'.
	 */
	desc = g_key_file_get_locale_string (keyfile,
			"Desktop Entry",
			"Comment",
			NULL,	/* default locale */
			NULL);
	if (! desc) desc = g_key_file_get_locale_string (keyfile,
			"Desktop Entry",
			"GenericName",
			NULL,	/* default locale */
			NULL);

	return desc;
}

char *
app_name_from_xdg_id (const char *xdg_id)
{
	char *name = NULL;
	g_autoptr(GKeyFile) keyfile = NULL;

	keyfile = desktop_keyfile_from_xdg_id (xdg_id);

	g_return_val_if_fail (keyfile, NULL);
			
	name = g_key_file_get_locale_string (keyfile,
			"Desktop Entry",
			"Name",
			NULL,	/* default locale */
			NULL);

	return name;
}

char *
exec_cmdline_from_xdg_id (const char *xdg_id)
{
	char *cmdline = NULL;
	g_autoptr(GKeyFile) keyfile = NULL;

	keyfile = desktop_keyfile_from_xdg_id (xdg_id);

	g_return_val_if_fail (keyfile, NULL);
			
	cmdline = g_key_file_get_string (keyfile,
			"Desktop Entry",
			"Exec",
			NULL);

	return cmdline;
}

gboolean
startup_notify_from_xdg_id (const char *xdg_id)
{
	gboolean startup_notify;
	g_autoptr(GKeyFile) keyfile = NULL;

	keyfile = desktop_keyfile_from_xdg_id (xdg_id);

	g_return_val_if_fail (keyfile, FALSE);

	startup_notify = g_key_file_get_boolean (keyfile,
			"Desktop Entry",
			"StartupNotify",
			NULL);

	return startup_notify;
}

gboolean
needs_terminal_from_xdg_id (const char *xdg_id)
{
	gboolean needs_terminal;
	g_autoptr(GKeyFile) keyfile = NULL;

	keyfile = desktop_keyfile_from_xdg_id (xdg_id);

	g_return_val_if_fail (keyfile, FALSE);

	needs_terminal = g_key_file_get_boolean (keyfile,
			"Desktop Entry",
			"Terminal",
			NULL);

	return needs_terminal;
}

void
apply_css_provider_to_widget_and_all_children (GtkWidget *widget,
		GtkCssProvider *provider)
{
	GtkWidget *child;
	GtkStyleContext *context;
	GtkStyleProvider *style_provider = GTK_STYLE_PROVIDER(provider);

	g_return_if_fail (GTK_IS_WIDGET (widget));
	g_return_if_fail (GTK_IS_CSS_PROVIDER(provider));

	context = gtk_widget_get_style_context (widget);
	gtk_style_context_add_provider (context,
			style_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

	for (child = gtk_widget_get_first_child (widget);
			child != NULL;
			child = gtk_widget_get_next_sibling (child))
    {
		context = gtk_widget_get_style_context (child);
		gtk_style_context_add_provider (context,
				style_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
	}
}

/* Helper: Sort a GPtrArray of AsApp by timestamp newest->oldest */
static int
as_apps_array_timestamp_compare_func (gconstpointer a, gconstpointer b)
{
	AsApp *app1 = *((AsApp **) a);
	AsApp *app2 = *((AsApp **) b);
	g_autoptr(AsRelease) rel;
	guint64 ts1, ts2;

	rel = as_app_get_release_default (app1);
	ts1 = as_release_get_timestamp (rel);
	rel = as_app_get_release_default (app2);
	ts2 = as_release_get_timestamp (rel);

	if (ts1 == ts2)
		return 0;
	else if (ts1 < ts2)
		return 1;
	else
		return -1;
}

char *
newest_version_of_app_from_installed_ref_finish (FlatpakInstalledRef *inst_ref,
		GAsyncResult *result)
{
	g_return_val_if_fail (g_task_is_valid (result, inst_ref), NULL);

	return g_task_propagate_pointer (G_TASK(result), NULL);
}

/* helper for the appstream info-getter functions */
static GPtrArray *
get_as_apps_from_inst_ref (FlatpakInstalledRef *inst_ref)
{
	GPtrArray *apps;
	g_autoptr(FlatpakInstallation) installation = NULL;
	g_autoptr(FlatpakRemote) remote = NULL;
	g_autoptr(GFile) appstream_dir = NULL;
	g_autoptr(GFile) appstream_file = NULL;
	g_autofree char *appstream_path = NULL;
	g_autoptr(AsStore) store = NULL;

	g_return_val_if_fail (FLATPAK_IS_INSTALLED_REF (inst_ref), NULL);

	installation = flatpak_installation_new_user (NULL, NULL);

	remote = flatpak_installation_get_remote_by_name (installation,
			flatpak_installed_ref_get_origin (inst_ref),
			NULL, NULL /* cancellable, error */);
	if (! remote) return NULL;

	appstream_dir = flatpak_remote_get_appstream_dir (remote, NULL);
	if (! appstream_dir) return NULL;

	appstream_path = g_build_filename (g_file_peek_path (appstream_dir),
			"appstream.xml.gz",
			NULL);

	appstream_file = g_file_new_for_path (appstream_path);

	store = as_store_new ();
	as_store_from_file (store, appstream_file,
			NULL, /* icon path */
			NULL, NULL); /* cancellable, error */

	apps = as_store_get_apps_by_id (store,
			flatpak_ref_get_name (FLATPAK_REF(inst_ref)));

	if (! apps || apps->len <= 0)
		return NULL;
	else
		return apps;
}

static void
newest_version_thread_func (GTask *task,
                    gpointer source_object,
                    gpointer task_data,
                    GCancellable *cancellable)
{
	FlatpakInstalledRef *inst_ref = FLATPAK_INSTALLED_REF (source_object);
	const char *version = NULL;			/* retval */
	GPtrArray *apps;

	/* Get array of apps from appstream and sort newest->oldest */
	apps = get_as_apps_from_inst_ref (inst_ref);
	if (! apps) goto out;

	g_ptr_array_sort (apps, as_apps_array_timestamp_compare_func);

	version = as_release_get_version (
			as_app_get_release_default (AS_APP(apps->pdata[0])));
	if (! version) goto out;

	g_task_return_pointer (task, g_strdup (version), g_free);
	return;

out:
	g_task_return_pointer (task, NULL, NULL);
}


void
newest_version_of_app_from_installed_ref_async (FlatpakInstalledRef *inst_ref,
	   	GCancellable *cancellable,
		GAsyncReadyCallback callback,
		gpointer user_data)
{
	GTask *task;

	g_return_if_fail (FLATPAK_IS_INSTALLED_REF (inst_ref));

	task = g_task_new (inst_ref, cancellable, callback, user_data);
	g_task_run_in_thread (task, newest_version_thread_func);
	g_object_unref (task);
}

/* [element-type FlatpakInstalledRef] */
GPtrArray *		
list_installed_refs_for_update_finish (FlatpakInstallation *installation,
		GAsyncResult *result)
{
	g_return_val_if_fail (g_task_is_valid (result, installation), NULL);

	return g_task_propagate_pointer (G_TASK(result), NULL);
}

/* Helper macro for list_installed_refs_for_update_thread_func */
#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, \
				"Error: %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
list_installed_refs_for_update_thread_func (GTask *task,
                    gpointer source_object,
                    gpointer task_data,
                    GCancellable *cancellable)
{
	FlatpakInstallation *installation = FLATPAK_INSTALLATION(source_object);
	GPtrArray *inst_refs;
	GError *local_error = NULL;

	inst_refs = flatpak_installation_list_installed_refs_for_update (
			installation,
			cancellable,
			&local_error);

	if (local_error)
		g_debug ("%s: %s", __func__, local_error->message);

	g_task_return_pointer (task, inst_refs, (GDestroyNotify)g_ptr_array_unref);
}
#undef HANDLE_ANY_ERRORS

void
list_installed_refs_for_update_async (FlatpakInstallation *installation,
		GCancellable *cancellable,
		GAsyncReadyCallback callback,
		gpointer user_data)
{
	GTask *task;

	g_return_if_fail (FLATPAK_IS_INSTALLATION (installation));

	task = g_task_new (installation, cancellable, callback, user_data);
	g_task_run_in_thread (task, list_installed_refs_for_update_thread_func);
	g_object_unref (task);
}


/* [element-type FlatpakInstalledRef] */
GPtrArray *
list_installed_refs_finish (FlatpakInstallation *installation,
			GAsyncResult *result)
{
	g_return_val_if_fail (g_task_is_valid (result, installation), NULL);

	return g_task_propagate_pointer (G_TASK(result), NULL);
}

static void
list_installed_refs_thread_func (GTask *task,
                    gpointer source_object,
                    gpointer task_data,
                    GCancellable *cancellable)
{
	FlatpakInstallation *installation = FLATPAK_INSTALLATION(source_object);
	GPtrArray *inst_refs;

	inst_refs = flatpak_installation_list_installed_refs (installation,
			cancellable,
			NULL);

	g_task_return_pointer (task, inst_refs, (GDestroyNotify)g_ptr_array_unref);
}

void
list_installed_refs_async (FlatpakInstallation *installation,
			GCancellable *cancellable,
			GAsyncReadyCallback callback,
			gpointer user_data)
{
	GTask *task;

	g_return_if_fail (FLATPAK_IS_INSTALLATION (installation));

	task = g_task_new (installation, cancellable, callback, user_data);
	g_task_run_in_thread (task, list_installed_refs_thread_func);
	g_object_unref (task);
}

void
create_app_info_dialog (FlatpakInstalledRef *inst_ref, GtkWindow *parent)
{
	GtkWidget *dialog;
	const char *app_xdgname;
	const char *app_prettyname;
	g_autofree char *menu_name = NULL;
	g_autofree char *desc = NULL;

	g_return_if_fail (FLATPAK_IS_INSTALLED_REF (inst_ref));

	app_xdgname = g_object_get_data (G_OBJECT(inst_ref), "app_xdgname");
	app_prettyname = g_object_get_data (G_OBJECT(inst_ref), "app_prettyname");
	menu_name = app_name_from_xdg_id (app_xdgname);
	desc = app_desc_from_xdg_id (app_xdgname);

	dialog = gtk_message_dialog_new_with_markup (parent,
			GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
			GTK_MESSAGE_INFO,
			GTK_BUTTONS_CLOSE,
			"<big><b>%s</b></big>\n\n"
			"<b>Application ID:</b> %s\n\n"
			"<b>System name:</b> %s\n\n"
			"<b>Description:</b> %s",
			app_prettyname,
			app_xdgname,
			menu_name,
			desc);

	g_signal_connect (dialog, "response",
			G_CALLBACK(gtk_window_destroy), NULL);

	gtk_widget_show (dialog);
}

/* DEBUG */
void
debug_print_keyfile (GKeyFile *keyfile)
{
	char **groups;
	int i;

	groups = g_key_file_get_groups (keyfile, NULL);
	for (i = 0; groups[i]; ++i)
	{
		char **keys;

		g_debug ("GROUP: [%s]", groups[i]);
		keys = g_key_file_get_keys (keyfile, groups[i], NULL, NULL);

		for (int j = 0; keys[j]; ++j)
		{
			g_debug ("key: %s - value: %s",
					keys[j],
					g_key_file_get_value (keyfile, groups[i], keys[j], NULL));
		}
	}
}
