/* Jungle Monkey
 * Copyright (C) 1999-2001  The Regents of the University of Michigan
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "jmmsp_cache.h"
#include "jmmsp_debug.h"
#include "util/util.h"

#include <sys/types.h>
#include <sys/time.h>



typedef struct _Entry
{
  JMMSPReply* reply;
  struct timeval time;

} Entry;


static void cache_add (JMMSPCache* cache, const JMMSPReply* reply);
static void cache_check (JMMSPCache* cache);

static void cache_query (JMMSPHandler* handler, JMMSP* jmmsp, 
			 const gchar* keyword);
static void cache_reply (JMMSPHandler* handler, JMMSP* jmmsp, 
			 const gchar* keyword, const JMMSPReply* reply);

static JMMSPHandlerFuncs cache_vtbl = { cache_query, cache_reply, NULL, NULL};


/* **************************************** */


JMMSPCache* 
jmmsp_cache_new (void)
{
  JMMSPCache* cache;

  cache = g_new0 (JMMSPCache, 1);
  jmmsp_handler_init (&cache->handler, &cache_vtbl);

  cache->entries        = NULL;
  cache->entry_last     = NULL;
  cache->entry_set	= g_hash_table_new (jmmsp_reply_hash, jmmsp_reply_equal);
  cache->cache_size     = 0;
  cache->max_cache_size = JMMSP_CACHE_SIZE_DEFAULT;
  cache->cache_timeout  = JMMSP_CACHE_TIMEOUT_DEFAULT;

  return cache;
}


void
jmmsp_cache_delete (JMMSPCache* cache)
{
  if (!cache)
    return;

  /* Delete entries */
  jmmsp_cache_clear (cache);
  g_hash_table_destroy (cache->entry_set);

  /* Remove handlers */
  jmmsp_handler_uninit (&cache->handler);

  g_free (cache);
}



void
jmmsp_cache_set_policy (JMMSPCache* cache, guint max_cache_size, guint cache_timeout)
{
  g_return_if_fail (cache);

  cache->max_cache_size = max_cache_size;
  cache->cache_timeout  = cache_timeout;

  cache_check (cache);
}


void
jmmsp_cache_clear (JMMSPCache* cache)
{
  GSList* i;

  g_return_if_fail (cache);

  for (i = cache->entries; i != NULL; i = i->next)
    {
      Entry* entry = (Entry*) i->data;

      jmmsp_reply_delete (entry->reply);
      g_free (entry);
    }

  g_slist_free (cache->entries);
  cache->entries = NULL;
  cache->entry_last = NULL;
  g_hash_table_destroy (cache->entry_set);
  cache->entry_set = g_hash_table_new (jmmsp_reply_hash, jmmsp_reply_equal);

  cache->cache_size = 0;
}


/* **************************************** */

static void
cache_add (JMMSPCache* cache, const JMMSPReply* reply)
{
  guint length;
  Entry* entry;
  GSList* new_list;

  g_return_if_fail (cache);
  g_return_if_fail (reply);

  if (cache->max_cache_size <= 0)
    return;

  JMMSPP (0x02, "cache_add: %s\n", reply->name);

  /* Skip if we already have it */
  if (g_hash_table_lookup_extended(cache->entry_set, reply, NULL, NULL))
    return;

  length = jmmsp_reply_size (reply);

  /* Ignore if too large */
  if (length > JMMSP_CACHE_MAX_ENTRY_SIZE)
    return;

  entry = g_new (Entry, 1);
  entry->reply = jmmsp_reply_clone (reply);
  g_assert (gettimeofday(&entry->time, NULL) == 0);

  /* Add to entry list */
  new_list = g_slist_alloc();
  new_list->data = entry;
  if (cache->entry_last)
    cache->entry_last->next = new_list;
  else
    cache->entries = new_list;
  cache->entry_last = new_list;
  g_hash_table_insert (cache->entry_set, entry->reply, NULL);

  cache->cache_size += length;

  cache_check (cache);
}


static void
cache_check (JMMSPCache* cache)
{
  Entry* entry;
  struct timeval timeofday;

  /* If the cache is too big, remove entries until small enough */
  while (cache->cache_size > cache->max_cache_size)
    {
      guint length;

      g_return_if_fail (cache->entries);

      entry = (Entry*) cache->entries->data;
      length = jmmsp_reply_size (entry->reply);

      JMMSPP (0x02, "cache_check: EVICT %s (size)\n", entry->reply->name);

      cache->entries = g_slist_remove (cache->entries, entry);
      if (!cache->entries)
	cache->entry_last = NULL;
      g_hash_table_remove (cache->entry_set, entry->reply);
      cache->cache_size -= length;

      jmmsp_reply_delete (entry->reply);
      g_free (entry);
    }


  /* Remove entries that have expired.  The cache is sorted, so this
     is not expensive. */
  if (cache->cache_timeout <= 0 || !cache->entries)
    return;

  g_assert (gettimeofday(&timeofday, NULL) == 0);

  while (cache->entries != NULL)
    {
      struct timeval diff;

      entry = (Entry*) cache->entries->data;
      g_assert (entry != NULL);

      timersub(&timeofday, &entry->time, &diff);

      if ((diff.tv_sec * 1000 + diff.tv_usec / 1000) > 
	  cache->cache_timeout)
	{
	  guint length;

	  JMMSPP (0x02, "cache_check: EVICT %s (time)\n", entry->reply->name);

	  length = jmmsp_reply_size (entry->reply);

	  cache->entries = g_slist_remove (cache->entries, entry);
	  if (!cache->entries)
	    cache->entry_last = NULL;
	  g_hash_table_remove (cache->entry_set, entry->reply);
	  cache->cache_size -= length;

	  jmmsp_reply_delete (entry->reply);
	  g_free (entry);
	}
      else
	break;
    }
}


/* **************************************** */


static void
cache_query (JMMSPHandler* handler, JMMSP* jmmsp, const gchar* keyword)
{
  JMMSPCache* cache = (JMMSPCache*) handler;
  GSList* i;
  int num = 0;

  JMMSPP (0x02, "cache_query: %s\n", keyword);

  /* Send REPLYs for things in cache that match */
  for (i = cache->entries; i != NULL; i = i->next)
    {
      Entry* entry = (Entry*) i->data;

      if (jmmsp_reply_matches(entry->reply, keyword))
	{
	  /* Send REPLY */
	  jmmsp_send_reply (jmmsp, keyword, entry->reply, -1);

	  /* Count it.  If sent enough, quit */
	  ++num;
	  if (num >= JMMSP_CACHE_MAX_REPLIES)
	    return;
	}
    }
}


static void 
cache_reply (JMMSPHandler* handler, JMMSP* jmmsp, 
	     const gchar* keyword, const JMMSPReply* reply)
{
  JMMSPCache* cache = (JMMSPCache*) handler;

  JMMSPP (0x02, "cache_reply: %s\n", reply->name);

  /* Add reply to cache */
  cache_add (cache, reply);
}


syntax highlighted by Code2HTML, v. 0.9.1