/***************************************************************************
 *                                                                         *
 *   SMCACHE.C                                                             *
 *                                                                         *
 *   Copyright (c) 1997      Galacticomm, Inc.      All Rights Reserved.   *
 *                                                                         *
 *   Caching API for searchable memory block API.                          *
 *                                                                         *
 *                                           - J. Alvrus    5/20/97        *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "smbapi.h"
#include "smcache.h"

#define FILREV "$Revision: 3 $"

static SMCLRU smcGenLRU(SMCACHE *smc);
static SMCPOS smcFindBlock(SMCACHE *smc,SMBPTR ref);
static INT smcComp(const VOID *target,const VOID *array,ULONG index);
VOID *smcBlockPtr(SMCACHE *smc,SMCPOS cpos);

MARKSOURCE(smcache)

SMCACHE *                          /*   pointer to tracking structure      */
smcOpen(                           /* open a cache for an SMB file         */
size_t blockSize,                  /*   size of block being cached         */
SMCPOS maxBlocks)                  /*   max blocks to be cached            */
{
     SMCACHE *smc;
     SMCBLOCK *track;
     SMCPOS i;

     if (maxBlocks > SMC_MAXSIZE) {
          catastro("Invalid cache size: %d",maxBlocks);
     }
     smc=alcmem(sizeof(SMCACHE));
     smc->track=(SMCBLOCK *)alcmem(maxBlocks*sizeof(SMCBLOCK));
     smc->cache=alcblok(maxBlocks,blockSize);
     smc->lru=0;
     smc->numBlocks=0;
     smc->maxBlocks=maxBlocks;
     for (i=0,track=smc->track ; i < maxBlocks ; ++i,++track) {
          track->block=SMBNULL;
          track->lru=0;
          track->pos=i;
     }
     return(smc);
}

VOID
smcClose(                          /* close an SMB file cache              */
SMCACHE *smc)                      /*   cache to free                      */
{
     free(smc->track);
     freblok(smc->cache);
     free(smc);
}

VOID *                             /*   block in cache, NULL if cache full */
smcAlloc(                          /* allocate a block in a cache          */
SMCACHE *smc,                      /*   cache to allocate in               */
SMBPTR ref)                        /*   reference of new block             */
{
     SMCBLOCK track;
     SMCPOS pos;
     INT cmp;

     if (smcFull(smc)) {
          return(NULL);
     }
     track=smc->track[smc->numBlocks];
     ASSERT(track.block == SMBNULL);
     track.block=ref;
     track.lru=smcGenLRU(smc);
     pos=(SMCPOS)binFindNear(&cmp,&ref,smc->track,(ULONG)smc->numBlocks,smcComp);
     memmove(&smc->track[pos+1],&smc->track[pos],
             (smc->numBlocks-pos)*sizeof(SMCBLOCK));
     smc->track[pos]=track;
     ++smc->numBlocks;
     return(smcBlockPtr(smc,track.pos));
}

VOID
smcFree(                           /* free a block in a cache              */
SMCACHE *smc,                      /*   cache to free in                   */
SMBPTR ref)                        /*   reference of block to free         */
{
     smcFreeByPos(smc,smcFindBlock(smc,ref));
}

VOID
smcFreeByPos(                      /* free block in cache by position      */
SMCACHE *smc,                      /*   cache to free in                   */
SMCPOS pos)                        /*   position of block                  */
{
     SMCBLOCK track;

     if (pos < 0 || pos >= smc->numBlocks) {
          return;
     }
     track=smc->track[pos];
     ASSERT(track.block != SMBNULL);
     track.block=SMBNULL;
     --smc->numBlocks;
     memmove(&smc->track[pos],&smc->track[pos+1],
             (smc->numBlocks-pos)*sizeof(SMCBLOCK));
     smc->track[smc->numBlocks]=track;
}

VOID *                             /*   block in cache, NULL if not present*/
smcGet(                            /* get pointer an block in a cache      */
SMCACHE *smc,                      /*   cache to retrieve from             */
SMBPTR ref)                        /*   block reference to retrieve        */
{
     SMCPOS pos;

     pos=smcFindBlock(smc,ref);
     if (pos < smc->numBlocks) {
          smc->track[pos].lru=smcGenLRU(smc);
          return(smcGetByPos(smc,pos));
     }
     return(NULL);
}

VOID *                             /*   block in cache                     */
smcGetByPos(                       /* get ptr to block by cache position   */
SMCACHE *smc,                      /*   cache to retrieve from             */
SMCPOS pos)                        /*   position of block                  */
{
     return(smcBlockPtr(smc,smc->track[pos].pos));
}

GBOOL
smcFull(                           /* is a cache full?                     */
SMCACHE *smc)                      /*   cache to check                     */
{
     return(smc->numBlocks >= smc->maxBlocks);
}

SMCPOS                             /*   cache position of block            */
smcFindLRUPos(                     /* find least-recently-used block       */
SMCACHE *smc)                      /*   cache to find in                   */
{
     SMCBLOCK *track;
     SMCLRU minlru;
     SMCPOS i,n,minpos;

     n=smc->numBlocks;
     minpos=0;
     minlru=smc->track[0].lru;
     for (i=1,track=&smc->track[1] ; i < n ; ++i,++track) {
          if (minlru > track->lru) {
               minlru=track->lru;
               minpos=i;
          }
     }
     return(minpos);
}

SMBPTR                             /*   returns reference to block         */
smcBlockFromPos(                   /* get block reference given cache pos  */
SMCACHE *smc,                      /*   cache to find in                   */
SMCPOS pos)                        /*   cache position to retrieve         */
{
     return(smc->track[pos].block);
}

static SMCLRU
smcGenLRU(                         /* generate an LRU value                */
SMCACHE *smc)                      /*   cache to find in                   */
{
     INT i;
     SMCLRU rv,minlru,maxlru;

     if ((rv=++smc->lru) == 0) {
          minlru=maxlru=smc->track[0].lru;
          for (i=1 ; i < smc->numBlocks ; ++i) {
               rv=smc->track[i].lru;
               if (rv > maxlru) {
                    maxlru=rv;
               }
               if (rv < minlru) {
                    minlru=rv;
               }
          }
          for (i=0 ; i < smc->numBlocks ; ++i) {
               smc->track[i].lru-=minlru;
          }
          rv=smc->lru=(maxlru-minlru)+1;
          ASSERT(rv != 0);
     }
     return(rv);
}

static SMCPOS                      /*   position or numBlocks if not found */
smcFindBlock(                      /* find a block in a cache              */
SMCACHE *smc,                      /*   cache to find in                   */
SMBPTR ref)                        /*   reference of block to find         */
{
     return((SMCPOS)binSearch(&ref,smc->track,(ULONG)smc->numBlocks,smcComp));
}

static INT                         /*   > 0 = target > test, etc.          */
smcComp(                           /* binary search comparison function    */
const VOID *target,                /*   target object                      */
const VOID *array,                 /*   array object                       */
ULONG index)                       /*   index of array element to test     */
{
     SMBPTR targ,test;

     targ=*((SMBPTR *)target);
     test=((SMCBLOCK *)array)[(INT)index].block;
     return(targ < test ? -1 : targ > test ? 1 : 0);
}

VOID *                             /*   block in cache                     */
smcBlockPtr(                       /* get ptr to block in cache memory     */
SMCACHE *smc,                      /*   cache to retrieve from             */
SMCPOS cpos)                       /*   position of block in cache memory  */
{
     return(ptrblok(smc->cache,cpos));
}
