/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* nautilus-icon-info.c
 * Copyright (C) 2007  Red Hat, Inc.,  Alexander Larsson <alexl@redhat.com>
 *
 * This 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.
 *
 * This 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 this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <string.h>
#include "nautilus-icon-info.h"
#include "nautilus-icon-names.h"
#include "nautilus-default-file-icon.h"
#include <gtk/gtk.h>
#include <gio/gio.h>

struct _NautilusIconInfo
{
	GObject parent;

	gboolean sole_owner;
	gint64 last_use_time;
	GdkPixbuf *pixbuf;
	
        char *icon_name;

	gint  orig_scale;
};

struct _NautilusIconInfoClass
{
	GObjectClass parent_class;
};

static void schedule_reap_cache (void);

G_DEFINE_TYPE (NautilusIconInfo,
	       nautilus_icon_info,
	       G_TYPE_OBJECT);

static void
nautilus_icon_info_init (NautilusIconInfo *icon)
{
	icon->last_use_time = g_get_monotonic_time ();
	icon->sole_owner = TRUE;
}

gboolean
nautilus_icon_info_is_fallback (NautilusIconInfo  *icon)
{
  return icon->pixbuf == NULL;
}

static void
pixbuf_toggle_notify (gpointer      info,
		      GObject      *object,
		      gboolean      is_last_ref)
{
	NautilusIconInfo  *icon = info;
	
	if (is_last_ref) {
		icon->sole_owner = TRUE;	
		g_object_remove_toggle_ref (object,
					    pixbuf_toggle_notify,
					    info);
		icon->last_use_time = g_get_monotonic_time ();
		schedule_reap_cache ();
	}
}

static void
nautilus_icon_info_finalize (GObject *object)
{
        NautilusIconInfo *icon;

        icon = NAUTILUS_ICON_INFO (object);

	if (!icon->sole_owner && icon->pixbuf) {
		g_object_remove_toggle_ref (G_OBJECT (icon->pixbuf),
					    pixbuf_toggle_notify,
					    icon);
	}

	if (icon->pixbuf) {
		g_object_unref (icon->pixbuf);
	}
	g_free (icon->icon_name);

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

static void
nautilus_icon_info_class_init (NautilusIconInfoClass *icon_info_class)
{
        GObjectClass *gobject_class;

        gobject_class = (GObjectClass *) icon_info_class;

        gobject_class->finalize = nautilus_icon_info_finalize;

}

NautilusIconInfo *
nautilus_icon_info_new_for_pixbuf (GdkPixbuf *pixbuf,
				   gint       scale)
{
	NautilusIconInfo *icon;

	icon = g_object_new (NAUTILUS_TYPE_ICON_INFO, NULL);

	if (pixbuf) {
		icon->pixbuf = g_object_ref (pixbuf);
	}

	icon->orig_scale = scale;
	
	return icon;
}

static NautilusIconInfo *
nautilus_icon_info_new_for_icon_info (GtkIconInfo *icon_info,
				      gint         scale)
{
	NautilusIconInfo *icon;
	const char *filename;
	char *basename, *p;

	icon = g_object_new (NAUTILUS_TYPE_ICON_INFO, NULL);

	icon->pixbuf = gtk_icon_info_load_icon (icon_info, NULL);

	filename = gtk_icon_info_get_filename (icon_info);
	if (filename != NULL) {
		basename = g_path_get_basename (filename);
		p = strrchr (basename, '.');
		if (p) {
			*p = 0;
		}
		icon->icon_name = basename;
	}

	icon->orig_scale = scale;
	
	return icon;
}


typedef struct  {
	GIcon *icon;
	int size;
} LoadableIconKey;

typedef struct {
	char *filename;
	int size;
} ThemedIconKey;

static GHashTable *loadable_icon_cache = NULL;
static GHashTable *themed_icon_cache = NULL;
static guint reap_cache_timeout = 0;

#define MICROSEC_PER_SEC ((guint64)1000000L)

static guint time_now;

static gboolean
reap_old_icon (gpointer  key,
	       gpointer  value,
	       gpointer  user_info)
{
	NautilusIconInfo *icon = value;
	gboolean *reapable_icons_left = user_info;

	if (icon->sole_owner) {
		if (time_now - icon->last_use_time > 30 * MICROSEC_PER_SEC) {
			/* This went unused 30 secs ago. reap */
			return TRUE;
		} else {
			/* We can reap this soon */
			*reapable_icons_left = TRUE;
		}
	}
	
	return FALSE;
}

static gboolean
reap_cache (gpointer data)
{
	gboolean reapable_icons_left;

	reapable_icons_left = TRUE;

	time_now = g_get_monotonic_time ();
	
	if (loadable_icon_cache) {
		g_hash_table_foreach_remove (loadable_icon_cache,
					     reap_old_icon,
					     &reapable_icons_left);
	}
	
	if (themed_icon_cache) {
		g_hash_table_foreach_remove (themed_icon_cache,
					     reap_old_icon,
					     &reapable_icons_left);
	}
	
	if (reapable_icons_left) {
		return TRUE;
	} else {
		reap_cache_timeout = 0;
		return FALSE;
	}
}

static void
schedule_reap_cache (void)
{
	if (reap_cache_timeout == 0) {
		reap_cache_timeout = g_timeout_add_seconds_full (0, 5,
								 reap_cache,
								 NULL, NULL);
	}
}

void
nautilus_icon_info_clear_caches (void)
{
	if (loadable_icon_cache) {
		g_hash_table_remove_all (loadable_icon_cache);
	}
	
	if (themed_icon_cache) {
		g_hash_table_remove_all (themed_icon_cache);
	}
}

static guint
loadable_icon_key_hash (LoadableIconKey *key)
{
	return g_icon_hash (key->icon) ^ key->size;
}

static gboolean
loadable_icon_key_equal (const LoadableIconKey *a,
			 const LoadableIconKey *b)
{
	return a->size == b->size &&
		g_icon_equal (a->icon, b->icon);
}

static LoadableIconKey *
loadable_icon_key_new (GIcon *icon, int size)
{
	LoadableIconKey *key;

	key = g_slice_new (LoadableIconKey);
	key->icon = g_object_ref (icon);
	key->size = size;

	return key;
}

static void
loadable_icon_key_free (LoadableIconKey *key)
{
	g_object_unref (key->icon);
	g_slice_free (LoadableIconKey, key);
}

static guint
themed_icon_key_hash (ThemedIconKey *key)
{
	return g_str_hash (key->filename) ^ key->size;
}

static gboolean
themed_icon_key_equal (const ThemedIconKey *a,
		       const ThemedIconKey *b)
{
	return a->size == b->size &&
		g_str_equal (a->filename, b->filename);
}

static ThemedIconKey *
themed_icon_key_new (const char *filename, int size)
{
	ThemedIconKey *key;

	key = g_slice_new (ThemedIconKey);
	key->filename = g_strdup (filename);
	key->size = size;

	return key;
}

static void
themed_icon_key_free (ThemedIconKey *key)
{
	g_free (key->filename);
	g_slice_free (ThemedIconKey, key);
}

NautilusIconInfo *
nautilus_icon_info_lookup (GIcon *icon,
			   int size,
			   int scale)
{
	NautilusIconInfo *icon_info;
	GdkPixbuf *pixbuf;
	
	if (G_IS_LOADABLE_ICON (icon)) {
		LoadableIconKey lookup_key;
		LoadableIconKey *key;
		GInputStream *stream;
		
		if (loadable_icon_cache == NULL) {
			loadable_icon_cache =
				g_hash_table_new_full ((GHashFunc)loadable_icon_key_hash,
						       (GEqualFunc)loadable_icon_key_equal,
						       (GDestroyNotify) loadable_icon_key_free,
						       (GDestroyNotify) g_object_unref);
		}
		
		lookup_key.icon = icon;
		lookup_key.size = size;

		icon_info = g_hash_table_lookup (loadable_icon_cache, &lookup_key);
		if (icon_info) {
			return g_object_ref (icon_info);
		}

		pixbuf = NULL;
		stream = g_loadable_icon_load (G_LOADABLE_ICON (icon),
					       size * scale,
					       NULL, NULL, NULL);
		if (stream) {
			pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream,
								      size * scale, size * scale,
								      TRUE,
								      NULL, NULL);
			g_input_stream_close (stream, NULL, NULL);
			g_object_unref (stream);
		}

		icon_info = nautilus_icon_info_new_for_pixbuf (pixbuf, scale);

		key = loadable_icon_key_new (icon, size);
		g_hash_table_insert (loadable_icon_cache, key, icon_info);

		return g_object_ref (icon_info);
	} else if (G_IS_THEMED_ICON (icon)) {
		const char * const *names;
		ThemedIconKey lookup_key;
		ThemedIconKey *key;
		GtkIconTheme *icon_theme;
		GtkIconInfo *gtkicon_info;
		const char *filename;

		if (themed_icon_cache == NULL) {
			themed_icon_cache =
				g_hash_table_new_full ((GHashFunc)themed_icon_key_hash,
						       (GEqualFunc)themed_icon_key_equal,
						       (GDestroyNotify) themed_icon_key_free,
						       (GDestroyNotify) g_object_unref);
		}
		
		names = g_themed_icon_get_names (G_THEMED_ICON (icon));

		icon_theme = gtk_icon_theme_get_default ();
		gtkicon_info = gtk_icon_theme_choose_icon_for_scale (icon_theme, (const char **)names,
								     size, scale, GTK_ICON_LOOKUP_FORCE_SIZE);

		if (gtkicon_info == NULL) {
			return nautilus_icon_info_new_for_pixbuf (NULL, scale);
		}

		filename = gtk_icon_info_get_filename (gtkicon_info);
		if (filename == NULL) {
			g_object_unref (gtkicon_info);
			return nautilus_icon_info_new_for_pixbuf (NULL, scale);
		}

		lookup_key.filename = (char *)filename;
		lookup_key.size = size;

		icon_info = g_hash_table_lookup (themed_icon_cache, &lookup_key);
		if (icon_info) {
			g_object_unref (gtkicon_info);
			return g_object_ref (icon_info);
		}
		
		icon_info = nautilus_icon_info_new_for_icon_info (gtkicon_info, scale);
		
		key = themed_icon_key_new (filename, size);
		g_hash_table_insert (themed_icon_cache, key, icon_info);

		g_object_unref (gtkicon_info);

		return g_object_ref (icon_info);
	} else {
                GdkPixbuf *pixbuf;
                GtkIconInfo *gtk_icon_info;

                gtk_icon_info = gtk_icon_theme_lookup_by_gicon_for_scale (gtk_icon_theme_get_default (),
									  icon,
									  size,
									  scale,
									  GTK_ICON_LOOKUP_FORCE_SIZE);
                if (gtk_icon_info != NULL) {
                        pixbuf = gtk_icon_info_load_icon (gtk_icon_info, NULL);
                        g_object_unref (gtk_icon_info);
                } else {
                        pixbuf = NULL;
                }

		icon_info = nautilus_icon_info_new_for_pixbuf (pixbuf, scale);

		if (pixbuf != NULL) {
			g_object_unref (pixbuf);
		}

		return icon_info;
        }
}

NautilusIconInfo *
nautilus_icon_info_lookup_from_name (const char *name,
				     int size,
				     int scale)
{
	GIcon *icon;
	NautilusIconInfo *info;

	icon = g_themed_icon_new (name);
	info = nautilus_icon_info_lookup (icon, size, scale);
	g_object_unref (icon);
	return info;
}

NautilusIconInfo *
nautilus_icon_info_lookup_from_path (const char *path,
				     int size,
				     int scale)
{
	GFile *icon_file;
	GIcon *icon;
	NautilusIconInfo *info;

	icon_file = g_file_new_for_path (path);
	icon = g_file_icon_new (icon_file);
	info = nautilus_icon_info_lookup (icon, size, scale);
	g_object_unref (icon);
	g_object_unref (icon_file);
	return info;
}

GdkPixbuf *
nautilus_icon_info_get_pixbuf_nodefault (NautilusIconInfo  *icon)
{
	GdkPixbuf *res;

	if (icon->pixbuf == NULL) {
		res = NULL;
	} else {
		res = g_object_ref (icon->pixbuf);

		if (icon->sole_owner) {
			icon->sole_owner = FALSE;
			g_object_add_toggle_ref (G_OBJECT (res),
						 pixbuf_toggle_notify,
						 icon);
		}
	}
	
	return res;
}


GdkPixbuf *
nautilus_icon_info_get_pixbuf (NautilusIconInfo *icon)
{
	GdkPixbuf *res;

	res = nautilus_icon_info_get_pixbuf_nodefault (icon);
	if (res == NULL) {
		res = gdk_pixbuf_new_from_data (nautilus_default_file_icon,
						GDK_COLORSPACE_RGB,
						TRUE,
						8,
						nautilus_default_file_icon_width,
						nautilus_default_file_icon_height,
						nautilus_default_file_icon_width * 4, /* stride */
						NULL, /* don't destroy info */
						NULL);
	} 
	
	return res;
}

GdkPixbuf *
nautilus_icon_info_get_pixbuf_nodefault_at_size (NautilusIconInfo  *icon,
						 gsize              forced_size)
{
	GdkPixbuf *pixbuf, *scaled_pixbuf;
	int w, h, s;
	double scale;

	pixbuf = nautilus_icon_info_get_pixbuf_nodefault (icon);

	if (pixbuf == NULL)
	  return NULL;

	w = gdk_pixbuf_get_width (pixbuf) / icon->orig_scale;
	h = gdk_pixbuf_get_height (pixbuf) / icon->orig_scale;
	s = MAX (w, h);
	if (s == forced_size) {
		return pixbuf;
	}

	scale = (double)forced_size / s;
	scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
						 w * scale, h * scale,
						 GDK_INTERP_BILINEAR);
	g_object_unref (pixbuf);
	return scaled_pixbuf;
}


GdkPixbuf *
nautilus_icon_info_get_pixbuf_at_size (NautilusIconInfo  *icon,
				       gsize              forced_size)
{
	GdkPixbuf *pixbuf, *scaled_pixbuf;
	int w, h, s;
	double scale;

	pixbuf = nautilus_icon_info_get_pixbuf (icon);

	w = gdk_pixbuf_get_width (pixbuf) / icon->orig_scale;
	h = gdk_pixbuf_get_height (pixbuf) / icon->orig_scale;
	s = MAX (w, h);
	if (s == forced_size) {
		return pixbuf;
	}

	scale = (double)forced_size / s;
	scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
						 w * scale, h * scale,
						 GDK_INTERP_BILINEAR);
	g_object_unref (pixbuf);
	return scaled_pixbuf;
}

const char *
nautilus_icon_info_get_used_name (NautilusIconInfo  *icon)
{
	return icon->icon_name;
}

gint
nautilus_get_icon_size_for_stock_size (GtkIconSize size)
{
  gint w, h;

  if (gtk_icon_size_lookup (size, &w, &h)) {
    return MAX (w, h);
  } 
  return NAUTILUS_CANVAS_ICON_SIZE_SMALL;
}
