#include <gtk/gtk.h>
#include <libgnomevfs/gnome-vfs-init.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <libgnomevfs/gnome-vfs-directory.h>
#include <string.h>
#include <libgnome/gnome-desktop-item.h>
#include "gnome-theme-info.h"

#define THEME_NAME "X-GNOME-Metatheme/Name"
#define THEME_COMMENT "X-GNOME-Metatheme/Comment"
#define GTK_THEME_KEY "X-GNOME-Metatheme/GtkTheme"
#define METACITY_THEME_KEY "X-GNOME-Metatheme/MetacityTheme"
#define SAWFISH_THEME_KEY "X-GNOME-Metatheme/SawfishTheme"
#define ICON_THEME_KEY "X-GNOME-Metatheme/IconTheme"
#define SOUND_THEME_KEY "X-GNOME-Metatheme/SoundTheme"
#define APPLICATION_FONT_KEY "X-GNOME-Metatheme/ApplicationFont"
#define BACKGROUND_IMAGE_KEY "X-GNOME-Metatheme/BackgroundImage"

/* Terminology used in this lib:
 *
 * /usr/share/themes, ~/.themes   -- top_theme_dir
 * top_theme_dir/theme_name/      -- common_theme_dir
 * /usr/share/icons, ~/.icons     -- top_icon_theme_dir
 * top_icon_theme_dir/theme_name/ -- icon_common_theme_dir
 *
 */

typedef struct _ThemeCallbackData
{
  GFunc func;
  gpointer data;
} ThemeCallbackData;

typedef struct {
  GnomeVFSMonitorHandle *common_theme_dir_handle;
  GnomeVFSMonitorHandle *gtk2_dir_handle;
  GnomeVFSMonitorHandle *keybinding_dir_handle;
  GnomeVFSMonitorHandle *metacity_dir_handle;
  gint priority;
} CommonThemeDirMonitorData;

typedef struct {
  GnomeVFSMonitorHandle *common_icon_theme_dir_handle;
  gint priority;
} CommonIconThemeDirMonitorData;

typedef struct {
  GHashTable *handle_hash;
  gint priority;
} CallbackTuple;
  

/* Hash tables */

/* The hashes_by_dir are indexed by an escaped uri of the common_theme_dir that
 * that particular theme is part of.  The data pointed to by them is a
 * GnomeTheme{Meta,Icon,}Info struct.  Note that the uri is of the form
 * "file:///home/username/.themes/foo", and not "/home/username/.themes/foo"
 */

/* The hashes_by_name are hashed by the index of the theme.  The data pointed to
 * by them is a GList whose data elements are GnomeTheme{Meta,Icon,}Info
 * structs.  This is because a theme can be found both in the users ~/.theme as
 * well as globally in $prefix.  All access to them must be done via helper
 * functions.
 */
static GList *callbacks = NULL;

static GHashTable *meta_theme_hash_by_uri;
static GHashTable *meta_theme_hash_by_name;
static GHashTable *icon_theme_hash_by_uri;
static GHashTable *icon_theme_hash_by_name;
static GHashTable *theme_hash_by_uri;
static GHashTable *theme_hash_by_name;
static gboolean initting = FALSE;

/* prototypes */
static gint                safe_strcmp                          (gchar                          *a_str,
								 gchar                          *b_str);
static gint                get_priority_from_data_by_hash       (GHashTable                     *hash_table,
								 gpointer                        data);
static void                add_data_to_hash_by_name             (GHashTable                     *hash_table,
								 gchar                          *name,
								 gpointer                        data);
static void                remove_data_from_hash_by_name        (GHashTable                     *hash_table,
								 const gchar                    *name,
								 gpointer                        data);
static gpointer            get_data_from_hash_by_name           (GHashTable                     *hash_table,
								 const gchar                    *name,
								 gint                            priority);

static GnomeThemeIconInfo *read_icon_theme                      (GnomeVFSURI                    *icon_theme_uri);
static void                handle_change_signal                 (GnomeThemeType                  type,
								 gpointer                        theme,
								 GnomeThemeChangeType            change_type,
								 GnomeThemeElement               element);
static void                update_theme_index                   (GnomeVFSURI                    *index_uri,
								 GnomeThemeElement               key_element,
								 gint                            priority);
static void                update_gtk2_index                    (GnomeVFSURI                    *gtk2_index_uri,
								 gint                            priority);
static void                update_keybinding_index              (GnomeVFSURI                    *keybinding_index_uri,
								 gint                            priority);
static void                update_metacity_index                (GnomeVFSURI                    *metacity_index_uri,
								 gint                            priority);
static void                update_common_theme_dir_index        (GnomeVFSURI                    *theme_index_uri,
								 gboolean                        icon_theme,
								 gint                            priority);
static void                update_meta_theme_index              (GnomeVFSURI                    *meta_theme_index_uri,
								 gint                            priority);
static void                update_icon_theme_index              (GnomeVFSURI                    *icon_theme_index_uri,
								 gint                            priority);
static void                gtk2_dir_changed                     (GnomeVFSMonitorHandle          *handle,
								 const gchar                    *monitor_uri,
								 const gchar                    *info_uri,
								 GnomeVFSMonitorEventType        event_type,
								 gpointer                        user_data);
static void                keybinding_dir_changed               (GnomeVFSMonitorHandle          *handle,
								 const gchar                    *monitor_uri,
								 const gchar                    *info_uri,
								 GnomeVFSMonitorEventType        event_type,
								 gpointer                        user_data);
static void                metacity_dir_changed                 (GnomeVFSMonitorHandle          *handle,
								 const gchar                    *monitor_uri,
								 const gchar                    *info_uri,
								 GnomeVFSMonitorEventType        event_type,
								 gpointer                        user_data);
static void                common_theme_dir_changed             (GnomeVFSMonitorHandle          *handle,
								 const gchar                    *monitor_uri,
								 const gchar                    *info_uri,
								 GnomeVFSMonitorEventType        event_type,
								 gpointer                        user_data);
static void                common_icon_theme_dir_changed        (GnomeVFSMonitorHandle          *handle,
								 const gchar                    *monitor_uri,
								 const gchar                    *info_uri,
								 GnomeVFSMonitorEventType        event_type,
								 gpointer                        user_data);
static void                top_theme_dir_changed                (GnomeVFSMonitorHandle          *handle,
								 const gchar                    *monitor_uri,
								 const gchar                    *info_uri,
								 GnomeVFSMonitorEventType        event_type,
								 gpointer                        user_data);
static void                top_icon_theme_dir_changed           (GnomeVFSMonitorHandle          *handle,
								 const gchar                    *monitor_uri,
								 const gchar                    *info_uri,
								 GnomeVFSMonitorEventType        event_type,
								 gpointer                        user_data);
static GnomeVFSResult      add_common_theme_dir_monitor         (GnomeVFSURI                    *theme_dir_uri,
								 gboolean                       *monitor_not_added,
								 CommonThemeDirMonitorData      *monitor_data,
								 GError                        **error);
static GnomeVFSResult      add_common_icon_theme_dir_monitor    (GnomeVFSURI                    *theme_dir_uri,
								 gboolean                       *monitor_not_added,
								 CommonIconThemeDirMonitorData  *monitor_data,
								 GError                        **error);
static void                remove_common_theme_dir_monitor      (CommonThemeDirMonitorData      *monitor_data);
static void                remove_common_icon_theme_dir_monitor (CommonIconThemeDirMonitorData  *monitor_data);
static GnomeVFSResult      real_add_top_theme_dir_monitor       (GnomeVFSURI                    *uri,
								 gboolean                       *monitor_not_added,
								 gint                            priority,
								 gboolean                        icon_theme,
								 GError                        **error);
static GnomeVFSResult      add_top_theme_dir_monitor            (GnomeVFSURI                    *uri,
								 gboolean                       *monitor_not_added,
								 gint                            priority,
								 GError                        **error);
static GnomeVFSResult      add_top_icon_theme_dir_monitor       (GnomeVFSURI                    *uri,
								 gboolean                       *monitor_not_added,
								 gint                            priority,
								 GError                        **error);

/* private functions */
static gint
safe_strcmp (gchar *a_str,
	     gchar *b_str)
{
  if (a_str == NULL && b_str != NULL)
    return -1;
  if (a_str != NULL && b_str == NULL)
    return 1;
  if (a_str == NULL && b_str == NULL)
    return 0;
  return strcmp (a_str, b_str);
}

static gint
get_priority_from_data_by_hash (GHashTable *hash_table,
				gpointer    data)
{
  gint theme_priority = 0;
  if (hash_table == meta_theme_hash_by_name)
    theme_priority = ((GnomeThemeMetaInfo *)data)->priority;
  else if (hash_table == icon_theme_hash_by_name)
    theme_priority = ((GnomeThemeIconInfo *)data)->priority;
  else if (hash_table == theme_hash_by_name)
    theme_priority = ((GnomeThemeInfo *)data)->priority;
  else
    g_assert_not_reached ();

  return theme_priority;
}
     

static void
add_data_to_hash_by_name (GHashTable *hash_table,
			  gchar      *name,
			  gpointer    data)
{
  GList *list;

  list = g_hash_table_lookup (hash_table, name);
  if (list == NULL)
    {
      list = g_list_append (list, data);
    }
  else
    {
      GList *list_ptr = list;
      gboolean added = FALSE;
      gint priority;

      priority = get_priority_from_data_by_hash (hash_table, data);
      while (list_ptr)
	{
	  gint theme_priority;

	  theme_priority = get_priority_from_data_by_hash (hash_table, list_ptr->data);

	  if (theme_priority == priority)
	    {
	      /* Swap it in */
	      list_ptr->data = data;
	      added = TRUE;
	      break;
	    }
	  if (theme_priority > priority)
	    {
	      list = g_list_insert_before (list, list_ptr, data);
	      added = TRUE;
	      break;
	    }
	  list_ptr = list_ptr->next;
	}
      if (! added)
	list = g_list_append (list, data);
    }
  g_hash_table_insert (hash_table, g_strdup (name), list);
}

static void
remove_data_from_hash_by_name (GHashTable  *hash_table,
			       const gchar *name,
			       gpointer     data)
{
  GList *list;

  list = g_hash_table_lookup (hash_table, name);

  list = g_list_remove (list, data);
  if (list == NULL)
    g_hash_table_remove (hash_table, name);
  else
    g_hash_table_insert (hash_table, g_strdup (name), list);
    
}

static gpointer
get_data_from_hash_by_name (GHashTable  *hash_table,
			    const gchar *name,
			    gint         priority)
{
  GList *list;

  list = g_hash_table_lookup (hash_table, name);

  /* -1 implies return the first one */
  if (priority == -1)
    {
      if (list)
	return list->data;

      return NULL;
    }
  while (list)
    {
      gint theme_priority ;

      theme_priority = get_priority_from_data_by_hash (hash_table, list->data);

      if (theme_priority == priority)
	return list->data;

      list = list->next;
    }
  return NULL;
}  
  
GnomeThemeMetaInfo *
gnome_theme_read_meta_theme (GnomeVFSURI *meta_theme_uri)
{
  GnomeThemeMetaInfo *meta_theme_info;
  GnomeVFSURI *common_theme_dir_uri;
  GnomeDesktopItem *meta_theme_ditem;
  gchar *meta_theme_file;
  const gchar *str;

  meta_theme_file = gnome_vfs_uri_to_string (meta_theme_uri, GNOME_VFS_URI_HIDE_NONE);
  meta_theme_ditem = gnome_desktop_item_new_from_uri (meta_theme_file, 0, NULL);
  if (meta_theme_ditem == NULL)
    {
      g_free (meta_theme_file);
      return NULL;
    }
  common_theme_dir_uri = gnome_vfs_uri_get_parent (meta_theme_uri);
  
  meta_theme_info = gnome_theme_meta_info_new ();
  meta_theme_info->path = meta_theme_file;
  meta_theme_info->name = gnome_vfs_uri_extract_short_name (common_theme_dir_uri);
  gnome_vfs_uri_unref (common_theme_dir_uri);
  str = gnome_desktop_item_get_localestring (meta_theme_ditem, THEME_NAME);

  if (!str)
     {
     str = gnome_desktop_item_get_localestring (meta_theme_ditem, GNOME_DESKTOP_ITEM_NAME);
     if (!str) /* shouldn't reach */
       {
         gnome_theme_meta_info_free (meta_theme_info);
         return NULL;
       }
     }
     
  meta_theme_info->readable_name = g_strdup (str);

  str = gnome_desktop_item_get_localestring (meta_theme_ditem, THEME_COMMENT);
  if (str == NULL)
    str = gnome_desktop_item_get_localestring (meta_theme_ditem, GNOME_DESKTOP_ITEM_COMMENT);
  if (str != NULL)
    meta_theme_info->comment = g_strdup (str);

  str = gnome_desktop_item_get_string (meta_theme_ditem, GNOME_DESKTOP_ITEM_ICON);
  if (str != NULL)
    meta_theme_info->icon_file = g_strdup (str);

  str = gnome_desktop_item_get_string (meta_theme_ditem, GTK_THEME_KEY);
  if (str == NULL)
    {
      gnome_theme_meta_info_free (meta_theme_info);
      return NULL;
    }
  meta_theme_info->gtk_theme_name = g_strdup (str);

  str = gnome_desktop_item_get_string (meta_theme_ditem, METACITY_THEME_KEY);
  if (str == NULL)
    {
      gnome_theme_meta_info_free (meta_theme_info);
      return NULL;
    }
  meta_theme_info->metacity_theme_name = g_strdup (str);

  str = gnome_desktop_item_get_string (meta_theme_ditem, ICON_THEME_KEY);
  if (str == NULL)
    {
      gnome_theme_meta_info_free (meta_theme_info);
      return NULL;
    }
  meta_theme_info->icon_theme_name = g_strdup (str);

  str = gnome_desktop_item_get_string (meta_theme_ditem, APPLICATION_FONT_KEY);
  if (str != NULL)
    meta_theme_info->application_font = g_strdup (str);
    
  str = gnome_desktop_item_get_string (meta_theme_ditem, BACKGROUND_IMAGE_KEY);
  if (str != NULL)
    meta_theme_info->background_image = g_strdup (str);
  
  gnome_desktop_item_unref (meta_theme_ditem);
  
  return meta_theme_info;
}

static GnomeThemeIconInfo *
read_icon_theme (GnomeVFSURI *icon_theme_uri)
{
  GnomeThemeIconInfo *icon_theme_info;
  GnomeDesktopItem *icon_theme_ditem;
  char *icon_theme_file;
  const gchar *name;
  const gchar *hidden_theme_icon;

  icon_theme_file = gnome_vfs_uri_to_string (icon_theme_uri, GNOME_VFS_URI_HIDE_NONE);
  icon_theme_ditem = gnome_desktop_item_new_from_uri (icon_theme_file, 0, NULL);
  if (icon_theme_ditem == NULL)
    {
      g_free (icon_theme_file);
      return NULL;
    }

  name = gnome_desktop_item_get_string (icon_theme_ditem, "Icon Theme/Name");
  if (name == NULL)
    {
      gnome_desktop_item_unref (icon_theme_ditem);
      g_free (icon_theme_file);
      return NULL;
    }
  hidden_theme_icon = gnome_desktop_item_get_string (icon_theme_ditem, "Icon Theme/Hidden");

  if (hidden_theme_icon == NULL || 
      strcmp (hidden_theme_icon, "false") == 0) 
    {
      icon_theme_info = gnome_theme_icon_info_new ();
      icon_theme_info->name = g_strdup (name);
      icon_theme_info->path = icon_theme_file;
    }
  else 
    {
      gnome_desktop_item_unref (icon_theme_ditem);
      return NULL;
    }

  gnome_desktop_item_unref (icon_theme_ditem);

  return icon_theme_info;
}

static void
handle_change_signal (GnomeThemeType       type,
		      gpointer             theme,
		      GnomeThemeChangeType change_type,
		      GnomeThemeElement    element)
{
#ifdef DEBUG
  gchar *type_str = NULL;
  gchar *element_str = NULL;
#endif
  gchar *uri = NULL;
  GList *list;

  if (initting)
    return;

  if (type == GNOME_THEME_TYPE_REGULAR)
    uri = g_strdup (((GnomeThemeInfo *)theme)->path);
  else if (type == GNOME_THEME_TYPE_METATHEME)
    uri = g_strdup (((GnomeThemeMetaInfo *)theme)->path);
  else if (type == GNOME_THEME_TYPE_ICON)
    uri = g_strdup (((GnomeThemeIconInfo *)theme)->path);
  
  for (list = callbacks; list; list = list->next)
  {
    ThemeCallbackData *callback_data = list->data;
    (* callback_data->func) (uri, callback_data->data);
  }

#ifdef DEBUG
  if (change_type == GNOME_THEME_CHANGE_CREATED)
    type_str = "created";
  else if (change_type == GNOME_THEME_CHANGE_CHANGED)
    type_str = "changed";
  else if (change_type == GNOME_THEME_CHANGE_DELETED)
    type_str = "deleted";

  if (element & GNOME_THEME_GTK_2)
    element_str = "gtk-2";
  else if (element & GNOME_THEME_GTK_2_KEYBINDING)
    element_str = "keybinding";
  if (element & GNOME_THEME_METACITY)
    element_str = "metacity";
  
  if (type == GNOME_THEME_TYPE_REGULAR)
    {
      g_print ("theme \"%s\" has a theme of type %s (priority %d) has been %s\n",
	       ((GnomeThemeInfo *) theme)->name,
	       element_str,
	       ((GnomeThemeInfo *) theme)->priority,
	       type_str);
    }
  else if (type == GNOME_THEME_TYPE_METATHEME)
    {
      g_print ("meta theme \"%s\" (priority %d) has been %s\n",
	       ((GnomeThemeMetaInfo *) theme)->name,
	       ((GnomeThemeMetaInfo *) theme)->priority,
	       type_str);
    }
  else if (type == GNOME_THEME_TYPE_ICON)
    {
      g_print ("icon theme \"%s\" (priority %d) has been %s\n",
	       ((GnomeThemeIconInfo *) theme)->name,
	       ((GnomeThemeIconInfo *) theme)->priority,
	       type_str);
    }
#endif
}

/* gtk2_index_uri should point to the gtkrc file that was modified */
static void
update_theme_index (GnomeVFSURI       *index_uri,
		    GnomeThemeElement  key_element,
		    gint               priority)
{
  GnomeVFSFileInfo *file_info;
  GnomeVFSResult result;
  gboolean theme_exists;
  GnomeThemeInfo *theme_info;
  GnomeVFSURI *parent;
  GnomeVFSURI *common_theme_dir_uri;
  gchar *common_theme_dir;

  /* First, we determine the new state of the file.  We do no more
   * sophisticated a test than "files exists and is a file" */
  file_info = gnome_vfs_file_info_new ();
  result = gnome_vfs_get_file_info_uri (index_uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
  if (result == GNOME_VFS_OK && file_info->type == GNOME_VFS_FILE_TYPE_REGULAR)
    theme_exists = TRUE;
  else
    theme_exists = FALSE;
  gnome_vfs_file_info_unref (file_info);

  /* Next, we see what currently exists */
  parent = gnome_vfs_uri_get_parent (index_uri);
  common_theme_dir_uri = gnome_vfs_uri_get_parent (parent);
  common_theme_dir = gnome_vfs_uri_to_string (common_theme_dir_uri, GNOME_VFS_URI_HIDE_NONE);

  theme_info = g_hash_table_lookup (theme_hash_by_uri, common_theme_dir);
  if (theme_info == NULL)
    {
      if (theme_exists)
	{
	  theme_info = gnome_theme_info_new ();
	  theme_info->path = g_strdup (common_theme_dir);
	  theme_info->name = gnome_vfs_uri_extract_short_name (common_theme_dir_uri);
	  theme_info->priority = priority;
 	  if (key_element & GNOME_THEME_GTK_2)
	    theme_info->has_gtk = TRUE;
	  else if (key_element & GNOME_THEME_GTK_2_KEYBINDING)
	    theme_info->has_keybinding = TRUE;
	  else if (key_element & GNOME_THEME_METACITY)
	    theme_info->has_metacity = TRUE;

	  g_hash_table_insert (theme_hash_by_uri, g_strdup (common_theme_dir), theme_info);
	  add_data_to_hash_by_name (theme_hash_by_name, theme_info->name, theme_info);
	  handle_change_signal (GNOME_THEME_TYPE_REGULAR, theme_info, GNOME_THEME_CHANGE_CREATED, key_element);
	}
    }
  else
    {
      gboolean theme_used_to_exist = FALSE;
      
      if (key_element & GNOME_THEME_GTK_2)
	{
	  theme_used_to_exist = theme_info->has_gtk;
	  theme_info->has_gtk = theme_exists;
	}
      else if (key_element & GNOME_THEME_GTK_2_KEYBINDING)
	{
	  theme_used_to_exist = theme_info->has_keybinding;
	  theme_info->has_keybinding = theme_exists;
	}
      else if (key_element & GNOME_THEME_METACITY)
	{
	  theme_used_to_exist = theme_info->has_metacity;
	  theme_info->has_metacity = theme_exists;
	}

      if (!theme_info->has_metacity && !theme_info->has_keybinding && !theme_info->has_gtk)
	{
	  g_hash_table_remove (theme_hash_by_uri, common_theme_dir);
	  remove_data_from_hash_by_name (theme_hash_by_name, theme_info->name, theme_info);
	}

      if (theme_exists && theme_used_to_exist)
	{
	  handle_change_signal (GNOME_THEME_TYPE_REGULAR, theme_info, GNOME_THEME_CHANGE_CHANGED, key_element);
	}
      else if (theme_exists && !theme_used_to_exist)
	{
	  handle_change_signal (GNOME_THEME_TYPE_REGULAR, theme_info, GNOME_THEME_CHANGE_CREATED, key_element);
	}
      else if (! theme_exists && theme_used_to_exist)
	{
	  handle_change_signal (GNOME_THEME_TYPE_REGULAR, theme_info, GNOME_THEME_CHANGE_DELETED, key_element);
	}

      if (!theme_info->has_metacity && !theme_info->has_keybinding && !theme_info->has_gtk)
	{
	  gnome_theme_info_free (theme_info);
	}
    }

  g_free (common_theme_dir);
  gnome_vfs_uri_unref (parent);
  gnome_vfs_uri_unref (common_theme_dir_uri);
}


static void
update_gtk2_index (GnomeVFSURI *gtk2_index_uri,
		   gint         priority)
{
  update_theme_index (gtk2_index_uri, GNOME_THEME_GTK_2, priority);
}

static void
update_keybinding_index (GnomeVFSURI *keybinding_index_uri,
			 gint         priority)
{
  update_theme_index (keybinding_index_uri, GNOME_THEME_GTK_2_KEYBINDING, priority);
}

static void
update_metacity_index (GnomeVFSURI *metacity_index_uri,
		       gint         priority)
{
  update_theme_index (metacity_index_uri, GNOME_THEME_METACITY, priority);
}

static void
update_common_theme_dir_index (GnomeVFSURI *theme_index_uri,
			       gboolean     icon_theme,
			       gint         priority)
{
  GnomeVFSFileInfo *file_info;
  GnomeVFSResult result;
  gboolean theme_exists;
  gpointer theme_info;
  gpointer old_theme_info;
  GnomeVFSURI *common_theme_dir_uri;
  gchar *common_theme_dir;
  GHashTable *hash_by_uri;
  GHashTable *hash_by_name;
  gchar *name = NULL;

  if (icon_theme)
    {
      hash_by_uri = icon_theme_hash_by_uri;
      hash_by_name = icon_theme_hash_by_name;
    }
  else
    {
      hash_by_uri = meta_theme_hash_by_uri;
      hash_by_name = meta_theme_hash_by_name;
    }
  /* First, we determine the new state of the file. */
  file_info = gnome_vfs_file_info_new ();
  result = gnome_vfs_get_file_info_uri (theme_index_uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
  if (result == GNOME_VFS_OK && file_info->type == GNOME_VFS_FILE_TYPE_REGULAR)
    {
      /* It's an interesting file.  Lets try to load it. */
      if (icon_theme)
	{
	  theme_info = read_icon_theme (theme_index_uri);
	  if (theme_info)
	    {
	      ((GnomeThemeIconInfo *) theme_info)->priority = priority;
	      theme_exists = TRUE;
	    }
	  else
	    {
	      theme_exists = FALSE;
	    }
	}
      else
	{
	  theme_info = gnome_theme_read_meta_theme (theme_index_uri);
	  if (theme_info)
	    {
	      ((GnomeThemeMetaInfo *) theme_info)->priority = priority;
	      theme_exists = TRUE;
	    }
	  else
	    {
	      theme_exists = FALSE;
	    }
	}
    }
  else
    {
      theme_info = NULL;
      theme_exists = FALSE;
    }
  gnome_vfs_file_info_unref (file_info);

  /* Next, we see what currently exists */
  common_theme_dir_uri = gnome_vfs_uri_get_parent (theme_index_uri);
  common_theme_dir = gnome_vfs_uri_to_string (common_theme_dir_uri, GNOME_VFS_URI_HIDE_NONE);

  old_theme_info = g_hash_table_lookup (hash_by_uri, common_theme_dir);

  if (theme_exists)
    {
      if (icon_theme)
	name = ((GnomeThemeIconInfo *)theme_info)->name;
      else
	name = ((GnomeThemeMetaInfo *)theme_info)->name;
    }
  if (old_theme_info == NULL)
    {
      if (theme_exists)
	{
	  g_hash_table_insert (hash_by_uri, g_strdup (common_theme_dir), theme_info);
	  add_data_to_hash_by_name (hash_by_name, name, theme_info);
	  handle_change_signal (icon_theme?GNOME_THEME_TYPE_ICON:GNOME_THEME_TYPE_METATHEME,
				theme_info, GNOME_THEME_CHANGE_CREATED, 0);
	}
    }
  else
    {
      if (theme_exists)
	{
	  gint cmp;

	  if (icon_theme)
	    cmp = gnome_theme_icon_info_compare (theme_info, old_theme_info);
	  else
	    cmp = gnome_theme_meta_info_compare (theme_info, old_theme_info);
	  if (cmp != 0)
	    {
	      g_hash_table_insert (hash_by_uri, g_strdup (common_theme_dir), theme_info);
	      add_data_to_hash_by_name (hash_by_name, name, theme_info);
	      handle_change_signal (icon_theme?GNOME_THEME_TYPE_ICON:GNOME_THEME_TYPE_METATHEME,
				    theme_info, GNOME_THEME_CHANGE_CHANGED, 0);
	      if (icon_theme)
		gnome_theme_icon_info_free (old_theme_info);
	      else
		gnome_theme_meta_info_free (old_theme_info);
	    }
	  else
	    {
	      if (icon_theme)
		gnome_theme_icon_info_free (theme_info);
	      else
		gnome_theme_meta_info_free (theme_info);
	    }
	}
      else
	{
	  if (icon_theme)
	    name = ((GnomeThemeIconInfo *)old_theme_info)->name;
	  else
	    name = ((GnomeThemeMetaInfo *)old_theme_info)->name;

	  g_hash_table_remove (hash_by_uri, common_theme_dir);
	  remove_data_from_hash_by_name (hash_by_name, name, old_theme_info);
	  handle_change_signal (icon_theme?GNOME_THEME_TYPE_ICON:GNOME_THEME_TYPE_METATHEME,
				old_theme_info, GNOME_THEME_CHANGE_DELETED, 0);
	  if (icon_theme)
	    gnome_theme_icon_info_free (old_theme_info);
	  else
	    gnome_theme_meta_info_free (old_theme_info);
	}
    }

  g_free (common_theme_dir);
  gnome_vfs_uri_unref (common_theme_dir_uri);
}

static void
update_meta_theme_index (GnomeVFSURI *meta_theme_index_uri,
			 gint         priority)
{
  update_common_theme_dir_index (meta_theme_index_uri, FALSE, priority);
}
static void
update_icon_theme_index (GnomeVFSURI *icon_theme_index_uri,
			 gint         priority)
{
  update_common_theme_dir_index (icon_theme_index_uri, TRUE, priority);
}

static void
gtk2_dir_changed (GnomeVFSMonitorHandle *handle,
		  const gchar *monitor_uri,
		  const gchar *info_uri,
		  GnomeVFSMonitorEventType event_type,
		  gpointer user_data)
{
  GnomeVFSURI *gtk2_dir_uri;
  gchar *affected_file;
  CommonThemeDirMonitorData *monitor_data;

  monitor_data = user_data;

  gtk2_dir_uri = gnome_vfs_uri_new (info_uri);
  affected_file = gnome_vfs_uri_extract_short_name (gtk2_dir_uri);

  /* The only file we care about is gtkrc */
  if (strcmp (affected_file, "gtkrc"))
    {
      g_free (affected_file);
      gnome_vfs_uri_unref (gtk2_dir_uri);
      return;
    }

  update_gtk2_index (gtk2_dir_uri, monitor_data->priority);

  g_free (affected_file);
  gnome_vfs_uri_unref (gtk2_dir_uri);
}


static void
keybinding_dir_changed (GnomeVFSMonitorHandle *handle,
			const gchar *monitor_uri,
			const gchar *info_uri,
			GnomeVFSMonitorEventType event_type,
			gpointer user_data)
{
  GnomeVFSURI *keybinding_dir_uri;
  gchar *affected_file;
  CommonThemeDirMonitorData *monitor_data;

  monitor_data = user_data;

  keybinding_dir_uri = gnome_vfs_uri_new (info_uri);
  affected_file = gnome_vfs_uri_extract_short_name (keybinding_dir_uri);

  /* The only file we care about is gtkrc */
  if (strcmp (affected_file, "gtkrc"))
    {
      g_free (affected_file);
      gnome_vfs_uri_unref (keybinding_dir_uri);
      return;
    }

  update_keybinding_index (keybinding_dir_uri, monitor_data->priority);

  g_free (affected_file);
  gnome_vfs_uri_unref (keybinding_dir_uri);
}

static void
metacity_dir_changed (GnomeVFSMonitorHandle *handle,
		      const gchar *monitor_uri,
		      const gchar *info_uri,
		      GnomeVFSMonitorEventType event_type,
		      gpointer user_data)
{
  GnomeVFSURI *metacity_dir_uri;
  gchar *affected_file;
  CommonThemeDirMonitorData *monitor_data;

  monitor_data = user_data;
  
  metacity_dir_uri = gnome_vfs_uri_new (info_uri);
  affected_file = gnome_vfs_uri_extract_short_name (metacity_dir_uri);

  /* The only file we care about is gtkrc */
  if (strcmp (affected_file, "metacity-theme-1.xml"))
    {
      g_free (affected_file);
      gnome_vfs_uri_unref (metacity_dir_uri);
      return;
    }

  update_metacity_index (metacity_dir_uri, monitor_data->priority);

  g_free (affected_file);
  gnome_vfs_uri_unref (metacity_dir_uri);
}

static void
common_theme_dir_changed (GnomeVFSMonitorHandle *handle,
			  const gchar *monitor_uri,
			  const gchar *info_uri,
			  GnomeVFSMonitorEventType event_type,
			  gpointer user_data)
{
  GnomeVFSURI *meta_theme_dir_uri;
  gchar *affected_file;
  CommonThemeDirMonitorData *monitor_data;

  monitor_data = user_data;

  meta_theme_dir_uri = gnome_vfs_uri_new (info_uri);
  affected_file = gnome_vfs_uri_extract_short_name (meta_theme_dir_uri);

  /* The only file we care about is index.theme */
  if (strcmp (affected_file, "index.theme"))
    {
      gnome_vfs_uri_unref (meta_theme_dir_uri);
      g_free (affected_file);
      return;
    }

  update_meta_theme_index (meta_theme_dir_uri, monitor_data->priority);

  g_free (affected_file);
  gnome_vfs_uri_unref (meta_theme_dir_uri);
}

static void
common_icon_theme_dir_changed (GnomeVFSMonitorHandle *handle,
			       const gchar *monitor_uri,
			       const gchar *info_uri,
			       GnomeVFSMonitorEventType event_type,
			       gpointer user_data)
{
  GnomeVFSURI *icon_theme_dir_uri;
  gchar *affected_file;
  CommonIconThemeDirMonitorData *monitor_data;

  monitor_data = user_data;

  icon_theme_dir_uri = gnome_vfs_uri_new (info_uri);
  affected_file = gnome_vfs_uri_extract_short_name (icon_theme_dir_uri);

  /* The only file we care about is index.theme*/
  if (strcmp (affected_file, "index.theme"))
    {
      gnome_vfs_uri_unref (icon_theme_dir_uri);
      g_free (affected_file);
      return;
    }
  update_icon_theme_index (icon_theme_dir_uri, monitor_data->priority);

  g_free (affected_file);
  gnome_vfs_uri_unref (icon_theme_dir_uri);
}

static void
top_theme_dir_changed (GnomeVFSMonitorHandle *handle,
		       const gchar *monitor_uri,
		       const gchar *info_uri,
		       GnomeVFSMonitorEventType event_type,
		       gpointer user_data)
{
  GnomeVFSResult result;
  CallbackTuple *tuple;
  GHashTable *handle_hash;
  CommonThemeDirMonitorData *monitor_data;
  GnomeVFSURI *common_theme_dir_uri;
  gint priority;

  common_theme_dir_uri = gnome_vfs_uri_new (info_uri);
  tuple = user_data;
  handle_hash = tuple->handle_hash;
  priority = tuple->priority;

  if (event_type == GNOME_VFS_MONITOR_EVENT_CREATED)
    {
      GnomeVFSFileInfo *file_info;

      monitor_data = g_new0 (CommonThemeDirMonitorData, 1);
      monitor_data->priority = priority;
      file_info = gnome_vfs_file_info_new ();
      result = gnome_vfs_get_file_info_uri (common_theme_dir_uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
      if (result == GNOME_VFS_OK && file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
	{
	  add_common_theme_dir_monitor (common_theme_dir_uri, NULL, monitor_data, NULL);
	  g_hash_table_insert (handle_hash, g_strdup(file_info->name), monitor_data);
	}
      gnome_vfs_file_info_unref (file_info);
    }
  else if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED)
    {
      gchar *name;

      name = gnome_vfs_uri_extract_short_name (common_theme_dir_uri);
      monitor_data = g_hash_table_lookup (handle_hash, name);
      if (monitor_data != NULL)
	{
	  remove_common_theme_dir_monitor (monitor_data);
	  g_hash_table_remove (handle_hash, name);
	  g_free (monitor_data);
	}
      g_free (name);
    }
  gnome_vfs_uri_unref (common_theme_dir_uri);
}

static void
top_icon_theme_dir_changed (GnomeVFSMonitorHandle    *handle,
			    const gchar              *monitor_uri,
			    const gchar              *info_uri,
			    GnomeVFSMonitorEventType  event_type,
			    gpointer                  user_data)
{
  GnomeVFSResult result;
  GHashTable *handle_hash;
  CallbackTuple *tuple;
  CommonIconThemeDirMonitorData *monitor_data;
  GnomeVFSURI *common_icon_theme_dir_uri;
  gint priority;

  common_icon_theme_dir_uri = gnome_vfs_uri_new (info_uri);
  tuple = user_data;
  handle_hash = tuple->handle_hash;
  priority = tuple->priority;

  if (event_type == GNOME_VFS_MONITOR_EVENT_CREATED)
    {
      GnomeVFSFileInfo *file_info;

      monitor_data = g_new0 (CommonIconThemeDirMonitorData, 1);
      monitor_data->priority = priority;
      file_info = gnome_vfs_file_info_new ();
      result = gnome_vfs_get_file_info_uri (common_icon_theme_dir_uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
      if (result == GNOME_VFS_OK && file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
	{
	  add_common_icon_theme_dir_monitor (common_icon_theme_dir_uri, NULL, monitor_data, NULL);
	  g_hash_table_insert (handle_hash, g_strdup(file_info->name), monitor_data);
	}
      gnome_vfs_file_info_unref (file_info);
    }
  else if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED)
    {
      gchar *name;

      name = gnome_vfs_uri_extract_short_name (common_icon_theme_dir_uri);
      monitor_data = g_hash_table_lookup (handle_hash, name);
      if (monitor_data != NULL)
	{
	  remove_common_icon_theme_dir_monitor (monitor_data);
	  g_hash_table_remove (handle_hash, name);
	  g_free (monitor_data);
	}
      g_free (name);
    }
  gnome_vfs_uri_unref (common_icon_theme_dir_uri);
}

/* Add a monitor to a common_theme_dir.
 */
static GnomeVFSResult
add_common_theme_dir_monitor (GnomeVFSURI                *theme_dir_uri,
			      gboolean                   *monitor_not_added,
			      CommonThemeDirMonitorData  *monitor_data,
			      GError                    **error)
{
  GnomeVFSResult result;
  gchar *uri_string;
  gboolean real_monitor_not_added = FALSE;
  GnomeVFSURI *subdir;
  GnomeVFSURI *index_uri;
  GnomeVFSFileInfo *file_info;

  index_uri = gnome_vfs_uri_append_file_name (theme_dir_uri, "index.theme");
  update_meta_theme_index (index_uri, monitor_data->priority);
  gnome_vfs_uri_unref (index_uri);

  /* Add the handle for this directory */
  uri_string = gnome_vfs_uri_to_string (theme_dir_uri, GNOME_VFS_URI_HIDE_NONE);
  result = gnome_vfs_monitor_add (& (monitor_data->common_theme_dir_handle),
				  uri_string,
				  GNOME_VFS_MONITOR_DIRECTORY,
				  common_theme_dir_changed,
				  monitor_data);
  g_free (uri_string);

  if (result == GNOME_VFS_ERROR_NOT_SUPPORTED)
    real_monitor_not_added = TRUE;
  else if (result != GNOME_VFS_OK)
    return result;

  /* gtk-2 theme subdir */
  subdir = gnome_vfs_uri_append_path (theme_dir_uri, "gtk-2.0");
  file_info = gnome_vfs_file_info_new ();
  result = gnome_vfs_get_file_info_uri (theme_dir_uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
  if (result == GNOME_VFS_OK && file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
    {
      index_uri = gnome_vfs_uri_append_file_name (subdir, "gtkrc");
      update_gtk2_index (index_uri, monitor_data->priority);
      gnome_vfs_uri_unref (index_uri);
    }
  uri_string = gnome_vfs_uri_to_string (subdir, GNOME_VFS_URI_HIDE_NONE);
  result = gnome_vfs_monitor_add (& (monitor_data->gtk2_dir_handle),
				  uri_string,
				  GNOME_VFS_MONITOR_DIRECTORY,
				  gtk2_dir_changed,
				  monitor_data);
  if (result == GNOME_VFS_ERROR_NOT_SUPPORTED)
    real_monitor_not_added = TRUE;
  g_free (uri_string);
  gnome_vfs_uri_unref (subdir);

  /* keybinding theme subdir */
  subdir = gnome_vfs_uri_append_path (theme_dir_uri, "gtk-2.0-key");
  gnome_vfs_file_info_clear (file_info);
  result = gnome_vfs_get_file_info_uri (theme_dir_uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
  if (result == GNOME_VFS_OK && file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
    {
      index_uri = gnome_vfs_uri_append_file_name (subdir, "gtkrc");
      update_keybinding_index (index_uri, monitor_data->priority);
      gnome_vfs_uri_unref (index_uri);
    }
  uri_string = gnome_vfs_uri_to_string (subdir, GNOME_VFS_URI_HIDE_NONE);
  result = gnome_vfs_monitor_add (& (monitor_data->keybinding_dir_handle),
				  uri_string,
				  GNOME_VFS_MONITOR_DIRECTORY,
				  keybinding_dir_changed,
				  monitor_data);
  if (result == GNOME_VFS_ERROR_NOT_SUPPORTED)
    real_monitor_not_added = TRUE;
  g_free (uri_string);
  gnome_vfs_uri_unref (subdir);

  /* metacity theme subdir */
  subdir = gnome_vfs_uri_append_path (theme_dir_uri, "metacity-1");
  gnome_vfs_file_info_clear (file_info);
  result = gnome_vfs_get_file_info_uri (theme_dir_uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
  if (file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
    {
      index_uri = gnome_vfs_uri_append_file_name (subdir, "metacity-theme-1.xml");
      update_metacity_index (index_uri, monitor_data->priority);
      gnome_vfs_uri_unref (index_uri);
    }
  uri_string = gnome_vfs_uri_to_string (subdir, GNOME_VFS_URI_HIDE_NONE);
  result = gnome_vfs_monitor_add (& (monitor_data->metacity_dir_handle),
				  uri_string,
				  GNOME_VFS_MONITOR_DIRECTORY,
				  metacity_dir_changed,
				  monitor_data);
  g_free (uri_string);
  if (result == GNOME_VFS_ERROR_NOT_SUPPORTED)
    real_monitor_not_added = TRUE;
  gnome_vfs_file_info_unref (file_info);
  gnome_vfs_uri_unref (subdir);

  if (monitor_not_added)
    *monitor_not_added = real_monitor_not_added;

  return GNOME_VFS_OK;
}

static GnomeVFSResult
add_common_icon_theme_dir_monitor (GnomeVFSURI                    *theme_dir_uri,
				   gboolean                       *monitor_not_added,
				   CommonIconThemeDirMonitorData  *monitor_data,
				   GError                        **error)
{
  GnomeVFSResult result;
  gchar *uri_string;
  gboolean real_monitor_not_added = FALSE;
  GnomeVFSURI *index_uri;

  /* Add the handle for this directory */
  index_uri = gnome_vfs_uri_append_file_name (theme_dir_uri, "index.theme");
  update_icon_theme_index (index_uri, monitor_data->priority);
  gnome_vfs_uri_unref (index_uri);

  uri_string = gnome_vfs_uri_to_string (theme_dir_uri, GNOME_VFS_URI_HIDE_NONE);
  result = gnome_vfs_monitor_add (& (monitor_data->common_icon_theme_dir_handle),
				  uri_string,
				  GNOME_VFS_MONITOR_DIRECTORY,
				  common_icon_theme_dir_changed,
				  monitor_data);
  g_free (uri_string);

  if (result == GNOME_VFS_ERROR_NOT_SUPPORTED)
    real_monitor_not_added = TRUE;
  else if (result != GNOME_VFS_OK)
    return result;

  if (monitor_not_added)
    *monitor_not_added = real_monitor_not_added;

  return GNOME_VFS_OK;
}

static void
remove_common_theme_dir_monitor (CommonThemeDirMonitorData *monitor_data)
{
  /* None of the possible errors here are interesting */
  gnome_vfs_monitor_cancel (monitor_data->common_theme_dir_handle);
  gnome_vfs_monitor_cancel (monitor_data->gtk2_dir_handle);
  gnome_vfs_monitor_cancel (monitor_data->keybinding_dir_handle);
  gnome_vfs_monitor_cancel (monitor_data->metacity_dir_handle);

}

static void
remove_common_icon_theme_dir_monitor (CommonIconThemeDirMonitorData *monitor_data)
{
  /* None of the possible errors here are interesting */
  gnome_vfs_monitor_cancel (monitor_data->common_icon_theme_dir_handle);
}

/* Add a monitor to a top dir.  These monitors persist for the duration of the
 * lib.
 */
static GnomeVFSResult
real_add_top_theme_dir_monitor (GnomeVFSURI  *uri,
				gboolean     *monitor_not_added,
				gint          priority,
				gboolean      icon_theme,
				GError      **error)
{
  GnomeVFSMonitorHandle *monitor_handle = NULL;
  GnomeVFSDirectoryHandle *directory_handle = NULL;
  GnomeVFSResult result;
  GnomeVFSFileInfo *file_info;
  gchar *uri_string;
  CallbackTuple *tuple;

  /* handle_hash is a hash of common_theme_dir names to their monitor_data.  We
   * use it to remove the monitor handles when a dir is removed.
   */
  tuple = g_new0 (CallbackTuple, 1);
  tuple->handle_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
  tuple->priority = priority;

  /* Check the URI */
  file_info = gnome_vfs_file_info_new ();
  gnome_vfs_get_file_info_uri (uri, file_info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
  if (file_info->type != GNOME_VFS_FILE_TYPE_DIRECTORY) {
    gnome_vfs_file_info_unref (file_info);
    return GNOME_VFS_ERROR_NOT_A_DIRECTORY;
  }
  gnome_vfs_file_info_unref (file_info);
  /* Monitor the top directory */
  uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);

  result = gnome_vfs_monitor_add (&monitor_handle,
				  uri_string,
				  GNOME_VFS_MONITOR_DIRECTORY,
				  icon_theme?top_icon_theme_dir_changed:top_theme_dir_changed,
				  tuple);

  g_free (uri_string);

  /* We can deal with NOT_SUPPORTED manually */
  if (result == GNOME_VFS_ERROR_NOT_SUPPORTED)
    *monitor_not_added = TRUE;
  else if (result != GNOME_VFS_OK)
    return result;

  /* Go through the directory to add monitoring */
  result = gnome_vfs_directory_open_from_uri (&directory_handle, uri, GNOME_VFS_FILE_INFO_DEFAULT);
  if (result != GNOME_VFS_OK)
    return result;

  file_info = gnome_vfs_file_info_new ();
  while (gnome_vfs_directory_read_next (directory_handle, file_info) == GNOME_VFS_OK)
    {
      GnomeVFSURI *theme_dir_uri;
      gpointer monitor_data;

      if (!(file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY || file_info->type == GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK)) {
	gnome_vfs_file_info_clear (file_info);
	continue;
      }
      if (file_info->name[0] == '.') {
	gnome_vfs_file_info_clear (file_info);
	continue;
      }

      /* Add the directory */
      theme_dir_uri = gnome_vfs_uri_append_path (uri, file_info->name);
      if (icon_theme)
	{
	  monitor_data = g_new0 (CommonIconThemeDirMonitorData, 1);
	  ((CommonIconThemeDirMonitorData *)monitor_data)->priority = priority;
	  add_common_icon_theme_dir_monitor (theme_dir_uri, monitor_not_added, monitor_data, error);
	}
      else
	{
	  monitor_data = g_new0 (CommonThemeDirMonitorData, 1);
	  ((CommonThemeDirMonitorData *)monitor_data)->priority = priority;
	  add_common_theme_dir_monitor (theme_dir_uri, monitor_not_added, monitor_data, error);
	}


      g_hash_table_insert (tuple->handle_hash, g_strdup(file_info->name), monitor_data);
      gnome_vfs_file_info_clear (file_info);
      gnome_vfs_uri_unref (theme_dir_uri);
    }

  gnome_vfs_file_info_unref (file_info);
  gnome_vfs_directory_close (directory_handle);
  if (result != GNOME_VFS_ERROR_EOF)
    return result;

  return GNOME_VFS_OK;
}

static GnomeVFSResult
add_top_theme_dir_monitor (GnomeVFSURI  *uri,
			   gboolean     *monitor_not_added,
			   gint          priority,
			   GError      **error)
{
  return real_add_top_theme_dir_monitor (uri, monitor_not_added, priority, FALSE, error);
}

static GnomeVFSResult
add_top_icon_theme_dir_monitor (GnomeVFSURI  *uri,
				gboolean     *monitor_not_added,
				gint          priority,
				GError      **error)
{
  return real_add_top_theme_dir_monitor (uri, monitor_not_added, priority, TRUE, error);
}

/* Public functions */


/* Generic Themes */
GnomeThemeInfo *
gnome_theme_info_new (void)
{
  GnomeThemeInfo *theme_info;

  theme_info = g_new0 (GnomeThemeInfo, 1);

  return theme_info;
}

void
gnome_theme_info_free (GnomeThemeInfo *theme_info)
{
  g_free (theme_info->path);
  g_free (theme_info->name);
  g_free (theme_info);
}

GnomeThemeInfo *
gnome_theme_info_find (const gchar *theme_name)
{
  return get_data_from_hash_by_name (theme_hash_by_name, theme_name, -1);
}


struct GnomeThemeInfoHashData
{
  gconstpointer user_data;
  GList *list;
};

static void
gnome_theme_info_find_by_type_helper (gpointer key,
				      gpointer value,
				      gpointer user_data)
{
  GList *list;
  GnomeThemeInfo *theme_info;
  struct GnomeThemeInfoHashData *hash_data = user_data;
  guint elements = GPOINTER_TO_INT (hash_data->user_data);
  gboolean add_theme = FALSE;

  list = value;
  theme_info = list->data;
  
  if (elements & GNOME_THEME_METACITY &&
      theme_info->has_metacity)
    add_theme = TRUE;
  if (elements & GNOME_THEME_GTK_2 &&
      theme_info->has_gtk)
    add_theme = TRUE;
  if (elements & GNOME_THEME_GTK_2_KEYBINDING &&
      theme_info->has_keybinding)
    add_theme = TRUE;

  if (add_theme)
    hash_data->list = g_list_prepend (hash_data->list, theme_info);
}

GList *
gnome_theme_info_find_by_type (guint elements)
{
  struct GnomeThemeInfoHashData data;
  data.user_data = GINT_TO_POINTER (elements);
  data.list = NULL;

  g_hash_table_foreach (theme_hash_by_name,
			gnome_theme_info_find_by_type_helper,
			&data);

  return data.list;
}

GnomeThemeInfo *
gnome_theme_info_find_by_uri (const gchar *theme_uri)
{
  g_return_val_if_fail (theme_uri != NULL, NULL);

  return g_hash_table_lookup (theme_hash_by_uri, theme_uri);
}


/* Icon themes */
GnomeThemeIconInfo *
gnome_theme_icon_info_new (void)
{
  GnomeThemeIconInfo *icon_theme_info;

  icon_theme_info = g_new0 (GnomeThemeIconInfo, 1);

  return icon_theme_info;
}

void
gnome_theme_icon_info_free (GnomeThemeIconInfo *icon_theme_info)
{
  g_free (icon_theme_info->name);
  g_free (icon_theme_info->path);
  g_free (icon_theme_info);
}

GnomeThemeInfo *
gnome_theme_icon_info_find (const gchar *icon_theme_name)
{
  g_return_val_if_fail (icon_theme_name != NULL, NULL);

  return get_data_from_hash_by_name (icon_theme_hash_by_name, icon_theme_name, -1);

}

static void
gnome_theme_icon_info_find_all_helper (gpointer key,
				       gpointer value,
				       gpointer user_data)
{
  GList *list = value;
  struct GnomeThemeInfoHashData *hash_data;

  list = value;
  hash_data = user_data;

  hash_data->list = g_list_prepend (hash_data->list, list->data);
}

GList *
gnome_theme_icon_info_find_all (void)
{
  
  struct GnomeThemeInfoHashData data;
  data.list = NULL;

  g_hash_table_foreach (icon_theme_hash_by_name,
			gnome_theme_icon_info_find_all_helper,
			&data);

  return data.list;
}


gint
gnome_theme_icon_info_compare (GnomeThemeIconInfo *a,
			       GnomeThemeIconInfo *b)
{
  gint cmp = 0;

  cmp = safe_strcmp (a->path, b->path);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->name, b->name);
  return cmp;
}


/* Meta themes*/
GnomeThemeMetaInfo *
gnome_theme_meta_info_new (void)
{
  GnomeThemeMetaInfo *meta_theme_info;

  meta_theme_info = g_new0 (GnomeThemeMetaInfo, 1);

  return meta_theme_info;
}

void
gnome_theme_meta_info_free (GnomeThemeMetaInfo *meta_theme_info)
{
  g_free (meta_theme_info->path);
  g_free (meta_theme_info->readable_name);
  g_free (meta_theme_info->name);
  g_free (meta_theme_info->comment);
  g_free (meta_theme_info->application_font);
  g_free (meta_theme_info->background_image);
  g_free (meta_theme_info->gtk_theme_name);
  g_free (meta_theme_info->icon_theme_name);
  g_free (meta_theme_info->metacity_theme_name);
  
  g_free (meta_theme_info);
}

void
gnome_theme_meta_info_print (GnomeThemeMetaInfo *meta_theme_info)
{
  g_print ("path: %s\n", meta_theme_info->path);
  g_print ("readable_name: %s\n", meta_theme_info->readable_name);
  g_print ("name: %s\n", meta_theme_info->name);
  g_print ("comment: %s\n", meta_theme_info->comment);
  g_print ("icon_file: %s\n", meta_theme_info->icon_file);
  g_print ("gtk_theme_name: %s\n", meta_theme_info->gtk_theme_name);
  g_print ("metacity_theme_name: %s\n", meta_theme_info->metacity_theme_name);
  g_print ("icon_theme_name: %s\n", meta_theme_info->icon_theme_name);
  g_print ("sawfish_theme_name: %s\n", meta_theme_info->sawfish_theme_name);
  g_print ("sound_theme_name: %s\n", meta_theme_info->sound_theme_name);
  g_print ("application_font: %s\n", meta_theme_info->application_font);
  g_print ("background_image: %s\n", meta_theme_info->background_image);
}

GnomeThemeMetaInfo *
gnome_theme_meta_info_find (const char *meta_theme_name)
{
  g_return_val_if_fail (meta_theme_name != NULL, NULL);

  return get_data_from_hash_by_name (meta_theme_hash_by_name, meta_theme_name, -1);
}

GnomeThemeMetaInfo *
gnome_theme_meta_info_find_by_uri (const char *theme_uri)
{
  g_return_val_if_fail (theme_uri != NULL, NULL);

  return g_hash_table_lookup (meta_theme_hash_by_uri, theme_uri);
}

static void
gnome_theme_meta_info_find_all_helper (gpointer key,
				       gpointer value,
				       gpointer user_data)
{
  GList *list = value;
  struct GnomeThemeInfoHashData *hash_data = user_data;

  hash_data->list = g_list_prepend (hash_data->list, list->data);
}

GList *
gnome_theme_meta_info_find_all (void)
{
  
  struct GnomeThemeInfoHashData data;
  data.list = NULL;

  g_hash_table_foreach (meta_theme_hash_by_name,
			gnome_theme_meta_info_find_all_helper,
			&data);

  return data.list;
}

gint
gnome_theme_meta_info_compare (GnomeThemeMetaInfo *a,
			       GnomeThemeMetaInfo *b)
{
  gint cmp = 0;

  cmp = safe_strcmp (a->path, b->path);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->readable_name, b->readable_name);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->name, b->name);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->comment, b->comment);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->icon_file, b->icon_file);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->gtk_theme_name, b->gtk_theme_name);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->metacity_theme_name, b->metacity_theme_name);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->icon_theme_name, b->icon_theme_name);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->sawfish_theme_name, b->sawfish_theme_name);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->sound_theme_name, b->sound_theme_name);
  if (cmp != 0) return cmp;
  
  cmp = safe_strcmp (a->application_font, b->application_font);
  if (cmp != 0) return cmp;
  
  cmp = safe_strcmp (a->background_image, b->background_image);
  return cmp;
}

void
gnome_theme_info_register_theme_change (GFunc    func,
					gpointer data)
{
  ThemeCallbackData *callback_data;

  g_return_if_fail (func != NULL);

  callback_data = g_new0 (ThemeCallbackData, 1);
  callback_data->func = func;
  callback_data->data = data;

  callbacks = g_list_prepend (callbacks, callback_data);
}

void
gnome_theme_init (gboolean *monitor_not_added)
{
  GnomeVFSURI *top_theme_dir_uri;
  gchar *top_theme_dir_string;
  gboolean real_monitor_not_added = FALSE;
  static gboolean initted = FALSE;
  GnomeVFSResult result;
  const gchar *gtk_data_dir;
  if (initted)
    return;

  initting = TRUE;

  meta_theme_hash_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  meta_theme_hash_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  icon_theme_hash_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  icon_theme_hash_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  theme_hash_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  theme_hash_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);


  /* Add all the toplevel theme dirs. */
  /* $datadir/themes */
  top_theme_dir_string = gtk_rc_get_theme_dir ();
  top_theme_dir_uri = gnome_vfs_uri_new (top_theme_dir_string);
  result = add_top_theme_dir_monitor (top_theme_dir_uri, &real_monitor_not_added, 1, NULL);
  g_free (top_theme_dir_string);
  gnome_vfs_uri_unref (top_theme_dir_uri);

  /* ~/.themes */
  top_theme_dir_string  = g_build_filename (g_get_home_dir (), ".themes", NULL);
  top_theme_dir_uri = gnome_vfs_uri_new (top_theme_dir_string);
  g_free (top_theme_dir_string);
  if (!gnome_vfs_uri_exists (top_theme_dir_uri))
    gnome_vfs_make_directory_for_uri (top_theme_dir_uri, 0775);
  result = add_top_theme_dir_monitor (top_theme_dir_uri, &real_monitor_not_added, 0, NULL);
  gnome_vfs_uri_unref (top_theme_dir_uri);

  /* The weird /usr/share/icons */
  top_theme_dir_uri = gnome_vfs_uri_new ("/usr/share/icons");
  if (!gnome_vfs_uri_exists (top_theme_dir_uri))
    gnome_vfs_make_directory_for_uri (top_theme_dir_uri, 0775);
  result = add_top_icon_theme_dir_monitor (top_theme_dir_uri, &real_monitor_not_added, 2, NULL);
  gnome_vfs_uri_unref (top_theme_dir_uri);

  /* $datadir/icons */
  gtk_data_dir = g_getenv ("GTK_DATA_PREFIX");
  if (gtk_data_dir)
    {
      top_theme_dir_string = g_build_filename (gtk_data_dir, "share", "icons", NULL);
    }
  else
    {
      top_theme_dir_string = g_build_filename (INSTALL_PREFIX, "share", "icons", NULL);
    }
  top_theme_dir_uri = gnome_vfs_uri_new (top_theme_dir_string);
  g_free (top_theme_dir_string);

  if (!gnome_vfs_uri_exists (top_theme_dir_uri))
    gnome_vfs_make_directory_for_uri (top_theme_dir_uri, 0775);
  result = add_top_icon_theme_dir_monitor (top_theme_dir_uri, &real_monitor_not_added, 1, NULL);
  gnome_vfs_uri_unref (top_theme_dir_uri);

  /* ~/.icons */
  top_theme_dir_string  = g_build_filename (g_get_home_dir (), ".icons", NULL);
  top_theme_dir_uri = gnome_vfs_uri_new (top_theme_dir_string);
  g_free (top_theme_dir_string);

  if (!gnome_vfs_uri_exists (top_theme_dir_uri))
    gnome_vfs_make_directory_for_uri (top_theme_dir_uri, 0775);
  result = add_top_icon_theme_dir_monitor (top_theme_dir_uri, &real_monitor_not_added, 0, NULL);
  gnome_vfs_uri_unref (top_theme_dir_uri);

  /* done */
  initted = TRUE;
  initting = FALSE;

  if (monitor_not_added)
    *monitor_not_added = real_monitor_not_added;
}

#if 0
int
main (int argc, char *argv[])
{
  gtk_init (&argc, &argv);
  gnome_vfs_init ();
  gboolean monitor_not_added = FALSE;

  gnome_theme_init (&monitor_not_added);

  gtk_main ();

  return 0;
}
#endif
