#include <u.h>
#include <libc.h>
#include <bio.h>
#include <flate.h>
#include <auth.h>
#include <fcall.h>
#include <ctype.h>
#include "tapefs.h"
#include "zip.h"

#define FORCE_LOWER     1       /* force filenames to lower case */
#define MUNGE_CR        1       /* replace '\r\n' with ' \n' */
#define High64 (1LL<<63)

/*
* File system for zip archives (read-only)
*/

enum {
       IS_MSDOS = 0,   /* creator OS (interpretation of external flags) */
       IS_RDONLY = 1,  /* file was readonly (external flags) */
       IS_TEXT = 1,    /* file was text  (internal flags) */
};

typedef struct Block Block;
struct Block{
       uchar *pos;
       uchar *limit;
};

static Biobuf *bin;
static ulong *crctab;
static ulong crc;

static int findCDir(Biobuf *);
static int header(Biobuf *, ZipHead *);
static int cheader(Biobuf *, ZipHead *);
static void trailer(Biobuf *, ZipHead *);
static char *getname(Biobuf *, int);
static int blwrite(void *, void *, int);
static ulong get4(Biobuf *);
static int get2(Biobuf *);
static int get1(Biobuf *);
static long msdos2time(int, int);

void
populate(char *name)
{
       char *p;
       Fileinf f;
       ZipHead zh;
       int ok, entries;

       crctab = mkcrctab(ZCrcPoly);
       ok = inflateinit();
       if(ok != FlateOk)
               sysfatal("inflateinit failed: %s", flateerr(ok));

       bin = Bopen(name, OREAD);
       if (bin == nil)
               error("Can't open argument file");

       entries = findCDir(bin);
       if(entries < 0)
               sysfatal("empty file");

       while(entries-- > 0){
               memset(&zh, 0, sizeof(zh));
               if(!cheader(bin, &zh))
                       break;
               f.addr = zh.off;
               if(zh.iattr & IS_TEXT)
                       f.addr |= High64;
               f.mode = (zh.madevers == IS_MSDOS && zh.eattr & IS_RDONLY)? 0444: 0644;
               if (zh.meth == 0 && zh.uncsize == 0){
                       p = strchr(zh.file, '\0');
                       if(p > zh.file && p[-1] == '/')
                               f.mode |= (DMDIR | 0111);
               }
               f.uid = 0;
               f.gid = 0;
               f.size = zh.uncsize;
               f.mdate = msdos2time(zh.modtime, zh.moddate);
               f.name = zh.file + ((zh.file[0] == '/')? 1: 0);
               poppath(f, 1);
               free(zh.file);
       }
       return ;
}

void
dotrunc(Ram *r)
{
       USED(r);
}

void
docreate(Ram *r)
{
       USED(r);
}

char *
doread(Ram *r, vlong off, long cnt)
{
       int i, err;
       Block bs;
       ZipHead zh;
       static Qid oqid;
       static char buf[Maxbuf];
       static uchar *cache = nil;

       if (cnt > Maxbuf)
               sysfatal("file too big (>%d)", Maxbuf);

       if (Bseek(bin, r->addr & 0x7FFFFFFFFFFFFFFFLL, 0) < 0)
               sysfatal("seek failed");

       memset(&zh, 0, sizeof(zh));
       if (!header(bin, &zh))
               sysfatal("cannot get local header");

       switch(zh.meth){
       case 0:
               if (Bseek(bin, off, 1) < 0)
                       sysfatal("seek failed");
               if (Bread(bin, buf, cnt) != cnt)
                       sysfatal("read failed");
               break;
       case 8:
               if (r->qid.path != oqid.path){
                       oqid = r->qid;
                       if (cache)
                               free(cache);
                       cache = emalloc(r->ndata);

                       bs.pos = cache;
                       bs.limit = cache+r->ndata;
                       if ((err = inflate(&bs, blwrite, bin, (int(*)(void*))Bgetc)) != FlateOk)
                               sysfatal("inflate failed - %s", flateerr(err));

                       if (blockcrc(crctab, crc, cache, r->ndata) != zh.crc)
                               fprint(2, "%s - crc failed", r->name);

                       if ((r->addr & High64) && MUNGE_CR){
                               for (i = 0; i < r->ndata -1; i++)
                                       if (cache[i] == '\r' && cache[i +1] == '\n')
                                               cache[i] = ' ';
                       }
               }
               memcpy(buf, cache+off, cnt);
               break;
       default:
               sysfatal("%d - unsupported compression method", zh.meth);
               break;
       }

       return buf;
}

void
popdir(Ram *r)
{
       USED(r);
}

void
dowrite(Ram *r, char *buf, long off, long cnt)
{
       USED(r); USED(buf); USED(off); USED(cnt);
}

int
dopermw(Ram *r)
{
       USED(r);
       return 0;
}

/*************************************************/

static int
findCDir(Biobuf *bin)
{
       vlong ecoff;
       long off;
       int entries, zclen;

       ecoff = Bseek(bin, -ZECHeadSize, 2);
       if(ecoff < 0)
               sysfatal("can't seek to header");
       off = 0;
       while(get4(bin) != ZECHeader){
               if(ecoff <= 0 || off >= 1024)
                       sysfatal("bad magic number");
               off++;
               ecoff--;
               Bseek(bin, ecoff, 0);
       }
       get2(bin);
       get2(bin);
       get2(bin);
       entries = get2(bin);
       get4(bin);
       off = get4(bin);
       zclen = get2(bin);
       while(zclen-- > 0)
               get1(bin);

       if(Bseek(bin, off, 0) != off)
               sysfatal("can't seek to contents");

       return entries;
}


static int
header(Biobuf *bin, ZipHead *zh)
{
       ulong v;
       int flen, xlen;

       v = get4(bin);
       if(v != ZHeader){
               if(v == ZCHeader)
                       return 0;
               sysfatal("bad magic on local header");
       }
       zh->extvers = get1(bin);
       zh->extos = get1(bin);
       zh->flags = get2(bin);
       zh->meth = get2(bin);
       zh->modtime = get2(bin);
       zh->moddate = get2(bin);
       zh->crc = get4(bin);
       zh->csize = get4(bin);
       zh->uncsize = get4(bin);
       flen = get2(bin);
       xlen = get2(bin);

       zh->file = getname(bin, flen);

       while(xlen-- > 0)
               get1(bin);
       return 1;
}

static int
cheader(Biobuf *bin, ZipHead *zh)
{
       ulong v;
       int flen, xlen, fclen;

       v = get4(bin);
       if(v != ZCHeader){
               if(v == ZECHeader)
                       return 0;
               sysfatal("bad magic number in file");
       }
       zh->madevers = get1(bin);
       zh->madeos = get1(bin);
       zh->extvers = get1(bin);
       zh->extos = get1(bin);
       zh->flags = get2(bin);
       zh->meth = get2(bin);
       zh->modtime = get2(bin);
       zh->moddate = get2(bin);
       zh->crc = get4(bin);
       zh->csize = get4(bin);
       zh->uncsize = get4(bin);
       flen = get2(bin);
       xlen = get2(bin);
       fclen = get2(bin);
       get2(bin);              /* disk number start */
       zh->iattr = get2(bin);  /* 1 == is-text-file */
       zh->eattr = get4(bin);  /* 1 == readonly-file */
       zh->off = get4(bin);

       zh->file = getname(bin, flen);

       while(xlen-- > 0)
               get1(bin);

       while(fclen-- > 0)
               get1(bin);

       return 1;
}

static int
blwrite(void *vb, void *buf, int n)
{
       Block *b = vb;
       if(n > b->limit - b->pos)
               n = b->limit - b->pos;
       memmove(b->pos, buf, n);
       b->pos += n;
       return n;
}


static void
trailer(Biobuf *bin, ZipHead *zh)
{
       if(zh->flags & ZTrailInfo){
               zh->crc = get4(bin);
               if(zh->crc == 0x08074b50)       /* thanks apple */
                       zh->crc = get4(bin);
               zh->csize = get4(bin);
               zh->uncsize = get4(bin);
       }
}

static char*
getname(Biobuf *bin, int len)
{
       char *s;
       int i, c;

       s = emalloc(len + 1);
       for(i = 0; i < len; i++){
               c = get1(bin);
               if(FORCE_LOWER)
                       c = tolower(c);
               s[i] = c;
       }
       s[i] = '\0';
       return s;
}


static ulong
get4(Biobuf *b)
{
       ulong v;
       int i, c;

       v = 0;
       for(i = 0; i < 4; i++){
               c = Bgetc(b);
               if(c < 0)
                       sysfatal("unexpected eof");
               v |= c << (i * 8);
       }
       return v;
}

static int
get2(Biobuf *b)
{
       int i, c, v;

       v = 0;
       for(i = 0; i < 2; i++){
               c = Bgetc(b);
               if(c < 0)
                       sysfatal("unexpected eof");
               v |= c << (i * 8);
       }
       return v;
}

static int
get1(Biobuf *b)
{
       int c;

       c = Bgetc(b);
       if(c < 0)
               sysfatal("unexpected eof");
       return c;
}

static long
msdos2time(int time, int date)
{
       Tm tm;

       memset(&tm, 0, sizeof(tm));
       tm.hour = time >> 11;
       tm.min = (time >> 5) & 63;
       tm.sec = (time & 31) << 1;
       tm.year = 80 + (date >> 9);
       tm.mon = ((date >> 5) & 15) - 1;
       tm.mday = date & 31;
       tm.zone[0] = '\0';
       tm.yday = 0;

       return tm2sec(&tm);
}