/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * Copyright (C) 2006 Christian Hammond <chipx86@chipx86.com>
 * Copyright (C) 2005 John (J5) Palmieri <johnp@redhat.com>
 * Copyright (C) 2010 Red Hat, Inc.
 * Copyright (C) 2011 Perberos <perberos@gmail.com>
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include "config.h"

#include "daemon.h"
#include "engines.h"

typedef struct {
	GModule*    module;
	guint       ref_count;

	/* callbacks */
	gboolean    (*theme_check_init)            (unsigned int major_ver, unsigned int minor_ver, unsigned int micro_ver);
	void        (*get_theme_info)              (char** theme_name, char** theme_ver, char** author, char** homepage);
	GtkWindow*  (*create_notification)         (UrlClickedCb url_clicked_cb);
	void        (*destroy_notification)        (GtkWindow* nw);
	void        (*show_notification)           (GtkWindow* nw);
	void        (*hide_notification)           (GtkWindow* nw);
	void        (*set_notification_hints)      (GtkWindow* nw, GVariant *hints);
	void        (*set_notification_text)       (GtkWindow* nw, const char* summary, const char* body);
	void        (*set_notification_icon)       (GtkWindow* nw, GdkPixbuf* pixbuf);
	void        (*set_notification_arrow)      (GtkWindow* nw, gboolean visible, int x, int y);
	void        (*add_notification_action)     (GtkWindow* nw, const char* label, const char* key, GCallback cb);
	void        (*clear_notification_actions)  (GtkWindow* nw);
	void        (*move_notification)           (GtkWindow* nw, int x, int y);
	void        (*set_notification_timeout)    (GtkWindow* nw, glong timeout);
	void        (*notification_tick)           (GtkWindow* nw, glong timeout);
	gboolean    (*get_always_stack)            (GtkWindow* nw);

} ThemeEngine;

static guint        theme_prop_notify_id = 0;
static ThemeEngine* active_engine = NULL;

static ThemeEngine* load_theme_engine(const char *name)
{
	ThemeEngine* engine;
	char* path;

	path = g_module_build_path (ENGINES_DIR, name);

	engine = g_new0(ThemeEngine, 1);
	engine->ref_count = 1;
	engine->module = g_module_open(path, G_MODULE_BIND_LAZY);

	g_free(path);

	if (engine->module == NULL)
			goto error;

	#define BIND_REQUIRED_FUNC(name) \
		if (!g_module_symbol(engine->module, #name, (gpointer*) &engine->name)) \
		{ \
			/* Too harsh! Fall back to default. */ \
			g_warning("Theme doesn't provide the required function '%s'", #name); \
			goto error; \
		}

	#define BIND_OPTIONAL_FUNC(name) \
		g_module_symbol(engine->module, #name, (gpointer*) &engine->name);

	BIND_REQUIRED_FUNC(theme_check_init);
	BIND_REQUIRED_FUNC(get_theme_info);
	BIND_REQUIRED_FUNC(create_notification);
	BIND_REQUIRED_FUNC(set_notification_text);
	BIND_REQUIRED_FUNC(set_notification_icon);
	BIND_REQUIRED_FUNC(set_notification_arrow);
	BIND_REQUIRED_FUNC(add_notification_action);
	BIND_REQUIRED_FUNC(clear_notification_actions);
	BIND_REQUIRED_FUNC(move_notification);

	BIND_OPTIONAL_FUNC(destroy_notification);
	BIND_OPTIONAL_FUNC(show_notification);
	BIND_OPTIONAL_FUNC(hide_notification);
	BIND_OPTIONAL_FUNC(set_notification_timeout);
	BIND_OPTIONAL_FUNC(set_notification_hints);
	BIND_OPTIONAL_FUNC(notification_tick);
	BIND_OPTIONAL_FUNC(get_always_stack);

	if (!engine->theme_check_init(NOTIFICATION_DAEMON_MAJOR_VERSION, NOTIFICATION_DAEMON_MINOR_VERSION, NOTIFICATION_DAEMON_MICRO_VERSION))
	{
		g_warning ("Theme doesn't work with this version of mate-notification-daemon");
		goto error;
	}

	return engine;

	error:

		if (engine->module != NULL && !g_module_close (engine->module))
		{
			g_warning("%s: %s", name, g_module_error());
		}

		g_free(engine);
		return NULL;
}

static void destroy_engine(ThemeEngine* engine)
{
	g_assert(engine->ref_count == 0);

	if (active_engine == engine)
		active_engine = NULL;

	g_module_close(engine->module);
	g_free(engine);
}

static gboolean theme_engine_destroy(ThemeEngine* engine)
{
	destroy_engine(engine);
	return FALSE;
}

static void theme_engine_unref(ThemeEngine* engine)
{
	engine->ref_count--;

	if (engine->ref_count == 0)
	{
		/*
		 * Destroy the engine in an idle loop since the last reference
		 * might have been the one of a notification which is being
		 * destroyed and that still has references to the engine
		 * module. This way, we're sure the notification is completely
		 * destroyed before the engine is.
		 */
		g_idle_add((GSourceFunc) theme_engine_destroy, engine);
	}
}

static void theme_changed_cb(GSettings *settings, gchar *key, gpointer user_data)
{
	if (active_engine == NULL)
	{
		return;
	}

	theme_engine_unref(active_engine);

	/* This is no longer the true active engine, so reset this. */
	active_engine = NULL;
}

static ThemeEngine* get_theme_engine(void)
{
	if (active_engine == NULL)
	{
		GSettings* gsettings = g_settings_new (GSETTINGS_SCHEMA);

		if (theme_prop_notify_id == 0)
		{
			theme_prop_notify_id = g_signal_connect (gsettings, "changed::" GSETTINGS_KEY_THEME, G_CALLBACK (theme_changed_cb), NULL);
		}

		char* enginename = g_settings_get_string(gsettings, GSETTINGS_KEY_THEME);
		if (enginename == NULL)
		{
			active_engine = load_theme_engine("standard");
			g_assert(active_engine != NULL);
		}
		else
		{
			active_engine = load_theme_engine(enginename);

			if (active_engine == NULL)
			{
				g_warning("Unable to load theme engine '%s'", enginename);
				active_engine = load_theme_engine("standard");
			}

			g_free(enginename);

			g_assert(active_engine != NULL);
		}
	}

	return active_engine;
}

GtkWindow* theme_create_notification(UrlClickedCb url_clicked_cb)
{
	ThemeEngine* engine = get_theme_engine();
	GtkWindow* nw = engine->create_notification(url_clicked_cb);
	g_object_set_data_full(G_OBJECT(nw), "_theme_engine", engine, (GDestroyNotify) theme_engine_unref);
	engine->ref_count++;
	return nw;
}

void theme_destroy_notification(GtkWindow* nw)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");

	if (engine->destroy_notification != NULL)
	{
		engine->destroy_notification(nw);
	}
	else
	{
		gtk_widget_destroy(GTK_WIDGET(nw));
	}
}

void theme_show_notification(GtkWindow* nw)
{
	ThemeEngine *engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");

	if (engine->show_notification != NULL)
	{
		engine->show_notification(nw);
	}
	else
	{
		gtk_widget_show(GTK_WIDGET(nw));
	}
}

void theme_hide_notification(GtkWindow* nw)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");

	if (engine->hide_notification != NULL)
	{
		engine->hide_notification(nw);
	}
	else
	{
		gtk_widget_hide(GTK_WIDGET(nw));
	}
}

void theme_set_notification_hints(GtkWindow* nw, GVariant *hints)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");

	if (engine->set_notification_hints != NULL)
	{
		engine->set_notification_hints(nw, hints);
	}
}

void theme_set_notification_timeout(GtkWindow* nw, glong timeout)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");

	if (engine->set_notification_timeout != NULL)
	{
		engine->set_notification_timeout(nw, timeout);
	}
}

void theme_notification_tick(GtkWindow* nw, glong remaining)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");

	if (engine->notification_tick != NULL)
	{
		engine->notification_tick(nw, remaining);
	}
}

void theme_set_notification_text(GtkWindow* nw, const char* summary, const char* body)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT (nw), "_theme_engine");
	engine->set_notification_text (nw, summary, body);
}

void theme_set_notification_icon(GtkWindow* nw, GdkPixbuf* pixbuf)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT (nw), "_theme_engine");
	engine->set_notification_icon(nw, pixbuf);
}

void theme_set_notification_arrow(GtkWindow* nw, gboolean visible, int x, int y)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT (nw), "_theme_engine");
	engine->set_notification_arrow(nw, visible, x, y);
}

void theme_add_notification_action(GtkWindow* nw, const char* label, const char* key, GCallback cb)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");
	engine->add_notification_action(nw, label, key, cb);
}

void theme_clear_notification_actions(GtkWindow* nw)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");
	engine->clear_notification_actions(nw);
}

void theme_move_notification(GtkWindow* nw, int x, int y)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");
	engine->move_notification(nw, x, y);
}

gboolean theme_get_always_stack(GtkWindow* nw)
{
	ThemeEngine* engine = g_object_get_data(G_OBJECT(nw), "_theme_engine");

	if (engine->get_always_stack != NULL)
	{
		return engine->get_always_stack(nw);
	}
	else
	{
		return FALSE;
	}
}
