#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);
}

