/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2006 Kouhei Sutou
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "kz-ext.h"
#include "kz-ext-impl.h"

#include "glib-utils.h"
#include "kz-marshalers.h"

#define EXT_NAME "kzext"

static GList *extensions = NULL;
static gboolean initted = FALSE;

typedef struct _KzExtInfo		KzExtInfo;

struct _KzExtInfo
{
	gchar *name;
	gchar *path;
	GModule *module;
};

static void
kz_ext_show_error (GModule *module)
{
	gchar *message;

	message = g_locale_to_utf8(g_module_error(), -1, NULL, NULL, NULL);

	if (module) {
		gchar *name;
		name = g_strdup(g_module_name(module));
		g_warning("%s: %s", name, message);
		g_free(name);
	}
	else
	{
		g_warning("%s", message);
	}

	g_free(message);
}

static gboolean
kz_ext_load_func (GModule *module, const gchar *func_name, gpointer *symbol)
{
	if (g_module_symbol(module, func_name, symbol))
	{
		return TRUE;
	}
	else
	{
		kz_ext_show_error(module);
		return FALSE;
	}
}

static void
kz_ext_close_module (GModule *module, gboolean success)
{
	KzExtExitFunc exit_func;
	KzExtExitFunc *exit_func_p;
	gpointer *p;

	exit_func_p = &exit_func;
	p = (gpointer *)exit_func_p;
	if (kz_ext_load_func(module, G_STRINGIFY(KZ_EXT_IMPL_EXIT), p))
		exit_func(success);
	else
		kz_ext_show_error(module);

	if (!g_module_close(module))
		kz_ext_show_error(module);
}

static void
kz_ext_info_free (KzExtInfo *info)
{
	if (info->name)
	{
		g_free(info->name);
		info->name = NULL;
	}

	if (info->path)
	{
		g_free(info->path);
		info->path = NULL;
	}

	if (info->module)
	{
		kz_ext_close_module(info->module, TRUE);
		info->module = NULL;
	}
}

static KzExtInfo *
kz_ext_info_load(const char *name, KzApp *app, void *init_address)
{
	gchar *mod_dir;
	gchar *mod_path;
	GModule *module;
	KzExtInfo *info = NULL;

	mod_dir = g_build_filename(EXTDIR, name, NULL);
	mod_path = g_module_build_path(mod_dir, EXT_NAME);
	if (g_file_test(mod_path, G_FILE_TEST_EXISTS))
		return NULL;
	module = g_module_open(mod_path, G_MODULE_BIND_LAZY);

	if (module)
	{
		KzExtInitFunc init_func;
		KzExtInitFunc *init_func_p;
		gpointer *p;

		init_func_p = &init_func;
		p = (gpointer *)init_func_p;
		if (kz_ext_load_func(module, G_STRINGIFY(KZ_EXT_IMPL_INIT), p))
		{
			init_func(app, init_address);
			info = g_new0(KzExtInfo, 1);
			info->name = g_strdup(name);
			info->path = g_strdup(mod_path);
			info->module = module;
		}
		else
		{
			kz_ext_close_module(module, FALSE);
		}
	}
	else
	{
		kz_ext_show_error(NULL);
	}

	g_free(mod_dir);
	g_free(mod_path);

	return info;
}

void
kz_ext_init(KzApp *app, void *init_address)
{
	GDir *dir;

	if (initted) return;

	extensions = NULL;

	dir = g_dir_open(EXTDIR, 0, NULL);
	if (dir) {
		const gchar *entry;
		KzExtInfo *info;

		while ((entry = g_dir_read_name(dir)))
		{
			info = kz_ext_info_load(entry, app, init_address);
			if (info)
				extensions = g_list_append(extensions, info);
		}

		g_dir_close(dir);
	}

	initted = TRUE;
}

static void
kz_ext_info_call_setup_func(KzExtInfo *info, KzWindow *kz)
{
	KzExtSetupFunc setup_func;
	KzExtSetupFunc *setup_func_p;
	gpointer *p;

	setup_func_p = &setup_func;
	p = (gpointer *)setup_func_p;
	if (kz_ext_load_func(info->module, G_STRINGIFY(KZ_EXT_IMPL_SETUP), p))
		setup_func(kz);
	else
		kz_ext_show_error(info->module);
}

void
kz_ext_setup(KzWindow *kz)
{
	if (!initted) return;

	g_list_foreach(extensions, (GFunc) kz_ext_info_call_setup_func, kz);
}

void
kz_ext_exit(void)
{
	if (!initted) return;

	g_list_foreach(extensions, (GFunc) kz_ext_info_free, NULL);
	g_list_free(extensions);
}
