/* Pango
 * pango-fontcache.c: Cache of XFontStructs by XLFD
 *
 * Copyright (C) 2000 Red Hat Software
 *
 * 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"
#include "pangox.h"

/* Font cache
 */

/* Number of fonts to retain after they are not otherwise referenced.
 */
#define CACHE_SIZE 16

typedef struct _CacheEntry CacheEntry;

struct _PangoXFontCache
{
  Display *display;

  GHashTable *forward;
  GHashTable *back;

  GList *mru;
  GList *mru_tail;
  int mru_count;
};

struct _CacheEntry
{
  char *xlfd;
  XFontStruct *fs;

  gint ref_count;
  GList *mru;
};

static void
free_cache_entry (char            *xlfd G_GNUC_UNUSED,
		  CacheEntry      *entry,
		  PangoXFontCache *cache)
{
  g_free (entry->xlfd);
  XFreeFont (cache->display, entry->fs);

  g_slice_free (CacheEntry, entry);
}

/**
 * pango_x_font_cache_free:
 * @cache: a #PangoXFontCache
 *
 * Frees a #PangoXFontCache and all associated memory. All fonts loaded
 * through this font cache will be freed along with the cache.
 **/
void
pango_x_font_cache_free (PangoXFontCache *cache)
{
  g_return_if_fail (cache != NULL);

  g_hash_table_foreach (cache->forward, (GHFunc)free_cache_entry, cache);

  g_hash_table_destroy (cache->forward);
  g_hash_table_destroy (cache->back);

  g_list_free (cache->mru);

  g_slice_free (PangoXFontCache, cache);
}

/**
 * pango_x_font_cache_new:
 * @display: an X display.
 *
 * Creates a font cache for the specified display.
 *
 * Return value: The newly allocated #PangoXFontCache, which should be
 *               freed with pango_x_font_cache_free().
 **/
PangoXFontCache *
pango_x_font_cache_new (Display *display)
{
  PangoXFontCache *cache;

  g_return_val_if_fail (display != NULL, NULL);

  cache = g_slice_new (PangoXFontCache);

  cache->display = display;

  cache->forward = g_hash_table_new (g_str_hash, g_str_equal);
  cache->back = g_hash_table_new (g_direct_hash, g_direct_equal);

  cache->mru = NULL;
  cache->mru_tail = NULL;
  cache->mru_count = 0;

  return cache;
}

static void
cache_entry_unref (PangoXFontCache *cache, CacheEntry *entry)
{
  if (g_atomic_int_dec_and_test (&entry->ref_count))
    {
      g_hash_table_remove (cache->forward, entry->xlfd);
      g_hash_table_remove (cache->back, entry->fs);

      free_cache_entry (NULL, entry, cache);
    }
}

/**
 * pango_x_font_cache_load:
 * @cache: a #PangoXFontCache
 * @xlfd: the X Logical Font Description to load.
 *
 * Loads a #XFontStruct from a X Logical Font Description. The
 * result may be newly loaded, or it may have been previously
 * stored.
 *
 * Return value: The font structure, or %NULL if the font could
 * not be loaded. In order to free this structure, you must call
 * pango_x_font_cache_unload().
 **/
XFontStruct *
pango_x_font_cache_load (PangoXFontCache *cache,
			 const char      *xlfd)
{
  CacheEntry *entry;

  g_return_val_if_fail (cache != NULL, NULL);
  g_return_val_if_fail (xlfd != NULL, NULL);

  entry = g_hash_table_lookup (cache->forward, xlfd);

  if (entry)
    {
      g_atomic_int_inc (&entry->ref_count);
    }
  else
    {
      XFontStruct *fs = XLoadQueryFont (cache->display, xlfd);

      if (!fs)
	return NULL;

      entry = g_slice_new (CacheEntry);

      entry->xlfd = g_strdup (xlfd);
      entry->fs = fs;

      entry->ref_count = 1;
      entry->mru = NULL;

      g_hash_table_insert (cache->forward, entry->xlfd, entry);
      g_hash_table_insert (cache->back, entry->fs, entry);
    }

  if (entry->mru)
    {
      if (cache->mru_count > 1 && entry->mru->prev)
	{
	  /* Move to the head of the mru list */

	  if (entry->mru == cache->mru_tail)
	    {
	      cache->mru_tail = cache->mru_tail->prev;
	      cache->mru_tail->next = NULL;
	    }
	  else
	    {
	      entry->mru->prev->next = entry->mru->next;
	      entry->mru->next->prev = entry->mru->prev;
	    }

	  entry->mru->next = cache->mru;
	  entry->mru->prev = NULL;
	  cache->mru->prev = entry->mru;
	  cache->mru = entry->mru;
	}
    }
  else
    {
      g_atomic_int_inc (&entry->ref_count);

      /* Insert into the mru list */

      if (cache->mru_count == CACHE_SIZE)
	{
	  CacheEntry *old_entry = cache->mru_tail->data;

	  cache->mru_tail = cache->mru_tail->prev;
	  cache->mru_tail->next = NULL;

	  g_list_free_1 (old_entry->mru);
	  old_entry->mru = NULL;
	  cache_entry_unref (cache, old_entry);
	}
      else
	cache->mru_count++;

      cache->mru = g_list_prepend (cache->mru, entry);
      if (!cache->mru_tail)
	cache->mru_tail = cache->mru;
      entry->mru = cache->mru;
    }

  return entry->fs;
}

/**
 * pango_x_font_cache_unload:
 * @cache: a #PangoXFontCache
 * @fs: the font structure to unload
 *
 * Frees a font structure previously loaded with pango_x_font_cache_load().
 **/
void
pango_x_font_cache_unload (PangoXFontCache *cache,
			   XFontStruct     *fs)
{
  CacheEntry *entry;

  g_return_if_fail (cache != NULL);
  g_return_if_fail (fs != NULL);

  entry = g_hash_table_lookup (cache->back, fs);
  g_return_if_fail (entry != NULL);

  cache_entry_unref (cache, entry);
}
