/*
 * libopensync - A synchronization framework
 * Copyright (C) 2004-2005  Armin Bauer <armin.bauer@opensync.org>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 * 
 */
 
#include "opensync.h"
#include "opensync_internals.h"


/**
 * @defgroup OSyncPluginPrivateAPI OpenSync Plugin Internals
 * @ingroup OSyncPrivate
 * @brief The private part of the plugins API
 * 
 */
/*@{*/

#ifndef DOXYGEN_SHOULD_SKIP_THIS
OSyncObjTypeSink *osync_objtype_sink_from_template(OSyncGroup *group, OSyncObjTypeTemplate *template)
{
	g_assert(group);
	g_assert(template);
	OSyncObjTypeSink *sink = g_malloc0(sizeof(OSyncObjTypeSink));
	OSyncObjType *type = osync_conv_find_objtype(group->conv_env, template->name);
	if (!type) {
		osync_debug("OSYNC", 0, "Unable to find objtype named %s to create objtype sink", template->name);
		return NULL;
	}
	sink->objtype = type;
	sink->enabled = TRUE;
	sink->write = TRUE;
	sink->read = TRUE;
	return sink;
}

OSyncObjFormatSink *osync_objformat_sink_from_template(OSyncGroup *group, OSyncObjFormatTemplate *template)
{
	OSyncObjFormatSink *sink = g_malloc0(sizeof(OSyncObjFormatSink));
	OSyncObjFormat *format = osync_conv_find_objformat(group->conv_env, template->name);
	if (!format)
		return NULL;
	sink->format = format;
	sink->functions.commit_change = template->commit_change;
	sink->functions.access = template->access;
	sink->functions.read = template->read;
	sink->functions.committed_all = template->committed_all;
	sink->functions.batch_commit = template->batch_commit;
	sink->extension_name = g_strdup(template->extension_name);
	return sink;
}

OSyncObjTypeTemplate *osync_plugin_find_objtype_template(OSyncPlugin *plugin, const char *objtypestr)
{
	GList *o;
	for (o = plugin->accepted_objtypes; o; o = o->next) {
		OSyncObjTypeTemplate *template = o->data;
		if (!strcmp(template->name, objtypestr))
			return template;
	}
	return NULL;
}

OSyncObjFormatTemplate *osync_plugin_find_objformat_template(OSyncObjTypeTemplate *type_template, const char *objformatstr)
{
	GList *f;
	for (f = type_template->formats; f; f = f->next) {
		OSyncObjFormatTemplate *template = f->data;
		if (!strcmp(template->name, objformatstr))
			return template;
	}
	return NULL;
}

OSyncObjFormatSink *osync_objtype_find_format_sink(OSyncObjTypeSink *sink, const char *formatstr)
{
	GList *f;
	for (f = sink->formatsinks; f; f = f->next) {
		OSyncObjFormatSink *sink = f->data;
		if (!strcmp(sink->format->name, formatstr))
			return sink;
	}
	return NULL;
}

void _osync_format_set_commit(OSyncObjTypeTemplate *template, const char *formatstr, OSyncFormatCommitFn commit_change)
{
	OSyncObjFormatTemplate *format_template = NULL;
	if (formatstr) {
		OSyncObjFormatTemplate *format_template = osync_plugin_find_objformat_template(template, formatstr);
		osync_assert_msg(format_template, "Unable to set commit function. Did you forget to add the objformat?");
		format_template->commit_change = commit_change;
	} else {
		GList *f = NULL;
		for (f = template->formats; f; f = f->next) {
			format_template = f->data;
			format_template->commit_change = commit_change;
		}
	}
}

void _osync_format_set_access(OSyncObjTypeTemplate *template, const char *formatstr, OSyncFormatAccessFn access)
{
	OSyncObjFormatTemplate *format_template = NULL;
	if (formatstr) {
		format_template = osync_plugin_find_objformat_template(template, formatstr);
		osync_assert_msg(format_template, "Unable to set commit function. Did you forget to add the objformat?");
		format_template->access = access;
	} else {
		GList *f = NULL;
		for (f = template->formats; f; f = f->next) {
			format_template = f->data;
			format_template->access = access;
		}
	}
}

void _osync_format_set_batch(OSyncObjTypeTemplate *template, const char *formatstr, OSyncFormatBatchCommitFn batch)
{
	OSyncObjFormatTemplate *format_template = NULL;
	if (formatstr) {
		format_template = osync_plugin_find_objformat_template(template, formatstr);
		osync_assert_msg(format_template, "Unable to set batch commit function. Did you forget to add the objformat?");
		format_template->batch_commit = batch;
	} else {
		GList *f = NULL;
		for (f = template->formats; f; f = f->next) {
			format_template = f->data;
			format_template->batch_commit = batch;
		}
	}
}
#endif

/*@}*/

/**
 * @defgroup OSyncPluginAPI OpenSync Plugin
 * @ingroup OSyncPublic
 * @brief Functions to register and manage plugins
 * 
 */
/*@{*/

/*! @brief This will create a new plugin struct
 * 
 * The plugin struct represents a sync plugin
 * 
 * @param env For which environment to register this plugin. May be NULL
 * @returns A pointer to a newly allocated plugin.
 * 
 */
OSyncPlugin *osync_plugin_new(OSyncEnv *env)
{
	OSyncPlugin *plugin = g_malloc0(sizeof(OSyncPlugin));
	g_assert(plugin);
	memset(&(plugin->info), 0, sizeof(plugin->info));
	memset(&(plugin->info.functions), 0, sizeof(plugin->info.functions));
	memset(&(plugin->info.timeouts), 0, sizeof(plugin->info.timeouts));
	
	//Set the default timeouts;
	plugin->info.timeouts.connect_timeout = 60;
	plugin->info.timeouts.sync_done_timeout = 60;
	plugin->info.timeouts.disconnect_timeout = 60;
	plugin->info.timeouts.get_changeinfo_timeout = 60;
	plugin->info.timeouts.get_data_timeout = 60;
	plugin->info.timeouts.commit_timeout = 60;
	plugin->info.timeouts.read_change_timeout = 60;
	
	plugin->info.plugin = plugin;
	plugin->info.config_type = NEEDS_CONFIGURATION;
	
	if (env) {
		env->plugins = g_list_append(env->plugins, plugin);
		plugin->env = env;
		plugin->real_plugin = env->current_module;
	}
	
	return plugin;
}


/*! @brief Registers a new plugin
 *
 * This function creates a new OSyncPluginInfo object, that
 * can be used to register a new plugin dynamically. This
 * can be used by a module to register multiple plugins,
 * instead of using get_info() function, that allows
 * registering of only one plugin.
 */
OSyncPluginInfo *osync_plugin_new_info(OSyncEnv *env)
{
	OSyncPlugin *plg = osync_plugin_new(env);
	osync_trace(TRACE_INTERNAL, "%s(%p): %p", __func__, env, plg);
	if (!plg)
		return NULL;

	return &plg->info;
}

/*! @brief Used to free a plugin
 * 
 * Frees a plugin
 * 
 * @param plugin Pointer to the plugin
 * 
 */
void osync_plugin_free(OSyncPlugin *plugin)
{
	osync_trace(TRACE_INTERNAL, "osync_plugin_free(%p)", plugin);
	g_assert(plugin);
	if (plugin->env)
		plugin->env->plugins = g_list_remove(plugin->env->plugins, plugin);

	//FIXME Free more stuff?
	g_free(plugin);
}

/*! @brief Used to look up a symbol on the plugin
 * 
 * Looks up and returns a function
 * 
 * @param plugin Pointer to the plugin
 * @param name The name of the function to look up
 * @param error Pointer to a error struct
 * @return Pointer to the function
 * 
 */
void *osync_plugin_get_function(OSyncPlugin *plugin, const char *name, OSyncError **error)
{
	void *function;
	if (!plugin->real_plugin) {
		osync_debug("OSPLG", 1, "You need to load a plugin before getting a function");
		osync_error_set(error, OSYNC_ERROR_MISCONFIGURATION, "You need to load a plugin before getting a function");
		return NULL;
	}
	
	if (!g_module_symbol (plugin->real_plugin, name, &function)) {
		osync_debug("OSPLG", 0, "Unable to locate symbol %s on plugin", name);
		osync_error_set(error, OSYNC_ERROR_PARAMETER, "Unable to locate symbol %s: %s", name, g_module_error());
		return NULL;
	}
	return function;
}

/*! @brief dlopen()s a format plugin
 * 
 * The get_info() function on the format plugin gets called
 * 
 * @param env The environment in which to open the plugin
 * @param path Where to find this plugin
 * @param error Pointer to a error struct
 * @return Pointer to the plugin on success, NULL otherwise
 * 
 */
osync_bool osync_module_load(OSyncEnv *env, const char *path, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, env, path, error);
	/* Check if this platform supports dynamic
	 * loading of modules */
	 
	if (!g_module_supported()) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "This platform does not support loading of modules");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}

	/* Try to open the module or fail if an error occurs */
	GModule *module = g_module_open(path, 0);
	if (!module) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to open module %s: %s", path, g_module_error());
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}
	
	/* Load the get_info symbol */
	void (* fct_info)(OSyncEnv *env) = NULL;
	void (** fct_infop)(OSyncEnv *env) = &fct_info;
	if (!g_module_symbol(module, "get_info", (void **)fct_infop)) {
		/* If there is no get_info symbol, the file must be a implementation library
		 * for one of the other modules. So dont throw an error error since it has been
		 * confusing users */
		osync_trace(TRACE_EXIT, "%s: Not loading implementation library", __func__);
		return TRUE;
	}
	env->modules = g_list_append(env->modules, module);
	
	/* Call the get_info function */
	env->current_module = module;
	fct_info(env);
	env->current_module = NULL;
	
	osync_trace(TRACE_EXIT, "%s: %p", __func__, module);
	return TRUE;
}

/*! @brief Closes a module
 * 
 * @param env The environment from which to remove the module
 * @param module The module to unload
 * 
 */
void osync_module_unload(OSyncEnv *env, GModule *module)
{
	osync_trace(TRACE_INTERNAL, "%s(%p, %p)", __func__, env, module);
	//FIXME Close the module! This crashes the evo2 plugin at the moment, i have no idea why...
	//g_module_close(plugin->real_plugin);
	env->modules = g_list_remove(env->modules, module);
}

/*! @brief Loads the modules from a given directory
 * 
 * Loads all modules from a directory into a osync environment
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param path The path where to look for plugins, NULL for the default sync module directory
 * @param must_exist If set to TRUE, this function will return an error if the directory does not exist
 * @param error Pointer to a error struct to return a error
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_module_load_dir(OSyncEnv *env, const char *path, osync_bool must_exist, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, env, path, error);
	GDir *dir;
	GError *gerror = NULL;
	char *filename = NULL;

	if (!path) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "Not path given to load the modules from");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}
	
	//Load all available shared libraries (plugins)
	if (!g_file_test(path, G_FILE_TEST_IS_DIR)) {
		if (must_exist) {
			osync_error_set(error, OSYNC_ERROR_GENERIC, "Path is not loadable");
			osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
			return FALSE;
		} else {
			osync_trace(TRACE_EXIT, "%s: Directory does not exist (non-fatal)", __func__);
			return TRUE;
		}
	}
	
	dir = g_dir_open(path, 0, &gerror);
	if (!dir) {
		osync_error_set(error, OSYNC_ERROR_IO_ERROR, "Unable to open directory %s: %s", path, gerror->message);
		g_error_free(gerror);
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}
  
	const gchar *de = NULL;
	while ((de = g_dir_read_name(dir))) {
		filename = g_strdup_printf ("%s/%s", path, de);
		
		if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR) || g_file_test(filename, G_FILE_TEST_IS_SYMLINK) || !g_pattern_match_simple("*.so", filename)) {
			g_free(filename);
			continue;
		}
		
		OSyncError *error = NULL;
		if (!osync_module_load(env, filename, &error)) {
			osync_debug("OSPLG", 0, "Unable to load plugin %s: %s", filename, error->message);
			osync_error_free(&error);
		}
		g_free(filename);
	}
	g_dir_close(dir);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

/*! @brief Returns the name of the loaded plugin
 * 
 * @param plugin Pointer to the plugin
 * @returns Name of the plugin
 * 
 */
const char *osync_plugin_get_name(OSyncPlugin *plugin)
{
	g_assert(plugin);
	return plugin->info.name;
}

/*! @brief Returns the long name of the loaded plugin
 * 
 * @param plugin Pointer to the plugin
 * @returns Long name of the plugin
 * 
 */
const char *osync_plugin_get_longname(OSyncPlugin *plugin)
{
	g_assert(plugin);
	return plugin->info.longname;
}

/*! @brief Returns the description of the plugin
 * 
 * @param plugin Pointer to the plugin
 * @returns Description of the plugin
 * 
 */
const char *osync_plugin_get_description(OSyncPlugin *plugin)
{
	g_assert(plugin);
	return plugin->info.description;
}

/*! @brief Returns the timeouts of the plugin
 * 
 * @param plugin Pointer to the plugin
 * @returns Timeouts of the plugin
 * 
 */
OSyncPluginTimeouts osync_plugin_get_timeouts(OSyncPlugin *plugin)
{
	g_assert(plugin);
	return plugin->info.timeouts;
}

/*! @brief Returns the plugin_info data, set by the plugin
 *
 * @param plugin Pointer to the plugin
 * @returns The void pointer set on plugin->info.plugin_data
 */
void *osync_plugin_get_plugin_data(OSyncPlugin *plugin)
{
	g_assert(plugin);
	return plugin->info.plugin_data;
}

/*! @brief Get full path for plugin module 
 *
 * @param plugin Pointer to the plugin
 * @returns full path of plugin module 
 */
const char *osync_plugin_get_path(OSyncPlugin *plugin)
{
	g_assert(plugin);
	return g_module_name(plugin->real_plugin);
}

/*! @brief Sets the commit function of a format
 * 
 * @param info Pointer to a plugin info struct to fill
 * @param objtypestr The name of the object type
 * @param formatstr The name of the format
 * @param commit_change The pointer to your commit_change function
 * 
 */
void osync_plugin_set_commit_objformat(OSyncPluginInfo *info, const char *objtypestr, const char *formatstr, OSyncFormatCommitFn commit_change)
{
	OSyncObjTypeTemplate *template = NULL;
	
	if (objtypestr) {
		OSyncObjTypeTemplate *template = osync_plugin_find_objtype_template(info->plugin, objtypestr);
		osync_assert_msg(template, "Unable to accept objformat. Did you forget to add the objtype?");
		_osync_format_set_commit(template, formatstr, commit_change);
	} else {
		GList *o = NULL;
		for (o = info->plugin->accepted_objtypes; o; o = o->next) {
			template = o->data;
			_osync_format_set_commit(template, formatstr, commit_change);
		}
	}
}

/*! @brief Sets the access function of a format
 * 
 * @param info Pointer to a plugin info struct to fill
 * @param objtypestr The name of the object type
 * @param formatstr The name of the format
 * @param access The pointer to your access function
 * 
 */
void osync_plugin_set_access_objformat(OSyncPluginInfo *info, const char *objtypestr, const char *formatstr, OSyncFormatAccessFn access)
{
	OSyncObjTypeTemplate *template = NULL;
	
	if (objtypestr) {
		//template = osync_plugin_find_objtype_template(info->plugin, objtypestr);
		//osync_assert_msg(template, "Unable to accept objformat. Did you forget to add the objtype?");
		//_osync_format_set_access(template, formatstr, access);
	} else {
		GList *o = NULL;
		for (o = info->plugin->accepted_objtypes; o; o = o->next) {
			template = o->data;
			_osync_format_set_access(template, formatstr, access);
		}
	}
}

/*! @brief Sets the read function of a format
 * 
 * @param info Pointer to a plugin info struct to fill
 * @param objtypestr The name of the object type
 * @param formatstr The name of the format
 * @param read The pointer to your read function
 * 
 */
void osync_plugin_set_read_objformat(OSyncPluginInfo *info, const char *objtypestr, const char *formatstr, OSyncFormatReadFn read)
{
	OSyncObjTypeTemplate *template = osync_plugin_find_objtype_template(info->plugin, objtypestr);
	osync_assert_msg(template, "Unable to accept objformat. Did you forget to add the objtype?");
	OSyncObjFormatTemplate *format_template = osync_plugin_find_objformat_template(template, formatstr);
	osync_assert_msg(format_template, "Unable to set commit function. Did you forget to add the objformat?");
	format_template->read = read;
}

/*! @brief Sets the batch_commit function of a format
 * 
 * @param info Pointer to a plugin info struct to fill
 * @param objtypestr The name of the object type
 * @param formatstr The name of the format
 * @param batch The pointer to your batch_commit function
 * 
 */
void osync_plugin_set_batch_commit_objformat(OSyncPluginInfo *info, const char *objtypestr, const char *formatstr, OSyncFormatBatchCommitFn batch)
{
	OSyncObjTypeTemplate *template = NULL;
	
	if (objtypestr) {
		template = osync_plugin_find_objtype_template(info->plugin, objtypestr);
		osync_assert_msg(template, "Unable to accept objformat. Did you forget to add the objtype?");
		_osync_format_set_batch(template, formatstr, batch);
	} else {
		GList *o = NULL;
		for (o = info->plugin->accepted_objtypes; o; o = o->next) {
			template = o->data;
			_osync_format_set_batch(template, formatstr, batch);
		}
	}
}

/*! @brief Sets the committed_all function of a format
 * 
 * @param info Pointer to a plugin info struct to fill
 * @param objtypestr The name of the object type
 * @param formatstr The name of the format
 * @param committed_all The pointer to your committed_all function
 * 
 */
void osync_plugin_set_committed_all_objformat(OSyncPluginInfo *info, const char *objtypestr, const char *formatstr, OSyncFormatCommittedAllFn committed_all)
{
	OSyncObjTypeTemplate *template = osync_plugin_find_objtype_template(info->plugin, objtypestr);
	osync_assert_msg(template, "Unable to accept objformat. Did you forget to add the objtype?");
	OSyncObjFormatTemplate *format_template = osync_plugin_find_objformat_template(template, formatstr);
	osync_assert_msg(format_template, "Unable to set committed_all function. Did you forget to add the objformat?");
	format_template->committed_all = committed_all;
}

/*! @brief Tells opensync that the plugin can accepts this object
 * 
 * Tells opensync that the plugin can accepts this object. Used by the plugin
 * in the get_info() function
 * 
 * @param info The plugin info on which to operate
 * @param objtypestr The name of the object which to accept
 * 
 */
void osync_plugin_accept_objtype(OSyncPluginInfo *info, const char *objtypestr)
{
	OSyncObjTypeTemplate *template = g_malloc0(sizeof(OSyncObjTypeTemplate));
	template->name = g_strdup(objtypestr);
	info->plugin->accepted_objtypes = g_list_append(info->plugin->accepted_objtypes, template);
}

/*! @brief Tells opensync that the plugin can accepts this format for the given object
 * 
 * Tells opensync that the plugin can accepts this format. Used by the plugin
 * in the get_info() function
 * 
 * @param info The plugin info on which to operate
 * @param objtypestr The name of the objecttype
 * @param formatstr The name of the format to accept
 * @param extension The name of the extension that the plugin wants. NULL if none
 * 
 */
void osync_plugin_accept_objformat(OSyncPluginInfo *info, const char *objtypestr, const char *formatstr, const char *extension)
{
	OSyncObjTypeTemplate *template = osync_plugin_find_objtype_template(info->plugin, objtypestr);
	osync_assert_msg(template, "Unable to accept objformat. Did you forget to add the objtype?");
	OSyncObjFormatTemplate *format_template = g_malloc0(sizeof(OSyncObjFormatTemplate));
	format_template->name = g_strdup(formatstr);
	if (extension)
		format_template->extension_name = g_strdup(extension);
	template->formats = g_list_append(template->formats, format_template);
}

/*@}*/


syntax highlighted by Code2HTML, v. 0.9.1