/*-
 * Copyright (c) 1998-1999 Shunsuke Akiyama <akiyama@jp.FreeBSD.org>.
 * All rights reserved.
 * Copyright (c) 1998-1999 X-TrueType Server Project, All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	Id: fontcache.c,v 1.19 1999/01/31 13:06:00 akiyama Exp $
 */
/* $XFree86: xc/lib/font/fontcache/fontcache.c,v 1.4 2001/04/05 17:42:28 dawes Exp $ */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "fontcache.h"

#define LOW_MARK	0
#define HI_MARK		1

#define PURGE_ENTRY	1
#define PURGE_BITMAP	2

typedef struct {
    long hiMark;	/* Cache hi water mark */
    long lowMark;	/* Cache low water mark */
    long allocated;	/* Cache allocated size */
    long used;		/* Cache used size */
} FontCacheSize_t;

static int CacheInitialized = 0;

static TAILQ_HEAD(FcInUseQueue, cache_entry) InUseQueueHead, *InUseQueue;
static TAILQ_HEAD(FcFreeQueue, cache_entry) FreeQueueHead, *FreeQueue;
static FCBCB FreeBitmapHead, *FreeBitmap;

static long CacheHiMark;
static long CacheLowMark;
static int CacheBalance;
static FontCacheSize_t HashSize;
static FontCacheSize_t AllocSize;
static int NeedPurgeCache;
static FontCacheStatistics CacheStatistics;

static void fc_assign_cache(void);
static int fc_assign_entry(void);
static void fc_flush_cache(void);
static int fc_get_bitmap_area(FontCacheEntryPtr, int);
static void fc_free_bitmap_area(FontCacheBitmapPtr);
static int fc_check_size(int);
static void fc_purge_cache(void);
static void fc_purge_bitmap(void);
static void fc_flush_cache_bitmap(void);
static void fc_flush_cache_inuse(void);
static void fc_flush_cache_free(void);
static void fc_purge_cache_entry(void);
static void fc_purge_cache_entry_pool(void);
static void fc_purge_bitmap_pool(void);


/*
 *  FontCacheInitialize()
 *
 *  Initialize cache work area.
 */

int
FontCacheInitialize()
{
#ifdef FONTCACHE
    int i;

    if (!CacheInitialized) {
	/*
	 *  first time initialization
	 */
#if defined(HASH_DEBUG) || defined(DEBUG)
	fprintf(stderr, "FontCacheInitialize: initializing cache\n");
#endif
	InUseQueue = &InUseQueueHead;
	TAILQ_INIT(InUseQueue);

	FreeQueue = &FreeQueueHead;
	TAILQ_INIT(FreeQueue);

	FreeBitmap = &FreeBitmapHead;
	FreeBitmap->index = 0;
	for (i = 0; i < FC_MEM_HASH_SIZE; i++) {
	    TAILQ_INIT(&FreeBitmap->head[i]);
	}

	CacheHiMark = FC_DEFAULT_CACHE_SIZE * 1024;	/* temporary */
	CacheLowMark = (CacheHiMark / 4) * 3;
	CacheBalance = FC_CACHE_BALANCE;

	NeedPurgeCache = 0;

	HashSize.allocated = HashSize.used = 0;
	AllocSize.allocated = AllocSize.used = 0;
	fc_assign_cache();
	fc_assign_entry();
#if defined(DEBUG)
	fprintf(stderr, "FontCacheInitialize: hi=%ld, lo=%ld, bal=%d\n",
        	CacheHiMark, CacheLowMark, CacheBalance);
#endif

	CacheInitialized = 1;
    } else {
	/*
	 *  second time or later case.
	 *  flush and reassign cache.
	 */
#if defined(HASH_DEBUG) || defined(DEBUG)
	fprintf(stderr, "FontCacheInitialize: initializing cache, again\n");
#endif
    }

    memset(&CacheStatistics, 0, sizeof (CacheStatistics));
#endif /* FONTCACHE */

    return 0;		/* make lint happy */
}

/*
 *  FontCacheChangeSettings()
 *
 *  Change cache size and reinitialize work areas.
 *
 *  Returns 0, if memory allocation failed.  Otherwise 1.
 */

int
FontCacheChangeSettings(FontCacheSettingsPtr cs)
{
    int result;

    if (!CacheInitialized) {
        FontCacheInitialize();
        if (!CacheInitialized)
            return 0;
    }

#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr,
	"FontCahceChangeSettings: hi-mark=%ld, low-mark=%ld, balance=%ld\n",
	cs->himark, cs->lowmark, cs->balance);
#endif

    fc_flush_cache();

    CacheHiMark = cs->himark;
    CacheLowMark = cs->lowmark;
    CacheBalance = cs->balance;

    fc_assign_cache();
    result = fc_assign_entry();

    return result;
}

/*
 *  FontCacheGetSettings()
 *
 *  Get current cache control parameters.
 */

void
FontCacheGetSettings(FontCacheSettingsPtr cs)
{
    if (!CacheInitialized) {
        FontCacheInitialize();
        if (!CacheInitialized)
            return;
    }

    cs->himark = CacheHiMark;
    cs->lowmark = CacheLowMark;
    cs->balance = CacheBalance;
}

/*
 *  FontCacheGetStatistics()
 *
 *  Get current cache statistics.
 */

void
FontCacheGetStatistics(FontCacheStatisticsPtr cs)
{
    if (!CacheInitialized) {
        FontCacheInitialize();
        if (!CacheInitialized)
            return;
    }

    CacheStatistics.purge_stat = NeedPurgeCache;
    CacheStatistics.balance = CacheBalance;
    CacheStatistics.f.usage = HashSize.used;
    CacheStatistics.v.usage = AllocSize.used;

    memcpy(cs, &CacheStatistics, sizeof (CacheStatistics));
}

/*
 *  FontCacheOpenCache()
 *
 *  Allocate font cache control block and initialize it.
 *
 *  Returns pointer to font cache control block.  Or returns NULL when
 *  detected illegal parameter or memory allocation failed.
 */

FCCBPtr
FontCacheOpenCache(void *arg)
{
    int linesize;
    FCCBPtr this;
    int size = 0, mask = 0;
    int i;

    static int sizes[] = { 16, 32, 64, 128, 0 };

    if (!CacheInitialized) {
        FontCacheInitialize();
        if (!CacheInitialized)
            return NULL;
    }

    linesize = (long)arg;
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "FontCacheOpenCache: line size=%d\n", linesize);
#endif

    for (i = 0; sizes[i] != 0; i++) {
	if (sizes[i] == linesize) {
	    size = linesize;
	    mask = linesize - 1;
	    break;
	}
    }
    if (sizes[i] == 0) {
	return NULL;
    }

    this = (FCCBPtr) malloc(sizeof (FCCB));
    if (this != NULL) {
	memset(this, 0, sizeof (FCCB));
	this->head = (FontCacheHeadPtr) malloc(sizeof (FontCacheHead) * size);
	if (this->head == NULL) {
	    free(this);
	    this = NULL;
	} else {
	    this->size = size;
	    this->mask = mask;
	    for (i = 0; i < size; i++) {
		TAILQ_INIT(&this->head[i]);
	    }
	}
    }

    return this;
}

/*
 *  FontCacheCloseCache()
 *
 *  Release font cache control block and all it's related entries.
 */

void
FontCacheCloseCache(FCCBPtr this)
{
    FontCacheEntryPtr entry, next;
    int i;
    int size;

    if (!CacheInitialized) {
	return;
    }

    size = this->size;
    for (i = 0; i < size; i++) {
	entry = TAILQ_FIRST(&this->head[i]);
	while (entry != NULL) {
	    /* remove entry from in-use queue, here */
	    TAILQ_REMOVE(InUseQueue, entry, c_lru);
	    
	    /* remove entry from the hash */
	    if (entry->bitmapsize > FC_SMALL_BITMAP_SIZE
		&& entry->charInfo.bits != NULL) {
		fc_free_bitmap_area(entry->bmp);
	    }
	    entry->charInfo.bits = NULL;
	    entry->bitmapsize = 0;

	    next = TAILQ_NEXT(entry, c_hash);
	    TAILQ_INSERT_HEAD(FreeQueue, entry, c_lru);
	    HashSize.used -= sizeof (FontCacheEntry);
	    entry = next;
	}
    }

    free(this->head);
    free(this);
}

/*
 *  FontCacheGetEntry()
 *
 *  Allocate font cache entry and initialize it.
 */

FontCacheEntryPtr
FontCacheGetEntry()
{
    FontCacheEntryPtr entry;
    FontCacheEntryPtr p;
    long size;

    /* scan in-use queue and purge if required */
    fc_purge_cache();

    /* allocate hash entry */
    if (TAILQ_EMPTY(FreeQueue)) {
	size = sizeof (FontCacheEntry);
	p = (FontCacheEntryPtr) malloc(size);
	if (p != NULL) {
	    TAILQ_INSERT_HEAD(FreeQueue, p, c_lru);
	    HashSize.allocated += size;
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "FontCachegetEntry: allocated new entry\n");
#endif
	}
    }

    if (!TAILQ_EMPTY(FreeQueue)) {
	entry = TAILQ_FIRST(FreeQueue);
	TAILQ_REMOVE(FreeQueue, entry, c_lru);
	memset(entry, 0, sizeof (FontCacheEntry));
    } else {
	entry = NULL;
    }

    return entry;
}

/*
 *  FontCacheGetBitmap()
 *
 *  Allocate font glyph bitmap area.
 *
 *  Note:
 *    Allocated area should be cleared.
 */

int
FontCacheGetBitmap(FontCacheEntryPtr entry, int size)
{
    int oldsize;
    int result;

    /* XXX */
    if ((AllocSize.used > AllocSize.hiMark - size) &&
       (size > FC_SMALL_BITMAP_SIZE)) {
      fc_purge_bitmap();
    }

    if (size < 0) /* wrong size */
      return 0;

    result = 0;
    oldsize = entry->bitmapsize;
    if (size <= FC_SMALL_BITMAP_SIZE) {
	/* use coresponding bitmap area */
	if (oldsize > FC_SMALL_BITMAP_SIZE) {
	    /* We don't need allocated area anymore */
	    fc_free_bitmap_area(entry->bmp);
	}
	entry->bitmapsize = size;
	if (size > 0) {
	entry->charInfo.bits = entry->bitmap;
	memset(entry->charInfo.bits, 0, size);
	} else
	  entry->charInfo.bits = NULL;
	  
	result = 1;
    } else {
	/* need extra bitmap area */
	if (entry->charInfo.bits == NULL) {
	    /* no any extra bitmap area */
	    if (fc_get_bitmap_area(entry, size)) {
		entry->bitmapsize = size;
		memset(entry->charInfo.bits, 0, size);
		if (fc_check_size(HI_MARK)) {
		    fc_purge_cache();
		}
		result = 1;
	    }
	} else {
	    /* we already have extra bitmap area */
	    if (oldsize == size) {
		/* same size, reuse it */
		memset(entry->charInfo.bits, 0, size);
		result = 1;
	    } else {
		/* different size */
		fc_free_bitmap_area(entry->bmp);
		if (fc_get_bitmap_area(entry, size)) {
		    entry->bitmapsize = size;
		    memset(entry->charInfo.bits, 0, size);
		    if (fc_check_size(HI_MARK)) {
			fc_purge_cache();
		    }
		    result = 1;
		}
	    }
	}
    }

    return result;
}

/*
 *  FontCacheSearchEntry()
 *
 *  Search an entry matched with the key from the hash.
 */

int
FontCacheSearchEntry(FCCBPtr this, int key, FontCacheEntryPtr *value)
{
    FontCacheHeadPtr head;
    FontCacheEntryPtr entry;
    int index;

    index = key & this->mask;
    head = &this->head[index];

    TAILQ_FOREACH(entry, head, c_hash) {
	if (entry->key == key) {
	    /* found, change position */
	    CacheStatistics.f.hits++;

	    TAILQ_REMOVE(InUseQueue, entry, c_lru);
	    TAILQ_INSERT_HEAD(InUseQueue, entry, c_lru);

	    TAILQ_REMOVE(head, entry, c_hash);
	    TAILQ_INSERT_HEAD(head, entry, c_hash);

	    /* purge least recentrly used cache entirs */
	    fc_purge_cache();

	    *value = entry;
	    return 1;
	}
    }

    /* purge least recentrly used cache entirs */
    fc_purge_cache();

    /* not found */
    CacheStatistics.f.misshits++;
    *value = NULL;
    return 0;
}

/*
 *  FontCacheInsertEntry()
 *
 *  Insert an entry into the cache pool.
 */

int
FontCacheInsertEntry(FCCBPtr this, int key, FontCacheEntryPtr entry)
{
    FontCacheHeadPtr head;
    int index;

    index = key & this->mask;
    head = &this->head[index];

    entry->key = key;
    entry->c_head = head;
    TAILQ_INSERT_HEAD(head, entry, c_hash);

    /* insert entry into in-use queue */
    TAILQ_INSERT_HEAD(InUseQueue, entry, c_lru);

    /* adjust cache in-use size */
    HashSize.used += sizeof (FontCacheEntry);
    if (fc_check_size(HI_MARK)) {
	fc_purge_cache();
    }

    return 1;
}

/*
 *  fc_assign_cache()
 *
 *  Assign cache size considered with cache balance rate.
 */

static void
fc_assign_cache()
{
    HashSize.hiMark = (CacheHiMark * CacheBalance) / 100;
    HashSize.lowMark = (CacheLowMark * CacheBalance) / 100;

    AllocSize.hiMark = (CacheHiMark * (100 - CacheBalance)) / 100;
    AllocSize.lowMark = (CacheLowMark * (100 - CacheBalance)) / 100;
}

/*
 *  fc_assign_entry()
 *
 *  Assign cache entry into free queue.
 *
 *  Returns 0, when memory allocation failed.  Otherwise 1.
 */

static int
fc_assign_entry()
{
    FontCacheEntryPtr entry;
    long used;
    int result = 1;

    used = 0;
    while ((used + sizeof (FontCacheEntry)) < HashSize.hiMark) {
	entry = (FontCacheEntryPtr) malloc(sizeof (FontCacheEntry));
	if (entry == NULL) {
	    fprintf(stderr, "fc_assign_entry: can't allocate memory.\n");
	    result = 0;
	    break;
	}
	TAILQ_INSERT_HEAD(FreeQueue, entry, c_lru);
	used += sizeof (FontCacheEntry);
	HashSize.allocated += sizeof (FontCacheEntry);
    }

    return result;
}

/*
 *  fc_get_bitmap_area()
 *
 *  Search allocated memory area from free bitmap hash pool.  If there
 *  is no entry, then allocate new bitmap area.
 *
 *  Returns 0, when memory allocation failed, otherwise 1.  And some
 *  sort of cache entry structure members were updated.
 */

static int
fc_get_bitmap_area(FontCacheEntryPtr this, int size)
{
    FontCacheBitmapHeadPtr head;
    FontCacheBitmapPtr bitmap;
    int index;
    int result = 0;

    index = size & FC_MEM_HASH_MASK;
    head = &FreeBitmap->head[index];
    TAILQ_FOREACH(bitmap, head, b_hash) {
	if (bitmap->key == size) {
	    TAILQ_REMOVE(head, bitmap, b_hash);
	    this->bmp = bitmap;
	    this->charInfo.bits = (char *) (bitmap + 1);
	    bitmap->b_entry = this;
	    result = 1;
	    CacheStatistics.v.hits++;
	    AllocSize.used += (size + sizeof (FontCacheBitmap));
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_get_bitmap_area: bitmap entry found in pool\n");
#endif
	    break;
	}
    }

    if (result == 0) {
        CacheStatistics.v.misshits++;
	bitmap = (FontCacheBitmapPtr) malloc(size + sizeof (FontCacheBitmap));
	if (bitmap != NULL) {
	    bitmap->b_entry = this;
	    bitmap->size = size + sizeof (FontCacheBitmap);
	    bitmap->key = size;
	    this->bmp = bitmap;
	    this->charInfo.bits = (char *) (bitmap + 1);
	    AllocSize.allocated += (size + sizeof (FontCacheBitmap));
	    AllocSize.used += (size + sizeof (FontCacheBitmap));
	    result = 1;
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_get_bitmap_area: bitmap entry allocated\n");
#endif
	} else {
	    this->bmp = NULL;
	    this->charInfo.bits = NULL;
	}
    }

    return result;
}

/*
 *  fc_free_bitmap_area()
 *
 *  Release allocated bitmap area into free hash pool.
 */

static void
fc_free_bitmap_area(FontCacheBitmapPtr this)
{
    FontCacheBitmapHeadPtr head;
    FontCacheEntryPtr entry;
    int index;

#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_free_bitmap_area: bitmap entry returns into pool\n");
#endif

    index = this->key & FC_MEM_HASH_MASK;
    head = &FreeBitmap->head[index];
    TAILQ_INSERT_HEAD(head, this, b_hash);

    AllocSize.used -= this->size;

    entry = this->b_entry;
    entry->bmp = NULL;
    entry->bitmapsize = 0;
}

/*
 *  fc_flush_cache_bitmap()
 *
 *  Flush all allocated bitmap area from the free hash pool.
 */

static void
fc_flush_cache_bitmap()
{
    FontCacheBitmapHeadPtr head;
    FontCacheBitmapPtr bitmap;
    int i;

    for (i = 0; i < FC_MEM_HASH_SIZE; i++) {
	head = &FreeBitmap->head[i];
	while (!TAILQ_EMPTY(head)) {
	    bitmap = TAILQ_FIRST(head);
	    TAILQ_REMOVE(head, bitmap, b_hash);

	    AllocSize.allocated -= bitmap->size;
	    free(bitmap);
	}
    }
}

/*
 *  fc_flush_cache_inuse()
 *
 *  Release all in-use cache entries.
 */

static void
fc_flush_cache_inuse()
{
    FontCacheEntryPtr entry;
    FontCacheHeadPtr head;

    while (!TAILQ_EMPTY(InUseQueue)) {
	/* remove this entry from in-use queue */
	entry = TAILQ_FIRST(InUseQueue);
	TAILQ_REMOVE(InUseQueue, entry, c_lru);

	/* remove this entry from hash */
	head = entry->c_head;
	TAILQ_REMOVE(head, entry, c_hash);

	/* release bitmap area */
	if (entry->bitmapsize > FC_SMALL_BITMAP_SIZE
	    && entry->charInfo.bits != NULL) {
	    fc_free_bitmap_area(entry->bmp);
	}
	entry->charInfo.bits = NULL;
	entry->bitmapsize = 0;

        /* release font-specific private area */
        if ( entry->vfuncs && entry->vfuncs->f_private_dispose )
            (*entry->vfuncs->f_private_dispose)(entry->f_private);
        entry->f_private = NULL;
        entry->vfuncs = NULL;

	/* add this entry to free queue */
	TAILQ_INSERT_HEAD(FreeQueue, entry, c_lru);

	/* adjust size */
	HashSize.used -= sizeof (FontCacheEntry);
    }
}

/*
 *  fc_flush_cache_free()
 *
 *  Flush all free cache entries from the free cache queue.
 */

static void
fc_flush_cache_free()
{
    FontCacheEntryPtr entry;

    /* release entire entries of the free queue */
    while (!TAILQ_EMPTY(FreeQueue)) {
	entry = TAILQ_FIRST(FreeQueue);
	TAILQ_REMOVE(FreeQueue, entry, c_lru);
	free(entry);
	HashSize.allocated -= sizeof (FontCacheEntry);
    }
}

/*
 *  fc_flush_cache()
 *
 *  Flush all cache entries and allocated bitmap area from the pool.
 */

static void
fc_flush_cache()
{
    fc_flush_cache_inuse();
    fc_flush_cache_bitmap();
    fc_flush_cache_free();

    memset(&CacheStatistics, 0, sizeof (CacheStatistics));
}

/*
 *  fc_check_size()
 *
 *  Check cache size, then return it's result.
 */

static int
fc_check_size(int mark)
{
    int result = 0;

    if (mark == LOW_MARK) {
	if (HashSize.used > HashSize.lowMark) {
	    result |= PURGE_ENTRY;
	}
	if (AllocSize.used > AllocSize.lowMark) {
	    result |= PURGE_BITMAP;
	}
    } else {
	if (HashSize.used > HashSize.hiMark) {
	    result |= PURGE_ENTRY;
	}
	if (AllocSize.used > AllocSize.hiMark) {
	    result |= PURGE_BITMAP;
	}
    }

    return result;
}

/*
 *  fc_purge_cache_entry()
 *
 *  Purge least recently used cache entry.
 */

static void
fc_purge_cache_entry()
{
    FontCacheHeadPtr head;
    FontCacheEntryPtr entry;
    int i;

    for (i = 0; i < FC_PURGE_PER_SCAN; i++) {
	/* get least recently used entry */
	entry = TAILQ_LAST(InUseQueue, FcInUseQueue);

#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_purge_cache_entry: purged: %p, %d\n",
	entry, entry->key);
#endif

	/* remove this entry from in-use queue */
	TAILQ_REMOVE(InUseQueue, entry, c_lru);

	/* remove this entry from the hash */
	head = entry->c_head;
	TAILQ_REMOVE(head, entry, c_hash);

	/* release bitmap area */
	if (entry->bitmapsize > FC_SMALL_BITMAP_SIZE
	    && entry->charInfo.bits != NULL) {
	    fc_free_bitmap_area(entry->bmp);
	    CacheStatistics.v.purged++;
	}
	entry->charInfo.bits = NULL;
	entry->bitmapsize = 0;

        /* release font-specific private area */
        if ( entry->vfuncs && entry->vfuncs->f_private_dispose )
            (*entry->vfuncs->f_private_dispose)(entry->f_private);
        entry->f_private = NULL;
        entry->vfuncs = NULL;

	/* add this entry to free queue */
	TAILQ_INSERT_HEAD(FreeQueue, entry, c_lru);

	HashSize.used -= sizeof (FontCacheEntry);
	CacheStatistics.f.purged++;
    }
}

/*
 *  fc_purge_cache_entry_pool()
 *
 *  Purge free cache entries, to adjust cache size.
 */

static void
fc_purge_cache_entry_pool()
{
    FontCacheEntryPtr entry;

    while (!TAILQ_EMPTY(FreeQueue)) {
	entry = TAILQ_LAST(FreeQueue, FcFreeQueue);
	TAILQ_REMOVE(FreeQueue, entry, c_lru);
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_purge_cache_entry_pool: purged from free queue: %p\n",
	entry);
#endif
	HashSize.allocated -= sizeof (FontCacheEntry);
	free(entry);
	if (HashSize.allocated <= HashSize.hiMark) {
	    break;
	}
    }
}

/*
 *  fc_purge_bitmap()
 *
 *  Purge least recently used allocated bitmap area.
 */

static void
fc_purge_bitmap()
{
    FontCacheEntryPtr entry, first;
    int purged = 0;

    /* release used entry, if required */
    first = TAILQ_FIRST(InUseQueue);
    if (first != NULL) {
	entry = TAILQ_LAST(InUseQueue, FcInUseQueue);
	while (purged < FC_PURGE_PER_SCAN) {
	    if (entry->bmp != NULL) {
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_purge_bitmap: purged from live queue: %p, %d(%d)\n",
	entry->bmp, entry->bmp->key, entry->bmp->size);
#endif
		fc_free_bitmap_area(entry->bmp);
		entry->charInfo.bits = NULL;
		CacheStatistics.v.purged++;
		purged++;
	    }
	    if (entry == first) {
		break;
	    }
	    entry = TAILQ_PREV(entry, FcInUseQueue, c_lru);
	}
    }
}

/*
 *  fc_purge_bitmap_pool()
 *
 *  Purge free bitmap area from pool, to adjust cache size.
 */

static void
fc_purge_bitmap_pool()
{
    int this, stop, quit;
    FontCacheBitmapHeadPtr head;
    FontCacheBitmapPtr bitmap;

    /* release free bitmap entry */
    this = FreeBitmap->index;
    stop = this;
    quit = 0;

    do {
	head = &FreeBitmap->head[this];
	while (!TAILQ_EMPTY(head)) {
	    bitmap = TAILQ_LAST(head, fcmem_head);
	    TAILQ_REMOVE(head, bitmap, b_hash);
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_purge_bitmap_pool: purged from pool: %p, %d(%d)\n",
	bitmap, bitmap->key, bitmap->size);
#endif
	    AllocSize.allocated -= bitmap->size;
	    free(bitmap);
	    if (AllocSize.allocated <= AllocSize.hiMark) {
		quit = 1;
		break;
	    }
	}
	this++;
	this &= FC_MEM_HASH_MASK;
    } while (this != stop && quit == 0);

    FreeBitmap->index++;
    FreeBitmap->index &= FC_MEM_HASH_MASK;
}

/*
 *  fc_purge_cache()
 *
 *  Purge font cache, if required.
 */

static void
fc_purge_cache()
{
    int strategy;

    if (NeedPurgeCache) {
	strategy = fc_check_size(LOW_MARK);
	switch (strategy) {
	case PURGE_ENTRY :
	    CacheStatistics.purge_runs++;
	    fc_purge_cache_entry();
	    break;
	case PURGE_BITMAP :
	    CacheStatistics.purge_runs++;
	    fc_purge_bitmap();
	    break;
	case (PURGE_ENTRY | PURGE_BITMAP) :
	    CacheStatistics.purge_runs++;
	    fc_purge_cache_entry();
	    fc_purge_bitmap();
	    break;
	default :
	    NeedPurgeCache = 0;
	    break;
	}
    } else {
	strategy = fc_check_size(HI_MARK);
	switch (strategy) {
	case PURGE_ENTRY :
	    if ((CacheBalance + FC_BALANCE_DIFFS) <= FC_BALANCE_HI) {
		CacheBalance += FC_BALANCE_DIFFS;
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_purge_cache: cache balance changed to %d\n", CacheBalance);
#endif
		fc_assign_cache();
		fc_purge_bitmap_pool();
	    } else {
		CacheStatistics.purge_runs++;
		NeedPurgeCache = 1;
		while (fc_check_size(HI_MARK) & PURGE_ENTRY) {
		    fc_purge_cache_entry();
		}
	    }
	    break;
	case PURGE_BITMAP :
	    if ((CacheBalance - FC_BALANCE_DIFFS) >= FC_BALANCE_LOW) {
		CacheBalance -= FC_BALANCE_DIFFS;
#if defined(HASH_DEBUG) || defined(DEBUG)
fprintf(stderr, "fc_purge_cache: cache balance changed to %d\n", CacheBalance);
#endif
		fc_assign_cache();
		fc_purge_cache_entry_pool();
	    } else {
		CacheStatistics.purge_runs++;
		NeedPurgeCache = 1;
		while (fc_check_size(HI_MARK) & PURGE_BITMAP) {
		    fc_purge_bitmap();
		}
	    }
	    break;
	case (PURGE_ENTRY | PURGE_BITMAP) :
	    CacheStatistics.purge_runs++;
	    NeedPurgeCache = 1;
	    while (fc_check_size(HI_MARK)) {
		fc_purge_cache_entry();
		fc_purge_bitmap();
	    }
	    break;
	default :
	    break;
	}
    }
}
