/* pixmap-cache.c -- Caching pixmaps
   $Id$

   Copyright (C) 2000 John Harper <john@dcs.warwick.ac.uk>

   This file is part of sawfish.

   sawfish is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   sawfish 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with sawfish; see the file COPYING.   If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

/* Define this to disable all pixmap caching */
#undef DISABLE_CACHE

/* Commentary:

   This file provides the pixmap cache, a mapping from (image, width,
   height) tuples to rendered pixmaps (both image and mask). This is
   only required when not using Imlib (Imlib has a built-in cache).

   It's implemented as two doubly-linked lists, each containing the
   same component nodes, but one list chains all pixmaps associated
   with a particular image object, the other chains all pixmaps in
   order of allocation/use (so that the oldest unused node can be
   discarded quickly when the cache overflows its upper bound).

   XXX Need to measure average length of image lists; if too long
   XXX they should be changed to doubly-linked hash tables. */

#include "sawfish.h"

static unsigned long cached_pixels, max_cached_pixels = 64 * 1024;
static unsigned long hits, misses;

#ifdef NEED_PIXMAP_CACHE

struct pixmap_cache_node_struct {
    pixmap_cache_node *next, *pred;
    pixmap_cache_node *newer, *older;
    Lisp_Image *im;
    int width, height;
    Pixmap p1, p2;
    int ref_count;
};

static pixmap_cache_node *oldest, *newest;

/* for debugging:
 * return number of cache entries, and
 * REFS is filled with Sum of the # of references. */
inline int cache_length(int *refs)
{
   pixmap_cache_node* i;
   int count = 0;
   int total_ref_count = 0;
   
    /* length */
   
   for (i= oldest; i != newest; i=i->newer)
      {
         total_ref_count +=  i->ref_count;
         count++;
      }

   *refs = total_ref_count;
   return count;
}

/* list manipulators */

static void
remove_from_age_list (pixmap_cache_node *n)
{
    if (n->newer != 0)
	n->newer->older = n->older;
    else
	newest = n->older;
    if (n->older != 0)
	n->older->newer = n->newer;
    else
	oldest = n->newer;
}

static void
prepend_to_age_list (pixmap_cache_node *n)
{
    n->newer = oldest;
    if (n->newer != 0)
	n->newer->older = n;
    oldest = n;
    n->older = 0;
    if (newest == 0)
	newest = n;
}

/* Symmetric case: add NODE as the Newest node (in the doubly linked list). */
static void
append_to_age_list (pixmap_cache_node *node)
{
    node->older = newest;          /* Oldest, because appending */
    if (node->older != 0)
	node->older->newer = node;
    newest = node;

    node->newer = 0;
    if (oldest == 0)
	oldest = node;
}

/* 1  chain per file!  */
static void
remove_from_image (pixmap_cache_node *n)
{
    if (n->next != 0)
	n->next->pred = n->pred;
    else
	n->im->pixmap_last = n->pred;
    if (n->pred != 0)
	n->pred->next = n->next;
    else
	n->im->pixmap_first = n->next;
}

static void
prepend_to_image (pixmap_cache_node *n)
{
    Lisp_Image *im = n->im;
    n->next = im->pixmap_first;
    if (n->next != 0)
	n->next->pred = n;
    im->pixmap_first = n;
    n->pred = 0;
    if (im->pixmap_last == 0)
	im->pixmap_last = n;
}

static void
free_node (pixmap_cache_node *n, bool dealloc)
{
    if (n->p1 != 0)
	XFreePixmap (dpy, n->p1);
    if (n->p2 != 0)
	XFreePixmap (dpy, n->p2);
    if (dealloc)
	rep_free (n);
}

static void
delete_node (pixmap_cache_node *n, bool dealloc)
{
    remove_from_image (n);
    remove_from_age_list (n);
    cached_pixels -= n->width * n->height;
    free_node (n, dealloc);
}

/* public interface */

bool
pixmap_cache_ref (Lisp_Image *im, int width, int height,
		  Pixmap *p1, Pixmap *p2)
{
    pixmap_cache_node *n;
    for (n = im->pixmap_first; n != 0; n = n->next)
    {
	if (n->width == width && n->height == height)
	{
	    remove_from_image (n);
	    prepend_to_image (n);
	    remove_from_age_list (n);
            append_to_age_list (n);
	    n->ref_count++;
	    *p1 = n->p1;
	    *p2 = n->p2;
	    hits++;
	    return TRUE;
	}
    }
    misses++;
    return FALSE;
}

void
pixmap_cache_unref (Lisp_Image *im, Pixmap p1, Pixmap p2)
{
    pixmap_cache_node *n;
    for (n = im->pixmap_first; n != 0; n = n->next)
    {
	if (n->p1 == p1 && n->p2 == p2)
	{
	    n->ref_count--;
#ifdef DISABLE_CACHE
	    if (n->ref_count == 0)
		delete_node (n, TRUE);
#endif		
	    return;
	}
    }
    fprintf (stderr, "warning: unref'ing unknown image in pixmap-cache\n");
}

void
pixmap_cache_set (Lisp_Image *im, int width, int height,
		  Pixmap p1, Pixmap p2)
{
    int pixel_count = width * height;
    pixmap_cache_node *n = 0;

    while (pixel_count + cached_pixels > max_cached_pixels)
    {
	/* remove oldest node */
	pixmap_cache_node *this = oldest;
	while (this != 0 && this->ref_count > 0)
	    this = this->newer;
	if (this == 0)
	    break;
	delete_node (this, n != 0);
	if (n == 0)
	    n = this;
    }

    if (n == 0)
	n = rep_alloc (sizeof (pixmap_cache_node));

    n->im = im;
    n->width = width;
    n->height = height;
    n->p1 = p1;
    n->p2 = p2;
    n->ref_count = 1;

    prepend_to_image (n);
    append_to_age_list (n);
    cached_pixels += pixel_count;
}

void
pixmap_cache_flush_image (Lisp_Image *im)
{
    pixmap_cache_node *n, *next;
    for (n = im->pixmap_first; n != 0; n = next)
    {
	next = n->next;
	remove_from_age_list (n);
	free_node (n, TRUE);
    }
    im->pixmap_first = im->pixmap_last = 0;
}

#endif /* NEED_PIXMAP_CACHE */

DEFUN ("pixmap-cache-control", Fpixmap_cache_control,
       Spixmap_cache_control, (repv max, repv reset), rep_Subr2)
/*
::doc:sawfish.wm.windows.subrs#pixmap-cache-images::
pixmap-cache-control  max-pixels reset

::end:: */

{
    int total_refs, total_nodes;
    repv result = Qnil;
    rep_GC_root gc_result;

    if (rep_INTP (max) && rep_INT (max) > 0)
	max_cached_pixels = rep_INT (max);

    total_nodes = cache_length(&total_refs);

    rep_PUSHGC(gc_result, result);
    result = rep_LIST_5 (
        rep_MAKE_INT (cached_pixels),
        rep_MAKE_INT (hits), rep_MAKE_INT (misses),
        rep_MAKE_INT (total_nodes),
        rep_MAKE_INT (total_refs));

    result = Fcons(rep_MAKE_INT (max_cached_pixels), result);
    if (reset != Qnil)
    {
        DB(("%s: resetting!\n", __FUNCTION__));
        misses = hits = 0;
    };
    
    rep_POPGC;
    return result;
}

DEFUN ("pixmap-cache-images", Fpixmap_cache_images, Spixmap_cache_images, (void), rep_Subr0)
/*
::doc:sawfish.wm.windows.subrs#pixmap-cache-images::
pixmap-cache-images

return the list of all the images in cache
::end:: */

{
   /* make & return a list of all the (image scale)  */
   
    repv out = Qnil;
    rep_GC_root gc_out;
    rep_PUSHGC(gc_out, out);

    pixmap_cache_node *this = oldest;

    for (this = oldest; this != 0; this = this->newer)
       /* images could get GC-ed! */
       out = Fcons (rep_LIST_3 (
                       rep_MAKE_INT (this->width),
                       rep_MAKE_INT (this->height),
                       (repv)this->im),
                    out);
    rep_POPGC;
    return out;
}

void
pixmap_cache_init (void)
{
    rep_ADD_SUBR (Spixmap_cache_control);
    rep_ADD_SUBR (Spixmap_cache_images);
}
