/*
 * Copyright 2024 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#define G_LOG_DOMAIN "FuClientList"

#include "config.h"

#include "fu-client-list.h"

struct _FuClientList {
	GObject parent_instance;
	GPtrArray *array;	     /* (element-type FuClientListItem) */
	GDBusConnection *connection; /* nullable */
};

typedef struct {
	FuClientList *self; /* no-ref */
	FuClient *client;   /* ref */
	guint watcher_id;
} FuClientListItem;

G_DEFINE_TYPE(FuClientList, fu_client_list, G_TYPE_OBJECT)

enum { PROP_0, PROP_CONNECTION, PROP_LAST };

enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_LAST };

static guint signals[SIGNAL_LAST] = {0};

static void
fu_client_list_emit_added(FuClientList *self, FuClient *client)
{
	g_debug("client %s added", fu_client_get_sender(client));
	g_signal_emit(self, signals[SIGNAL_ADDED], 0, client);
}

static void
fu_client_list_emit_removed(FuClientList *self, FuClient *client)
{
	g_debug("client %s removed", fu_client_get_sender(client));
	g_signal_emit(self, signals[SIGNAL_REMOVED], 0, client);
}

static void
fu_client_list_sender_name_vanished_cb(GDBusConnection *connection,
				       const gchar *name,
				       gpointer user_data)
{
	FuClientListItem *item = (FuClientListItem *)user_data;
	FuClientList *self = FU_CLIENT_LIST(item->self);
	g_autoptr(FuClient) client = g_object_ref(item->client);
	fu_client_remove_flag(client, FU_CLIENT_FLAG_ACTIVE);
	g_ptr_array_remove(self->array, item);
	fu_client_list_emit_removed(self, client);
}

FuClient *
fu_client_list_register(FuClientList *self, const gchar *sender)
{
	FuClient *client;
	FuClientListItem *item;

	g_return_val_if_fail(FU_IS_CLIENT_LIST(self), NULL);

	/* already exists */
	client = fu_client_list_get_by_sender(self, sender);
	if (client != NULL)
		return client;

	/* create and watch */
	item = g_new0(FuClientListItem, 1);
	item->self = self;
	item->client = fu_client_new(sender);
	if (self->connection != NULL && sender != NULL) {
		item->watcher_id =
		    g_bus_watch_name_on_connection(self->connection,
						   sender,
						   G_BUS_NAME_WATCHER_FLAGS_NONE,
						   NULL,
						   fu_client_list_sender_name_vanished_cb,
						   item,
						   NULL);
	}
	g_ptr_array_add(self->array, item);

	/* success */
	fu_client_list_emit_added(self, item->client);
	return g_object_ref(item->client);
}

GPtrArray *
fu_client_list_get_all(FuClientList *self)
{
	g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
	g_return_val_if_fail(FU_IS_CLIENT_LIST(self), NULL);
	for (guint i = 0; i < self->array->len; i++) {
		FuClientListItem *item = g_ptr_array_index(self->array, i);
		g_ptr_array_add(array, g_object_ref(item->client));
	}
	return g_steal_pointer(&array);
}

FuClient *
fu_client_list_get_by_sender(FuClientList *self, const gchar *sender)
{
	g_return_val_if_fail(FU_IS_CLIENT_LIST(self), NULL);
	for (guint i = 0; i < self->array->len; i++) {
		FuClientListItem *item = g_ptr_array_index(self->array, i);
		if (g_strcmp0(sender, fu_client_get_sender(item->client)) == 0)
			return g_object_ref(item->client);
	}
	return NULL;
}

static void
fu_client_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	FuClientList *self = FU_CLIENT_LIST(object);
	switch (prop_id) {
	case PROP_CONNECTION:
		g_value_set_object(value, self->connection);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
fu_client_list_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	FuClientList *self = FU_CLIENT_LIST(object);
	switch (prop_id) {
	case PROP_CONNECTION:
		self->connection = g_value_dup_object(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
fu_client_list_item_free(FuClientListItem *item)
{
	if (item->watcher_id > 0)
		g_bus_unwatch_name(item->watcher_id);
	g_object_unref(item->client);
	g_free(item);
}

static void
fu_client_list_init(FuClientList *self)
{
	self->array = g_ptr_array_new_with_free_func((GDestroyNotify)fu_client_list_item_free);
}

static void
fu_client_list_finalize(GObject *obj)
{
	FuClientList *self = FU_CLIENT_LIST(obj);
	g_ptr_array_unref(self->array);
	if (self->connection != NULL)
		g_object_unref(self->connection);
	G_OBJECT_CLASS(fu_client_list_parent_class)->finalize(obj);
}

static void
fu_client_list_class_init(FuClientListClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);
	GParamSpec *pspec;

	object_class->get_property = fu_client_list_get_property;
	object_class->set_property = fu_client_list_set_property;
	object_class->finalize = fu_client_list_finalize;

	pspec = g_param_spec_object("connection",
				    NULL,
				    NULL,
				    G_TYPE_DBUS_CONNECTION,
				    G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
	g_object_class_install_property(object_class, PROP_CONNECTION, pspec);

	signals[SIGNAL_ADDED] = g_signal_new("added",
					     G_TYPE_FROM_CLASS(object_class),
					     G_SIGNAL_RUN_LAST,
					     0,
					     NULL,
					     NULL,
					     g_cclosure_marshal_generic,
					     G_TYPE_NONE,
					     1,
					     FU_TYPE_CLIENT);
	signals[SIGNAL_REMOVED] = g_signal_new("removed",
					       G_TYPE_FROM_CLASS(object_class),
					       G_SIGNAL_RUN_LAST,
					       0,
					       NULL,
					       NULL,
					       g_cclosure_marshal_generic,
					       G_TYPE_NONE,
					       1,
					       FU_TYPE_CLIENT);
}

FuClientList *
fu_client_list_new(GDBusConnection *connection)
{
	FuClientList *self;
	g_return_val_if_fail(connection == NULL || G_IS_DBUS_CONNECTION(connection), NULL);
	self = g_object_new(FU_TYPE_CLIENT_LIST, "connection", connection, NULL);
	return FU_CLIENT_LIST(self);
}
