/* Pango
 * pangowin32-fontcache.c: Cache of HFONTs by LOGFONTW
 *
 * Copyright (C) 2000 Red Hat Software
 * Copyright (C) 2000 Tor Lillqvist
 * Copyright (C) 2007 Novell, Inc.
 *
 * 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 <stdio.h>

#include "pangowin32-private.h"

/* Font cache
 */

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

typedef struct _CacheEntry CacheEntry;

/**
 * PangoWin32FontCache:
 *
 * A `PangoWin32FontCache` caches HFONTs by their LOGFONT descriptions.
 */
struct _PangoWin32FontCache
{
  GHashTable *forward;
  GHashTable *back;

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

struct _CacheEntry
{
  LOGFONTW logfontw;
  HFONT hfont;

  gint ref_count;
  GList *mru;
};

static void
free_cache_entry (LOGFONTW            *logfont,
		  CacheEntry          *entry,
		  PangoWin32FontCache *cache)
{
  if (!DeleteObject (entry->hfont))
    PING (("DeleteObject for hfont %p failed", entry->hfont));

  g_slice_free (CacheEntry, entry);
}

/**
 * pango_win32_font_cache_free:
 * @cache: a `PangoWin32FontCache`
 *
 * Frees a `PangoWin32FontCache` and all associated memory.
 *
 * All fonts loaded through this font cache will be freed
 * along with the cache.
 */
void
pango_win32_font_cache_free (PangoWin32FontCache *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 (PangoWin32FontCache, cache);
}

static guint
wcs_hash (gconstpointer v)
{
  /* 31 bit hash function */
  const wchar_t *p = v;
  guint32 h = *p;

  if (h)
    for (p += 1; *p != '\0'; p++)
      h = (h << 5) - h + *p;

  return h;
}

static guint
logfontw_hash (gconstpointer v)
{
  const LOGFONTW *lfp = v;

  return wcs_hash (lfp->lfFaceName) +
    (lfp->lfItalic != 0) +
    lfp->lfWeight/10 +
    lfp->lfOrientation +
    abs (lfp->lfHeight) * 10;
}

static gint
logfontw_equal (gconstpointer v1,
		gconstpointer v2)
{
  const LOGFONTW *lfp1 = v1, *lfp2 = v2;

  return (wcscmp (lfp1->lfFaceName, lfp2->lfFaceName) == 0
	  && lfp1->lfPitchAndFamily == lfp2->lfPitchAndFamily
	  && lfp1->lfStrikeOut == lfp2->lfStrikeOut
	  && lfp1->lfUnderline == lfp2->lfUnderline
	  && (lfp1->lfItalic != 0) == (lfp2->lfItalic != 0)
	  && lfp1->lfWeight == lfp2->lfWeight
	  && lfp1->lfOrientation == lfp2->lfOrientation
	  && lfp1->lfEscapement == lfp2->lfEscapement
	  && lfp1->lfWidth == lfp2->lfWidth
	  && lfp1->lfHeight == lfp2->lfHeight);
}

/**
 * pango_win32_font_cache_new:
 *
 * Creates a font cache.
 *
 * Return value: The new font cache. This must be freed with
 * pango_win32_font_cache_free().
 **/
PangoWin32FontCache *
pango_win32_font_cache_new (void)
{
  PangoWin32FontCache *cache;

  cache = g_slice_new (PangoWin32FontCache);

  cache->forward = g_hash_table_new (logfontw_hash, logfontw_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 (PangoWin32FontCache *cache,
		   CacheEntry          *entry)
{
  if (g_atomic_int_dec_and_test (&entry->ref_count))
    {
      PING (("removing cache entry %p", entry->hfont));

      g_hash_table_remove (cache->forward, &entry->logfontw);
      g_hash_table_remove (cache->back, entry->hfont);

      free_cache_entry (NULL, entry, cache);
    }
}

/**
 * pango_win32_font_cache_load:
 * @cache: a `PangoWin32FontCache`
 * @logfont: a pointer to a LOGFONTA structure describing the font to load.
 *
 * Creates a HFONT from a LOGFONTA. The
 * result may be newly loaded, or it may have been previously
 * stored
 *
 * Return value: (nullable): The font structure, or %NULL if the font
 *   could not be loaded. In order to free this structure, you must call
 *   [method@Pango.Win32FontCache.unload].
 */
HFONT
pango_win32_font_cache_load (PangoWin32FontCache *cache,
			     const LOGFONTA      *lfp)
{
  LOGFONTW lf;

  /* We know that the lfFaceName is the last member in the structs */
  *(LOGFONTA *)&lf = *lfp;
  
  if (!MultiByteToWideChar (CP_ACP, MB_ERR_INVALID_CHARS,
			    lfp->lfFaceName, -1,
			    lf.lfFaceName, G_N_ELEMENTS (lf.lfFaceName)))
    return NULL;

  return pango_win32_font_cache_loadw (cache, &lf);
}

/**
 * pango_win32_font_cache_loadw:
 * @cache: a `PangoWin32FontCache`
 * @logfont: a pointer to a LOGFONTW structure describing the font to load.
 *
 * Creates a HFONT from a LOGFONTW. The
 * result may be newly loaded, or it may have been previously
 * stored
 *
 * Return value: (nullable): The font structure, or %NULL if the font
 *   could not be loaded. In order to free this structure, you must call
 *   [method@Pango.Win32FontCache.unload].
 *
 * Since: 1.16
 */
HFONT
pango_win32_font_cache_loadw (PangoWin32FontCache *cache,
			      const LOGFONTW      *lfp)
{
  CacheEntry *entry;
  LOGFONTW lf;
  HFONT hfont;
  int tries;

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

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

  if (entry)
    {
      g_atomic_int_inc (&entry->ref_count);
      PING (("increased refcount for cache entry %p: %d", entry->hfont, entry->ref_count));
    }
  else
    {
      BOOL font_smoothing;
      lf = *lfp;
      SystemParametersInfo (SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0);
      /* use ClearType if the global system settings ask for it. */
      if (font_smoothing)
	{
	  UINT smoothing_type;

#ifndef SPI_GETFONTSMOOTHINGTYPE
#define SPI_GETFONTSMOOTHINGTYPE 0x200a
#endif
#ifndef FE_FONTSMOOTHINGCLEARTYPE
#define FE_FONTSMOOTHINGCLEARTYPE 2
#endif
#ifndef CLEARTYPE_QUALITY
#define CLEARTYPE_QUALITY 5
#endif
	  SystemParametersInfo (SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing_type, 0);
	  lf.lfQuality =
	    (font_smoothing ?
	     (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE ?
	      CLEARTYPE_QUALITY : ANTIALIASED_QUALITY) :
	     DEFAULT_QUALITY);
	}
      else
	lf.lfQuality = (font_smoothing ? ANTIALIASED_QUALITY : DEFAULT_QUALITY);
      lf.lfCharSet = DEFAULT_CHARSET;
      for (tries = 0; ; tries++)
	{
	  PING (("... trying CreateFontIndirect "
		 "height=%ld,width=%ld,escapement=%ld,orientation=%ld,"
		 "weight=%ld,%s%s%s"
		 "charset=%d,outprecision=%d,clipprecision=%d,"
		 "quality=%d,pitchandfamily=%#.02x,facename=\"%S\")",
		 lf.lfHeight, lf.lfWidth, lf.lfEscapement, lf.lfOrientation,
		 lf.lfWeight, (lf.lfItalic ? "italic," : ""),
		 (lf.lfUnderline ? "underline," : ""),
		 (lf.lfStrikeOut ? "strikeout," : ""),
		 lf.lfCharSet, lf.lfOutPrecision, lf.lfClipPrecision,
		 lf.lfQuality, lf.lfPitchAndFamily, lf.lfFaceName));
	  hfont = CreateFontIndirectW (&lf);

	  if (hfont != NULL)
	    {
	      PING (("Success! hfont=%p", hfont));
	      break;
	    }

	  /* If we fail, try some similar fonts often found on Windows. */
	  if (tries == 0)
	    {
	      gchar *p = g_utf16_to_utf8 (lf.lfFaceName, -1, NULL, NULL, NULL);
	      if (!p)
		; /* Nothing */
	      else if (g_ascii_strcasecmp (p, "helvetica") == 0)
		wcscpy (lf.lfFaceName, L"arial");
	      else if (g_ascii_strcasecmp (p, "new century schoolbook") == 0)
		wcscpy (lf.lfFaceName, L"century schoolbook");
	      else if (g_ascii_strcasecmp (p, "courier") == 0)
		wcscpy (lf.lfFaceName, L"courier new");
	      else if (g_ascii_strcasecmp (p, "lucida") == 0)
		wcscpy (lf.lfFaceName, L"lucida sans unicode");
	      else if (g_ascii_strcasecmp (p, "lucidatypewriter") == 0)
		wcscpy (lf.lfFaceName, L"lucida console");
	      else if (g_ascii_strcasecmp (p, "times") == 0)
		wcscpy (lf.lfFaceName, L"times new roman");
	      g_free (p);
	    }
	  else if (tries == 1)
	    {
	      gchar *p = g_utf16_to_utf8 (lf.lfFaceName, -1, NULL, NULL, NULL);
	      if (!p)
		; /* Nothing */
	      else if (g_ascii_strcasecmp (p, "courier") == 0)
		{
		  wcscpy (lf.lfFaceName, L"");
		  lf.lfPitchAndFamily |= FF_MODERN;
		}
	      else if (g_ascii_strcasecmp (p, "times new roman") == 0)
		{
		  wcscpy (lf.lfFaceName, L"");
		  lf.lfPitchAndFamily |= FF_ROMAN;
		}
	      else if (g_ascii_strcasecmp (p, "helvetica") == 0
		       || g_ascii_strcasecmp (p, "lucida") == 0)
		{
		  wcscpy (lf.lfFaceName, L"");
		  lf.lfPitchAndFamily |= FF_SWISS;
		}
	      else
		{
		  wcscpy (lf.lfFaceName, L"");
		  lf.lfPitchAndFamily = (lf.lfPitchAndFamily & 0x0F) | FF_DONTCARE;
		}
	      g_free (p);
	    }
	  else
	    break;
	  tries++;
	}

      if (!hfont)
	return NULL;

      entry = g_slice_new (CacheEntry);

      entry->logfontw = lf;
      entry->hfont = hfont;

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

      g_hash_table_insert (cache->forward, &entry->logfontw, entry);
      g_hash_table_insert (cache->back, entry->hfont, 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->hfont;
}

/**
 * pango_win32_font_cache_unload:
 * @cache: a `PangoWin32FontCache`
 * @hfont: the HFONT to unload
 *
 * Frees a font structure previously loaded with
 * [method@Pango.Win32FontCache.load].
 */
void
pango_win32_font_cache_unload (PangoWin32FontCache *cache,
			       HFONT		    hfont)
{
  CacheEntry *entry;

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

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

  cache_entry_unref (cache, entry);
}
