/***********************************************************************\

   Filename: memory.c

\***********************************************************************/

#include <memory.h>
#include "proxlib.h"
#include "proxio.h"
#include "spmemory.h"
#include "scdef.h"
#include "sp.h"
#include "spdef.h"

#ifndef MM_NBLKS
#define MM_NBLKS 4              /* Default number of blocks to use */
#endif

/* Control structure for each buffer. */

typedef struct mb_struct {
   HANDLE  mb_file;        /* file the block came from */
   UCHAR   *mb_blk;        /* block-sized buffer (actual data) */
   int     mb_blknum;      /* block's block number in the file */
   UCHAR   mb_flags;       /* priority and usage requested */
   UCHAR   mb_age;         /* how long block has been in list */
   struct mb_struct *mb_newer;/* pointer to newer block */
   struct mb_struct *mb_older;/* pointer to older block */
} MEMBLK;

int Memup[] = { 0 };           /* Memory is initialized */
static MEMBLK Memlist[MM_NBLKS];/* Memory headers */
static MEMBLK *Memnewest[1];       /* Newest memory block */

/* Initialize the block buffering system. */

meminit()
{
register MEMBLK *mp;

   /* If this has already been done, return. */

   if (Memup[0])
       return(1);

   /* Allocate space for each memory block; initialize each control
      structure. */

   for (mp = Memlist + MM_NBLKS; --mp >= Memlist; )
   {
        if (!(mp->mb_blk = (char *) zalloc(_SPTHID[0], MM_BLKSIZE)))
            return(0);
        mp->mb_file = H_ERROR;
        mp->mb_flags = 0;
        mp->mb_newer = mp - 1;
        mp->mb_older = mp + 1;
   }

   /* Make the linked list into a circular queue. */

   Memlist[0].mb_newer = &Memlist[MM_NBLKS - 1];
   Memlist[MM_NBLKS - 1].mb_older = Memlist;

   /* Initialize the top of memory pointer. */

   Memnewest[0] = Memlist;

   /* Memory has been initialized */

   Memup[0] = 1;
   return(1);
}

/* See if a block for a given file is in memory. */

MEMBLK *memcheck(file, blknum)
HANDLE  file;                   /* The file the block is in. */
int     blknum;                 /* The block number. */
{
register MEMBLK *mp;

   mp = Memnewest[0];
   while (mp->mb_file != file || mp->mb_blknum != blknum)
   {
       mp = mp->mb_older;
       if (mp == Memnewest[0])
           return (NULL);
   }
   return (mp);
}

/* Put a buffer at the top of the list.  This is the only routine that 
   changes the memory control links.  */ 

void
memlink(mp)
register MEMBLK *mp;
{
register MEMBLK *new;

   /* Do nothing if the buffer is already at the top of the list. */

   new = Memnewest[0];
   if (new == mp)
       return;

   /* Unlink the buffer from the list. */

   mp->mb_newer->mb_older = mp->mb_older;
   mp->mb_older->mb_newer = mp->mb_newer;

   /* Add it to the top of the list. */

   Memnewest[0] = mp;
   mp->mb_newer = new->mb_newer;
   mp->mb_older = new;
   new->mb_newer->mb_older = mp;
   new->mb_newer = mp;
}

/* Make a buffer unused.  If the buffer contained modified data, write that 
   data to disk.  */ 

memdel(mp)
register MEMBLK *mp;
{
   /* Write the block, if it was modified. */

   if (!memwrite(mp))
       return (FALSE);

   /* make the buffer unused. */

   mp->mb_file = H_ERROR;
   mp->mb_flags = 0;

   /* Make this buffer the newest; since the list is circular, moving
           the newest pointer down to the next oldest will make this block 
           the oldest.  */ 

   memlink(mp);
   Memnewest[0] = (Memnewest[0])->mb_older;
   return (TRUE);
}

/* Write a block to disk. */

memwrite(mp)
register MEMBLK *mp;
{
   /* Do not write it if it was not modified. */

   if (!(mp->mb_flags & MM_WRITE))
       return (TRUE);

   /* Seek to the proper place and then write the buffer. */

   if (stdseek((long)mp->mb_blknum * MM_BLKSIZE, mp->mb_file)
       || stdwrite((char *)mp->mb_blk, MM_BLKSIZE, mp->mb_file)
         != MM_BLKSIZE)
       return (FALSE);

   /* Mark the buffer as no longer modified. */

   mp->mb_flags &= ~MM_WRITE;
   return (TRUE);
}

/* Get a buffer for a block. */

UCHAR *
memread(blknum, file, flags)
int     blknum;         /* Block number in file. */
HANDLE  file;           /* File to read. */
int     flags;          /* Read flags. */
{
MEMBLK *mp;

        /* Look for the block in memory.  If it is not in memory then read 
           it.  */ 

   if ((mp = memcheck(file, blknum)) == NULL)
   {
       /* Find a buffer to read into. */

       mp = Memnewest[0];
       while (1)
       {
           mp = mp->mb_newer;
           if (mp == Memnewest[0])
           {
               mp = mp->mb_newer;
               break;
           }
           if (mp->mb_age >= (flags & MM_PRMASK))
               break;
       }
       /* Free the buffer. */

       if (!memdel(mp))
           return (NULL);

       /* if MM_READ was specified, read the block from the file */

       if (flags & MM_READ)
       {
           if (stdseek((long)blknum * MM_BLKSIZE, file) || stdread((char *)
             mp->mb_blk, MM_BLKSIZE, file) != MM_BLKSIZE)
               return (NULL);
       }

       /* Initialize the memory data. */

       mp->mb_file = file;
       mp->mb_blknum = blknum;
       mp->mb_flags = flags;
   }
   else
   /* Make this block the newest; update its modify flag; store its
      priority and return the block. */

   mp->mb_flags |= flags & MM_WRITE;
   mp->mb_age = flags & MM_PRMASK;
   memlink(mp);

   return (mp->mb_blk);
}

/* If file is NULLPTR write all modified blocks to disk.  If not, release 
   all memory associated with the file. */

memflush(file)
HANDLE  file;                   /* The file to flush. */
{
MEMBLK *mp;

   for (mp = Memlist + MM_NBLKS; --mp >= Memlist; )
   {
       if (!file)
       {
           if (!memwrite(mp))
               return (ERROR);
       }
       else if (mp->mb_file == file && !memdel(mp))
           return (ERROR);
   }
   return (OKAY);
}

/* Change the block number associated with a block already in memory. */

int
memswitch(file, oldnum, newnum)
HANDLE  file;                   /* The file to do it to. */
int     oldnum;                 /* The old block number  */
int     newnum;                 /* The new block number  */
{
register MEMBLK *mp;

   if ((mp = memcheck(file, oldnum)) == NULL) {
       return (FALSE);
   }
   mp->mb_blknum = newnum;
   return (TRUE);
}
