/*

Copyright (C) 2000 - 2004 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <dirent.h>
#include <glib.h>

#include <nd.h>
#include <nd_gui.h>
#include <nd_plugin.h>
#include <nd_prefs.h>
#include <nd_trace_registry.h>
#include <callbacks.h>
#include <interface.h>
#include <support.h>

struct nd_plugin
{
  const char *(*name) (void);
  const char *(*description) (void);
  const char *(*author) (void);
  const char *(*version) (void);

  gboolean    (*init) (void);
  void        (*run) (LND_Trace *trace);

  char        *filename;
  lt_dlhandle  lt;
};

static GList *plugins = NULL;  /* list<ND_Plugin*> */

static ND_Plugin *plugin_new(char *filename);
static void       plugin_scan(const char *dirname);
static gboolean   plugin_hook_up(ND_Plugin *p);
static void       plugin_load(void);
static void       plugin_setup_gui(void);
static int        plugin_cmp(ND_Plugin *p1, char *filename);

static const char  *plugin_dummy_name(void) { return _("Unnamed plugin."); }
static const char  *plugin_dummy_description(void) { return _("No description given."); }
static const char  *plugin_dummy_author(void) { return _("No author(s) specified."); }
static const char  *plugin_dummy_version(void) { return _("No version specified."); }
static gboolean     plugin_dummy_init(void) { return TRUE; }
static void         plugin_dummy_run(LND_Trace *trace) { return; TOUCH(trace); }


static ND_Plugin *
plugin_new(char *filename)
{
  ND_Plugin *p;

  p = (ND_Plugin*) g_new0(ND_Plugin, 1);
  D_ASSERT_PTR(p);
  if (!p)
    return NULL;

  if (filename)
    p->filename = g_strdup(filename);

  /* We hook in dummy callbacks for segfault protection */
  p->name        = plugin_dummy_name;
  p->description = plugin_dummy_description;
  p->author      = plugin_dummy_author;
  p->version     = plugin_dummy_version;
  p->init        = plugin_dummy_init;
  p->run         = plugin_dummy_run;
  
  return p;
}


static void
plugin_free(ND_Plugin *plugin)
{
  if (!plugin)
    return;
  
  lt_dlclose(plugin->lt);
  g_free(plugin->filename);
  g_free(plugin);
}


static int    
plugin_cmp(ND_Plugin *p1, char *filename)
{
  if (!p1 || !filename)
    return (-1);

  return (strcmp(p1->filename, filename));
}


static void
plugin_scan(const char *dir)
{
  DIR             *dirp;
  struct dirent   *de;
  char            *dot;
  struct stat      st;
  char             path[MAXPATHLEN];

  D(("Scanning '%s' for plugins\n", dir));

  if (! (dirp = opendir(dir)))
    return;

  for (de = readdir(dirp); de; de = readdir(dirp))
    {
      g_snprintf(path, MAXPATHLEN, "%s/%s", dir, de->d_name);

      if (stat(path, &st) != 0)
	{
	  D(("Could not stat %s\n", path));
	  continue;
	}

      if (! S_ISREG(st.st_mode))
	{
	  D(("Skipping %s, not a regular file.\n", path));
	  continue;
	}

      /* Chop off any file extension, if found. libltdl takes
       * care of that.
       */
      if ( (dot = strrchr(path, '.')))
	*dot = '\0';

      if (!g_list_find_custom(plugins, path, (GCompareFunc)plugin_cmp))
	plugins = g_list_append(plugins, plugin_new(path));
    }

  closedir(dirp);
}


static gboolean
plugin_hook_up(ND_Plugin *plugin)
{
  lt_ptr_t     sym;

  if ((sym = lt_dlsym(plugin->lt, "name")) != NULL)
    plugin->name = sym;
  if ((sym = lt_dlsym(plugin->lt, "description")) != NULL)
    plugin->description = sym;
  if ((sym = lt_dlsym(plugin->lt, "author")) != NULL)
    plugin->author = sym;
  if ((sym = lt_dlsym(plugin->lt, "version")) != NULL)
    plugin->version = sym;

  if ((sym = lt_dlsym(plugin->lt, "init")) != NULL)
    plugin->init = sym;
  if ((sym = lt_dlsym(plugin->lt, "run")) != NULL)
    plugin->run = sym;

  return plugin->init();
}


static gint
plugin_strcmp(gconstpointer a, gconstpointer b)
{
  ND_Plugin *p1 = (ND_Plugin *) a;
  ND_Plugin *p2 = (ND_Plugin *) b;

  return strcmp(p1->name(), p2->name());
}

static void       
plugin_load(void)
{
  GList       *l, *l2;
  ND_Plugin   *plugin;
  char         s[MAXPATHLEN];

  for (l = g_list_first(plugins); l; l = g_list_next(l))
    {
      plugin = (ND_Plugin*)l->data;

      if (! (plugin->lt = lt_dlopenext(plugin->filename)) ||
	  ! plugin_hook_up(plugin))
	{
	  if (! nd_runtime_options.show_plugins)
	    {
	      g_snprintf(s, MAXPATHLEN, 
			 _("Unable to load plugin '%s'.\n"
			   "If this plugin depends on other plugins,\n"
			   "make sure you have those installed."),
			 (plugin->name != plugin_dummy_name ? plugin->name() : plugin->filename));
	      nd_dialog_message(_("Plugin error"), s, FALSE);
	    }

	  l->data = NULL;
	  plugin_free(plugin);
	  continue;
	}

      D(("Hooked up protocol %s\n", plugin->name()));
    }

  /* We may have deleted some plugins above that failed to initialize.
   * Clear out those list items in a separate iteration.
   */
  for (l = g_list_first(plugins); l; )
    {
      if (! l->data)
	{
	  l2 = l;
	  l = g_list_next(l);
	  plugins = g_list_remove_link(plugins, l2);
	  g_list_free_1(l2);
	}
      else
	{
	  l = g_list_next(l);
	}
    }

  /* Now sort the list of plugins alphanumerically,
   * so that we automatically get sorted menus etc.
   */
  plugins = g_list_sort(plugins, plugin_strcmp);
}


static void       
plugin_setup_gui(void)
{
  GtkItemFactory      *item_factory;
  GtkItemFactoryEntry *entries;
  GtkWidget           *plugins_menu, *menu;
  GList               *l;
  ND_Plugin           *plugin;
  int                  i, num_entries;

  /* FIXME -- it seems to me that this code is leaking two GtkItemFactories
   * per execution. However I don't know how I properly clean these up,
   * help appreciated.
   */

  num_entries = g_list_length(plugins);

  if (num_entries == 0)
    return;

  item_factory = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", NULL);
  entries = g_new0(GtkItemFactoryEntry, num_entries + 2);
  
  for (i = 0, l = g_list_first(plugins); l; i++, l = g_list_next(l))
    {
      plugin = (ND_Plugin*)l->data;

      if (plugin->run == plugin_dummy_run)
	continue;

      entries[i].path = (gchar*) plugin->name();
      entries[i].callback = on_plugin_activate;
      gtk_item_factory_create_item(item_factory, &entries[i], plugin, 1);
    }

  /* For the plugin menu, we need two more entries, a separator and
   * another button that lets us reload the plugins.
   */
  entries[num_entries].path = "/sep1";
  entries[num_entries].item_type = "<Separator>";
  gtk_item_factory_create_item(item_factory, &entries[num_entries], NULL, 1);

  entries[num_entries+1].path = _("/Reload plugins");
  entries[num_entries+1].callback = on_reload_plugins_activate;
  gtk_item_factory_create_item(item_factory, &entries[num_entries+1], NULL, 1);

  ND_GTK_GET(menu, nd_toplevel_window, "plugins");

  /* First clean up old one, if any */
  gtk_menu_item_remove_submenu(GTK_MENU_ITEM(menu));
  plugins_menu = gtk_item_factory_get_widget (item_factory, "<main>");
  gtk_menu_item_set_submenu (GTK_MENU_ITEM(menu), plugins_menu);
  g_free(entries);




  /* Hook plugin names into "About Plugins" menu ... */

  item_factory = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", NULL);
  entries = g_new0(GtkItemFactoryEntry, num_entries);
  
  for (i = 0, l = g_list_first(plugins); l; i++, l = g_list_next(l))
    {
      plugin = (ND_Plugin*)l->data;

      entries[i].path = (gchar*) plugin->name();
      entries[i].callback = on_about_plugin_activate;
      gtk_item_factory_create_item(item_factory, &entries[i], plugin, 1);
    }

  ND_GTK_GET(menu, nd_toplevel_window, "about_plugins");
  
  /* First clean up old one, if any */
  gtk_menu_item_remove_submenu(GTK_MENU_ITEM(menu));
  plugins_menu = gtk_item_factory_get_widget (item_factory, "<main>");
  gtk_menu_item_set_submenu (GTK_MENU_ITEM(menu), plugins_menu);
  g_free(entries);
}


static void
plugin_unload(void)
{
  GList     *l;
  ND_Plugin *pl;

  if (!plugins)
    return;

  for (l = plugins; l; l = g_list_next(l))
    {
      pl = (ND_Plugin *) l->data;

      plugin_free(pl);
      l->data = NULL;
    }

  g_list_free(plugins);
  plugins = NULL;
}


void 
nd_plugin_init(void)
{
  static gboolean deja_vu = FALSE;

  D_ENTER;

  if (deja_vu)
    plugin_unload();

  if (lt_dlinit() != 0)
    {
      fprintf(stderr, "Could not initialize libltdl -- exiting.\n");
      exit(-1);
    }

  plugin_scan(nd_prefs_get_plugin_dir_user());
  plugin_scan(nd_prefs_get_plugin_dir_global());
  plugin_load();
  plugin_setup_gui();

  if (deja_vu)
    {
      char msg[MAXPATHLEN];
      int  num_plugins = g_list_length(plugins);

      if (num_plugins != 1)
	g_snprintf(msg, MAXPATHLEN, _("%i plugins (re)loaded."), num_plugins);
      else
	g_snprintf(msg, MAXPATHLEN, _("%i plugin (re)loaded."), num_plugins);

      nd_gui_statusbar_set(msg);
    }

  deja_vu = TRUE;
  D_RETURN;
}


void    
nd_plugin_run(ND_Plugin *plugin)
{
  LND_Trace *trace;

  trace = nd_trace_registry_get_current();

  plugin->run(trace);
}


const char   *
nd_plugin_get_name(ND_Plugin *plugin)
{
  if (!plugin)
    return (NULL);

  return plugin->name();
}


const char   *
nd_plugin_get_description(ND_Plugin *plugin)
{
  if (!plugin)
    return (NULL);

  return plugin->description();
}


const char   *
nd_plugin_get_author(ND_Plugin *plugin)
{
  if (!plugin)
    return (NULL);

  return plugin->author();
}


const char   *
nd_plugin_get_version(ND_Plugin *plugin)
{
  if (!plugin)
    return (NULL);

  return plugin->version();
}


void    
nd_plugin_show_about(ND_Plugin *plugin)
{
  char      *shortname;
  char       s[MAXPATHLEN];
  GtkWidget *w;
  GtkWidget *label;

  D_ASSERT_PTR(plugin);
  if (!plugin)
    return;

  w = create_plugin_about_dialog();

  if ( (shortname = strrchr(plugin->name(), '/')))
    shortname++;
  else
    {
      shortname = (gchar*) plugin->name();
      D(("Warning -- plugin name '%s' doesn't contain slashes.\n", plugin->name()));
    }

  g_snprintf(s, MAXPATHLEN, "%s, %s.", shortname, plugin->version());
  ND_GTK_GET(label, w, "plugin_name_label");
  gtk_label_set_text(GTK_LABEL(label), s);
  
  g_snprintf(s, MAXPATHLEN, _("Author: %s"), plugin->author());
  ND_GTK_GET(label, w, "plugin_author_label");
  gtk_label_set_text(GTK_LABEL(label), s);

  ND_GTK_GET(label, w, "plugin_description_label");
  gtk_label_set_text(GTK_LABEL(label), plugin->description());
  
  gtk_widget_show(w);  
}


void           
nd_plugin_foreach(ND_PluginFunc callback,
		  void *user_data)
{
  GList *l;

  if (!callback)
    return;
  
  for (l = g_list_first(plugins); l; l = g_list_next(l))
    callback((ND_Plugin *)l->data, user_data);
}



syntax highlighted by Code2HTML, v. 0.9.1