#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "flashfs.h"

static  int     nextfnum;
static  Intmap* map;
static  Dir     dirproto;

static  char    user[]  = "flash";

       Entry   *root;
       ulong   used;
       ulong   limit;
       ulong   maxwrite;

enum
{
       debug   = 0,
};

static int
fnum(void)
{
       return ++nextfnum;
}

static void
maxfnum(int n)
{
       if(n > nextfnum)
               nextfnum = n;
}

static int
hash(char *s)
{
       int c, d, h;

       h = 0;
       while((c = *s++) != '\0') {
               d = c;
               c ^= c << 6;
               h += (c << 11) ^ (c >> 1);
               h ^= (d << 14) + (d << 7) + (d << 4) + d;
       }

       if(h < 0)
               return ~h;
       return h;
}

static void
dirinit(Entry *e)
{
       Entry **t;

       e->size = 0;
       t = emalloc9p(HSIZE * sizeof(Entry*));
       memset(t, 0, HSIZE * sizeof(Entry*));
       e->htab = t;
       e->files = nil;
       e->readers = nil;
}

static void
fileinit(Entry *e)
{
       e->size = 0;
       e->gen[0].head = nil;
       e->gen[0].tail = nil;
       e->gen[1].head = nil;
       e->gen[1].tail = nil;
}

static void
elfree(Extent *x)
{
       Extent *t;

       while(x != nil) {
               t = x->next;
               used -= x->size;
               free(x);
               x = t;
       }
}

static void
extfree(Entry *e)
{
       elfree(e->gen[0].head);
       elfree(e->gen[1].head);
}

static void
efree(Entry *e)
{
       if(debug)
               fprint(2, "free %s\n", e->name);

       if(e->mode & DMDIR)
               free(e->htab);
       else
               extfree(e);

       free(e->name);
       free(e);
}

void
einit(void)
{
       Entry *e;

       e = emalloc9p(sizeof(Entry));
       e->ref = 1;
       e->parent = nil;
       dirinit(e);
       e->name = estrdup9p("");
       e->fnum = 0;
       e->mode = DMDIR | 0775;
       e->mnum = 0;
       e->mtime = 0;
       root = e;
       map = allocmap(nil);
}

static void
dumptree(Entry *e, int n)
{
       Entry *l;

       if(debug)
               fprint(2, "%d %s %d\n", n, e->name, e->ref);

       if(e->mode & DMDIR) {
               n++;
               for(l = e->files; l != nil; l = l->fnext)
                       dumptree(l, n);
       }
}

void
edump(void)
{
       if(debug)
               dumptree(root, 0);
}

Entry *
elookup(ulong key)
{
       if(key == 0)
               return root;
       maxfnum(key);
       return lookupkey(map, key);
}

Extent *
esum(Entry *e, int sect, ulong addr, int *more)
{
       Exts *x;
       Extent *t, *u;

       x = &e->gen[eparity];
       t = x->tail;
       if(t == nil || t->sect != sect || t->addr != addr)
               return nil;
       u = t->prev;
       if(u != nil) {
               u->next = nil;
               *more = 1;
       }
       else {
               x->head = nil;
               *more = 0;
       }
       x->tail = u;
       x = &e->gen[eparity^1];
       u = x->head;
       t->next = u;
       x->head = t;
       if(u == nil)
               x->tail = t;
       else
               u->prev = t;
       return t;
}

void
edestroy(Entry *e)
{
       e->ref--;
       if(e->ref == 0)
               efree(e);
}

Entry *
ecreate(Entry *d, char *name, ulong n, ulong mode, ulong mtime, char **err)
{
       int h;
       Entry *e, *f;

       h = hash(name) & HMASK;
       for(e = d->htab[h]; e != nil; e = e->hnext) {
               if(strcmp(e->name, name) == 0) {
                       *err = Eexists;
                       return nil;
               }
       }

       e = emalloc9p(sizeof(Entry));
       e->ref = 1;
       e->parent = d;
       d->ref++;
       f = d->htab[h];
       e->hnext = f;
       e->hprev = nil;
       if(f != nil)
               f->hprev = e;
       d->htab[h] = e;
       f = d->files;
       e->fnext = f;
       e->fprev = nil;
       if(f != nil)
               f->fprev = e;
       d->files = e;

       d->ref--;
       e->ref++;
       e->name = estrdup9p(name);
       if(n == 0)
               n = fnum();
       else
               maxfnum(n);
       insertkey(map, n, e);
       e->fnum = n;
       e->mode = mode & d->mode;
       e->mnum = 0;
       e->mtime = mtime;

       if(e->mode & DMDIR)
               dirinit(e);
       else
               fileinit(e);

       d->mtime = mtime;
       return e;
}

void
etrunc(Entry *e, ulong n, ulong mtime)
{
       extfree(e);
       deletekey(map, e->fnum);
       if(n == 0)
               n = fnum();
       else
               maxfnum(n);
       e->fnum = n;
       e->mnum = 0;
       e->mtime = mtime;
       insertkey(map, n, e);
       fileinit(e);
       e->parent->mtime = mtime;
}

char *
eremove(Entry *e)
{
       Dirr *r;
       Entry *d, *n, *p;

       d = e->parent;
       if(d == nil)
               return Eperm;

       if((e->mode & DMDIR) != 0 && e->files != nil)
               return Edirnotempty;

       p = e->hprev;
       n = e->hnext;
       if(n != nil)
               n->hprev = p;
       if(p != nil)
               p->hnext = n;
       else
               d->htab[hash(e->name) & HMASK] = n;

       for(r = d->readers; r != nil; r = r->next) {
               if(r->cur == e)
                       r->cur = e->fnext;
       }

       p = e->fprev;
       n = e->fnext;
       if(n != nil)
               n->fprev = p;
       if(p != nil)
               p->fnext = n;
       else
               d->files = n;

       e->parent = nil;
       d->ref--;
       deletekey(map, e->fnum);
       edestroy(e);
       return nil;
}

Entry *
ewalk(Entry *d, char *name, char **err)
{
       Entry *e;

       if((d->mode & DMDIR) == 0) {
               *err = Enotdir;
               return nil;
       }

       if(strcmp(name, "..") == 0) {
               e = d->parent;
               if(e == nil)
                       return d;
               edestroy(d);
               e->ref++;
               return e;
       }

       for(e = d->htab[hash(name) & HMASK]; e != nil; e = e->hnext) {
               if(strcmp(e->name, name) == 0) {
                       d->ref--;
                       e->ref++;
                       return e;
               }
       }

       *err = Enonexist;
       return nil;
}

static void
eread0(Extent *e, Extent *x, uchar *a, ulong n, ulong off)
{
       uchar *a0, *a1;
       ulong n0, n1, o0, o1, d, z;

       for(;;) {
               while(e != nil) {
                       if(off < e->off + e->size && off + n > e->off) {
                               if(off >= e->off) {
                                       d = off - e->off;
                                       z = e->size - d;
                                       if(n <= z) {
                                               readdata(e->sect, a, n, e->addr + d);
                                               return;
                                       }
                                       readdata(e->sect, a, z, e->addr + d);
                                       a += z;
                                       n -= z;
                                       off += z;
                               }
                               else {
                                       a0 = a;
                                       n0 = e->off - off;
                                       o0 = off;
                                       a += n0;
                                       n -= n0;
                                       off += n0;
                                       z = e->size;
                                       if(n <= z) {
                                               readdata(e->sect, a, n, e->addr);
                                               a = a0;
                                               n = n0;
                                               off = o0;
                                       }
                                       else {
                                               readdata(e->sect, a, z, e->addr);
                                               a1 = a + z;
                                               n1 = n - z;
                                               o1 = off + z;
                                               if(n0 < n1) {
                                                       eread0(e->next, x, a0, n0, o0);
                                                       a = a1;
                                                       n = n1;
                                                       off = o1;
                                               }
                                               else {
                                                       eread0(e->next, x, a1, n1, o1);
                                                       a = a0;
                                                       n = n0;
                                                       off = o0;
                                               }
                                       }
                               }
                       }
                       e = e->next;
               }

               if(x == nil)
                       break;

               e = x;
               x = nil;
       }

       memset(a, 0, n);
}

ulong
eread(Entry *e, int parity, void *a, ulong n, ulong off)
{
       if(n + off >= e->size)
               n = e->size - off;
       if(n <= 0)
               return 0;
       eread0(e->gen[parity].head, e->gen[parity^1].head, a, n, off);
       return n;
}

void
ewrite(Entry *e, Extent *x, int parity, ulong mtime)
{
       ulong z;
       Extent *t;

       t = e->gen[parity].head;
       x->next = t;
       x->prev = nil;
       e->gen[parity].head = x;
       if(t == nil)
               e->gen[parity].tail = x;
       else
               t->prev = x;
       if(mtime != 0)
               e->mtime = mtime;
       used += x->size;
       z = x->off + x->size;
       if(z > e->size)
               e->size = z;
}

ulong
echmod(Entry *e, ulong mode, ulong mnum)
{
       if(mnum != 0)
               e->mnum = mnum;
       else
               e->mnum++;
       e->mode &= ~0777;
       e->mode |= mode;
       return e->mnum;
}

Qid
eqid(Entry *e)
{
       Qid qid;

       if(e->mode & DMDIR)
               qid.type = QTDIR;
       else
               qid.type = 0;
       qid.path = e->fnum;
       return qid;
}

void
estat(Entry *e, Dir *d, int alloc)
{
       d->type = 'z';
       d->dev = 0;
       if(alloc) {
               d->uid = estrdup9p(user);
               d->gid = estrdup9p(user);
               d->muid = estrdup9p(user);
               d->name = estrdup9p(e->name);
       }
       else {
               d->uid = user;
               d->gid = user;
               d->muid = user;
               d->name = e->name;
       }
       d->mode = e->mode;
       d->length = e->size;
       d->atime = e->mtime;
       d->mtime = e->mtime;
       d->qid = eqid(e);
}

Dirr *
ediropen(Entry *e)
{
       Dirr *d, *t;

       d = emalloc9p(sizeof(Dirr));
       d->dir = e;
       d->cur = e->files;
       t = e->readers;
       d->next = t;
       d->prev = nil;
       if(t != nil)
               t->prev = d;
       e->readers = d;
       e->ref++;
       return d;
}

int
edirread(Dirr *r, char *a, long n)
{
       Dir d;
       Entry *e;
       int m, x;

       m = 0;
       for(e = r->cur; e != nil; e = e->fnext) {
               estat(e, &d, 0);
               x = convD2M(&d, (uchar *)a, n);
               if(x <= BIT16SZ)
                       break;
               a += x;
               n -= x;
               m += x;
       }

       r->cur = e;
       return m;
}

void
edirclose(Dirr *d)
{
       Entry *e;
       Dirr *p, *n;

       e = d->dir;
       p = d->prev;
       n = d->next;
       if(n != nil)
               n->prev = p;
       if(p != nil)
               p->next = n;
       else
               e->readers = n;

       edestroy(e);
       free(d);
}

static  Renum   R;

static void
xrenum(Extent *x)
{
       while(x != nil) {
               if(x->sect == R.old)
                       x->sect = R.new;
               x = x->next;
       }
}

static void
renum(Entry *e)
{
       if(e->mode & DMDIR) {
               for(e = e->files; e != nil; e = e->fnext)
                       renum(e);
       }
       else {
               xrenum(e->gen[0].head);
               xrenum(e->gen[1].head);
       }
}

void
erenum(Renum *r)
{
       R = *r;
       renum(root);
}