/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * 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.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
 */

#include <opencm.h>

/* Entity caching logic: the client (or the server) will frequently
 * end up fetching the same entity over and over -- for example the
 * entity associated with the current user. Rather than do that, it
 * makes more sense to cache the result.
 */
typedef struct EcacheNode EcacheNode;
struct EcacheNode {
  rbnode rbn;

#if 0
  tnhash_t tnHash;
  const char *trueName;
  Serializable *ob;
#endif

  /* Implement ageing using a doubly linked list now */
  EcacheNode *next;
  EcacheNode *prev;		/* points to next oldest */
};

struct EntityCache {
  uint32_t sz;
  uint32_t max;
  rbtree *tree;

  EcacheNode *oldest;		/* pointer to oldest */
};

static void
ecache_unlink(EntityCache *ec, EcacheNode *ecn)
{
  assert(ec->oldest);
  if (ec->oldest == ecn) {
    if (ecn->next == ecn)
      ec->oldest = 0;
    else
      ec->oldest = ecn->next;
  }

  ecn->prev->next = ecn->next;
  ecn->next->prev = ecn->prev;

  ecn->next = ecn;
  ecn->prev = ecn;
}

static void
ecache_dolink(EntityCache *ec, EcacheNode *ecn)
{
  assert (ecn->next == ecn);
  assert (ecn->prev == ecn);

  if (ec->oldest == 0) {
    ec->oldest = ecn;
    return;
  }
  else {
    EcacheNode *curTop = ec->oldest->prev;

    ecn->prev = curTop;
    ecn->next = curTop->next;

    curTop->next->prev = ecn;
    curTop->next = ecn;
  }
}

EntityCache *
ecache_create(uint32_t max)
{
  EntityCache *ec = GC_MALLOC(sizeof(EntityCache));
  ec->oldest = 0;
  ec->sz = 0;
  ec->max = max;
  ec->tree = rbtree_create(rbtree_s_cmp, rbtree_s_cmpkey, FALSE);

  return ec;
}

Serializable *
ecache_lookup(EntityCache *ec, const char *tn)
{
  struct rbkey key;
  rbnode *rn;

  key.vp = tn;
  key.w = truename_hash(tn);

  rn = rbtree_find(ec->tree, &key);

  if (rn == TREE_NIL)
    return 0;

  ecache_unlink(ec, (EcacheNode *)rn);
  ecache_dolink(ec, (EcacheNode *)rn);

  return (Serializable *) rn->data;
}

void 
ecache_insert(EntityCache *ec, const char *tn, Serializable *s)
{
  EcacheNode *nd = (EcacheNode *)ecache_lookup(ec, tn);

  if (nd)
    return;

  /* If ec->max == 0, no limit on cache size. */
  if (ec->max && ec->sz == ec->max) {
    nd = ec->oldest;
    ecache_unlink(ec, nd);
    rbtree_remove(ec->tree, &nd->rbn);
    ec->sz--;
  }

  {
    EcacheNode *nd = GC_MALLOC(sizeof(EcacheNode));

    nd->rbn.value.vp = tn;
    nd->rbn.value.w = truename_hash(tn);
    nd->rbn.data = s;
    nd->prev = nd;
    nd->next = nd;

    rbtree_insert(ec->tree, &nd->rbn);

    assert (rbtree_find(ec->tree, &nd->rbn.value) == &nd->rbn);

    ecache_dolink(ec, nd);
    ec->sz++;
  }
}

