/* Cache.c v1.2   Caching control routines */

/*
        This is part of ODS2 written by Paul Nankervis,
        email address:  Paulnank@au1.ibm.com

        ODS2 is distributed freely for all members of the
        VMS community to use. However all derived works
        must maintain comments in their source to acknowledge
        the contibution of the original author.
*/

/*  Caching seems to have a big impact on performance!!!
    This version based on number of objects - probably
    should change it to use space occupied by objects.
    Currently use vanilla binary trees - could either
    use hashing or balanced trees for better performance.  */

/*  The theory is that all cachable objects share a common
    cache pool. Any object with a reference count of zero
    is a candidate for destruction. All cacheable objects
    have a 'struct CACHE' as the first item of the object
    so that cache pointers and object pointers are interchangeable.
    All cache objects are also part of a binary tree so that
    they can be located. There are many instances of these
    binary trees: for files on a volume, for chunks (segments)
    of files, etc. Also each object can have an object manager:
    a routine to handle special object deletion requirements
    (for example to remove all file chunks before removing a
    file), or to flush objects (write modified chunks to disk).  */


#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "ssdef.h"
#include "cache.h"


#define DEBUG on

/* Set limits for number of unaccessed cache entries... */

#define CACHELIM  64
#define CACHEGOAL 25

int cachesearches = 0;
int cachecreated = 0;
int cachedeleted = 0;
int cachepeak = 0;
int cachecount = 0;
int cachefreecount = 0;

struct CACHE cachelist = {NULL,NULL,NULL,&cachelist,&cachelist,NULL,0,0,1};


/* cacheshow - to print cache statistics */

void cacheshow(void)
{
    printf("CACHESHOW Searches: %d Created: %d Peak: %d Count: %d Free: %d\n",
           cachesearches,cachecreated,cachepeak,cachecount,cachefreecount);
}


void cachedump(void)
{
    register struct CACHE *cacheobj = cachelist.lstcache;
    printf("%8.8s %8.8s %8.8s %8.8s %8.8s %8.8s %8.8s cachelist\n","Object",
           "Parent","Left","Right","Value","Status","Count");
    while (cacheobj != &cachelist) {
        printf("%8p %8p %8p %8p %8x %8x %8d\n",
               cacheobj,cacheobj->parent,cacheobj->left,cacheobj->right,
               cacheobj->keyval,cacheobj->status,cacheobj->refcount);
        cacheobj = cacheobj->lstcache;
    }
}


/* cacheprint - debugging tool to print out a cache subtree... */

void cacheprint(struct CACHE *cacheobj,int level)
{
    if (cacheobj != NULL) {
        int i;
        cacheprint(cacheobj->left,level + 1);
        for (i = 0; i < level; i++) printf("    ");
        printf("%8x %8x %8x %d\n",
               cacheobj,cacheobj->keyval,cacheobj->status,cacheobj->refcount);
        cacheprint(cacheobj->right,level + 1);
    }
}


/* cacherefcount - compute reference count for cache subtree... */

int cacherefcount(struct CACHE *cacheobj)
{
    register int refcount = 0;
    if (cacheobj != NULL) {
        if (cacheobj->left != NULL) refcount += cacherefcount(cacheobj->left);
        if (cacheobj->right != NULL) refcount += cacherefcount(cacheobj->right);
        refcount += cacheobj->refcount;
    }
    return refcount;
}


/* cachefree - blow away item from cache - allow item to select a proxy (!)
   and adjust 'the tree' containing the item. */

int freeactive = 0;             /* In case object managers re-call cachefree... :-( */

struct CACHE *cachefree(struct CACHE *cacheobj)
{
    if (cacheobj->refcount != 0) {
        char str[100];
        printf("cachelist, object to delete still inuse?\n");
        printf("%8p %8p %8p %8p %8x %8x %8d\n",
               cacheobj,cacheobj->parent,cacheobj->left,cacheobj->right,
               cacheobj->keyval,cacheobj->status,cacheobj->refcount);
        printf("Type RETURN: ");
        fgets(str, sizeof(str)-1, stdin);
        return NULL;
    } else {
        register struct CACHE *proxyobj;
        while (cacheobj->objmanager != NULL) {
            freeactive = 1;
            proxyobj = (*cacheobj->objmanager) (cacheobj);
            freeactive = 0;
            if (proxyobj == NULL) return NULL;
            if (proxyobj == cacheobj) break;
            cacheobj = proxyobj;
        }
        if ((cacheobj->status & CACHE_MODIFIED) && cacheobj->objmanager == NULL)
            printf("cachelist No manager to write modified cache\n");
        cacheobj->lstcache->nxtcache = cacheobj->nxtcache;
        cacheobj->nxtcache->lstcache = cacheobj->lstcache;
        if (cacheobj->parent != NULL) { /* Check if in tree... */
            if (cacheobj->left == NULL) {
                if (cacheobj->right == NULL) {
                    *(cacheobj->parent) = NULL;
                } else {
                    cacheobj->right->parent = cacheobj->parent;
                    *(cacheobj->parent) = cacheobj->right;
                }
            } else {
                if (cacheobj->right == NULL) {
                    cacheobj->left->parent = cacheobj->parent;
                    *(cacheobj->parent) = cacheobj->left;
                } else {
                    register struct CACHE *leftpath = cacheobj->left;
                    register struct CACHE *rightpath = cacheobj->right;
                    while (1) {
                        if (leftpath->right == NULL) {
                            leftpath->right = cacheobj->right;
                            cacheobj->right->parent = &leftpath->right;
                            cacheobj->left->parent = cacheobj->parent;
                            *(cacheobj->parent) = cacheobj->left;
                            break;
                        } else {
                            if (rightpath->left == NULL) {
                                rightpath->left = cacheobj->left;
                                cacheobj->left->parent = &rightpath->left;
                                cacheobj->right->parent = cacheobj->parent;
                                *(cacheobj->parent) = cacheobj->right;
                                break;
                            } else {
                                leftpath = leftpath->right;
                                rightpath = rightpath->left;
                            }
                        }
                    }
                }
            }
        }
        if (--cachecount < 0) printf("cachelist, cache current too small\n");
        cachefreecount--;
        cachedeleted--;
#ifdef DEBUG
        cacheobj->parent = NULL;
        cacheobj->left = NULL;
        cacheobj->right = NULL;
        cacheobj->nxtcache = NULL;
        cacheobj->lstcache = NULL;
        cacheobj->objmanager = NULL;
        cacheobj->keyval = 0;
        cacheobj->status = 0;
        cacheobj->refcount = 0;
#endif
        free(cacheobj);
        return cacheobj;
    }
}


/* cachepurge - trim size of free list */

void cachepurge()
{
    register struct CACHE *cacheobj = cachelist.lstcache;
    while (cachefreecount > CACHEGOAL && cacheobj != &cachelist) {
        register struct CACHE *lastobj = cacheobj->lstcache;
#ifdef DEBUG
        if (cacheobj->lstcache->nxtcache != cacheobj ||
            cacheobj->nxtcache->lstcache != cacheobj ||
            *(cacheobj->parent) != cacheobj) {
            printf("CACHE pointers in bad shape! %p %p %p - %p\n",
                   cacheobj->lstcache->nxtcache,cacheobj,cacheobj->nxtcache->lstcache,*(cacheobj->parent));
        }
#endif
        if (cacheobj->refcount == 0) {
            if (cachefree(cacheobj) != lastobj) cacheobj = lastobj;
        } else {
            cacheobj = lastobj;
        }
    }
}



/* cacheflush - flush modified entries in cache */

void cacheflush()
{
    register struct CACHE *cacheobj = cachelist.lstcache;
    while (cacheobj != &cachelist) {
        if (cacheobj->status & CACHE_MODIFIED) {
            if (cacheobj->objmanager != NULL) {
                (*cacheobj->objmanager) (cacheobj);
            } else {
                printf("CACHEFLUSH No manager to write modified cache\n");
            }
        }
        cacheobj = cacheobj->lstcache;
    }
}


/* cachedelete - delete an object from cache */

struct CACHE *cachedelete(struct CACHE *cacheobj)
{
    if (cacheobj != NULL) {
        register struct CACHE *cachedel;
        do {
            cachedel = cachefree(cacheobj);
        } while (cachedel != NULL && cachedel != cacheobj);
    }
    return cacheobj;
}



/* cachedeltree: delete cache subtree */

void cachedeltree(struct CACHE *cacheobj)
{
    if (cacheobj != NULL) {
        if (cacheobj->left != NULL) cachedeltree(cacheobj->left);
        if (cacheobj->right != NULL) cachedeltree(cacheobj->right);
        if (cacheobj->refcount == 0) cachedelete(cacheobj);
    }
}




/* cacheuntouch - decrement object reference count */

unsigned cacheuntouch(struct CACHE *cacheobj,unsigned reuse,unsigned modflg)
{
    if (cacheobj->refcount < 1) {
        char str[100];
        printf("%8p %8p %8p %8p %8x %8x %8d\n",
               cacheobj,cacheobj->parent,cacheobj->left,cacheobj->right,
               cacheobj->keyval,cacheobj->status,cacheobj->refcount);
        printf("CACHE untouch TOO FAR!\n");
        printf("Type RETURN: ");
        fgets(str, sizeof(str), stdin);
        return 64;
    } else {
        if (modflg) {
            if ((cacheobj->status & CACHE_WRITE) == 0) return SS$_WRITLCK;
            cacheobj->status |= CACHE_MODIFIED;
        }
        if (--cacheobj->refcount == 0) {
            /* Move to new list position... */
            if (reuse == 0 && cacheobj != cachelist.lstcache) {
                cacheobj->lstcache->nxtcache = cacheobj->nxtcache;
                cacheobj->nxtcache->lstcache = cacheobj->lstcache;
                cacheobj->nxtcache = &cachelist;
                cacheobj->lstcache = cachelist.lstcache;
                cachelist.lstcache->nxtcache = cacheobj;
                cachelist.lstcache = cacheobj;
            }
            cacheobj->status &= ~CACHE_WRITE;
            if (++cachefreecount > CACHELIM && freeactive == 0) cachepurge();
        }
    }
    return 1;
}


/* cachetouch - add one to reference count */

void cachetouch(struct CACHE *cacheobj)
{
    if (cacheobj->refcount++ < 1) {
#ifdef DEBUG
        if (cacheobj->lstcache->nxtcache != cacheobj ||
            cacheobj->nxtcache->lstcache != cacheobj ||
            *(cacheobj->parent) != cacheobj) {
            printf("CACHE pointers in bad shape! %p %p %p - %p\n",
                   cacheobj->lstcache->nxtcache,cacheobj,cacheobj->nxtcache->lstcache,*(cacheobj->parent));
        }
#endif
        cachefreecount--;
    }
    /* Move object to head of list... */
    if (cacheobj != cachelist.nxtcache) {
        cacheobj->lstcache->nxtcache = cacheobj->nxtcache;
        cacheobj->nxtcache->lstcache = cacheobj->lstcache;
        cacheobj->lstcache = &cachelist;
        cacheobj->nxtcache = cachelist.nxtcache;
        cachelist.nxtcache->lstcache = cacheobj;
        cachelist.nxtcache = cacheobj;
    }
}


/* cachesearch - to find or create cache entries...

        The grand plan here was to use a hash code as a quick key
        and call a compare function for duplicates. So far no data
        type actually works like this - they either have a unique binary
        key, or all records rely on the compare function - sigh!
        Never mind, the potential is there!  :-)   */

void *cachesearch(void **root,unsigned keyval,unsigned keylen,void *key,
                  int (*cmpfunc) (unsigned keylen,void *key,void *node),
                  unsigned *createsize)
{
    register struct CACHE *parentobj = NULL;
    register struct CACHE *cacheobj,**parent = (struct CACHE **) root;
    cachesearches++;
    while ((cacheobj = *parent) != NULL) {
        parentobj = cacheobj;
        if (cacheobj->keyval == keyval) {
            register int cmp = 0;
            if (cmpfunc != NULL)
                cmp = (*cmpfunc) (keylen,key,(void *) cacheobj);
            if (cmp == 0) {
                cachetouch(cacheobj);
                return cacheobj;
            } else {
                if (cmp < 0) {
                    parent = &cacheobj->left;
                } else {
                    parent = &cacheobj->right;
                }
            }
        } else {
            if (cacheobj->keyval < keyval) {
                parent = &cacheobj->left;
            } else {
                parent = &cacheobj->right;
            }
        }
    }
    if (*createsize > sizeof(struct CACHE)) {
        cacheobj = (struct CACHE *) malloc(*createsize);
        if (cacheobj != NULL) {
            cacheobj->parent = parent;
            cacheobj->left = NULL;
            cacheobj->right = NULL;
            cacheobj->objmanager = NULL;
            cacheobj->keyval = keyval;
            cacheobj->status = 0;
            cacheobj->refcount = 1;
            *parent = cacheobj;
            *createsize = 0;
            cachecreated++;
            /* Add to cache list... */
            cacheobj->lstcache = &cachelist;
            cacheobj->nxtcache = cachelist.nxtcache;
            cachelist.nxtcache->lstcache = cacheobj;
            cachelist.nxtcache = cacheobj;
            if (parentobj != NULL) {
                /* Attempt to mix up the tree a little... */
                if (parentobj->left == NULL) {
                    *parentobj->parent = cacheobj;
                    cacheobj->parent = parentobj->parent;
                    parentobj->parent = &cacheobj->left;
                    cacheobj->left = parentobj;
                    parentobj->right = NULL;
                } else {
                    if (parentobj->right == NULL) {
                        *parentobj->parent = cacheobj;
                        cacheobj->parent = parentobj->parent;
                        parentobj->parent = &cacheobj->right;
                        cacheobj->right = parentobj;
                        parentobj->left = NULL;
                    }
                }
            }
            if (cachecount++ >= cachepeak) cachepeak = cachecount;
        }
    }
    return cacheobj;
}
