#include "gd.h"
#include "gdhelpers.h"

#ifdef HAVE_LIBFREETYPE
#define NEED_CACHE 1
#endif

#ifdef NEED_CACHE

/*
 * gdcache.c
 *
 * Caches of pointers to user structs in which the least-recently-used
 * element is replaced in the event of a cache miss after the cache has
 * reached a given size.
 *
 * John Ellson  (ellson@graphviz.org)  Oct 31, 1997
 *
 * Test this with:
 *               gcc -o gdcache -g -Wall -DTEST -DNEED_CACHE gdcache.c -lgd
 *               or
 *               gcc -o gdcache -g -Wall -DTEST -DNEED_CACHE gdcache.c libgd.a
 *
 * The cache is implemented by a singly-linked list of elements
 * each containing a pointer to a user struct that is being managed by
 * the cache.
 *
 * The head structure has a pointer to the most-recently-used
 * element, and elements are moved to this position in the list each
 * time they are used.  The head also contains pointers to three
 * user defined functions:
 *              - a function to test if a cached userdata matches some keydata
 *              - a function to provide a new userdata struct to the cache
 *          if there has been a cache miss.
 *              - a function to release a userdata struct when it is
 *          no longer being managed by the cache
 *
 * In the event of a cache miss the cache is allowed to grow up to
 * a specified maximum size.  After the maximum size is reached then
 * the least-recently-used element is discarded to make room for the
 * new.  The most-recently-returned value is always left at the
 * beginning of the list after retrieval.
 *
 * In the current implementation the cache is traversed by a linear
 * search from most-recent to least-recent.  This linear search
 * probably limits the usefulness of this implementation to cache
 * sizes of a few tens of elements.
 */

#include "gdcache.h"

/*********************************************************/
/* implementation                                        */
/*********************************************************/

/* create a new cache */
gdCache_head_t *gdCacheCreate(int size, gdCacheTestFn_t gdCacheTest, gdCacheFetchFn_t gdCacheFetch,
                              gdCacheReleaseFn_t gdCacheRelease)
{
    gdCache_head_t *head;

    head = (gdCache_head_t *)gdMalloc(sizeof(gdCache_head_t));
    if (!head) {
        return NULL;
    }

    head->mru = NULL;
    head->size = size;
    head->gdCacheTest = gdCacheTest;
    head->gdCacheFetch = gdCacheFetch;
    head->gdCacheRelease = gdCacheRelease;

    return head;
}

void gdCacheDelete(gdCache_head_t *head)
{
    gdCache_element_t *elem, *prev;

    elem = head->mru;
    while (elem) {
        (*(head->gdCacheRelease))(elem->userdata);
        prev = elem;
        elem = elem->next;
        gdFree((char *)prev);
    }
    gdFree((char *)head);
}

void *gdCacheGet(gdCache_head_t *head, void *keydata)
{
    int i = 0;
    gdCache_element_t *elem, *prev = NULL, *prevprev = NULL;
    void *userdata;

    elem = head->mru;
    while (elem) {
        if ((*(head->gdCacheTest))(elem->userdata, keydata)) {
            if (i) {
                /* if not already most-recently-used */
                /* relink to top of list */
                prev->next = elem->next;
                elem->next = head->mru;
                head->mru = elem;
            }
            return elem->userdata;
        }
        prevprev = prev;
        prev = elem;
        elem = elem->next;
        i++;
    }
    userdata = (*(head->gdCacheFetch))(&(head->error), keydata);
    if (!userdata) {
        /* if there was an error in the fetch then don't cache */
        return NULL;
    }

    if (i < head->size) {
        /* cache still growing - add new elem */
        elem = (gdCache_element_t *)gdMalloc(sizeof(gdCache_element_t));
        if (!elem) {
            (*(head->gdCacheRelease))(userdata);
            return NULL;
        }
    } else {
        /* cache full - replace least-recently-used */
        if (!prevprev) {
            /* cache size is 1 */
            head->mru = NULL;
        } else {
            /* prevprev becomes new end of list */
            prevprev->next = NULL;
        }
        elem = prev;
        if (!elem) {
            /* Happen only with an invalid cache size (<= 0). Bad state but
             * still worth handling it here before deref */
            (*(head->gdCacheRelease))(userdata);
            return NULL;
        }
        (*(head->gdCacheRelease))(elem->userdata);
    }

    /* relink to top of list */
    elem->next = head->mru;
    head->mru = elem;
    elem->userdata = userdata;

    return userdata;
}

/*********************************************************/
/* test stub                                             */
/*********************************************************/

#ifdef TEST

#include <stdio.h>

typedef struct {
    int key;
    int value;
} key_value_t;

static int cacheTest(void *map, void *key) { return (((key_value_t *)map)->key == *(int *)key); }

static void *cacheFetch(char **error, void *key)
{
    key_value_t *map;

    map = (key_value_t *)gdMalloc(sizeof(key_value_t));
    if (!map) {
        *error = "gdMalloc failed";
        return NULL;
    }
    map->key = *(int *)key;
    map->value = 3;

    *error = NULL;

    return (void *)map;
}

static void cacheRelease(void *map) { gdFree((char *)map); }

int main(int argc, char **argv)
{
    gdCache_head_t *cacheTable;
    int elem, key;

    cacheTable = gdCacheCreate(3, cacheTest, cacheFetch, cacheRelease);
    if (!cacheTable) {
        exit(1);
    }

    key = 20;
    elem = *(int *)gdCacheGet(cacheTable, &key);
    key = 30;
    elem = *(int *)gdCacheGet(cacheTable, &key);
    key = 40;
    elem = *(int *)gdCacheGet(cacheTable, &key);
    key = 50;
    elem = *(int *)gdCacheGet(cacheTable, &key);
    key = 30;
    elem = *(int *)gdCacheGet(cacheTable, &key);
    key = 30;
    elem = *(int *)gdCacheGet(cacheTable, &key);

    gdCacheDelete(cacheTable);

    return 0;
}

#endif /* TEST */
#endif /* NEED_CACHE */
