/* databuff.c - Buffer management routines */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* |_o_o|\\ Copyright (c) 1988 The Software Distillery.  All Rights Reserved */
/* |. o.| || This program may not be distributed without the permission of   */
/* | .  | || the authors:                             BBS: (919) 481-6436    */
/* | o  | ||   John Toebes     John Mainwaring    Jim Cooper                 */
/* |  . |//    Bruce Drake     Gordon Keener      Dave Baker                 */
/* ======                                                                    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Buffer management routines : */
/* ModBlock GetBlock */
/* FindBuffer AllocBuffers FreeBuffers FlushBuffers FlushOne */

#include "handler.h"

char *GetBlock(global, key)
GLOBAL global;
KEY key;
{
int i;

   i = FindBuffer(global, key, 1);

   if (i == -1)
      return(NULL);

   BUG(("GetBlock: Key %ld Buff %08lx\n", key, global->blkbuf[i].bb_blk));
   return(global->blkbuf[i].bb_blk);
}

char *ModBlock(global, key)
GLOBAL global;
KEY key;
{
int i;

   /* Make sure we have the rights to write to the disk */
   if (global->diskstate != ID_VALIDATED)
      {
      BUG(("ModBlock - Write protected disk\n"));
      global->pkt->dp_Res1 = DOS_FALSE;
      global->pkt->dp_Res2 = ERROR_DISK_WRITE_PROTECTED;
      return(NULL);
      }

   /* Locate the block in a buffer */
   i = FindBuffer(global, key, 1);
 
   /* Gee, it wasn't found, just return an error */
   if (i == -1)
      return(NULL);

   /* Mark it as having been touched so we flush it later */
   global->blkbuf[i].bb_dirty = 1;

   BUG(("ModBlock: Key %ld Buff %08lx\n", key, global->blkbuf[i].bb_blk));
   return(global->blkbuf[i].bb_blk);
}

int FindBuffer(global, key, readin)
register GLOBAL global;
KEY key;
int readin;
{
register int i;
int num;
struct BlockBuffer *tmpglob;

   /* First pass, go through and see if it is already in a buffer */
   for (i = 0; i< global->dskenv.de_numbufs; i++)
      {
      if (global->blkbuf[i].bb_key == key &&
          global->blkbuf[i].bb_used)
         {
         if (global->blkbuf[i].bb_priority < 0xff)
            global->blkbuf[i].bb_priority++;
         return(i);
         }
      }

   /* For some reason it wasn't in a buffer already (imagine that).  So we  */
   /* want to go through and decrement all the priorities of the buffers    */
   /* and select a candidate for flushing to hold our new block.  Note that */
   /* an empty buffer is assumed to have a priority of 0 while a buffer in  */
   /* use will never be decremented below 1                                 */
   tmpglob = &global->blkbuf[0];
   num = 0;
   for (i = 0; i < global->dskenv.de_numbufs; i++)
      {
      if (global->blkbuf[i].bb_used &&
          (global->blkbuf[i].bb_priority > 1))
         global->blkbuf[i].bb_priority--;

      if (global->blkbuf[i].bb_priority < tmpglob->bb_priority)
         {
         tmpglob = &global->blkbuf[i];
         num = i;
         }
      }

   /* Now tmpglob points to our new buffer.  Empty it out if necessary */
   FlushOne(global, num);

   /* Mark it for the new buffer that we want to use it as */
   tmpglob->bb_used = 1;
   tmpglob->bb_key = key;
   tmpglob->bb_priority = global->dskenv.de_numbufs;
   tmpglob->bb_dirty = 0;   /* Hopefully FlushOne reset it but who knows */

   /* Did they ask us to read it in? */
   if (readin)
      {
      if (ReadPhy(global, tmpglob->bb_blk, key))
         {
         BUG(("Failure reading in key %ld\n", key));
         tmpglob->bb_used = 0;
         tmpglob->bb_key = -1;
         tmpglob->bb_priority = 0;
         num = -1;  /* indicate the failure */
         }
      }

   return(num);
}
void FreeBlkBuffs(global)
GLOBAL global;
{
    BUG(("FreeBlkBuffs\n"));
    FreeMem((char *)global->blkbuf,((sizeof(struct BlockBuffer))*
                                   (global->dskenv.de_numbufs)));
    global->blkbuf = NULL;
}

int AllocBlkBuffs(global, newsize)
GLOBAL global;
int newsize;    
{
    struct BlockBuffer *tmpbuff;
    int     i;

    BUG(("AllocBlkBuffs size:%ld\n",newsize));
    if ((tmpbuff = (struct BlockBuffer *)AllocMem(((sizeof(struct BlockBuffer))
                   * newsize),(MEMF_PUBLIC | MEMF_CLEAR)))== NULL){  
         BUG(("AllocBlkBuffs Failed\n")); 
         return(0);
    }
    /* successfully allocated copy stuff over and free old */
    if (global->blkbuf){
        for(i=0;i < (global->dskenv.de_numbufs);i++){    
          if (i < newsize){ 
            tmpbuff[i].bb_blk = global->blkbuf[i].bb_blk;
          }
          if (i >= newsize){
            FreeMem(global->blkbuf[i].bb_blk,
                    ((global->dskenv.de_sizeblock) * BYTESPERLONG));
          }
        }  
        FreeBlkBuffs(global);   
    }
    BUG(("Allocated blk buffs at:%08lx\n",tmpbuff));
    /* allocate memory for data blocks */
    for(i=0;i < newsize;i++){    
       if (tmpbuff[i].bb_blk == NULL){
          if((tmpbuff[i].bb_blk = (char *)
             AllocMem(((global->dskenv.de_sizeblock) * BYTESPERLONG),
                        global->dskenv.de_membuftype))==NULL){
             /* we will loose some bytes here for now */
             global->dskenv.de_numbufs = i;
             return(NULL);
          }
       }
       tmpbuff[i].bb_key = -1;
    }
    global->blkbuf = tmpbuff;
    global->dskenv.de_numbufs = newsize;
    return(-1);
}

void FlushOne(global, num)
GLOBAL global;
int num;
{
   /* Does this buffer need to be written out to disk ? */
   if (global->blkbuf[num].bb_dirty &&
       global->blkbuf[num].bb_used)
      {
      /* Ok, see if it needs to be checksummed */
      if (global->blkbuf[num].bb_used != ST_BITMAP)
          DoCheckSum(global, global->blkbuf[num].bb_blk);

      /* At last we get to write out the block */
      while(WritePhy(global,
                     global->blkbuf[num].bb_blk, global->blkbuf[num].bb_key))
         {
         if (!request(global, REQ_ERROR, NULL))
            break;
         ResetDrive(global);
         }
      }

   /* Now we need to reset it so it doesn't get written out again */
   global->blkbuf[num].bb_dirty = 0;

}

void FlushBuffers(global, purge)
GLOBAL global;
int purge;
{
register int i;

   for (i = 0; i < global->dskenv.de_numbufs; i++)
      {
      FlushOne(global, i);

      /* Did they want us to forget about when we are done ? */
      if (purge)
         {
         /* If we don't want the buffer any more we need to zap it */
         global->blkbuf[i].bb_used = 0;
         global->blkbuf[i].bb_key = -1;
         global->blkbuf[i].bb_priority = 0;
         }
      }
}
