/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpmoduleinfo.c
 * (C) 1999 Austin Donnelly <austin@gimp.org>
 *
 * 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 of the License, 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 "config.h"

#include <string.h>

#include <glib-object.h>

#include "libgimp/gimpmodule.h"

#include "core-types.h"

#include "gimpmoduleinfo.h"

#include "libgimp/gimpintl.h"


enum
{
  MODIFIED,
  LAST_SIGNAL
};


static void   gimp_module_info_class_init (GimpModuleInfoObjClass *klass);
static void   gimp_module_info_init       (GimpModuleInfoObj      *mod);

static void   gimp_module_info_finalize   (GObject                *object);


static guint module_info_signals[LAST_SIGNAL];

static GimpObjectClass *parent_class = NULL;


GType
gimp_module_info_get_type (void)
{
  static GType module_info_type = 0;

  if (! module_info_type)
    {
      static const GTypeInfo module_info_info =
      {
        sizeof (GimpModuleInfoObjClass),
        NULL,           /* base_init */
        NULL,           /* base_finalize */
        (GClassInitFunc) gimp_module_info_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (GimpModuleInfoObj),
        0,              /* n_preallocs */
        (GInstanceInitFunc) gimp_module_info_init,
      };

      module_info_type = g_type_register_static (GIMP_TYPE_OBJECT,
						 "GimpModuleInfoObj",
						 &module_info_info, 0);
    }

  return module_info_type;
}

static void
gimp_module_info_class_init (GimpModuleInfoObjClass *klass)
{
  GObjectClass *object_class;

  object_class = G_OBJECT_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  module_info_signals[MODIFIED] =
    g_signal_new ("modified",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpModuleInfoObjClass, modified),
		  NULL, NULL,
		  g_cclosure_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  object_class->finalize = gimp_module_info_finalize;

  klass->modified        = NULL;
}

static void
gimp_module_info_init (GimpModuleInfoObj *module_info)
{
  module_info->fullpath          = NULL;
  module_info->state             = GIMP_MODULE_STATE_ERROR;
  module_info->on_disk           = FALSE;
  module_info->load_inhibit      = FALSE;
  module_info->refs              = 0;

  module_info->info              = NULL;
  module_info->module            = NULL;
  module_info->last_module_error = NULL;
  module_info->init              = NULL;
  module_info->unload            = NULL;
}

static void
gimp_module_info_finalize (GObject *object)
{
  GimpModuleInfoObj *mod;

  mod = GIMP_MODULE_INFO (object);

  /* if this trips, then we're onto some serious lossage in a moment */
  g_return_if_fail (mod->refs == 0);

  if (mod->last_module_error)
    {
      g_free (mod->last_module_error);
      mod->last_module_error = NULL;
    }

  if (mod->fullpath)
    {
      g_free (mod->fullpath);
      mod->fullpath = NULL;
    }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

GimpModuleInfoObj *
gimp_module_info_new (const gchar *filename)
{
  GimpModuleInfoObj *module_info;

  g_return_val_if_fail (filename != NULL, NULL);

  module_info = g_object_new (GIMP_TYPE_MODULE_INFO, NULL);

  module_info->fullpath = g_strdup (filename);
  module_info->on_disk  = TRUE;

  return module_info;
}

void
gimp_module_info_modified (GimpModuleInfoObj *module_info)
{
  g_return_if_fail (GIMP_IS_MODULE_INFO (module_info));

  g_signal_emit (G_OBJECT (module_info), module_info_signals[MODIFIED], 0);
}

void
gimp_module_info_set_load_inhibit (GimpModuleInfoObj *module_info,
                                   const gchar       *inhibit_list)
{
  gchar       *p;
  gint         pathlen;
  const gchar *start;
  const gchar *end;

  g_return_if_fail (GIMP_IS_MODULE_INFO (module_info));
  g_return_if_fail (module_info->fullpath != NULL);

  module_info->load_inhibit = FALSE;

  if (! inhibit_list || ! strlen (inhibit_list))
    return;

  p = strstr (inhibit_list, module_info->fullpath);
  if (!p)
    return;

  /* we have a substring, but check for colons either side */
  start = p;
  while (start != inhibit_list && *start != G_SEARCHPATH_SEPARATOR)
    start--;

  if (*start == G_SEARCHPATH_SEPARATOR)
    start++;

  end = strchr (p, G_SEARCHPATH_SEPARATOR);
  if (! end)
    end = inhibit_list + strlen (inhibit_list);

  pathlen = strlen (module_info->fullpath);

  if ((end - start) == pathlen)
    module_info->load_inhibit = TRUE;
}

static void
gimp_module_info_set_last_error (GimpModuleInfoObj *module_info,
                                 const gchar       *error_str)
{
  if (module_info->last_module_error)
    g_free (module_info->last_module_error);

  module_info->last_module_error = g_strdup (error_str);
}


/*
 * FIXME: currently this will fail badly on EMX
 */
#ifdef __EMX__
extern void gimp_color_selector_register   (void);
extern void gimp_color_selector_unregister (void);
extern void dialog_register                (void);
extern void dialog_unregister              (void);

static struct main_funcs_struc
{
  gchar *name;
  void (* func) (void);
} 

gimp_main_funcs[] =
{
  { "gimp_color_selector_register",   gimp_color_selector_register },
  { "gimp_color_selector_unregister", gimp_color_selector_unregister },
  { "dialog_register",                dialog_register },
  { "dialog_unregister",              dialog_unregister },
  { NULL, NULL }
};
#endif

void
gimp_module_info_module_load (GimpModuleInfoObj *module_info, 
                              gboolean           verbose)
{
  gpointer symbol;

  g_return_if_fail (GIMP_IS_MODULE_INFO (module_info));
  g_return_if_fail (module_info->fullpath != NULL);
  g_return_if_fail (module_info->module == NULL);

  module_info->module = g_module_open (module_info->fullpath,
                                       G_MODULE_BIND_LAZY);

  if (! module_info->module)
    {
      module_info->state = GIMP_MODULE_STATE_ERROR;

      gimp_module_info_set_last_error (module_info, g_module_error ());

      if (verbose)
	g_warning (_("module load error: %s: %s"),
		   module_info->fullpath, module_info->last_module_error);
      return;
    }

#ifdef __EMX__
  if (g_module_symbol (module_info->module, "gimp_main_funcs", &symbol))
    {
      *(struct main_funcs_struc **) symbol = gimp_main_funcs;
    }
#endif

  /* find the module_init symbol */
  if (! g_module_symbol (module_info->module, "module_init", &symbol))
    {
      module_info->state = GIMP_MODULE_STATE_ERROR;

      gimp_module_info_set_last_error (module_info,
                                       "missing module_init() symbol");

      if (verbose)
	g_warning ("%s: module_init() symbol not found",
                   module_info->fullpath);

      g_module_close (module_info->module);
      module_info->module = NULL;
      return;
    }

  module_info->init = symbol;

  /* loaded modules are assumed to have a ref of 1 */
  gimp_module_info_module_ref (module_info);

  /* run module's initialisation */
  if (module_info->init (&module_info->info) == GIMP_MODULE_UNLOAD)
    {
      module_info->state = GIMP_MODULE_STATE_LOAD_FAILED;
      gimp_module_info_module_unref (module_info);
      module_info->info = NULL;
      return;
    }

  /* module is now happy */
  module_info->state = GIMP_MODULE_STATE_LOADED_OK;

  /* do we have an unload function? */
  if (g_module_symbol (module_info->module, "module_unload", &symbol))
    {
      module_info->unload = symbol;
    }
}

static void
gimp_module_info_module_unload_completed_callback (gpointer data)
{
  GimpModuleInfoObj *module_info;

  module_info = (GimpModuleInfoObj *) data;

  g_return_if_fail (module_info->state == GIMP_MODULE_STATE_UNLOAD_REQUESTED);

  /* lose the ref we gave this module when we loaded it,
   * since the module's now happy to be unloaded.
   */
  gimp_module_info_module_unref (module_info);

  module_info->info  = NULL;
  module_info->state = GIMP_MODULE_STATE_UNLOADED_OK;

  gimp_module_info_modified (module_info);
}

static gboolean
gimp_module_info_module_idle_unref (gpointer data)
{
  GimpModuleInfoObj *module_info;

  module_info = (GimpModuleInfoObj *) data;

  gimp_module_info_module_unref (module_info);

  return FALSE;
}

void
gimp_module_info_module_unload (GimpModuleInfoObj *module_info,
                                gboolean           verbose)
{
  g_return_if_fail (GIMP_IS_MODULE_INFO (module_info));
  g_return_if_fail (module_info->module != NULL);
  g_return_if_fail (module_info->unload != NULL);

  if (module_info->state == GIMP_MODULE_STATE_UNLOAD_REQUESTED)
    return;

  module_info->state = GIMP_MODULE_STATE_UNLOAD_REQUESTED;

  /* Send the unload request.  Need to ref the module so we don't
   * accidentally unload it while this call is in progress (eg if the
   * callback is called before the unload function returns).
   */
  gimp_module_info_module_ref (module_info);

  module_info->unload (module_info->info->shutdown_data,
                       gimp_module_info_module_unload_completed_callback,
                       module_info);

  g_idle_add (gimp_module_info_module_idle_unref, module_info);
}

void
gimp_module_info_module_ref (GimpModuleInfoObj *module_info)
{
  g_return_if_fail (GIMP_IS_MODULE_INFO (module_info));
  g_return_if_fail (module_info->refs >= 0);
  g_return_if_fail (module_info->module != NULL);

  module_info->refs++;
}

void
gimp_module_info_module_unref (GimpModuleInfoObj *module_info)
{
  g_return_if_fail (GIMP_IS_MODULE_INFO (module_info));
  g_return_if_fail (module_info->refs > 0);
  g_return_if_fail (module_info->module != NULL);

  module_info->refs--;

  if (module_info->refs == 0)
    {
      g_module_close (module_info->module);
      module_info->module = NULL;
    }
}
