/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include <string.h>

#include "visu_plugins.h"
#include "visu_tools.h"
#include "visu_basic.h"

/**
 * SECTION:visu_plugins
 * @short_description: Introduces the basic handling of plug-ins.
 *
 * <para>
 * Plug-ins are made of shared library presenting some common routines. These routines are of kind:
 * <itemizedlist>
 * <listitem><para>
 * pluginsInitFunc(), such a routine is called at V_Sim startup. It should initialise all things required by the module. For instance, if the module is used to add a load method for atomic rendering, it should call visu_rendering_addFileFormat() to declare itself to V_Sim.
 * </para></listitem>
 * <listitem><para>
 * pluginsDescriptionFunc(), is used to get a short description of the plug-in.
 * </para></listitem>
 * <listitem><para>
 * pluginsAuthorsFunc(), gives a list of authors for the plug-in.
 * </para></listitem>
 * <listitem><para>
 * pluginsIconFunc(), returns a path where a small icon representing the plug-in can be found.
 * </para></listitem>
 * </itemizedlist>
 * All these routines must be named using the name of the module. When the module is loaded into memory, V_Sim will try to find these methods and will store them into the #VisuPlugin structure for future calls.
 * </para>
 * <para>
 * At the present time, only the Unix version is functional. Moreover, currently, no stable API is available from the main program. This should be corrected quickly.
 * </para>
 */

/**
 * VisuPlugin_struct:
 * @hook: a pointer to the opened shared library ;
 * @name: the name of the module (on UNIX it is lib{name}.so) ;
 * @init: a pointer on the init method ;
 * @getDescription: a pointer to the description method ;
 * @getAuthors: a pointer to the author method.
 * @getIcon: a pointer to the icon method.
 *
 * This structure is used to deals with a plugin. It gives handles
 * to the public method of the plugin.
 */
struct _VisuPlugin
{
  GModule *hook;

  gchar *name;

  VisuPluginInitFunc init;
  VisuPluginInitFunc initGtk;
  VisuPluginInfoFunc getDescription;
  VisuPluginInfoFunc getAuthors;
  VisuPluginInfoFunc getIcon;
  VisuPluginFreeFunc finalise;
};

/* Local variables. */
GList *presentPlugins = NULL;

/**
 * visu_plugins_getListLoaded:
 *
 * On startup, plugins are loaded according to a list present in the configuration
 * file. It is possible to access the list of all loaded plugins with this method.
 *
 * Returns: (element-type VisuPlugin) (transfer none): a #GList owned by V_Sim of #VisuPlugin objects.
 */
GList* visu_plugins_getListLoaded()
{
  if (!presentPlugins)
    visu_plugins_init(NULL);

  return presentPlugins;
}

static gboolean visuPluginLoad_byPath(const gchar* path, GError **error)
{
  gchar *func, *name, *ptChar, *libname;
  VisuPlugin *plugin;
  gboolean res;

  plugin = g_malloc(sizeof(VisuPlugin));
  plugin->hook = g_module_open(path, 0); /* G_MODULE_BIND_LOCAL); */
  if (plugin->hook)
    {
      libname = g_path_get_basename(path);
      name = g_strdup(libname + 3);
      g_free(libname);
#if SYSTEM_WIN32 == 1
      ptChar = g_strrstr(name, "-");
#endif
#if SYSTEM_X11 == 1
      ptChar = strchr(name, '.');
#endif
      if (ptChar)
	*ptChar = '\0';
      func = g_strdup_printf("%sInit", name);
      res = g_module_symbol(plugin->hook, func, (void*)&(plugin->init));
      g_free(func);
      if (!res)
	{
          g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                      "The plugin '%s' doesn't have any %sInit() method.",
                      name, name);
	  g_free(plugin);
	  g_free(name);
	  return FALSE;
	}
      func = g_strdup_printf("%sGet_description", name);
      res = g_module_symbol(plugin->hook, func, (void*)&(plugin->getDescription));
      g_free(func);
      if (!res)
	{
          g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                      "The plugin '%s' doesn't have any %sGet_description() method.",
                      name, name);
	  g_free(plugin);
	  g_free(name);
	  return FALSE;
	}
      func = g_strdup_printf("%sGet_authors", name);
      res = g_module_symbol(plugin->hook, func, (void*)&(plugin->getAuthors));
      g_free(func);
      if (!res)
	{
          g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                      "The plugin '%s' doesn't have any %sGet_authors() method.",
                      name, name);
	  g_free(plugin);
	  g_free(name);
	  return FALSE;
	}
      func = g_strdup_printf("%sGet_icon", name);
      res = g_module_symbol(plugin->hook, func, (void*)&(plugin->getIcon));
      if (!res)
	plugin->getIcon = (VisuPluginInfoFunc)0;
      g_free(func);
      func = g_strdup_printf("%sInitGtk", name);
      res = g_module_symbol(plugin->hook, func, (void*)&(plugin->initGtk));
      g_free(func);
      if (!res)
	plugin->initGtk = (VisuPluginInitFunc)0;
      func = g_strdup_printf("%sFree", name);
      res = g_module_symbol(plugin->hook, func, (void*)&(plugin->finalise));
      g_free(func);
      if (!res)
	plugin->finalise = (VisuPluginFreeFunc)0;
      plugin->name = g_strdup(name);
      g_free(name);
      presentPlugins = g_list_prepend(presentPlugins, (gpointer)plugin);
    }
  else
    {
      g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                  "The plugin '%s' is not a loadable module, error:\n%s.",
                  path, g_module_error());
      g_free(plugin);
      return FALSE;
    }
  
  return TRUE;
}

static GList* visuPluginsParseDir(const gchar* path)
{
  GDir *gdir;
  const gchar *fileFromDir;
  GPatternSpec *pattern;
  GList* lst;

  lst = (GList*)0;
  gdir = g_dir_open(path, 0, NULL);
  if (!gdir)
    return lst;

  pattern = g_pattern_spec_new("lib*."G_MODULE_SUFFIX);
  fileFromDir = g_dir_read_name(gdir);
  while (fileFromDir)
    {
      if (g_pattern_match_string(pattern, fileFromDir))
	lst = g_list_prepend(lst, g_build_filename(path, fileFromDir, NULL));
      fileFromDir = g_dir_read_name(gdir);
    }
  g_dir_close(gdir);
  g_pattern_spec_free(pattern);
  
  return lst;
}

/* Parse install directory and $XDG_CONFIG_HOME/v_sim/plugins to find
   files and create a gchar** array of names (as full path). */
static gchar** visuPluginsGet_installedPlugins()
{
  gchar **data, *path;
  GList* lst_install, *lst_home, *tmpLst;
  int size;

  /* Try the install dir. */
  lst_install = visuPluginsParseDir(V_SIM_PLUGINS_DIR);
  /* Try the home dir. */
  path = g_build_filename(V_SIM_LOCAL_CONF_DIR, "plugins", NULL);
  lst_home = visuPluginsParseDir(path);
  g_free(path);

  size = g_list_length(lst_install) + g_list_length(lst_home) + 1;
  data = g_malloc(sizeof(gchar*) * size);
  
  size = 0;
  tmpLst = lst_install;
  while(tmpLst)
    {
      data[size] = (gchar*)tmpLst->data;
      size += 1;
      tmpLst = g_list_next(tmpLst);
    }
  g_list_free(lst_install);
  tmpLst = lst_home;
  while(tmpLst)
    {
      data[size] = (gchar*)tmpLst->data;
      size += 1;
      tmpLst = g_list_next(tmpLst);
    }
  g_list_free(lst_home);
  data[size] = (gchar*)0;

  return data;
}

/**
 * visu_plugins_init:
 * @error: an error location.
 *
 * Initialise this part of code. Should not be called (called once
 * by V_Sim on startup only). It try to load
 * all plugins found in the installation directory and in the user directory.
 */
void visu_plugins_init(GError **error)
{
  gchar **plugins;
  GError *perr;
  int i;

  if (g_module_supported() && !presentPlugins)
    {
      plugins = visuPluginsGet_installedPlugins();
      for (i = 0; plugins[i]; i++)
	{
	  DBG_fprintf(stderr, "Visu Plugins : try to load the plugin '%s'.\n",
		      plugins[i]);
          perr = (GError*)0;
	  if (visuPluginLoad_byPath(plugins[i], &perr))
	    ((VisuPlugin*)presentPlugins->data)->init();
          if ((!error || !*error) && perr)
            g_propagate_error(error, perr);
          else
            g_clear_error(&perr);
	}
      g_strfreev(plugins);
    }
}

/**
 * visu_plugins_free:
 *
 * Finalise the part of V_Sim related to plug-ins. Should not be
 * called (called once by V_Sim on stopping only).
 *
 * Since: 3.7
 */
void visu_plugins_free()
{
  GList *plugins;

  plugins = visu_plugins_getListLoaded();
  for (plugins = visu_plugins_getListLoaded(); plugins;
       plugins = g_list_next(plugins))
    {
      DBG_fprintf(stderr, "Visu Plugins: try to finalise the plugin '%s'.\n",
                  ((VisuPlugin*)plugins->data)->name);
      if (((VisuPlugin*)plugins->data)->finalise)
        ((VisuPlugin*)plugins->data)->finalise();
    }
}

/**
 * visu_plugin_getName:
 * @plug: a #VisuPlugin object.
 *
 * Return a string with the name.
 *
 * Returns: a private string.
 *
 * Since: 3.6
 */
const gchar* visu_plugin_getName(VisuPlugin *plug)
{
  g_return_val_if_fail(plug, (const gchar*)0);
  
  return plug->name;
}
/**
 * visu_plugin_getDescription:
 * @plug: a #VisuPlugin object.
 *
 * Return a string with the description of the plugin.
 *
 * Returns: a private string.
 *
 * Since: 3.6
 */
const gchar* visu_plugin_getDescription(VisuPlugin *plug)
{
  g_return_val_if_fail(plug, (const gchar*)0);

  if (plug->getDescription)
    return plug->getDescription();
  else
    return (const gchar*)0;
}
/**
 * visu_plugin_getIconPath:
 * @plug: a #VisuPlugin object.
 *
 * Return a string with the path to find an icon representing the plugin.
 *
 * Returns: a private string.
 *
 * Since: 3.6
 */
const gchar* visu_plugin_getIconPath(VisuPlugin *plug)
{
  g_return_val_if_fail(plug, (const gchar*)0);

  if (plug->getIcon)
    return plug->getIcon();
  else
    return (const gchar*)0;
}
/**
 * visu_plugin_getAuthors:
 * @plug: a #VisuPlugin object.
 *
 * Return a string with the list of authors.
 *
 * Returns: a private string.
 *
 * Since: 3.6
 */
const gchar* visu_plugin_getAuthors(VisuPlugin *plug)
{
  g_return_val_if_fail(plug, (const gchar*)0);

  if (plug->getAuthors)
    return plug->getAuthors();
  else
    return (const gchar*)0;
}
/**
 * visu_plugin_initGtk:
 * @plug: a #VisuPlugin object.
 *
 * Run the initGtk() method of the plugin, if it exists.
 *
 * Since: 3.6
 */
void visu_plugin_initGtk(VisuPlugin *plug)
{
  g_return_if_fail(plug);

  if (plug->initGtk)
    plug->initGtk();
}
