#include <u.h>
#include <libc.h>
#include "cformat.h"
#include "lru.h"
#include "bcache.h"

int
bcinit(Bcache *bc, int f, int bsize)
{
       Bbuf *b;

       /*
        *  allocate space for all buffers
        *  point all buffers into outer space
        */
       bc->dfirst = 0;
       bc->bsize = bsize;
       bc->f = f;
       lruinit(bc);
       for(b = bc->bb; b < &bc->bb[Nbcache]; b++){
               b->inuse = 0;
               b->next = 0;
               b->dirty = 0;
               if(b->data == 0)
                       b->data = (char *)malloc(bc->bsize);
               if(b->data == 0)
                       return -1;
               lruadd(bc, b);
       }

       return 0;
}

/*
*  Find a buffer for block b.  If it's dirty, write it out.
*/
Bbuf *
bcfind(Bcache *bc, ulong bno)
{
       Bbuf *b;

       if(bno == Notabno)
               error("bcfind: Notabno");
       bno &= ~Indbno;

       /*
        *  if we already have a buffer for this bno, use it
        */
       for(b = bc->bb; b < &bc->bb[Nbcache]; b++)
               if(b->inuse && b->bno==bno)
                       goto out;

       /*
        *  get least recently used block
        */
       b = (Bbuf*)bc->lnext;
out:
       /*
        *  if dirty, write it out
        */
       if(b->dirty)
               if(bcwrite(bc, b) < 0)
                       warning("writing dirty page");
       lruref(bc, b);
       return b;
}

/*
*  allocate a buffer block for a block.  it's guaranteed to be there till
*  the next Nbcache bcread's.
*/
Bbuf *
bcalloc(Bcache *bc, ulong bno)
{
       Bbuf *b;

       b = bcfind(bc, bno);
       bno &= ~Indbno;
       b->bno = bno;
       b->inuse = 1;
       return b;
}

/*
*  read a block into a buffer cache.  it's guaranteed to be there till
*  the next Nbcache bcread's.
*/
Bbuf *
bcread(Bcache *bc, ulong bno)
{
       Bbuf *b;

       b = bcfind(bc, bno);
       bno &= ~Indbno;
       if(b->bno!=bno || !b->inuse)
               /*
                *  read in the one we really want
                */
               if(bread(bc, bno, b->data) < 0){
                       b->inuse = 0;
                       return 0;
               }
       b->bno = bno;
       b->inuse = 1;
       return b;
}

/*
*  mark a page dirty, if it's already dirty force a write
*
*      N.B: ordering is important.
*/
void
bcmark(Bcache *bc, Bbuf *b)
{
       lruref(bc, b);

       if(b->dirty){
               bcwrite(bc, b);
               return;
       }

       b->dirty = 1;
       if(bc->dfirst)
               bc->dlast->next = b;
       else
               bc->dfirst = b;
       bc->dlast = b;
}

/*
*  write out a page (and all preceding dirty ones)
*/
int
bcwrite(Bcache *bc, Bbuf *b)
{
       Bbuf *nb;

       /*
        *  write out all preceding pages
        */
       while(nb = bc->dfirst){
               if(bwrite(bc, nb->bno, nb->data) < 0)
                       return -1;
               nb->dirty = 0;
               bc->dfirst = nb->next;
               nb->next = 0;
               if(nb == b)
                       return 0;
       }

       /*
        *  write out this page
        */
       if(bwrite(bc, b->bno, b->data) < 0)
               return -1;
       b->dirty = 0;
       b->next = 0;
       return 0;
}

/*
*  write out all dirty pages (in order)
*/
int
bcsync(Bcache *bc)
{
       if(bc->dfirst)
               return bcwrite(bc, bc->dlast);
       return 0;
}

/*
*  read a block from disk
*/
int
bread(Bcache *bc, ulong bno, void *buf)
{
       uvlong x = (uvlong)bno * bc->bsize;

       if(pread(bc->f, buf, bc->bsize, x) != bc->bsize)
               return -1;
       return 0;
}

/*
*  write a block to disk
*/
int
bwrite(Bcache *bc, ulong bno, void *buf)
{
       uvlong x = (uvlong)bno * bc->bsize;

       if(pwrite(bc->f, buf, bc->bsize, x) != bc->bsize)
               return -1;
       return 0;
}