/*
 * panel-applet-factory.c: panel applet writing API.
 *
 * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include "panel-applet.h"
#include "panel-applet-private.h"
#include "panel-applet-factory.h"

struct _PanelAppletFactory {
	GObject    base;

	gchar     *factory_id;
	guint      n_applets;
	GType      applet_type;
	GClosure  *closure;

	GHashTable      *applets;
	guint            next_uid;

	GDBusConnection *connection;
	gint             owner_id;
	gint             registration_id;
};

struct _PanelAppletFactoryClass {
	GObjectClass base_class;
};

#define PANEL_APPLET_FACTORY_OBJECT_PATH  "/org/gnome/panel/applet/%s"
#define PANEL_APPLET_FACTORY_SERVICE_NAME "org.gnome.panel.applet.%s"

G_DEFINE_TYPE (PanelAppletFactory, panel_applet_factory, G_TYPE_OBJECT)

static GHashTable *factories = NULL;

static void
panel_applet_factory_finalize (GObject *object)
{
	PanelAppletFactory *factory = PANEL_APPLET_FACTORY (object);

	g_hash_table_remove (factories, factory->factory_id);

	if (g_hash_table_size (factories) == 0) {
		g_hash_table_unref (factories);
		factories = NULL;
	}

	if (factory->registration_id) {
		g_dbus_connection_unregister_object (factory->connection, factory->registration_id);
		factory->registration_id = 0;
	}

	if (factory->owner_id) {
		g_bus_unown_name (factory->owner_id);
		factory->owner_id = 0;
	}

	if (factory->factory_id) {
		g_free (factory->factory_id);
		factory->factory_id = NULL;
	}

	if (factory->applets) {
		g_hash_table_unref (factory->applets);
		factory->applets = NULL;
	}

	if (factory->closure) {
		g_closure_unref (factory->closure);
		factory->closure = NULL;
	}

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

static void
panel_applet_factory_init (PanelAppletFactory *factory)
{
	factory->applets = g_hash_table_new (NULL, NULL);
	factory->next_uid = 1;
}

static void
panel_applet_factory_class_init (PanelAppletFactoryClass *klass)
{
	GObjectClass *g_object_class = G_OBJECT_CLASS (klass);

	g_object_class->finalize = panel_applet_factory_finalize;
}

static void
panel_applet_factory_applet_removed (PanelAppletFactory *factory,
				     GObject            *applet)
{
	guint uid;

	uid = GPOINTER_TO_UINT (g_object_get_data (applet, "uid"));

	g_hash_table_remove (factory->applets, GUINT_TO_POINTER (uid));

	factory->n_applets--;
	if (factory->n_applets == 0)
		g_object_unref (factory);
}

PanelAppletFactory *
panel_applet_factory_new (const gchar *factory_id,
			  GType        applet_type,
			  GClosure    *closure)
{
	PanelAppletFactory *factory;

	factory = PANEL_APPLET_FACTORY (g_object_new (PANEL_TYPE_APPLET_FACTORY, NULL));
	factory->factory_id = g_strdup (factory_id);
	factory->applet_type = applet_type;
	factory->closure = g_closure_ref (closure);

	if (factories == NULL) {
		factories = g_hash_table_new (g_str_hash, g_str_equal);
	}

	g_hash_table_insert (factories, factory->factory_id, factory);

	return factory;
}

static void
set_applet_constructor_properties (GObject  *applet,
				   GVariant *props)
{
	GVariantIter iter;
	GVariant    *value;
	gchar       *key;

	g_variant_iter_init (&iter, props);
	while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) {
		GVariantClass variant_class;

		variant_class = g_variant_classify (value);

		if (variant_class == G_VARIANT_CLASS_UINT32) {
			guint32 v = g_variant_get_uint32 (value);

			g_object_set (applet, key, v, NULL);
		} else if (variant_class == G_VARIANT_CLASS_STRING) {
			const gchar *v = g_variant_get_string (value, NULL);

			g_object_set (applet, key, v, NULL);
		} else if (variant_class == G_VARIANT_CLASS_BOOLEAN) {
			gboolean v = g_variant_get_boolean (value);

			g_object_set (applet, key, v, NULL);
		} else {
			g_assert_not_reached ();
		}
	}
}


static void
panel_applet_factory_get_applet (PanelAppletFactory    *factory,
				 GDBusConnection       *connection,
				 GVariant              *parameters,
				 GDBusMethodInvocation *invocation)
{
	GObject     *applet;
	const gchar *applet_id;
	GVariant    *props;
	GVariant    *return_value;
	guint32      uid;
	const gchar *object_path;

	g_variant_get (parameters, "(&s@a{sv})", &applet_id, &props);

	applet = g_object_new (factory->applet_type,
			       "id", applet_id,
			       "connection", connection,
			       "closure", factory->closure,
			       NULL);
	factory->n_applets++;
	g_object_weak_ref (applet, (GWeakNotify) panel_applet_factory_applet_removed, factory);

	set_applet_constructor_properties (applet, props);
	g_variant_unref (props);

	uid = factory->next_uid++;
	object_path = panel_applet_get_object_path (PANEL_APPLET (applet));

	g_hash_table_insert (factory->applets, GUINT_TO_POINTER (uid), applet);
	g_object_set_data (applet, "uid", GUINT_TO_POINTER (uid));

	return_value = g_variant_new ("(ou)", object_path, uid);

	g_dbus_method_invocation_return_value (invocation, return_value);
}

static void
method_call_cb (GDBusConnection       *connection,
		const gchar           *sender,
		const gchar           *object_path,
		const gchar           *interface_name,
		const gchar           *method_name,
		GVariant              *parameters,
		GDBusMethodInvocation *invocation,
		gpointer               user_data)
{
	PanelAppletFactory *factory = PANEL_APPLET_FACTORY (user_data);

	if (g_strcmp0 (method_name, "GetApplet") == 0) {
		panel_applet_factory_get_applet (factory, connection, parameters, invocation);
	}
}

static const gchar introspection_xml[] =
	"<node>"
	    "<interface name='org.gnome.panel.applet.AppletFactory'>"
	      "<method name='GetApplet'>"
	        "<arg name='applet_id' type='s' direction='in'/>"
	        "<arg name='props' type='a{sv}' direction='in'/>"
	        "<arg name='applet' type='o' direction='out'/>"
	        "<arg name='uid' type='u' direction='out'/>"
	      "</method>"
	    "</interface>"
	  "</node>";

static const GDBusInterfaceVTable interface_vtable = {
	method_call_cb,
	NULL,
	NULL
};

static GDBusNodeInfo *introspection_data = NULL;

static void
on_bus_acquired (GDBusConnection    *connection,
		  const gchar        *name,
		  PanelAppletFactory *factory)
{
	gchar  *object_path;
	GError *error = NULL;

	if (!introspection_data)
		introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
	object_path = g_strdup_printf (PANEL_APPLET_FACTORY_OBJECT_PATH, factory->factory_id);
	factory->connection = connection;
	factory->registration_id = g_dbus_connection_register_object (connection,
					   object_path,
					   introspection_data->interfaces[0],
					   &interface_vtable,
					   factory, NULL,
					   &error);
	if (error) {
		g_printerr ("Failed to register object %s: %s\n", object_path, error->message);
		g_error_free (error);
	}

	g_free (object_path);
}

static void
on_name_lost (GDBusConnection    *connection,
	      const gchar        *name,
	      PanelAppletFactory *factory)
{
	g_object_unref (factory);
}

gboolean
panel_applet_factory_register_service (PanelAppletFactory *factory)
{
	gchar *service_name;

	service_name = g_strdup_printf (PANEL_APPLET_FACTORY_SERVICE_NAME, factory->factory_id);
	factory->owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
			service_name,
			G_BUS_NAME_OWNER_FLAGS_NONE,
			(GBusAcquiredCallback) on_bus_acquired,
			NULL,
			(GBusNameLostCallback) on_name_lost,
			factory, NULL);
	g_free (service_name);

	return TRUE;
}

GtkWidget *
panel_applet_factory_get_applet_widget (const gchar *id,
                                        guint        uid)
{
	PanelAppletFactory *factory;
	GObject            *object;

	if (!factories)
		return NULL;

	factory = g_hash_table_lookup (factories, id);
	if (!factory)
		return NULL;

	object = g_hash_table_lookup (factory->applets, GUINT_TO_POINTER (uid));
	if (!object || !GTK_IS_WIDGET (object))
		return NULL;

	return GTK_WIDGET (object);
}
