#include <glib.h>
#include "rc.h"
#include "tile_cache.h"
#include "tile_swap.h"

/*  This is the percentage of the maximum cache size that should be cleared
 *   from the cache when an eviction is necessary
 */
#define FREE_QUANTUM 0.1

static void  tile_cache_init       (void);
static void  tile_cache_zorch_next (void);
static guint tile_cache_hash       (Tile *tile);


static int initialize = TRUE;
static GHashTable *tile_hash_table = NULL;
static TileList tile_list = {0,0,TILE_WIDTH * TILE_HEIGHT * 4,0,0};

int TileList_IsLast(Tile* tile)
{	return ((GList*)tile == tile_list.tail);
}

void	TileList_Remove(Tile* tile)
{	if ((GList*)tile == tile_list.tail)
	{	tile_list.tail = tile_list.tail->prev;
	}
	else
	{	tile_list.head = g_list_remove_link (tile_list.head, (GList*)tile);
	}
	if(!tile_list.head)
	{	tile_list.tail = NULL;
}	}

void TileList_Append(Tile* tile)
{	tile_list.tail = g_list_append (tile_list.tail, (GList*)tile);
	if (!tile_list.head)
	{	tile_list.head = tile_list.tail;
	}
	tile_list.tail = g_list_last (tile_list.tail);
}

GList* TileList_GetTail()
{	return tile_list.tail;
}

void	TileList_MoveToEnd(Tile* tile)
{	if(TileList_IsLast(tile))
	{	/*tile_list_tail = tile_list_tail->prev;*/
		return;
	}
	TileList_Remove(tile);
/*	g_list_free (tmp);
	tmp=0;
*/	/* Remove the old reference to the tiles list node
	*  in the tile hash table.
	*/
	g_hash_table_remove (tile_hash_table, tile);
/*	TileList_Append(tile);*/
	tile_list_tail->next = tile;
	tile->prev=tile_list.tail;
	tile->next
	tile_list_tail->prev
	
	/* Add the tiles list node to the tile hash table. The
	*  list node is indexed by the tile itself. This makes
	*  for a quick lookup of which list node the tile is in.
	*/
	g_hash_table_insert (tile_hash_table,tile,TileList_GetTail());
}

int TileList_MakeRoom()
{	if ((tile_list.cur_cache_size + tile_list.max_tile_size) > tile_list.max_cache_size)
	{	while( tile_list.head 
		&& (tile_list.cur_cache_size + tile_list.max_cache_size * FREE_QUANTUM) > tile_list.max_cache_size)
		{	tile_cache_flush_tile1();
		}
		if ((tile_list.cur_cache_size + tile_list.max_tile_size) > tile_list.max_cache_size)
		{	return 0;
	}	}
	return 1;
}

void tile_cache_insert(Tile *tile)
{	GList *tmp;
	d_assert(tile->bpp<=6);
	if(initialize)
	{	tile_cache_init();
	}
	/* First check and see if the tile is already
	*  in the cache. In that case we will simply place
	*  it at the end of the tile list to indicate that
	*  it was the most recently accessed tile.
	*/
	tmp = g_hash_table_lookup(tile_hash_table,tile);
	if (tmp)
	{	/* The tile was already in the cache. Place it at
		*  the end of the tile list.
		*/
		TileList_MoveToEnd(tmp);
		return;
	}
	/* The tile was not in the cache. First check and see
	*  if there is room in the cache. If not then we'll have
	*  to make room first. Note: it might be the case that the
	*  cache is smaller than the size of a tile in which case
	*  it won't be possible to put it in the cache.
	*/
	if(!TileList_MakeRoom())
	{	return;
	}
	/* Place the tile at the end of the tile list.
	*/
	TileList_Append(tile);
	/* Add the tiles list node to the tile hash table.
	*/
	g_hash_table_insert(tile_hash_table,tile,tile);
	/* Note the increase in the number of bytes the cache
	*  is referencing.
	*/
	tile_list.cur_cache_size += tile_size(tile);
	/* Reference the tile so that it won't be swapped out
	*  to disk. Swap the tile in if necessary.
	* "tile_ref" cannot be used here since it calls this
	*  function.
	*/
	tile->ref_count += 1;
	{	extern int tile_ref_count;
		tile_ref_count += 1;
	}
	if (tile->ref_count == 1)
	{	tile_swap_in (tile);
		/*  the tile must be clean  */
		tile->dirty = FALSE;
	}
}

void tile_cache_flush(Tile* tile)
{	GList *tmp;
	d_assert(tile->bpp<=6);
	if (initialize)
	{	tile_cache_init();
	}
	/* Find where the tile is in the cache.
	*/
	tmp = g_hash_table_lookup(tile_hash_table, tile);
	if(!tmp)
	{	return;
	}
	/* If the tile is in the cache, then remove it from the
	*  tile list.
	*/
	TileList_Remove(tile);
	g_free(tile);
	tile=0;
	/* Remove the tile from the tile hash table.
	*/
	g_hash_table_remove(tile_hash_table,tile);
	/* Note the decrease in the number of bytes the cache
	*  is referencing.
	*/
	cur_cache_size -= tile_size(tile);

	/* Unreference the tile.
	* "tile_unref" may be used here since it does not call
	*  this function (or any of the cache functions).
	*/
	tile_unref(tile,FALSE);
}

void
tile_cache_set_size (unsigned long cache_size)
{
  if (initialize)
    tile_cache_init ();

  max_cache_size = cache_size;

  if (max_cache_size >= max_tile_size)
    {
      while (cur_cache_size > max_cache_size)
	tile_cache_zorch_next ();
    }
}


static void
tile_cache_init ()
{
  if (initialize)
    {
      initialize = FALSE;

      tile_hash_table = g_hash_table_new ((GHashFunc) tile_cache_hash, NULL);

      max_cache_size = tile_cache_size;
    }
}

static void
tile_cache_flush_tile1()
{
  if (tile_list_head)
    tile_cache_flush ((Tile*) tile_list_head->data);
}

static guint
tile_cache_hash (Tile *tile)
{
  return (gulong) tile;
}
