/*
 * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include <fwupdplugin.h>

#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <locale.h>
#include <stdlib.h>

#include "fu-history.h"
#include "fu-plugin-private.h"
#include "fu-spawn.h"
#include "fu-util-common.h"

typedef enum {
	FU_OFFLINE_FLAG_NONE = 0,
	FU_OFFLINE_FLAG_ENABLE = 1 << 0,
	FU_OFFLINE_FLAG_USE_PROGRESS = 1 << 1,
} FuOfflineFlag;

struct FuUtilPrivate {
	gchar *splash_cmd;
	GTimer *splash_timer;
	FuOfflineFlag splash_flags;
};

static gboolean
fu_offline_set_splash_progress(FuUtilPrivate *priv, guint percentage, GError **error)
{
	g_autofree gchar *str = g_strdup_printf("%u", percentage);
	const gchar *argv[] = {priv->splash_cmd, "system-update", "--progress", str, NULL};

	/* call into plymouth if installed */
	if (priv->splash_flags == FU_OFFLINE_FLAG_NONE) {
		/* TRANSLATORS: console message when not using plymouth */
		g_printerr("%s: %u%%\n", _("Percentage complete"), percentage);
		return TRUE;
	}

	/* fall back to really old mode that should be supported by anything */
	if ((priv->splash_flags & FU_OFFLINE_FLAG_USE_PROGRESS) == 0) {
		argv[1] = "display-message";
		argv[2] = "--text";
	}
	return fu_spawn_sync(argv, NULL, NULL, 200, NULL, error);
}

static gboolean
fu_offline_set_splash_mode(FuUtilPrivate *priv, GError **error)
{
	g_autoptr(GError) error_local = NULL;
	const gchar *argv[] = {priv->splash_cmd, "change-mode", "--system-upgrade", NULL};

	/* call into plymouth if installed */
	if (priv->splash_cmd == NULL) {
		/* TRANSLATORS: console message when no Plymouth is installed */
		g_printerr("%s\n", _("Installing Firmware…"));
		return TRUE;
	}

	/* try the new fancy mode, then fall back to really old mode */
	if (!fu_spawn_sync(argv, NULL, NULL, 1500, NULL, &error_local)) {
		argv[2] = "--updates";
		if (!fu_spawn_sync(argv, NULL, NULL, 1500, NULL, error)) {
			g_prefix_error(error, "%s: ", error_local->message);
			return FALSE;
		}
		priv->splash_flags = FU_OFFLINE_FLAG_ENABLE;
		return TRUE;
	}

	/* success */
	priv->splash_flags = FU_OFFLINE_FLAG_ENABLE | FU_OFFLINE_FLAG_USE_PROGRESS;
	return TRUE;
}

static gboolean
fu_offline_set_splash_reboot(FuUtilPrivate *priv, GError **error)
{
	g_autoptr(GError) error_local = NULL;
	const gchar *argv[] = {priv->splash_cmd, "change-mode", "--reboot", NULL};

	/* call into plymouth if installed */
	if (priv->splash_flags == FU_OFFLINE_FLAG_NONE) {
		/* TRANSLATORS: console message when not using plymouth */
		g_printerr("%s\n", _("Rebooting…"));
		return TRUE;
	}

	/* try the new fancy mode, then fall back to really old mode */
	if (!fu_spawn_sync(argv, NULL, NULL, 200, NULL, &error_local)) {
		/* fall back to really old mode that should be supported */
		argv[2] = "--shutdown";
		if (!fu_spawn_sync(argv, NULL, NULL, 200, NULL, error)) {
			g_prefix_error(error, "%s: ", error_local->message);
			return FALSE;
		}
		return TRUE;
	}

	/* success */
	return TRUE;
}

static void
fu_offline_client_notify_cb(GObject *object, GParamSpec *pspec, gpointer user_data)
{
	FuUtilPrivate *priv = (FuUtilPrivate *)user_data;
	FwupdClient *client = FWUPD_CLIENT(object);

	/* rate limit to 1 second */
	if (g_timer_elapsed(priv->splash_timer, NULL) < 1.f ||
	    fwupd_client_get_percentage(client) < 5)
		return;
	fu_offline_set_splash_progress(priv, fwupd_client_get_percentage(client), NULL);
	g_timer_reset(priv->splash_timer);
}

static void
fu_util_private_free(FuUtilPrivate *priv)
{
	if (priv->splash_timer != NULL)
		g_timer_destroy(priv->splash_timer);
	g_free(priv->splash_cmd);
	g_free(priv);
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free)
#pragma clang diagnostic pop

int
main(int argc, char *argv[])
{
	gint vercmp;
	guint cnt = 0;
	g_autofree gchar *link = NULL;
	g_autofree gchar *target = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG);
	g_autofree gchar *trigger = fu_path_from_kind(FU_PATH_KIND_OFFLINE_TRIGGER);
	g_autoptr(FuHistory) history = NULL;
	g_autoptr(FwupdClient) client = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) results = NULL;
	g_autoptr(FuUtilPrivate) priv = g_new0(FuUtilPrivate, 1);

	setlocale(LC_ALL, "");

	bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR);
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
	textdomain(GETTEXT_PACKAGE);

#ifndef SUPPORTED_BUILD
	/* make critical warnings fatal */
	(void)g_setenv("G_DEBUG", "fatal-criticals", FALSE);
#endif

	/* verify this is pointing to our cache */
	link = g_file_read_link(trigger, NULL);
	if (link == NULL)
		return EXIT_SUCCESS;
	if (g_strcmp0(link, target) != 0)
		return EXIT_SUCCESS;

	/* do this first to avoid a loop if this tool segfaults */
	(void)g_unlink(trigger);

	/* ensure root user */
#ifdef HAVE_GETUID
	if (getuid() != 0 || geteuid() != 0) {
		/* TRANSLATORS: the user needs to stop playing with stuff */
		g_printerr("%s\n", _("This tool can only be used by the root user"));
		return EXIT_FAILURE;
	}
#endif

	/* find plymouth, but not an error if not found */
	priv->splash_cmd = g_find_program_in_path("plymouth");
	priv->splash_timer = g_timer_new();

	/* ensure D-Bus errors are registered */
	fwupd_error_quark();

	/* get prepared updates */
	history = fu_history_new();
	results = fu_history_get_devices(history, &error);
	if (results == NULL) {
		/* TRANSLATORS: we could not get the devices to update offline */
		g_printerr("%s: %s\n", _("Failed to get pending devices"), error->message);
		return EXIT_FAILURE;
	}

	/* connect to the daemon */
	client = fwupd_client_new();
	g_signal_connect(FWUPD_CLIENT(client),
			 "notify::percentage",
			 G_CALLBACK(fu_offline_client_notify_cb),
			 priv);
	if (!fwupd_client_connect(client, NULL, &error)) {
		/* TRANSLATORS: we could not talk to the fwupd daemon */
		g_printerr("%s: %s\n", _("Failed to connect to daemon"), error->message);
		return EXIT_FAILURE;
	}

	/* set up splash */
	if (!fu_offline_set_splash_mode(priv, &error)) {
		/* TRANSLATORS: we could not talk to plymouth */
		g_printerr("%s: %s\n", _("Failed to set splash mode"), error->message);
		return EXIT_FAILURE;
	}

	/* apply each update */
	for (guint i = 0; i < results->len; i++) {
		FwupdDevice *dev = g_ptr_array_index(results, i);
		FwupdRelease *rel = fwupd_device_get_release_default(dev);

		/* check not already done */
		if (fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_PENDING)
			continue;

		/* tell the user what's going to happen */
		vercmp = fu_version_compare(fwupd_device_get_version(dev),
					    fwupd_release_get_version(rel),
					    fwupd_device_get_version_format(dev));
		if (vercmp == 0) {
			/* TRANSLATORS: the first replacement is a display name
			 * e.g. "ColorHugALS" and the second is a version number
			 * e.g. "1.2.3" */
			g_print(_("Reinstalling %s with %s... "),
				fwupd_device_get_name(dev),
				fwupd_release_get_version(rel));
		} else if (vercmp > 0) {
			/* TRANSLATORS: the first replacement is a display name
			 * e.g. "ColorHugALS" and the second and third are
			 * version numbers e.g. "1.2.3" */
			g_print(_("Downgrading %s from %s to %s... "),
				fwupd_device_get_name(dev),
				fwupd_device_get_version(dev),
				fwupd_release_get_version(rel));
		} else if (vercmp < 0) {
			/* TRANSLATORS: the first replacement is a display name
			 * e.g. "ColorHugALS" and the second and third are
			 * version numbers e.g. "1.2.3" */
			g_print(_("Updating %s from %s to %s... "),
				fwupd_device_get_name(dev),
				fwupd_device_get_version(dev),
				fwupd_release_get_version(rel));
		}
		if (!fwupd_client_install(client,
					  fwupd_device_get_id(dev),
					  fwupd_release_get_filename(rel),
					  FWUPD_INSTALL_FLAG_ALLOW_REINSTALL |
					      FWUPD_INSTALL_FLAG_ALLOW_OLDER |
					      FWUPD_INSTALL_FLAG_OFFLINE,
					  NULL,
					  &error)) {
			g_printerr("%s: %s\n",
				   /* TRANSLATORS: we could not install for some reason */
				   _("Failed to install firmware update"),
				   error->message);
			return EXIT_FAILURE;
		}
		cnt++;
	}

	/* nothing to do */
	if (cnt == 0) {
		/* TRANSLATORS: nothing was updated offline */
		g_printerr("%s\n", _("No updates were applied"));
		return EXIT_FAILURE;
	}

	/* reboot */
	fu_offline_set_splash_reboot(priv, NULL);
	if (!fu_util_update_reboot(&error)) {
		/* TRANSLATORS: we could not reboot for some reason */
		g_printerr("%s: %s\n", _("Failed to reboot"), error->message);
		return EXIT_FAILURE;
	}

	/* success */
	g_print("%s\n", _("Done!"));
	return EXIT_SUCCESS;
}
