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

#include "common.h"

#include "gfi-progress.h"
#include "gfi-application-window.h"
#include "gfi-list-widget.h"
#include "assistant.h"
#include "error-dialog.h"

/* Needed to check whether user is root */
#include <unistd.h>

static GFile *target_flatpakref_file;
static GfiProgress *progress;
static GtkWidget *manager;
static GtkWidget *assistant;
static char *opt_install_filename;
static char *opt_remove_string;

GOptionEntry entries[] = {
	{ "version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL,
		"Show the application version", NULL },

	{ "install", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_install_filename,
		"Install an application via Flatpak", "flatpakref file" },

	{ "remove", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_remove_string,
		"Remove a Flatpak application", "Valid Flatpak ref string" },

	{ NULL }
};

static void
error_cb (GfiProgress *prog, gpointer user_data)
{
	GtkApplication *app = GTK_APPLICATION(user_data);
	GtkWidget *error_dialog;
	GCancellable *cancellable;
	g_autofree char *error_message = NULL;

	g_return_if_fail (GTK_IS_APPLICATION (app));
	g_return_if_fail (prog == progress);

	cancellable = gfi_progress_get_cancellable (progress);
	error_dialog = gfi_error_dialog_new (GTK_WINDOW(assistant));

	if (g_cancellable_is_cancelled (cancellable))
	{
		char *cancel_title = "Cancelled";

		g_object_set (error_dialog,
				"text", cancel_title,
				NULL);

		error_message = g_strdup ("The operation has been cancelled.");
	}
	else
	{
		error_message = g_strdup_printf ("An error has occurred which will "
				"cause the installation to abort:\n\n%s",
				gfi_progress_get_last_error_msg (progress));
	}

	g_object_set (error_dialog,
			"secondary-text", error_message,
			NULL);

	g_debug ("%s: destroying GfiProgress object.", __func__);
	g_clear_object (&progress);

	gtk_widget_show (error_dialog);
}

static void
transaction_done_cb (GfiProgress *prog, gpointer user_data)
{
	GtkApplication *app = GTK_APPLICATION(user_data);

	g_debug ("%s: destroying GfiProgress object.", __func__);
	g_clear_object (&progress);

	/* If the manager is running, refresh the list widget (it might not be,
	 * if the installer is launched directly */
	if (manager)
		gfi_application_window_refresh (GFI_APPLICATION_WINDOW(manager));
}

static gboolean
setup_progress (GtkApplication *app)
{
	/* nb: If we got here - transaction_done_cb has likely not happened.
	 */
	if (progress)
	{
		g_warning ("%s: Progress object already exists. Returning.",
				__func__);
		return FALSE;
	}

	progress = gfi_progress_new ();

	g_signal_connect (progress, "error",
			G_CALLBACK(error_cb), app);
	g_signal_connect (progress, "transaction-done",
			G_CALLBACK(transaction_done_cb), app);

	return TRUE;
}

/* Nb: this only applies to the 'baked in' GtkAssistant cancel button, and
 * not for the 'custom' cancel button that we utilize for the *progress*
 * page (remind me never to use GtkAssistant again...)
 */
static void
assistant_cancel_cb (GtkAssistant *a, gpointer user_data)
{
	g_return_if_fail (a == GTK_ASSISTANT(assistant));

	g_debug ("%s: destroying GfiProgress object.", __func__);
	g_clear_object (&progress);
}

static void
launch_assistant (GtkApplication *app, GtkWindow *parent)
{
	g_return_if_fail (GFI_IS_PROGRESS (progress));

	assistant = gfi_assistant_new (progress);
	gtk_application_add_window (app, GTK_WINDOW(assistant));

	if (parent)
	{
		gtk_window_set_transient_for (GTK_WINDOW(assistant), parent);
		gtk_window_set_modal (GTK_WINDOW(assistant), TRUE);
	}

	g_signal_connect (assistant, "cancel", 
			G_CALLBACK(assistant_cancel_cb), NULL);

	gtk_widget_show (assistant);
}

/* helper function for the {upgrade,remove}-requested cb's */
static void
handle_list_widget_request (FlatpakRef *ref, GfiProgressActionType type)
{
	GtkApplication *app;

	g_return_if_fail (GFI_IS_APPLICATION_WINDOW (manager));

	app = gtk_window_get_application (GTK_WINDOW(manager));

	if (! setup_progress (app))
	{
		/* FIXME */
		g_debug ("%s: setup_progress failed. "
				"ERROR HANDLING NOT IMPLEMENTED", __func__);
		return;
	}
	
	if (! gfi_progress_load_ref (progress, ref, type))
	{
		/* FIXME */
		g_debug ("%s: gfi_progress_load_ref FAILED. "
				"ERROR HANDLING NOT IMPLEMENTED", __func__);
		return;
	}

	gfi_progress_start (progress);
	launch_assistant (app, GTK_WINDOW(manager));
}

static void
list_widget_remove_requested_cb (GfiListWidget *unused,
		FlatpakRef *ref,
		gpointer user_data)
{
	handle_list_widget_request (ref, GFI_PROGRESS_ACTION_TYPE_REMOVE);
}

static void
list_widget_upgrade_requested_cb (GfiListWidget *unused,
		FlatpakRef *ref,
		gpointer user_data)
{
	handle_list_widget_request (ref, GFI_PROGRESS_ACTION_TYPE_UPGRADE);
}

static void
activate_manager (GtkApplication *app)
{
	GtkWidget *remove_list_widget;
	GtkWidget *upgrade_list_widget;

	manager = gfi_application_window_new (app);

	remove_list_widget = gfi_application_window_get_list_widget (
			GFI_APPLICATION_WINDOW(manager),
			GFI_LIST_WIDGET_TYPE_REMOVE);

	upgrade_list_widget = gfi_application_window_get_list_widget (
			GFI_APPLICATION_WINDOW(manager),
			GFI_LIST_WIDGET_TYPE_UPGRADE);

	g_signal_connect (remove_list_widget, "remove-requested",
			G_CALLBACK(list_widget_remove_requested_cb), NULL);

	g_signal_connect (upgrade_list_widget, "upgrade-requested",
			G_CALLBACK(list_widget_upgrade_requested_cb), NULL);

	gtk_widget_show (manager);
}

static void
activate_install (GtkApplication *app)
{
	GtkWidget *w;

	if (! G_IS_FILE (target_flatpakref_file))
	{
		g_printerr ("No target flatpakref file specified.\n\n"
				"Run ``%s --help'' for usage.\n",
				g_get_prgname ());
		exit (1);
	}

	setup_progress (app);

	if (! gfi_progress_load_flatpakref (progress, target_flatpakref_file))
	{
		g_printerr ("%s: Invalid .flatpakref file.\n",
				g_get_prgname ());
		exit (1);
	}

	gfi_progress_start (progress);

	launch_assistant (app, NULL);
}

static int
command_line_cb (GApplication	*application,
		GApplicationCommandLine	*command_line,
		gpointer				user_data)
{
	if (opt_install_filename)
	{
		/* No error handling. Acc. to TFM it's better to just pass the gfile
		 * now and deal with any issues later.
		 */
		target_flatpakref_file = g_file_new_for_path (opt_install_filename);
		activate_install (GTK_APPLICATION(application));
	}
	else if (opt_remove_string)
	{
		g_debug ("%s: remove -- NOT IMPLEMENTED", __func__);
	}
	else
	{
		activate_manager (GTK_APPLICATION(application));
	}
	return 0;
}

/* Callback to process "handle-local-options" signal.
 * From TFM:
 * ``If you have handled your options and want to exit the process, return a
 * non-negative option, 0 for success, and a positive value for failure. To
 * continue, return -1 to let the default option processing continue.''
 */
static int
handle_local_options (GApplication *application,
		GVariantDict *options,
		gpointer      user_data)
{
	if (opt_install_filename && opt_remove_string)
	{
		g_printerr ("%s: Only one option (install or remove) "
				"permitted at a time.\n",
				g_get_prgname ());
		return 1;
	}

	if (g_variant_dict_contains (options, "version"))
	{
		g_print ("This is %s, version %s\n",
				PACKAGE_NAME, PACKAGE_VERSION);

		return 0;
	}
	return -1;
}

int
main (int argc, char *argv[])
{
	GtkApplication *app;
	int status;
	g_autofree char *desc = NULL;

	/* No root allowed */

	if (getuid() == 0)
	{
		g_printerr ("This application may not be run as root.\n");
		exit (1);
	}

	app = gtk_application_new (XDG_NAME,
			G_APPLICATION_HANDLES_COMMAND_LINE);

	desc = g_strdup_printf ("%s - GTK Flatpak Installer", PACKAGE_NAME);

	g_application_add_main_option_entries (G_APPLICATION(app), entries);
	g_application_set_option_context_summary (G_APPLICATION(app), desc);

	g_signal_connect (app, "handle-local-options",
			G_CALLBACK(handle_local_options), NULL);
	g_signal_connect (app, "command-line",
			G_CALLBACK(command_line_cb), NULL);

	status = g_application_run (G_APPLICATION(app), argc, argv);
	g_object_unref (app);

	return status;
}
