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

int     icformat(Disk*, ulong);

/*
*  read in the disk structures,  return -1 if the format
*  is inconsistent.
*/
int
dinit(Disk *d, int f, int psize, char *expname)
{
       ulong   i;
       uvlong  length;
       char    buf[1024];
       Bbuf    *b;
       Dalloc  *ba;
       Dir     *dir;

       /*
        *  get disk size
        */
       dir = dirfstat(f);
       if(dir == nil){
               perror("dinit: stat");
               return -1;
       }
       length = dir->length;
       free(dir);

       /*
        *  read first physical block to get logical block size, # of inodes,
        *  and # of allocation blocks
        */
       if(seek(f, 0, 0) < 0){
               perror("dinit: seek");
               return -1;
       }
       if(read(f, buf, sizeof(buf)) != sizeof(buf)){
               perror("dinit: read");
               return -1;
       }
       ba = (Dalloc*)buf;
       if(ba->bsize <= 0){
               fprint(2, "dinit: bsize 0x%lux<= 0\n", ba->bsize);
               return -1;
       }
       if((ba->bsize % psize) != 0){
               fprint(2, "dinit: logical bsize (%lud) not multiple of physical (%ud)\n",
                       ba->bsize, psize);
               return -1;
       }
       d->bsize = ba->bsize;
       d->nb = length/d->bsize;
       d->b2b = (d->bsize - sizeof(Dahdr))*8;
       d->nab = (d->nb+d->b2b-1)/d->b2b;
       d->p2b = d->bsize/sizeof(Dptr);
       strncpy(d->name, ba->name, sizeof d->name);

       if (expname != nil && strncmp(d->name, expname, sizeof d->name) != 0) {
               /* Mismatch with recorded name; fail here to force a format */
               fprint(2, "cfs: name mismatch\n");
               return -1;
       }

       /*
        *  check allocation blocks for consistency
        */
       if(bcinit(d, f, d->bsize) < 0){
               fprint(2, "dinit: couldn't init block cache\n");
               return -1;
       }
       for(i = 0; i < d->nab; i++){
               b = bcread(d, i);
               if(b == 0){
                       perror("dinit: read");
                       return -1;
               }
               ba = (Dalloc*)b->data;
               if(ba->magic != Amagic){
                       fprint(2, "dinit: bad magic in alloc block %uld\n", i);
                       return -1;
               }
               if(d->bsize != ba->bsize){
                       fprint(2, "dinit: bad bsize in alloc block %uld\n", i);
                       return -1;
               }
               if(d->nab != ba->nab){
                       fprint(2, "dinit: bad nab in alloc block %uld\n", i);
                       return -1;
               }
               if(strncmp(d->name, ba->name, sizeof(d->name))){
                       fprint(2, "dinit: bad name in alloc block %uld\n", i);
                       return -1;
               }
       }
       return 0;
}

/*
*  format the disk as a cache
*/
int
dformat(Disk *d, int f, char *name, ulong bsize, ulong psize)
{
       int     i;
       uvlong  length;
       Bbuf    *b;
       Dalloc  *ba;
       Dir     *dir;
       Dptr    dptr;

       fprint(2, "formatting disk\n");

       /*
        *  calculate basic numbers
        */
       dir = dirfstat(f);
       if(dir == nil)
               return -1;
       length = dir->length;
       d->bsize = bsize;
       if((d->bsize % psize) != 0){
               fprint(2, "cfs: logical bsize not multiple of physical\n");
               return -1;
       }
       d->nb = length/d->bsize;
       d->b2b = (d->bsize - sizeof(Dahdr))*8;
       d->nab = (d->nb+d->b2b-1)/d->b2b;
       d->p2b = d->bsize/sizeof(Dptr);

       /*
        *  init allocation blocks
        */
       if(bcinit(d, f, d->bsize) < 0)
               return -1;
       for(i = 0; i < d->nab; i++){
               b = bcalloc(d, i);
               if(b == 0){
                       perror("cfs: bcalloc");
                       return -1;
               }
               memset(b->data, 0, d->bsize);
               ba = (Dalloc*)b->data;
               ba->magic = Amagic;
               ba->bsize = d->bsize;
               ba->nab = d->nab;
               strncpy(ba->name, name, sizeof(ba->name));
               bcmark(d, b);
       }

       /*
        *  allocate allocation blocks
        */
       for(i = 0; i < d->nab; i++)
               if(dalloc(d, &dptr) == Notabno){
                       fprint(2, "can't allocate allocation blocks\n");
                       return -1;
               }

       return bcsync(d);
}

/*
*  allocate a block from a bit vector page
*
*  a return value of Notabno means no blocks left
*/
static ulong
_balloc(Dalloc *ba, ulong max)
{
       int len;        /* number of valid words */
       ulong i;        /* bit position in long */
       ulong m;        /* 1<<i */
       ulong v;        /* old value of long */
       ulong *p, *e;

       /*
        *  find a word with a 0 bit
        */
       len = (max+BtoUL-1)/BtoUL;
       for(p = ba->bits, e = p + len; p < e; p++)
               if(*p != 0xFFFFFFFF)
                       break;
       if(p == e)
               return Notabno;

       /*
        *  find the first 0 bit
        */
       v = *p;
       for(m = 1, i = 0; i < BtoUL; i++, m <<= 1)
               if((m|v) != v)
                       break;

       /*
        *  calculate block number
        */
       i += (p - ba->bits)*BtoUL;
       if(i >= max)
               return Notabno;

       /*
        *  set bit to 1
        */
       *p = v | m;
       return i;
}

/*
*  allocate a block
*
*  return Notabno if none left
*/
ulong
dalloc(Disk *d, Dptr *p)
{
       ulong   bno, max, rv;
       Bbuf    *b;
       Dalloc  *ba;

       max = d->nb;
       for(bno = 0; bno < d->nab; bno++){
               b = bcread(d, bno);
               ba = (Dalloc*)b->data;
               rv = _balloc(ba, max > d->b2b ? d->b2b : max);
               if(rv != Notabno){
                       rv = bno*d->b2b + rv;
                       if(p){
                               p->start = p->end = 0;
                               p->bno = rv;
                       }
                       bcmark(d, b);
                       return rv;
               }
               max -= d->b2b;
       }
       if(p)
               p->bno = Notabno;
       return Notabno;
}

/*
*  allocate a block of pointers
*/
ulong
dpalloc(Disk *d, Dptr *p)
{
       Bbuf *b;
       Dptr *sp, *ep;

       if(dalloc(d, p) == Notabno)
               return Notabno;

       /*
        *  allocate the page and invalidate all the
        *  pointers
        */
       b = bcalloc(d, p->bno);
       if(b == 0)
               return -1;
       sp = (Dptr*)b->data;
       for(ep = sp + d->p2b; sp < ep; sp++){
               sp->bno = Notabno;
               sp->start = sp->end = 0;
       }
       p->bno |= Indbno;
       p->start = 0;
       p->end = d->bsize;

       /*
        *  mark the page as dirty
        */
       bcmark(d, b);
       return 0;
}

/*
*  free a block
*/
int
_bfree(Disk *d, ulong i)
{
       ulong bno, m;
       ulong *p;
       Bbuf *b;
       Dalloc *ba;

       /*
        *  get correct allocation block
        */
       bno = i/d->b2b;
       if(bno >= d->nab)
               return -1;
       b = bcread(d, bno);
       if(b == 0)
               return -1;
       ba = (Dalloc*)b->data;

       /*
        *  change bit
        */
       i -= bno*d->b2b;
       p = ba->bits + (i/BtoUL);
       m = 1<<(i%BtoUL);
       *p &= ~m;
       bcmark(d, b);

       return 0;
}

/*
*  free a block (or blocks)
*/
int
dfree(Disk *d, Dptr *dp)
{
       ulong bno;
       Dptr *sp, *ep;
       Bbuf *b;

       bno = dp->bno;
       dp->bno = Notabno;

       /*
        *  nothing to free
        */
       if(bno == Notabno)
               return 0;

       /*
        *  direct pointer
        */
       if((bno & Indbno) == 0)
               return _bfree(d, bno);

       /*
        *  first indirect page
        */
       bno &= ~Indbno;
       _bfree(d, bno);

       /*
        *  then all the pages it points to
        *
        *      DANGER: this algorithm may fail if there are more
        *              allocation blocks than block buffers
        */
       b = bcread(d, bno);
       if(b == 0)
               return -1;
       sp = (Dptr*)b->data;
       for(ep = sp + d->p2b; sp < ep; sp++)
               if(dfree(d, sp) < 0)
                       return -1;
       return 0;
}