/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 * vi:set noexpandtab tabstop=8 shiftwidth=8:
 *
 * Copyright (C) 2016-2018 Richard Hughes <richard@hughsie.com>
 * Copyright (C) 2019 Kalev Lember <klember@redhat.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <glib.h>

#include "gs-packagekit-helper.h"
#include "packagekit-common.h"

struct _GsPackagekitHelper {
	GObject			 parent_instance;

	/* Map of source/package ID to `GsApp` instance: */
	GHashTable		*apps;  /* (not nullable) (owned) (element-type utf8 GsApp) */

	GsApp			*progress_app;  /* (owned) (nullable) */
	GsAppList		*progress_list;  /* (owned) (nullable) */
	GsPlugin		*plugin;  /* (owned) (not nullable) */
	gboolean		 allow_emit_updates_changed;
};

G_DEFINE_TYPE (GsPackagekitHelper, gs_packagekit_helper, G_TYPE_OBJECT)

void
gs_packagekit_helper_cb (PkProgress *progress, PkProgressType type, gpointer user_data)
{
	GsPackagekitHelper *self = GS_PACKAGEKIT_HELPER (user_data);
	GsPlugin *plugin = gs_packagekit_helper_get_plugin (self);
	const gchar *package_id = pk_progress_get_package_id (progress);
	GsApp *app = NULL;

	/* optional */
	if (self->progress_app != NULL)
		app = self->progress_app;
	else if (package_id != NULL)
		app = gs_packagekit_helper_get_app_by_id (self, package_id);

	if (type == PK_PROGRESS_TYPE_STATUS) {
		PkStatusEnum status = pk_progress_get_status (progress);

		/* If we’re installing or removing a package, this may
		 * invalidate a previously-returned pending OS upgrade’s list of
		 * packages.
		 *
		 * FIXME: We can’t currently emit a more specific signal on the
		 * OS upgrade’s #GsApp, because it’s built by the
		 * fedora-pkgdb-collections plugin rather than the PackageKit
		 * plugin. The functionality from fedora-pkgdb-collections would
		 * have to be merged into PackageKit so the right #GsApp is
		 * accessible to modify its download state.
		 * */
		if ((self->allow_emit_updates_changed) &&
		    (status == PK_STATUS_ENUM_INSTALL ||
		     status == PK_STATUS_ENUM_UPDATE ||
		     status == PK_STATUS_ENUM_CLEANUP ||
		     status == PK_STATUS_ENUM_REMOVE) &&
		    (app == NULL ||
		     (gs_app_get_kind (app) != AS_COMPONENT_KIND_OPERATING_SYSTEM &&
		      gs_app_get_id (app) != NULL))) {
			/* this callback can be called many times in a row; limit how often
			   the GUI part is notified, to not refresh the Updates page too often */
			static gint64 last_notify = 0;
			gint64 current_time = g_get_real_time ();

			/* Just out-of-blue chosen 3 minutes interval between notifications */
			if (current_time - last_notify >= G_USEC_PER_SEC * 60 * 3) {
				g_debug ("notify about updates-changed from %s", G_STRFUNC);
				last_notify = current_time;
				gs_plugin_updates_changed (plugin);
			}
		}
	} else if (type == PK_PROGRESS_TYPE_PERCENTAGE) {
		gint percentage = pk_progress_get_percentage (progress);
		if (app != NULL && percentage >= 0 && percentage <= 100)
			gs_app_set_progress (app, (guint) percentage);
		if (self->progress_list != NULL && percentage >= 0 && percentage <= 100)
			gs_app_list_override_progress (self->progress_list, (guint) percentage);
	}

	/* Only go from TRUE to FALSE - it doesn't make sense for a package
	 * install to become uncancellable later on */
	if (app != NULL && gs_app_get_allow_cancel (app))
		gs_app_set_allow_cancel (app, pk_progress_get_allow_cancel (progress));
}

void
gs_packagekit_helper_add_app (GsPackagekitHelper *self, GsApp *app)
{
	GPtrArray *source_ids = gs_app_get_source_ids (app);

	g_return_if_fail (GS_IS_PACKAGEKIT_HELPER (self));
	g_return_if_fail (GS_IS_APP (app));

	for (guint i = 0; i < source_ids->len; i++) {
		const gchar *source_id = g_ptr_array_index (source_ids, i);
		g_hash_table_insert (self->apps,
				     g_strdup (source_id),
				     g_object_ref (app));
	}
}

void
gs_packagekit_helper_set_progress_app (GsPackagekitHelper *self, GsApp *progress_app)
{
	g_return_if_fail (GS_IS_PACKAGEKIT_HELPER (self));
	g_return_if_fail (progress_app == NULL || GS_IS_APP (progress_app));

	g_set_object (&self->progress_app, progress_app);
}

void
gs_packagekit_helper_set_progress_list (GsPackagekitHelper *self, GsAppList *progress_list)
{
	g_return_if_fail (GS_IS_PACKAGEKIT_HELPER (self));
	g_return_if_fail (progress_list == NULL || GS_IS_APP_LIST (progress_list));

	g_set_object (&self->progress_list, progress_list);
}

/*
 * gs_packagekit_helper_set_allow_emit_updates_changed:
 * @self: a #GsPackagekitHelper
 * @allow_emit_updates_changed: whether to allow emission of the updates-changed signal
 *
 * Set whether to allow emitting the #GsPlugin:updates-changed signal at any time through the task,
 * or whether to block it.
 *
 * FIXME: This is only needed to work around a signal emission loop caused by interaction
 * between the fedora-pkgdb-collections and PackageKit plugins. When the fedora-pkgdb-collections
 * plugin is removed, this API should be removed.
 * See !1817 and #2462.
 */
void
gs_packagekit_helper_set_allow_emit_updates_changed (GsPackagekitHelper *self,
						     gboolean allow_emit_updates_changed)
{
	g_return_if_fail (GS_IS_PACKAGEKIT_HELPER (self));

	self->allow_emit_updates_changed = allow_emit_updates_changed;
}

GsPlugin *
gs_packagekit_helper_get_plugin (GsPackagekitHelper *self)
{
	g_return_val_if_fail (GS_IS_PACKAGEKIT_HELPER (self), NULL);

	return self->plugin;
}

GsApp *
gs_packagekit_helper_get_app_by_id (GsPackagekitHelper *self, const gchar *package_id)
{
	g_return_val_if_fail (GS_IS_PACKAGEKIT_HELPER (self), NULL);
	g_return_val_if_fail (package_id != NULL, NULL);

	return g_hash_table_lookup (self->apps, package_id);
}

static void
gs_packagekit_helper_finalize (GObject *object)
{
	GsPackagekitHelper *self = GS_PACKAGEKIT_HELPER (object);

	g_object_unref (self->plugin);
	g_clear_object (&self->progress_app);
	g_clear_object (&self->progress_list);
	g_hash_table_unref (self->apps);

	G_OBJECT_CLASS (gs_packagekit_helper_parent_class)->finalize (object);
}

static void
gs_packagekit_helper_class_init (GsPackagekitHelperClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	object_class->finalize = gs_packagekit_helper_finalize;
}

static void
gs_packagekit_helper_init (GsPackagekitHelper *self)
{
	self->apps = g_hash_table_new_full (g_str_hash, g_str_equal,
					    g_free, (GDestroyNotify) g_object_unref);
	self->allow_emit_updates_changed = TRUE;
}

GsPackagekitHelper *
gs_packagekit_helper_new (GsPlugin *plugin)
{
	GsPackagekitHelper *self;

	g_return_val_if_fail (GS_IS_PLUGIN (plugin), NULL);

	self = g_object_new (GS_TYPE_PACKAGEKIT_HELPER, NULL);
	self->plugin = g_object_ref (plugin);
	return GS_PACKAGEKIT_HELPER (self);
}
