/* cache.c: Object cache routines.
 * This code is based on code written by Marcus J. Ranum.  That code, and
 * therefore this derivative work, are Copyright (C) 1991, Marcus J. Ranum,
 * all rights reserved. */

#define _POSIX_SOURCE

#include <stdio.h>
#include "cache.h"
#include "object.h"
#include "memory.h"
#include "db.h"
#include "lookup.h"
#include "log.h"
#include "util.h"
#include "config.h"
#include "ident.h"

/* Store dummy objects for chain heads and tails.  This is a little storage-
 * intensive, but it simplifies and speeds up the list operations. */
Object *active;
Object *inactive;

/* Requires: Shouldn't be called twice.
 * Modifies: active, inactive.
 * Effects: Builds an array of object chains in inactive, and an array of
 *	    empty object chains in active. */
void init_cache(void)
{
    Object *obj;
    int	i, j;

    active = EMALLOC(Object, CACHE_WIDTH);
    inactive = EMALLOC(Object, CACHE_WIDTH);

    for (i = 0; i < CACHE_WIDTH; i++) {
	/* Active list starts out empty. */
	active[i].next = active[i].prev = &active[i];

	/* Inactive list begins as a chain of empty objects. */
	inactive[i].next = inactive[i].prev = &inactive[i];
	for (j = 0; j < CACHE_DEPTH; j++) {
	    obj = EMALLOC(Object, 1);
	    obj->dbref = -1;
	    obj->prev = &inactive[i];
	    obj->next = inactive[i].next;
	    obj->prev->next = obj->next->prev = obj;
	}
    }
}

/* Requires: Initialized cache.
 * Modifies: Contents of active, inactive, database files
 * Effects: Returns an object holder linked to the head of the appropriate
 *	    active chain.  Gets the object holder from the tail of the inactive
 *	    chain, swapping out the object there if necessary.  If the inactive
 *	    inactive chain is empty, then we create a new holder. */
Object *cache_get_holder(long dbref)
{
    int ind = dbref % CACHE_WIDTH;
    Object *obj;

    if (inactive[ind].next != &inactive[ind]) {
	/* Use the object at the tail of the inactive list. */
	obj = inactive[ind].prev;

	/* Check if we need to swap anything out. */
	if (obj->dbref != -1) {
	    if (obj->dirty) {
		if (!db_put(obj, obj->dbref))
		    panic("Could not store an object.");
	    }
	    object_free(obj);
	}

	/* Unlink it from the inactive list. */
	obj->prev->next = obj->next;
	obj->next->prev = obj->prev;
    } else {
	/* Allocate a new object. */
	obj = EMALLOC(Object, 1);
    }

    /* Link the object a the head of the active chain. */
    obj->prev = &active[ind];
    obj->next = active[ind].next;
    obj->prev->next = obj->next->prev = obj;

    obj->dirty = 0;
    obj->dead = 0;
    obj->refs = 1;
    obj->dbref = dbref;
    return obj;
}

/* Requires: Initialized cache.
 * Modifies: Contents of active, inactive, database files
 * Effects: Returns the object associated with dbref, getting it from the cache
 *	    or from disk.  If the object is in the inactive chain or is on
 *	    disk, it will be linked into the active chain.  Returns NULL if no
 *	    object exists with the given dbref. */
Object *cache_retrieve(long dbref)
{
    int ind = dbref % CACHE_WIDTH;
    Object *obj;

    if (dbref < 0)
	return NULL;

    /* Search active chain for object. */
    for (obj = active[ind].next; obj != &active[ind]; obj = obj->next) {
	if (obj->dbref == dbref) {
	    obj->refs++;
	    return obj;
	}
    }

    /* Search inactive chain for object. */
    for (obj = inactive[ind].next; obj != &inactive[ind]; obj = obj->next) {
	if (obj->dbref == dbref) {
	    /* Remove object from inactive chain. */
	    obj->next->prev = obj->prev;
	    obj->prev->next = obj->next;

	    /* Install object at head of active chain. */
	    obj->prev = &active[ind];
	    obj->next = active[ind].next;
	    obj->prev->next = obj->next->prev = obj;

	    obj->refs = 1;
	    return obj;
	}
    }

    /* Cache miss.  Find an object to load in from disk. */
    obj = cache_get_holder(dbref);

    /* Read the object into the place-holder, if it's on disk. */
    if (db_get(obj, dbref)) {
	return obj;
    } else {
	/* Oops.  Install holder at tail of inactive chain and return NULL. */
	obj->dbref = -1;
	obj->prev->next = obj->next;
	obj->next->prev = obj->prev;
	obj->prev = inactive[ind].prev;
	obj->next = &inactive[ind];
	obj->prev->next = obj->next->prev = obj;
	return NULL;
    }
}

Object *cache_grab(Object *obj)
{
    obj->refs++;
    return obj;
}

/* Requires: Initialized cache.  obj should point to an active object.
 * Modifies: obj, contents of active and inactive, database files.
 * Effects: Decreases the refcount on obj, unlinking it from the active chain
 *	    if the refcount hits zero.  If the object is marked dead, then it
 *	    is destroyed when it is unlinked from the active chain. */
void cache_discard(Object *obj)
{
    int ind;

    if (!obj)
      return;

    /* Decrease reference count. */
    obj->refs--;
    if (obj->refs)
	return;

    ind = obj->dbref % CACHE_WIDTH;

    /* Reference count hit 0; remove from active chain. */
    obj->prev->next = obj->next;
    obj->next->prev = obj->prev;

    if (obj->dead) {
	/* The object is dead; remove it from the database, and install it at
	 * the tail of the inactive chain.  Be careful about this, since
	 * object_destroy() can fiddle with the cache.  We're safe as long as
	 * obj isn't in any chains at the time of db_del(). */
	db_del(obj->dbref);
	object_destroy(obj);
	obj->dbref = -1;
	obj->prev = inactive[ind].prev;
	obj->next = &inactive[ind];
	obj->prev->next = obj->next->prev = obj;
    } else {
	/* Install at head of inactive chain. */
	obj->prev = &inactive[ind];
	obj->next = inactive[ind].next;
	obj->prev->next = obj->next->prev = obj;
    }
}

/* Requires: Initialized cache.
 * Effects: Returns nonzero if an object exists with the given dbref. */
int cache_check(long dbref)
{
    int ind = dbref % CACHE_WIDTH;
    Object *obj;

    if (dbref < 0)
	return 0;

    /* Search active chain. */
    for (obj = active[ind].next; obj != &active[ind]; obj = obj->next) {
	if (obj->dbref == dbref)
	    return 1;
    }

    /* Search inactive chain. */
    for (obj = inactive[ind].next; obj != &inactive[ind]; obj = obj->next) {
	if (obj->dbref == dbref)
	    return 1;
    }

    /* Check database on disk. */
    return db_check(dbref);
}

/* Requires: Initialized cache.
 * Modifies: Database files.
 * Effects: Writes out all objects in the cache which are marked dirty. */
void cache_sync(void)
{
    int i;
    Object *obj;

    /* Traverse all the active and inactive chains. */
    for (i = 0; i < CACHE_WIDTH; i++) {
	/* Check active chain. */
	for (obj = active[i].next; obj != &active[i]; obj = obj->next) {
	    if (obj->dirty) {
		if (!db_put(obj, obj->dbref))
		    panic("Could not store an object.");
		obj->dirty = 0;
	    }
	}

	/* Check inactive chain. */
	for (obj = inactive[i].next; obj != &inactive[i]; obj = obj->next) {
	    if (obj->dbref != -1 && obj->dirty) {
		if (!db_put(obj, obj->dbref))
		    panic("Could not store an object.");
		obj->dirty = 0;
	    }
	}
    }

    db_flush();
}

Object *cache_first(void)
{
    long dbref;

    cache_sync();
    dbref = lookup_first_dbref();
    if (dbref == -1)
	return NULL;
    return cache_retrieve(dbref);
}

Object *cache_next(void)
{
    long dbref;

    dbref = lookup_next_dbref();
    if (dbref == -1)
	return NULL;
    return cache_retrieve(dbref);
}

/* Called during main loop to verify that no objects are active. */
/* JBB: Well, actually, its not called.  Should really be re-written to check
   the suspended task list to see what is not really active and what is dirty 
*/
void cache_sanity_check(void)
{
    int i;

    for (i = 0; i < CACHE_WIDTH; i++) {
	if (active[i].next != &active[i])
	    panic("Active objects at start of main loop.");
    }
}