/* mate-theme-info.c - MATE Theme information
 *
 * Copyright (C) 2002 Jonathan Blandford <jrb@gnome.org>
 * Copyright (C) 2011 Perberos
 * All rights reserved.
 *
 * This file is part of the Mate Library.
 *
 * The Mate Library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * The Mate 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Mate Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

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

#include <glib.h>
#include <glib/gi18n.h>
#include <gmodule.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gio/gio.h>
#include <string.h>
#include <libmate-desktop/mate-desktop-item.h>
#include "mate-theme-info.h"
#include "gtkrc-utils.h"

#include <X11/Xcursor/Xcursor.h>

#define THEME_NAME "X-GNOME-Metatheme/Name"
#define THEME_COMMENT "X-GNOME-Metatheme/Comment"
#define GTK_THEME_KEY "X-GNOME-Metatheme/GtkTheme"
#define GTK_COLOR_SCHEME_KEY "X-GNOME-Metatheme/GtkColorScheme"
#define MARCO_THEME_KEY "X-GNOME-Metatheme/MetacityTheme"
#define ICON_THEME_KEY "X-GNOME-Metatheme/IconTheme"
#define CURSOR_THEME_KEY "X-GNOME-Metatheme/CursorTheme"
#define NOTIFICATION_THEME_KEY "X-GNOME-Metatheme/NotificationTheme"
#define CURSOR_SIZE_KEY "X-GNOME-Metatheme/CursorSize"
#define APPLICATION_FONT_KEY "X-GNOME-Metatheme/ApplicationFont"
#define DOCUMENTS_FONT_KEY "X-GNOME-Metatheme/DocumentsFont"
#define DESKTOP_FONT_KEY "X-GNOME-Metatheme/DesktopFont"
#define WINDOWTITLE_FONT_KEY "X-GNOME-Metatheme/WindowTitleFont"
#define MONOSPACE_FONT_KEY "X-GNOME-Metatheme/MonospaceFont"
#define BACKGROUND_IMAGE_KEY "X-GNOME-Metatheme/BackgroundImage"
#define HIDDEN_KEY "X-GNOME-Metatheme/Hidden"

/* 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 {
  ThemeChangedCallback   func;
  gpointer               data;
} ThemeCallbackData;

typedef struct {
  GFileMonitor  *common_theme_dir_handle;
  GFileMonitor  *gtk2_dir_handle;
  GFileMonitor  *keybinding_dir_handle;
  GFileMonitor  *marco_dir_handle;
  gint           priority;
} CommonThemeDirMonitorData;

typedef struct {
  GFileMonitor  *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
 * MateTheme{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 MateTheme{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 *cursor_theme_hash_by_uri;
static GHashTable *cursor_theme_hash_by_name;
static GHashTable *theme_hash_by_uri;
static GHashTable *theme_hash_by_name;
static gboolean    initting = FALSE;

/* private functions */
static gint
safe_strcmp (const gchar *a_str,
             const gchar *b_str)
{
  if (a_str && b_str)
    return strcmp (a_str, b_str);
  else
    return a_str - b_str;
}

static GFileType
get_file_type (GFile *file)
{
  GFileType file_type = G_FILE_TYPE_UNKNOWN;
  GFileInfo *file_info;

  file_info = g_file_query_info (file,
                                 G_FILE_ATTRIBUTE_STANDARD_TYPE,
                                 G_FILE_QUERY_INFO_NONE,
                                 NULL, NULL);
  if (file_info != NULL) {
    file_type = g_file_info_get_file_type (file_info);
    g_object_unref (file_info);
  }

  return file_type;
}

static void
add_theme_to_hash_by_name (GHashTable *hash_table,
                           gpointer    data)
{
  MateThemeCommonInfo *info = data;
  GList *list;

  list = g_hash_table_lookup (hash_table, info->name);
  if (list == NULL) {
    list = g_list_append (list, info);
  } else {
    GList *list_ptr = list;
    gboolean added = FALSE;

    while (list_ptr) {
      gint theme_priority;

      theme_priority = ((MateThemeCommonInfo *) list_ptr->data)->priority;

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

static void
remove_theme_from_hash_by_name (GHashTable *hash_table,
                                gpointer    data)
{
  MateThemeCommonInfo *info = data;
  GList *list;

  list = g_hash_table_lookup (hash_table, info->name);

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

static MateThemeCommonInfo *
get_theme_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) {
    return list ? list->data : NULL;
  }

  while (list) {
    MateThemeCommonInfo *info = (MateThemeCommonInfo *) list->data;

    if (info->priority == priority)
      return info;

    list = list->next;
  }
  return NULL;
}

static gint
theme_compare (MateThemeCommonInfo *a,
               MateThemeCommonInfo *b)
{
  gint cmp;

  g_return_val_if_fail (a->type == b->type, a->type - b->type);

  switch (a->type) {
  case MATE_THEME_TYPE_METATHEME:
    cmp = mate_theme_meta_info_compare (
                    (MateThemeMetaInfo *) a, (MateThemeMetaInfo *) b);
    break;
  case MATE_THEME_TYPE_ICON:
    cmp = mate_theme_icon_info_compare (
                    (MateThemeIconInfo *) a, (MateThemeIconInfo *) b);
    break;
  case MATE_THEME_TYPE_CURSOR:
    cmp = mate_theme_cursor_info_compare (
                    (MateThemeCursorInfo *) a, (MateThemeCursorInfo *) b);
    break;
  default:
    /* not supported at this time */
    g_assert_not_reached ();
  }

  return cmp;
}

static void
theme_free (MateThemeCommonInfo *info)
{
  switch (info->type) {
  case MATE_THEME_TYPE_METATHEME:
    mate_theme_meta_info_free ((MateThemeMetaInfo *) info);
    break;
  case MATE_THEME_TYPE_ICON:
    mate_theme_icon_info_free ((MateThemeIconInfo *) info);
    break;
  case MATE_THEME_TYPE_REGULAR:
    mate_theme_info_free ((MateThemeInfo *) info);
    break;
  case MATE_THEME_TYPE_CURSOR:
    mate_theme_cursor_info_free ((MateThemeCursorInfo *) info);
    break;
  default:
    g_assert_not_reached ();
  }
}

GQuark
mate_theme_info_error_quark (void)
{
  return g_quark_from_static_string ("mate-theme-info-error-quark");
}

MateThemeMetaInfo *
mate_theme_read_meta_theme (GFile *meta_theme_uri)
{
  MateThemeMetaInfo *meta_theme_info;
  GFile             *common_theme_dir_uri;
  MateDesktopItem   *meta_theme_ditem;
  gchar             *meta_theme_file;
  const gchar       *str;
  gchar             *scheme;

  meta_theme_file = g_file_get_uri (meta_theme_uri);
  meta_theme_ditem = mate_desktop_item_new_from_uri (meta_theme_file, 0, NULL);
  g_free (meta_theme_file);

  if (meta_theme_ditem == NULL)
    return NULL;

  common_theme_dir_uri = g_file_get_parent (meta_theme_uri);

  meta_theme_info = mate_theme_meta_info_new ();

  /* meta_theme_info->path */
  meta_theme_info->path = g_file_get_path (meta_theme_uri);

  /* meta_theme_info->name */
  meta_theme_info->name = g_file_get_basename (common_theme_dir_uri);

  g_object_unref (common_theme_dir_uri);

  /* meta_theme_info->readable_name */
  str = mate_desktop_item_get_localestring (meta_theme_ditem,
                                            THEME_NAME);
  if (str == NULL) {
    str = mate_desktop_item_get_localestring (meta_theme_ditem,
                                              MATE_DESKTOP_ITEM_NAME);
    if (str == NULL) /* shouldn't reach */
      goto mate_theme_read_meta_theme_out;
  }
  meta_theme_info->readable_name = g_strdup (str);

  /* meta_theme_info->comment */
  str = mate_desktop_item_get_localestring (meta_theme_ditem,
                                            THEME_COMMENT);
  if (str == NULL)
    str = mate_desktop_item_get_localestring (meta_theme_ditem,
                                              MATE_DESKTOP_ITEM_COMMENT);
  if (str != NULL)
    meta_theme_info->comment = g_strdup (str);

  /* meta_theme_info->icon_file */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      MATE_DESKTOP_ITEM_ICON);
  if (str != NULL)
    meta_theme_info->icon_file = g_strdup(str);

  /* meta_theme_info->gtk_theme_name */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      GTK_THEME_KEY);
  if (str == NULL)
    goto mate_theme_read_meta_theme_out;
  meta_theme_info->gtk_theme_name = g_strdup (str);

  /* meta_theme_info->gtk_color_scheme */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      GTK_COLOR_SCHEME_KEY);
  if (str == NULL || str[0] == '\0')
    scheme = gtkrc_get_color_scheme_for_theme (meta_theme_info->gtk_theme_name);
  else
    scheme = g_strdup(str);

  if (scheme != NULL) {
    meta_theme_info->gtk_color_scheme = scheme;

    for (; *scheme != '\0'; scheme++)
      if (*scheme == ',')

    *scheme = '\n';
  }

  /* meta_theme_info->marco_theme_name */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      MARCO_THEME_KEY);
  if (str == NULL)
    goto mate_theme_read_meta_theme_out;
  meta_theme_info->marco_theme_name = g_strdup (str);

  /* meta_theme_info->icon_theme_name */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      ICON_THEME_KEY);
  if (str == NULL)
    goto mate_theme_read_meta_theme_out;
  meta_theme_info->icon_theme_name = g_strdup (str);

  /* meta_theme_info->notification_theme_name */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      NOTIFICATION_THEME_KEY);
  if (str != NULL)
     meta_theme_info->notification_theme_name = g_strdup (str);

  /* meta_theme_info->cursor_theme_name
   * meta_theme_info->cursor_size */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      CURSOR_THEME_KEY);
  if (str != NULL) {
    meta_theme_info->cursor_theme_name = g_strdup (str);

    str = mate_desktop_item_get_string (meta_theme_ditem,
                                        CURSOR_SIZE_KEY);
    if (str != NULL)
      meta_theme_info->cursor_size = (int) g_ascii_strtoll (str, NULL, 10);
    else
      meta_theme_info->cursor_size = 24;
  } else {
    meta_theme_info->cursor_theme_name = g_strdup ("default");
    meta_theme_info->cursor_size = 24;
  }

  /* meta_theme_info->application_font */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      APPLICATION_FONT_KEY);
  if (str != NULL)
    meta_theme_info->application_font = g_strdup (str);

  /* meta_theme_info->documents_font */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      DOCUMENTS_FONT_KEY);
  if (str != NULL)
    meta_theme_info->documents_font = g_strdup (str);

  /* meta_theme_info->desktop_font */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      DESKTOP_FONT_KEY);
  if (str != NULL)
    meta_theme_info->desktop_font = g_strdup (str);

  /* meta_theme_info->windowtitle_font */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      WINDOWTITLE_FONT_KEY);
  if (str != NULL)
    meta_theme_info->windowtitle_font = g_strdup (str);

  /* meta_theme_info->monospace_font */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      MONOSPACE_FONT_KEY);
  if (str != NULL)
    meta_theme_info->monospace_font = g_strdup (str);

  /* meta_theme_info->background_image */
  str = mate_desktop_item_get_string (meta_theme_ditem,
                                      BACKGROUND_IMAGE_KEY);
  if (str != NULL)
      meta_theme_info->background_image = g_strdup (str);

  /* meta_theme_info->hidden */
  meta_theme_info->hidden = mate_desktop_item_get_boolean (meta_theme_ditem,
                                                           HIDDEN_KEY);

  mate_desktop_item_unref (meta_theme_ditem);

  return meta_theme_info;

mate_theme_read_meta_theme_out:
  mate_theme_meta_info_free (meta_theme_info);
  return NULL;
}

static MateThemeIconInfo *
read_icon_theme (GFile *icon_theme_uri)
{
  MateThemeIconInfo *icon_theme_info;
  MateDesktopItem *icon_theme_ditem;
  gchar *icon_theme_file;
  gchar *dir_name;
  const gchar *name;
  const gchar *directories;

  icon_theme_file = g_file_get_uri (icon_theme_uri);
  icon_theme_ditem = mate_desktop_item_new_from_uri (icon_theme_file, 0, NULL);
  g_free (icon_theme_file);

  if (icon_theme_ditem == NULL)
    return NULL;

  name = mate_desktop_item_get_localestring (icon_theme_ditem,
                                             "Icon Theme/Name");
  if (!name) {
    name = mate_desktop_item_get_localestring (icon_theme_ditem,
                                               MATE_DESKTOP_ITEM_NAME);
    if (!name)
      goto read_icon_theme_out;
  }

  /* If index.theme has no Directories entry, it is only a cursor theme */
  directories = mate_desktop_item_get_string (icon_theme_ditem,
                                              "Icon Theme/Directories");
  if (directories == NULL)
    goto read_icon_theme_out;

  icon_theme_info = mate_theme_icon_info_new ();
  icon_theme_info->readable_name = g_strdup (name);
  icon_theme_info->path = g_file_get_path (icon_theme_uri);
  icon_theme_info->hidden = mate_desktop_item_get_boolean (icon_theme_ditem,
                                                           "Icon Theme/Hidden");
  dir_name = g_path_get_dirname (icon_theme_info->path);
  icon_theme_info->name = g_path_get_basename (dir_name);
  g_free (dir_name);

  mate_desktop_item_unref (icon_theme_ditem);

  return icon_theme_info;

read_icon_theme_out:
  mate_desktop_item_unref (icon_theme_ditem);
  return NULL;
}

static void
add_default_cursor_theme (void)
{
  MateThemeCursorInfo *theme_info;

  theme_info = mate_theme_cursor_info_new ();
  theme_info->path = g_strdup ("builtin");
  theme_info->name = g_strdup ("default");
  theme_info->readable_name = g_strdup (_("Default Pointer"));
  theme_info->sizes = g_array_sized_new (FALSE, FALSE, sizeof (gint), 0);

  g_hash_table_insert (cursor_theme_hash_by_uri, theme_info->path, theme_info);
  add_theme_to_hash_by_name (cursor_theme_hash_by_name, theme_info);
}

static GdkPixbuf *
gdk_pixbuf_from_xcursor_image (XcursorImage *cursor)
{
  GdkPixbuf *pixbuf;
#define BUF_SIZE sizeof(guint32) * cursor->width * cursor->height
  guchar *buf = g_malloc0 (BUF_SIZE);
  guchar *it;

  for (it = buf; it < (buf + BUF_SIZE); it += 4) {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
    /* on little endianess it's BGRA to RGBA */
    it[0] = ((guchar *) (cursor->pixels))[it - buf + 2];
    it[1] = ((guchar *) (cursor->pixels))[it - buf + 1];
    it[2] = ((guchar *) (cursor->pixels))[it - buf + 0];
    it[3] = ((guchar *) (cursor->pixels))[it - buf + 3];
#else
    /* on big endianess it's ARGB to RGBA */
    it[0] = ((guchar *) cursor->pixels)[it - buf + 1];
    it[1] = ((guchar *) cursor->pixels)[it - buf + 2];
    it[2] = ((guchar *) cursor->pixels)[it - buf + 3];
    it[3] = ((guchar *) cursor->pixels)[it - buf + 0];
#endif
  }

  pixbuf = gdk_pixbuf_new_from_data ((const guchar *) buf,
                                     GDK_COLORSPACE_RGB, TRUE, 8,
                                     cursor->width, cursor->height,
                                     cursor->width * 4,
                                     (GdkPixbufDestroyNotify) g_free,
                                     NULL);

  if (!pixbuf)
    g_free (buf);

  return pixbuf;
}

static MateThemeCursorInfo *
read_cursor_theme (GFile *cursor_theme_uri)
{
  MateThemeCursorInfo *cursor_theme_info = NULL;
  GFile *parent_uri, *cursors_uri;

  const gint filter_sizes[] = { 12, 16, 18, 24, 32, 36, 40, 48, 64, 96, 128 };
  const gint num_sizes = G_N_ELEMENTS (filter_sizes);

  parent_uri = g_file_get_parent (cursor_theme_uri);
  cursors_uri = g_file_get_child (parent_uri, "cursors");

  if (get_file_type (cursors_uri) == G_FILE_TYPE_DIRECTORY) {
    GArray *sizes;
    XcursorImage *cursor;
    GdkPixbuf *thumbnail = NULL;
    gchar *name;
    gint i;

    name = g_file_get_basename (parent_uri);

    sizes = g_array_sized_new (FALSE, FALSE, sizeof (gint), num_sizes);

    for (i = 0; i < num_sizes; ++i) {
      cursor = XcursorLibraryLoadImage ("left_ptr", name, filter_sizes[i]);

      if (cursor) {
        if (cursor->size == filter_sizes[i]) {
          g_array_append_val (sizes, filter_sizes[i]);

          if (thumbnail == NULL && i >= 1)
            thumbnail = gdk_pixbuf_from_xcursor_image (cursor);
        }

        XcursorImageDestroy (cursor);
      }
    }

    if (sizes->len == 0) {
      g_array_free (sizes, TRUE);
      g_free (name);
    } else {
      MateDesktopItem *cursor_theme_ditem;
      gchar *cursor_theme_file;

      if (!thumbnail) {
        cursor = XcursorLibraryLoadImage ("left_ptr", name,
                                          g_array_index (sizes, gint, 0));
        if (cursor) {
          thumbnail = gdk_pixbuf_from_xcursor_image (cursor);
          XcursorImageDestroy (cursor);
        }
      }

      cursor_theme_info = mate_theme_cursor_info_new ();
      cursor_theme_info->path = g_file_get_path (parent_uri);
      cursor_theme_info->name = name;
      cursor_theme_info->sizes = sizes;
      cursor_theme_info->thumbnail = thumbnail;

      cursor_theme_file = g_file_get_path (cursor_theme_uri);
      cursor_theme_ditem = mate_desktop_item_new_from_file (cursor_theme_file, 0, NULL);
      g_free (cursor_theme_file);

      if (cursor_theme_ditem != NULL) {
        const gchar *readable_name;

        readable_name = mate_desktop_item_get_string (cursor_theme_ditem,
                                                      "Icon Theme/Name");
        if (readable_name)
          cursor_theme_info->readable_name = g_strdup (readable_name);
        else
          cursor_theme_info->readable_name = g_strdup (name);

        cursor_theme_info->hidden = mate_desktop_item_get_boolean (cursor_theme_ditem,
                                                                   "Icon Theme/Hidden");

        mate_desktop_item_unref (cursor_theme_ditem);
      } else {
        cursor_theme_info->readable_name = g_strdup (name);
      }
    }
  }

  g_object_unref (cursors_uri);
  g_object_unref (parent_uri);

  return cursor_theme_info;
}

static void
handle_change_signal (gpointer             data,
                      MateThemeChangeType  change_type,
                      MateThemeElement     element_type)
{
#ifdef DEBUG
  gchar *type_str = NULL;
  gchar *change_str = NULL;
  gchar *element_str = NULL;
#endif
  MateThemeCommonInfo *theme = data;
  GList *list;

  if (initting)
    return;

  for (list = callbacks; list; list = list->next) {
    ThemeCallbackData *callback_data = list->data;
    (* callback_data->func) (theme, change_type, element_type, callback_data->data);
  }

#ifdef DEBUG
  if (theme->type == MATE_THEME_TYPE_METATHEME)
    type_str = "meta";
  else if (theme->type == MATE_THEME_TYPE_ICON)
    type_str = "icon";
  else if (theme->type == MATE_THEME_TYPE_CURSOR)
    type_str = "cursor";
  else if (theme->type == MATE_THEME_TYPE_REGULAR) {
    if (element_type & MATE_THEME_GTK_2)
      element_str = "gtk-2";
    else if (element_type & MATE_THEME_GTK_2_KEYBINDING)
      element_str = "keybinding";
    else if (element_type & MATE_THEME_MARCO)
      element_str = "marco";
  }

  if (change_type == MATE_THEME_CHANGE_CREATED)
    change_str = "created";
  else if (change_type == MATE_THEME_CHANGE_CHANGED)
    change_str = "changed";
  else if (change_type == MATE_THEME_CHANGE_DELETED)
    change_str = "deleted";

  if (type == MATE_THEME_TYPE_REGULAR) {
    g_print ("theme \"%s\" has a theme of type %s (priority %d) has been %s\n",
             theme->name,
             element_str,
             theme->priority,
             type_str);
  } else if (type_str != NULL) {
    g_print ("%s theme \"%s\" (priority %d) has been %s\n",
             type_str,
             theme->name,
             theme->priority,
             type_str);
    }
#endif
}

/* index_uri should point to the gtkrc file that was modified */
static void
update_theme_index (GFile            *index_uri,
                    MateThemeElement  key_element,
                    gint              priority)
{
  gboolean theme_exists;
  MateThemeInfo *theme_info;
  GFile *parent;
  GFile *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" */
  theme_exists = (get_file_type (index_uri) == G_FILE_TYPE_REGULAR);

  /* Next, we see what currently exists */
  parent = g_file_get_parent (index_uri);
  common_theme_dir_uri = g_file_get_parent (parent);
  common_theme_dir = g_file_get_path (common_theme_dir_uri);

  theme_info = g_hash_table_lookup (theme_hash_by_uri, common_theme_dir);
  if (theme_info == NULL) {
    if (theme_exists) {
      theme_info = mate_theme_info_new ();
      theme_info->path = g_strdup (common_theme_dir);
      theme_info->name = g_file_get_basename (common_theme_dir_uri);
      theme_info->readable_name = g_strdup (theme_info->name);
      theme_info->priority = priority;
      if (key_element & MATE_THEME_GTK_2)
        theme_info->has_gtk = TRUE;
      else if (key_element & MATE_THEME_GTK_2_KEYBINDING)
        theme_info->has_keybinding = TRUE;
      else if (key_element & MATE_THEME_MARCO)
        theme_info->has_marco = TRUE;

      g_hash_table_insert (theme_hash_by_uri, g_strdup (common_theme_dir), theme_info);
      add_theme_to_hash_by_name (theme_hash_by_name, theme_info);
      handle_change_signal (theme_info, MATE_THEME_CHANGE_CREATED, key_element);
    }
  } else {
    gboolean theme_used_to_exist = FALSE;

    if (key_element & MATE_THEME_GTK_2) {
      theme_used_to_exist = theme_info->has_gtk;
      theme_info->has_gtk = (theme_exists != FALSE);
    } else if (key_element & MATE_THEME_GTK_2_KEYBINDING) {
      theme_used_to_exist = theme_info->has_keybinding;
      theme_info->has_keybinding = (theme_exists != FALSE);
    } else if (key_element & MATE_THEME_MARCO) {
      theme_used_to_exist = theme_info->has_marco;
      theme_info->has_marco = (theme_exists != FALSE);
    }

    if (!theme_info->has_marco && !theme_info->has_keybinding && !theme_info->has_gtk) {
      g_hash_table_remove (theme_hash_by_uri, common_theme_dir);
      remove_theme_from_hash_by_name (theme_hash_by_name, theme_info);
    }

    if (theme_exists && theme_used_to_exist) {
      handle_change_signal (theme_info, MATE_THEME_CHANGE_CHANGED, key_element);
    } else if (theme_exists && !theme_used_to_exist) {
      handle_change_signal (theme_info, MATE_THEME_CHANGE_CREATED, key_element);
    } else if (!theme_exists && theme_used_to_exist) {
      handle_change_signal (theme_info, MATE_THEME_CHANGE_DELETED, key_element);
    }

    if (!theme_info->has_marco && !theme_info->has_keybinding && !theme_info->has_gtk) {
      mate_theme_info_free (theme_info);
    }
  }

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

static void
update_gtk2_index (GFile *gtk2_index_uri,
                   gint   priority)
{
  update_theme_index (gtk2_index_uri,
                      MATE_THEME_GTK_2,
                      priority);
}

static void
update_keybinding_index (GFile *keybinding_index_uri,
                         gint   priority)
{
  update_theme_index (keybinding_index_uri,
                      MATE_THEME_GTK_2_KEYBINDING,
                      priority);
}

static void
update_marco_index (GFile *marco_index_uri,
                    gint   priority)
{
  update_theme_index (marco_index_uri,
                      MATE_THEME_MARCO,
                      priority);
}

static void
update_common_theme_dir_index (GFile          *theme_index_uri,
                               MateThemeType   type,
                               gint            priority)
{
  gboolean theme_exists;
  MateThemeCommonInfo *theme_info = NULL;
  MateThemeCommonInfo *old_theme_info;
  GFile *common_theme_dir_uri;
  gchar *common_theme_dir;
  GHashTable *hash_by_uri;
  GHashTable *hash_by_name;

  if (type == MATE_THEME_TYPE_ICON) {
    hash_by_uri = icon_theme_hash_by_uri;
    hash_by_name = icon_theme_hash_by_name;
  } else if (type == MATE_THEME_TYPE_CURSOR) {
    hash_by_uri = cursor_theme_hash_by_uri;
    hash_by_name = cursor_theme_hash_by_name;
  } else {
    hash_by_uri = meta_theme_hash_by_uri;
    hash_by_name = meta_theme_hash_by_name;
  }

  if (type != MATE_THEME_TYPE_CURSOR) {
    /* First, we determine the new state of the file. */
    if (get_file_type (theme_index_uri) == G_FILE_TYPE_REGULAR) {
      /* It's an interesting file. Let's try to load it. */
      if (type == MATE_THEME_TYPE_ICON)
        theme_info = (MateThemeCommonInfo *) read_icon_theme (theme_index_uri);
      else
        theme_info = (MateThemeCommonInfo *) mate_theme_read_meta_theme (theme_index_uri);
    } else {
      theme_info = NULL;
    }

  }
  /* cursor themes don't necessarily have an index file, so try those in any case */
  else {
    theme_info = (MateThemeCommonInfo *) read_cursor_theme (theme_index_uri);
  }

  if (theme_info) {
    theme_info->priority = priority;
    theme_exists = TRUE;
  } else {
    theme_exists = FALSE;
  }

  /* Next, we see what currently exists */
  common_theme_dir_uri = g_file_get_parent (theme_index_uri);
  common_theme_dir = g_file_get_path (common_theme_dir_uri);
  g_object_unref (common_theme_dir_uri);

  old_theme_info = (MateThemeCommonInfo *) g_hash_table_lookup (hash_by_uri, common_theme_dir);

  if (old_theme_info == NULL) {
    if (theme_exists) {
      g_hash_table_insert (hash_by_uri, g_strdup (common_theme_dir), theme_info);
      add_theme_to_hash_by_name (hash_by_name, theme_info);
      handle_change_signal (theme_info, MATE_THEME_CHANGE_CREATED, 0);
    }
  } else {
    if (theme_exists) {
      if (theme_compare (theme_info, old_theme_info) != 0) {
        /* Remove old theme */
        g_hash_table_remove (hash_by_uri, common_theme_dir);
        remove_theme_from_hash_by_name (hash_by_name, old_theme_info);
        g_hash_table_insert (hash_by_uri, g_strdup (common_theme_dir), theme_info);
        add_theme_to_hash_by_name (hash_by_name, theme_info);
        handle_change_signal (theme_info, MATE_THEME_CHANGE_CHANGED, 0);
        theme_free (old_theme_info);
      } else {
        theme_free (theme_info);
      }
    } else {
      g_hash_table_remove (hash_by_uri, common_theme_dir);
      remove_theme_from_hash_by_name (hash_by_name, old_theme_info);

      handle_change_signal (old_theme_info, MATE_THEME_CHANGE_DELETED, 0);
      theme_free (old_theme_info);
    }
  }

  g_free (common_theme_dir);
}

static void
update_meta_theme_index (GFile *meta_theme_index_uri,
                         gint   priority)
{
  update_common_theme_dir_index (meta_theme_index_uri,
                                 MATE_THEME_TYPE_METATHEME,
                                 priority);
}

static void
update_icon_theme_index (GFile *icon_theme_index_uri,
                         gint   priority)
{
  update_common_theme_dir_index (icon_theme_index_uri,
                                 MATE_THEME_TYPE_ICON,
                                 priority);
}

static void
update_cursor_theme_index (GFile *cursor_theme_index_uri,
                           gint   priority)
{
  update_common_theme_dir_index (cursor_theme_index_uri,
                                 MATE_THEME_TYPE_CURSOR,
                                 priority);
}

static void
gtk2_dir_changed (GFileMonitor              *monitor,
                  GFile                     *file,
                  GFile                     *other_file,
                  GFileMonitorEvent          event_type,
                  CommonThemeDirMonitorData *monitor_data)
{
  gchar *affected_file;

  affected_file = g_file_get_basename (file);

  /* The only file we care about is gtkrc */
  if (!strcmp (affected_file, "gtkrc")) {
    update_gtk2_index (file, monitor_data->priority);
  }

  g_free (affected_file);
}

static void
keybinding_dir_changed (GFileMonitor              *monitor,
                        GFile                     *file,
                        GFile                     *other_file,
                        GFileMonitorEvent          event_type,
                        CommonThemeDirMonitorData *monitor_data)
{
  gchar *affected_file;

  affected_file = g_file_get_basename (file);

  /* The only file we care about is gtkrc */
  if (!strcmp (affected_file, "gtkrc")) {
    update_keybinding_index (file, monitor_data->priority);
  }

  g_free (affected_file);
}

static void
marco_dir_changed (GFileMonitor              *monitor,
                   GFile                     *file,
                   GFile                     *other_file,
                   GFileMonitorEvent          event_type,
                   CommonThemeDirMonitorData *monitor_data)
{
  gchar *affected_file;

  affected_file = g_file_get_basename (file);

  /* The only file we care about is metacity-theme-(1|2).xml */
  if (!strcmp (affected_file, "metacity-theme-1.xml") || !strcmp (affected_file, "metacity-theme-2.xml")) {
    update_marco_index (file, monitor_data->priority);
  }

  g_free (affected_file);
}

static void
common_theme_dir_changed (GFileMonitor              *monitor,
                          GFile                     *file,
                          GFile                     *other_file,
                          GFileMonitorEvent          event_type,
                          CommonThemeDirMonitorData *monitor_data)
{
  gchar *affected_file;

  affected_file = g_file_get_basename (file);

  /* The only file we care about is index.theme */
  if (!strcmp (affected_file, "index.theme")) {
    update_meta_theme_index (file, monitor_data->priority);
  }

  g_free (affected_file);
}

static void
common_icon_theme_dir_changed (GFileMonitor                  *monitor,
                               GFile                         *file,
                               GFile                         *other_file,
                               GFileMonitorEvent              event_type,
                               CommonIconThemeDirMonitorData *monitor_data)
{
  gchar *affected_file;

  affected_file = g_file_get_basename (file);

  /* The only file we care about is index.theme */
  if (!strcmp (affected_file, "index.theme")) {
    update_icon_theme_index (file, monitor_data->priority);
    update_cursor_theme_index (file, monitor_data->priority);
  }
  /* and the cursors subdir for cursor themes */
  else if (!strcmp (affected_file, "cursors")) {
    /* always call update_cursor_theme_index with the index.theme URI */
    GFile *parent, *index;

    parent = g_file_get_parent (file);
    index = g_file_get_child (parent, "index.theme");
    g_object_unref (parent);

    update_cursor_theme_index (index, monitor_data->priority);

    g_object_unref (index);
  }

  g_free (affected_file);
}

/* Add a monitor to a common_theme_dir. */
static gboolean
add_common_theme_dir_monitor (GFile                      *theme_dir_uri,
                              CommonThemeDirMonitorData  *monitor_data,
                              GError                    **error)
{
  GFile *uri, *subdir;
  GFileMonitor *monitor;

  uri = g_file_get_child (theme_dir_uri, "index.theme");
  update_meta_theme_index (uri, monitor_data->priority);
  g_object_unref (uri);

  /* Add the handle for this directory */
  monitor = g_file_monitor_file (theme_dir_uri, G_FILE_MONITOR_NONE, NULL, NULL);
  if (monitor == NULL)
    return FALSE;

  g_signal_connect (monitor, "changed",
                    (GCallback) common_theme_dir_changed,
                    monitor_data);

  monitor_data->common_theme_dir_handle = monitor;

  /* gtk-2 theme subdir */
  subdir = g_file_get_child (theme_dir_uri, "gtk-2.0");
  uri = g_file_get_child (subdir, "gtkrc");
  if (g_file_query_exists (uri, NULL)) {
    update_gtk2_index (uri, monitor_data->priority);
  }
  g_object_unref (uri);

  monitor = g_file_monitor_directory (subdir, G_FILE_MONITOR_NONE, NULL, NULL);
  if (monitor != NULL) {
    g_signal_connect (monitor, "changed",
                      (GCallback) gtk2_dir_changed,
                      monitor_data);
  }
  monitor_data->gtk2_dir_handle = monitor;
  g_object_unref (subdir);

  /* keybinding theme subdir */
  subdir = g_file_get_child (theme_dir_uri, "gtk-2.0-key");
  uri = g_file_get_child (subdir, "gtkrc");
  if (g_file_query_exists (uri, NULL)) {
    update_keybinding_index (uri, monitor_data->priority);
  }
  g_object_unref (uri);

  monitor = g_file_monitor_directory (subdir, G_FILE_MONITOR_NONE, NULL, NULL);
  if (monitor != NULL) {
    g_signal_connect (monitor, "changed",
                      (GCallback) keybinding_dir_changed,
                      monitor_data);
  }
  monitor_data->keybinding_dir_handle = monitor;
  g_object_unref (subdir);

  /* marco theme subdir */
  subdir = g_file_get_child (theme_dir_uri, "metacity-1");
  uri = g_file_get_child (subdir, "metacity-theme-2.xml");
  if (g_file_query_exists (uri, NULL)) {
    update_marco_index (uri, monitor_data->priority);
  }
  else {
    g_object_unref (uri);
    uri = g_file_get_child (subdir, "metacity-theme-1.xml");
    if (g_file_query_exists (uri, NULL)) {
      update_marco_index (uri, monitor_data->priority);
    }
  }
  g_object_unref (uri);

  monitor = g_file_monitor_directory (subdir, G_FILE_MONITOR_NONE, NULL, NULL);
  if (monitor != NULL) {
    g_signal_connect (monitor, "changed",
                      (GCallback) marco_dir_changed,
                      monitor_data);
  }
  monitor_data->marco_dir_handle = monitor;
  g_object_unref (subdir);

  return TRUE;
}

static gboolean
add_common_icon_theme_dir_monitor (GFile                          *theme_dir_uri,
                                   CommonIconThemeDirMonitorData  *monitor_data,
                                   GError                        **error)
{
  GFile *index_uri;
  GFileMonitor *monitor;

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

  monitor = g_file_monitor_file (theme_dir_uri, G_FILE_MONITOR_NONE, NULL, NULL);
  if (monitor == NULL)
    return FALSE;

  g_signal_connect (monitor, "changed",
                    (GCallback) common_icon_theme_dir_changed,
                    monitor_data);

  monitor_data->common_icon_theme_dir_handle = monitor;
  return TRUE;
}

static void
remove_common_theme_dir_monitor (CommonThemeDirMonitorData *monitor_data)
{
  g_file_monitor_cancel (monitor_data->common_theme_dir_handle);
  g_file_monitor_cancel (monitor_data->gtk2_dir_handle);
  g_file_monitor_cancel (monitor_data->keybinding_dir_handle);
  g_file_monitor_cancel (monitor_data->marco_dir_handle);
}

static void
remove_common_icon_theme_dir_monitor (CommonIconThemeDirMonitorData *monitor_data)
{
  g_file_monitor_cancel (monitor_data->common_icon_theme_dir_handle);
}

static void
top_theme_dir_changed (GFileMonitor      *monitor,
                       GFile             *file,
                       GFile             *other_file,
                       GFileMonitorEvent  event_type,
                       CallbackTuple     *tuple)
{
  GHashTable *handle_hash;
  CommonThemeDirMonitorData *monitor_data;
  gint priority;

  handle_hash = tuple->handle_hash;
  priority = tuple->priority;

  if (event_type == G_FILE_MONITOR_EVENT_CREATED) {
    if (get_file_type (file) == G_FILE_TYPE_DIRECTORY) {
      monitor_data = g_new0 (CommonThemeDirMonitorData, 1);
      monitor_data->priority = priority;
      add_common_theme_dir_monitor (file, monitor_data, NULL);
      g_hash_table_insert (handle_hash, g_file_get_basename (file), monitor_data);
    }

  } else if (event_type == G_FILE_MONITOR_EVENT_DELETED) {
    gchar *name;

    name = g_file_get_basename (file);
    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 (name);
  }
}

static void
top_icon_theme_dir_changed (GFileMonitor      *monitor,
                            GFile             *file,
                            GFile             *other_file,
                            GFileMonitorEvent  event_type,
                            CallbackTuple     *tuple)
{
  GHashTable *handle_hash;
  CommonIconThemeDirMonitorData *monitor_data;
  gint priority;

  handle_hash = tuple->handle_hash;
  priority = tuple->priority;

  if (event_type == G_FILE_MONITOR_EVENT_CREATED) {
    if (get_file_type (file) == G_FILE_TYPE_DIRECTORY) {
      monitor_data = g_new0 (CommonIconThemeDirMonitorData, 1);
      monitor_data->priority = priority;
      add_common_icon_theme_dir_monitor (file, monitor_data, NULL);
      g_hash_table_insert (handle_hash, g_file_get_basename (file), monitor_data);
    }

  } else if (event_type == G_FILE_MONITOR_EVENT_DELETED) {
    gchar *name;

    name = g_file_get_basename (file);
    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 (name);
  }
}

/* Add a monitor to a top dir.  These monitors persist for the duration of the
 * lib.
 */
static gboolean
real_add_top_theme_dir_monitor (GFile    *uri,
                                gint      priority,
                                gboolean  icon_theme,
                                GError  **error)
{
  GFileInfo *file_info;
  GFileMonitor *monitor;
  GFileEnumerator *enumerator;
  CallbackTuple *tuple;

  /* Check the URI */
  if (get_file_type (uri) != G_FILE_TYPE_DIRECTORY)
    return FALSE;

  /* 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_new (CallbackTuple, 1);
  tuple->handle_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_free);
  tuple->priority = priority;

  /* Monitor the top directory */
  if ((monitor = g_file_monitor_directory (uri, G_FILE_MONITOR_NONE, NULL, NULL)) != NULL) {
    g_signal_connect (monitor, "changed",
                      (GCallback) (icon_theme ? top_icon_theme_dir_changed : top_theme_dir_changed),
                      tuple);
  } else {
    g_hash_table_destroy (tuple->handle_hash);
    g_free (tuple);
    return FALSE;
  }

  /* Go through the directory to add monitoring */
  enumerator = g_file_enumerate_children (uri,
                                          G_FILE_ATTRIBUTE_STANDARD_TYPE ","
                                          G_FILE_ATTRIBUTE_STANDARD_NAME,
                                          G_FILE_QUERY_INFO_NONE,
                                          NULL, NULL);
  if (enumerator == NULL)
    return FALSE;

  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, NULL))) {
    GFileType type = g_file_info_get_file_type (file_info);

    if (type == G_FILE_TYPE_DIRECTORY || type == G_FILE_TYPE_SYMBOLIC_LINK) {
      GFile *child;
      const gchar *name;
      gpointer data;

      /* Add the directory */
      name = g_file_info_get_name (file_info);
      child = g_file_get_child (uri, name);

      if (icon_theme) {
        CommonIconThemeDirMonitorData *monitor_data;
        monitor_data = g_new0 (CommonIconThemeDirMonitorData, 1);
        monitor_data->priority = priority;
        add_common_icon_theme_dir_monitor (child, monitor_data, error);
        data = monitor_data;
      } else {
        CommonThemeDirMonitorData *monitor_data;
        monitor_data = g_new0 (CommonThemeDirMonitorData, 1);
        monitor_data->priority = priority;
        add_common_theme_dir_monitor (child, monitor_data, error);
        data = monitor_data;
      }
      g_object_unref (child);

      g_hash_table_insert (tuple->handle_hash, g_strdup (name), data);
    }
    g_object_unref (file_info);
  }
  g_file_enumerator_close (enumerator, NULL, NULL);

  return TRUE;
}

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

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

/* Public functions */

/* GTK/Marco/keybinding Themes */
MateThemeInfo *
mate_theme_info_new (void)
{
  MateThemeInfo *theme_info;

  theme_info = g_new0 (MateThemeInfo, 1);
  theme_info->type = MATE_THEME_TYPE_REGULAR;

  return theme_info;
}

void
mate_theme_info_free (MateThemeInfo *theme_info)
{
  g_free (theme_info->path);
  g_free (theme_info->name);
  g_free (theme_info->readable_name);
  g_free (theme_info);
}

MateThemeInfo *
mate_theme_info_find (const gchar *theme_name)
{
  return (MateThemeInfo *)
         get_theme_from_hash_by_name (theme_hash_by_name, theme_name, -1);
}

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

static void
mate_theme_info_find_by_type_helper (gpointer key,
                                      GList *list,
                                      struct MateThemeInfoHashData *hash_data)
{
  guint elements = GPOINTER_TO_INT (hash_data->user_data);

  do {
    MateThemeInfo *theme_info = list->data;

    if ((elements & MATE_THEME_MARCO && theme_info->has_marco) ||
        (elements & MATE_THEME_GTK_2 && theme_info->has_gtk) ||
        (elements & MATE_THEME_GTK_2_KEYBINDING && theme_info->has_keybinding)) {
      hash_data->list = g_list_prepend (hash_data->list, theme_info);
      return;
    }

    list = list->next;
  } while (list);
}

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

  g_hash_table_foreach (theme_hash_by_name,
                        (GHFunc) mate_theme_info_find_by_type_helper,
                        &data);

  return data.list;
}

static void
mate_theme_info_find_all_helper (const gchar  *key,
                                 GList        *list,
                                 GList       **themes)
{
  /* only return visible themes */
  if (!((MateThemeCommonInfo*) list->data)->hidden)
    *themes = g_list_prepend(*themes, list->data);
}

gchar*
gtk_theme_info_missing_engine (const gchar *gtk_theme,
                               gboolean     name_only)
{
  gchar* engine = NULL;
  gchar* gtkrc;

  gtkrc = gtkrc_find_named (gtk_theme);

  if (gtkrc) {
    GSList *engines = NULL;
    GSList *l;

    gtkrc_get_details (gtkrc, &engines, NULL);
    g_free (gtkrc);

    for (l = engines; l; l = l->next) {
      /* This code do not work on distros with more of one gtk theme
       * engine path. Like debian. But yes on others like Archlinux.
       * Example, debian use:
       * /usr/lib/i386-linux-gnu/2.10.0/engines/
       * and /usr/lib/2.10.0/engines/
       *
       * some links
       * http://forums.linuxmint.com/viewtopic.php?f=190&t=85015
       */
      gchar *full = g_module_build_path (GTK_ENGINE_DIR, l->data);
      gboolean found = g_file_test (full, G_FILE_TEST_EXISTS);
      if (!found) {
         if (name_only) {
           engine = g_strdup(l->data);
           g_free (full);
         } else {
           engine = full;
         }
         break;
       }

       g_free(full);
    }

    g_slist_foreach (engines, (GFunc) g_free, NULL);
    g_slist_free (engines);
  }
  return engine;
}

/* Icon themes */
MateThemeIconInfo *
mate_theme_icon_info_new (void)
{
  MateThemeIconInfo *icon_theme_info;

  icon_theme_info = g_new0 (MateThemeIconInfo, 1);
  icon_theme_info->type = MATE_THEME_TYPE_ICON;

  return icon_theme_info;
}

void
mate_theme_icon_info_free (MateThemeIconInfo *icon_theme_info)
{
  g_free (icon_theme_info->name);
  g_free (icon_theme_info->readable_name);
  g_free (icon_theme_info->path);
  g_free (icon_theme_info);
}

MateThemeIconInfo *
mate_theme_icon_info_find (const gchar *icon_theme_name)
{
  g_return_val_if_fail (icon_theme_name != NULL, NULL);

  return (MateThemeIconInfo *)
         get_theme_from_hash_by_name (icon_theme_hash_by_name, icon_theme_name, -1);
}

GList *
mate_theme_icon_info_find_all (void)
{
  GList *list = NULL;

  g_hash_table_foreach (icon_theme_hash_by_name,
                        (GHFunc) mate_theme_info_find_all_helper,
                        &list);

  return list;
}

gint
mate_theme_icon_info_compare (MateThemeIconInfo *a,
                              MateThemeIconInfo *b)
{
  gint cmp;

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

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

/* Cursor themes */
MateThemeCursorInfo *
mate_theme_cursor_info_new (void)
{
  MateThemeCursorInfo *theme_info;

  theme_info = g_new0 (MateThemeCursorInfo, 1);
  theme_info->type = MATE_THEME_TYPE_CURSOR;

  return theme_info;
}

void
mate_theme_cursor_info_free (MateThemeCursorInfo *cursor_theme_info)
{
  g_free (cursor_theme_info->name);
  g_free (cursor_theme_info->readable_name);
  g_free (cursor_theme_info->path);
  g_array_free (cursor_theme_info->sizes, TRUE);
  if (cursor_theme_info->thumbnail != NULL)
    g_object_unref (cursor_theme_info->thumbnail);
  g_free (cursor_theme_info);
}

MateThemeCursorInfo *
mate_theme_cursor_info_find (const gchar *cursor_theme_name)
{
  g_return_val_if_fail (cursor_theme_name != NULL, NULL);

  return (MateThemeCursorInfo *)
         get_theme_from_hash_by_name (cursor_theme_hash_by_name, cursor_theme_name, -1);
}

GList *
mate_theme_cursor_info_find_all (void)
{
  GList *list = NULL;

  g_hash_table_foreach (cursor_theme_hash_by_name,
                        (GHFunc) mate_theme_info_find_all_helper,
                        &list);

  return list;
}

gint
mate_theme_cursor_info_compare (MateThemeCursorInfo *a,
                                MateThemeCursorInfo *b)
{
  gint cmp;

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

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

/* Meta themes */
MateThemeMetaInfo*
mate_theme_meta_info_new (void)
{
  MateThemeMetaInfo* theme_info;

  theme_info = g_new0 (MateThemeMetaInfo, 1);
  theme_info->type = MATE_THEME_TYPE_METATHEME;

  return theme_info;
}

void
mate_theme_meta_info_free (MateThemeMetaInfo *meta_theme_info)
{
  g_free (meta_theme_info->application_font);
  g_free (meta_theme_info->background_image);
  g_free (meta_theme_info->comment);
  g_free (meta_theme_info->cursor_theme_name);
  g_free (meta_theme_info->desktop_font);
  g_free (meta_theme_info->documents_font);
  g_free (meta_theme_info->gtk_color_scheme);
  g_free (meta_theme_info->gtk_theme_name);
  g_free (meta_theme_info->icon_file);
  g_free (meta_theme_info->icon_theme_name);
  g_free (meta_theme_info->marco_theme_name);
  g_free (meta_theme_info->monospace_font);
  g_free (meta_theme_info->name);
  g_free (meta_theme_info->notification_theme_name);
  g_free (meta_theme_info->path);
  g_free (meta_theme_info->sound_theme_name);
  g_free (meta_theme_info->windowtitle_font);
  g_free (meta_theme_info->readable_name);
  g_free (meta_theme_info);
}

gboolean
mate_theme_meta_info_validate (const MateThemeMetaInfo   *info,
                               GError                   **error)
{
  MateThemeInfo *theme;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  theme = mate_theme_info_find (info->gtk_theme_name);
  if (!theme || !theme->has_gtk) {
    g_set_error (error, MATE_THEME_ERROR, MATE_THEME_ERROR_GTK_THEME_NOT_AVAILABLE,
                 _("This theme will not look as intended because the required GTK+ theme '%s' is not installed."),
                info->gtk_theme_name);
    return FALSE;
  }

  theme = mate_theme_info_find (info->marco_theme_name);
  if (!theme || !theme->has_marco) {
    g_set_error (error, MATE_THEME_ERROR, MATE_THEME_ERROR_WM_THEME_NOT_AVAILABLE,
                 _("This theme will not look as intended because the required window manager theme '%s' is not installed."),
                 info->marco_theme_name);
    return FALSE;
  }

  if (!mate_theme_icon_info_find (info->icon_theme_name)) {
    g_set_error (error, MATE_THEME_ERROR, MATE_THEME_ERROR_ICON_THEME_NOT_AVAILABLE,
                 _("This theme will not look as intended because the required icon theme '%s' is not installed."),
                 info->icon_theme_name);
    return FALSE;
  }

  return TRUE;
}

MateThemeMetaInfo *
mate_theme_meta_info_find (const char *meta_theme_name)
{
  g_return_val_if_fail(meta_theme_name != NULL, NULL);

  return (MateThemeMetaInfo*) get_theme_from_hash_by_name (meta_theme_hash_by_name, meta_theme_name, -1);
}

GList *
mate_theme_meta_info_find_all (void)
{
  GList *list = NULL;

  g_hash_table_foreach (meta_theme_hash_by_name,
                        (GHFunc) mate_theme_info_find_all_helper,
                        &list);

  return list;
}

gint
mate_theme_meta_info_compare (MateThemeMetaInfo *a,
                              MateThemeMetaInfo *b)
{
  gint cmp;

  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->gtk_color_scheme, b->gtk_color_scheme);
  if (cmp != 0) return cmp;

  cmp = safe_strcmp (a->marco_theme_name, b->marco_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->notification_theme_name, b->notification_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->documents_font, b->documents_font);
  if (cmp != 0) return cmp;

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

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

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

  return safe_strcmp (a->background_image, b->background_image);
}

void
mate_theme_info_register_theme_change (ThemeChangedCallback func,
                                       gpointer             data)
{
  ThemeCallbackData *callback_data;

  g_return_if_fail (func != NULL);

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

  callbacks = g_list_prepend (callbacks, callback_data);
}

gboolean
mate_theme_color_scheme_parse (const gchar *scheme,
                               GdkRGBA     *colors)
{
  gchar **color_scheme_strings, **color_scheme_pair, *current_string;
  gint i;

  if (!scheme || !strcmp (scheme, ""))
    return FALSE;

  /* initialise the array */
  for (i = 0; i < NUM_SYMBOLIC_COLORS; i++)
    colors[i].red = colors[i].green = colors[i].blue = 0;

  /* The color scheme string consists of name:color pairs, separated by
   * newlines, so first we split the string up by new line */

  color_scheme_strings = g_strsplit (scheme, "\n", 0);

  /* loop through the name:color pairs, and save the color if we recognise the name */
  i = 0;
  while ((current_string = color_scheme_strings[i++])) {
    color_scheme_pair = g_strsplit (current_string, ":", 0);

    if (color_scheme_pair[0] != NULL && color_scheme_pair[1] != NULL) {
      g_strstrip (color_scheme_pair[0]);
      g_strstrip (color_scheme_pair[1]);

      if (!strcmp ("fg_color", color_scheme_pair[0]))
        gdk_rgba_parse (&colors[COLOR_FG], color_scheme_pair[1]);
      else if (!strcmp ("bg_color", color_scheme_pair[0]))
        gdk_rgba_parse (&colors[COLOR_BG], color_scheme_pair[1]);
      else if (!strcmp ("text_color", color_scheme_pair[0]))
        gdk_rgba_parse (&colors[COLOR_TEXT], color_scheme_pair[1]);
      else if (!strcmp ("base_color", color_scheme_pair[0]))
        gdk_rgba_parse (&colors[COLOR_BASE], color_scheme_pair[1]);
      else if (!strcmp ("selected_fg_color", color_scheme_pair[0]))
        gdk_rgba_parse (&colors[COLOR_SELECTED_FG], color_scheme_pair[1]);
      else if (!strcmp ("selected_bg_color", color_scheme_pair[0]))
        gdk_rgba_parse ( &colors[COLOR_SELECTED_BG], color_scheme_pair[1]);
      else if (!strcmp ("tooltip_fg_color", color_scheme_pair[0]))
        gdk_rgba_parse (&colors[COLOR_TOOLTIP_FG], color_scheme_pair[1]);
      else if (!strcmp ("tooltip_bg_color", color_scheme_pair[0]))
        gdk_rgba_parse (&colors[COLOR_TOOLTIP_BG], color_scheme_pair[1]);
    }

    g_strfreev (color_scheme_pair);
  }

  g_strfreev (color_scheme_strings);

  return TRUE;
}

gboolean
mate_theme_color_scheme_equal (const gchar *s1,
                               const gchar *s2)
{
  GdkRGBA c1[NUM_SYMBOLIC_COLORS], c2[NUM_SYMBOLIC_COLORS];
  int i;

  if (!mate_theme_color_scheme_parse (s1, c1) ||
      !mate_theme_color_scheme_parse (s2, c2))
    return FALSE;

  for (i = 0; i < NUM_SYMBOLIC_COLORS; ++i) {
    if (!gdk_rgba_equal (&c1[i], &c2[i]))
      return FALSE;
  }

  return TRUE;
}

void
mate_theme_init ()
{
  const gchar * const * dirs;
  GFile *top_theme_dir;
  gchar *top_theme_dir_string;
  static gboolean initted = FALSE;
  gchar **search_path;
  gint i, n;

  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);
  cursor_theme_hash_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  cursor_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 following the XDG Base Directory Specification */
  dirs = g_get_system_data_dirs ();
  if (dirs != NULL)
    for (; *dirs != NULL; ++dirs) {
      top_theme_dir_string = g_build_filename (*dirs, "themes", NULL);
      top_theme_dir = g_file_new_for_path (top_theme_dir_string);
      g_free (top_theme_dir_string);
      add_top_theme_dir_monitor (top_theme_dir, 1, NULL);
      g_object_unref (top_theme_dir);
    }

  /* ~/.themes */
  top_theme_dir_string = g_build_filename (g_get_home_dir (), ".themes", NULL);
  top_theme_dir = g_file_new_for_path (top_theme_dir_string);
  g_free (top_theme_dir_string);
  if (!g_file_query_exists (top_theme_dir, NULL))
    g_file_make_directory (top_theme_dir, NULL, NULL);
  add_top_theme_dir_monitor (top_theme_dir, 0, NULL);
  g_object_unref (top_theme_dir);

  /* ~/.icons */
  top_theme_dir_string = g_build_filename (g_get_home_dir (), ".icons", NULL);
  top_theme_dir = g_file_new_for_path (top_theme_dir_string);
  g_free (top_theme_dir_string);
  if (!g_file_query_exists (top_theme_dir, NULL))
    g_file_make_directory (top_theme_dir, NULL, NULL);
  g_object_unref (top_theme_dir);

  /* icon theme search path */
  gtk_icon_theme_get_search_path (gtk_icon_theme_get_default (), &search_path, &n);
  for (i = 0; i < n; ++i) {
    top_theme_dir = g_file_new_for_path (search_path[i]);
    add_top_icon_theme_dir_monitor (top_theme_dir, i, NULL);
    g_object_unref (top_theme_dir);
  }
  g_strfreev (search_path);

  /* if there's a separate xcursors dir, add that as well */
  if (strcmp (XCURSOR_ICONDIR, "/usr/share/icons")) {
    top_theme_dir = g_file_new_for_path (XCURSOR_ICONDIR);
    add_top_icon_theme_dir_monitor (top_theme_dir, 1, NULL);
    g_object_unref (top_theme_dir);
  }

  /* make sure we have the default theme */
  if (!mate_theme_cursor_info_find ("default"))
    add_default_cursor_theme ();

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