/*
 * Copyright (C) 2010 Guido Guenther <agx@sigxcpu.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "ka-plugin-loader.h"
#include "ka-plugin.h"
#include "ka-settings.h"
#include "ka-applet-priv.h"

#include <gmodule.h>

G_DEFINE_TYPE (KaPluginLoader, ka_plugin_loader, G_TYPE_OBJECT)

#define GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), KA_TYPE_PLUGIN_LOADER, KaPluginLoaderPrivate))

typedef struct _KaPluginLoaderPrivate KaPluginLoaderPrivate;

struct _KaPluginLoaderPrivate {
	KaApplet *applet;
	GSList *active_plugins;
};


static KaPlugin*
load_plugin (const char *path)
{
	KaPlugin *plugin = NULL;
	GModule *module;
	KaPluginCreateFunc plugin_create_func;
	int *major_plugin_version, *minor_plugin_version;

	module = g_module_open (path, G_MODULE_BIND_LAZY);
	if (!module) {
		g_warning ("Could not load plugin %s: %s", path, g_module_error ());
		return NULL;
	}

	if (!g_module_symbol (module, "ka_plugin_major_version",(gpointer *) &major_plugin_version)) {
		g_warning ("Could not load plugin %s: Missing major version info", path);
		goto out;
	}

	if (*major_plugin_version != KA_PLUGIN_MAJOR_VERSION) {
		g_warning ("Could not load plugin %s: Plugin major version %d, %d is required",
				   path, *major_plugin_version, KA_PLUGIN_MAJOR_VERSION);
		goto out;
	}

	if (!g_module_symbol (module, "ka_plugin_minor_version", (gpointer *) &minor_plugin_version)) {
		g_warning ("Could not load plugin %s: Missing minor version info", path);
		goto out;
	}

	if (*minor_plugin_version != KA_PLUGIN_MINOR_VERSION) {
		g_warning ("Could not load plugin %s: Plugin minor version %d, %d is required",
				   path, *minor_plugin_version, KA_PLUGIN_MINOR_VERSION);
		goto out;
	}

	if (!g_module_symbol (module, "ka_plugin_create", (gpointer *) &plugin_create_func)) {
		g_warning ("Could not load plugin %s: %s", path, g_module_error ());
		goto out;
	}

	plugin = (*plugin_create_func) ();
	if (plugin) {
		g_object_weak_ref (G_OBJECT (plugin), (GWeakNotify) g_module_close, module);
		g_message ("Loaded plugin %s", ka_plugin_get_name (plugin));
	} else
		g_warning ("Could not load plugin %s: initialization failed", path);
out:
	if (!plugin)
		g_module_close (module);

	return plugin;
}


static void
load_plugins (KaPluginLoader *self)
{
	int i;
	KaPluginLoaderPrivate *priv = GET_PRIVATE (self);
	GSettings *settings;
	char **plugins = NULL;

	if (!g_module_supported ()) {
		g_warning ("GModules are not supported on your platform!");
		return;
	}
	settings = g_settings_get_child(ka_applet_get_settings (priv->applet),
                                        KA_SETTING_CHILD_PLUGINS);

	/* For now we only load the plugins on program startup */
	plugins = g_settings_get_strv(settings,
                                      KA_SETTING_KEY_PLUGINS_ENABLED);

	if (!plugins) {
		g_message ("No plugins to load");
		return;
	}

	for (i = 0; plugins[i]; i++) {
		char *path;
		char *fname;
		KaPlugin *plugin;

		fname = g_strdup_printf("ka-plugin-%s", plugins[i]);
		path = g_module_build_path (KA_PLUGINS_DIR, fname);

		plugin = load_plugin (path);
		if (plugin) {
			ka_plugin_activate(plugin, priv->applet);
			priv->active_plugins = g_slist_prepend (priv->active_plugins, plugin);
		}
		g_free (fname);
		g_free (path);
	}
	g_strfreev (plugins);
	g_object_unref (settings);
}


static void
deactivate_plugin(gpointer plugin, gpointer user_data)
{
	KaApplet *applet = KA_APPLET (user_data);

	KA_DEBUG ("Deactivating plugin %s", ka_plugin_get_name (plugin));
	ka_plugin_deactivate (plugin, applet);
}


static void
ka_plugin_loader_dispose(GObject *object)
{
	KaPluginLoader *self = KA_PLUGIN_LOADER(object);
	KaPluginLoaderPrivate *priv = GET_PRIVATE (self);
	GObjectClass *parent_class = G_OBJECT_CLASS (ka_plugin_loader_parent_class);

	/* We need to do this before dropping the ref on applet */
	g_slist_foreach (priv->active_plugins, deactivate_plugin, priv->applet);
	g_slist_free (priv->active_plugins);

	if (priv->applet)
		priv->applet = NULL;

	if (parent_class->dispose != NULL)
		parent_class->dispose (object);
}


static void
ka_plugin_loader_class_init (KaPluginLoaderClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);

	object_class->dispose = ka_plugin_loader_dispose;
	g_type_class_add_private (klass, sizeof (KaPluginLoaderPrivate));
}


static void
ka_plugin_loader_init (KaPluginLoader *self)
{
	KaPluginLoaderPrivate *priv = GET_PRIVATE (self);
	priv->active_plugins = NULL;
}


static KaPluginLoader*
ka_plugin_loader_new (void)
{
	return g_object_new (KA_TYPE_PLUGIN_LOADER, NULL);
}


KaPluginLoader*
ka_plugin_loader_create (KaApplet* applet)
{
	KaPluginLoader *loader;
	KaPluginLoaderPrivate *priv;

	loader = ka_plugin_loader_new();
	priv = GET_PRIVATE (loader);
	priv->applet = applet;
	load_plugins (loader);

	return loader;
}
