#include "lib9.h"
#include "logfs.h"
#include "local.h"
#include "fcall.h"

static void
maxpath(LogfsServer *server, ulong p)
{
	if(p > server->path)
		server->path = p;
}

static char *
recreate(LogfsServer *server, LogMessage *s, int *ok)
{
	Entry *parent;
	char *errmsg;
	Entry *e;
	Path *p;

	parent = logfspathmapfinde(server->pathmap, s->path);
	if(parent == nil)
		return "can't find parent";
	if(logfspathmapfindentry(server->pathmap, s->u.create.newpath) != nil){
		Entry *d = logfspathmapfinde(server->pathmap, s->u.create.newpath);
		if(d == nil)
			print("existing was nil\n");
		else{
			print("existing: name=%q d=%d path=%8.8llux uid=%q gid=%q perm=%#uo\n",
				d->name, d->deadandgone, d->qid.path, d->uid, d->gid, d->perm);
		}
		return "duplicate path";
	}
	if((parent->qid.type & QTDIR) == 0)
		return Enotdir;
	errmsg = logfsentrynew(server, 1, s->u.create.newpath, parent,
		s->u.create.name, s->u.create.uid, s->u.create.gid, s->u.create.mtime, s->u.create.uid,
		s->u.create.perm, s->u.create.cvers, 0, &e);
	if(errmsg) {
		*ok = 0;
		return errmsg;
	}
	/* p guaranteed to be non null */
	errmsg = logfspathmapnewentry(server->pathmap, s->u.create.newpath, e, &p);
	if(errmsg) {
		logfsfreemem(e);
		*ok = 0;
		return errmsg;
	}
	e->next = parent->u.dir.list;
	parent->u.dir.list = e;
	return nil;
}

static char *
reremove(LogfsServer *server, LogMessage *s, int *ok)
{
	Entry *oe;
	Entry *parent;
	Entry **ep;
	Entry *e;
	char *ustmuid;

	USED(ok);
	oe = logfspathmapfinde(server->pathmap, s->path);
	if(oe == nil)
		return logfseunknownpath;
	parent = oe->parent;
	if(parent == oe)
		return "tried to remove root";
	if((parent->qid.type & QTDIR) == 0)
		return Enotdir;
	if((oe->qid.type & QTDIR) != 0 && oe->u.dir.list)
		return logfsenotempty;
	for(ep = &parent->u.dir.list; e = *ep; ep = &e->next)
		if(e == oe)
			break;
	if(e == nil)
		return logfseinternal;
	ustmuid = logfsisustadd(server->is, s->u.remove.muid);
	if(ustmuid == nil)
		return Enomem;
	parent->mtime = s->u.remove.mtime;
	parent->muid = ustmuid;
	logfspathmapdeleteentry(server->pathmap, s->path);
	*ep = e->next;
	if(e->inuse > 1) {
		print("replay: entry inuse > 1\n");
		e->inuse = 1;
	}
	logfsentryclunk(e);
	return nil;
}

static char *
retrunc(LogfsServer *server, LogMessage *s, int *ok)
{
	Entry *e;
	char *ustmuid;

	USED(ok);
	e = logfspathmapfinde(server->pathmap, s->path);
	if(e == nil)
		return logfseunknownpath;
	if((e->qid.type & QTDIR) != 0)
		return Eperm;
	if(e->u.file.cvers >= s->u.trunc.cvers)
		return "old news";
	ustmuid = logfsisustadd(server->is, s->u.trunc.muid);
	if(ustmuid == nil)
		return Enomem;
	e->muid = ustmuid;
	e->mtime = s->u.trunc.mtime;
	e->qid.vers++;
	e->u.file.cvers = s->u.trunc.cvers;
	/*
	 * zap all extents
	 */
	logfsextentlistreset(e->u.file.extent);
	e->u.file.length = 0;
	return nil;
}

static char *
rewrite(LogfsServer *server, LogMessage *s, int *ok)
{
	Entry *e;
	char *ustmuid;
	Extent extent;
	char *errmsg;

	USED(ok);
	e = logfspathmapfinde(server->pathmap, s->path);
	if(e == nil)
		return logfseunknownpath;
	if((e->qid.type & QTDIR) != 0)
		return Eperm;
	if(e->u.file.cvers != s->u.write.cvers)
		return nil;
	ustmuid = logfsisustadd(server->is, s->u.write.muid);
	if(ustmuid == nil)
		return Enomem;
	extent.min = s->u.write.offset;
	extent.max = s->u.write.offset + s->u.write.count;
	extent.flashaddr = s->u.write.flashaddr;
	errmsg = logfsextentlistinsert(e->u.file.extent, &extent, nil);
	if(errmsg)
		return errmsg;
	e->mtime = s->u.write.mtime;
	e->muid = ustmuid;
	if(extent.max > e->u.file.length)
		e->u.file.length = extent.max;
	/* TODO forsyth increments vers here; not sure whether necessary */
	return nil;
}

static char *
rewstat(LogfsServer *server, LogMessage *s, int *ok)
{
	Entry *e;
	char *errmsg;
	char *cname, *ustgid, *ustmuid;
	char *ustuid;

	USED(ok);
	e = logfspathmapfinde(server->pathmap, s->path);
	if(e == nil)
		return logfseunknownpath;
	cname = nil;
	ustuid = nil;
	ustgid = nil;
	ustmuid = nil;
	if(s->u.wstat.name) {
		cname = strdup(s->u.wstat.name);
		if(cname == nil) {
		memerror:
			errmsg = Enomem;
			goto fail;
		}
	}
	if(s->u.wstat.uid) {
		ustuid = logfsisustadd(server->is, s->u.wstat.uid);
		if(ustuid == nil)
			goto memerror;
	}
	if(s->u.wstat.gid) {
		ustgid = logfsisustadd(server->is, s->u.wstat.gid);
		if(ustgid == nil)
			goto memerror;
	}
	if(s->u.wstat.muid) {
		ustmuid = logfsisustadd(server->is, s->u.wstat.muid);
		if(ustmuid == nil)
			goto memerror;
	}
	if(cname) {
		logfsfreemem(e->name);
		e->name = cname;
		cname = nil;
	}
	if(ustuid)
		e->uid = ustuid;
	if(ustgid)
		e->gid = ustgid;
	if(ustmuid)
		e->muid = ustmuid;
	if(s->u.wstat.perm != ~0)
		e->perm = (e->perm & DMDIR) | (s->u.wstat.perm & ~DMDIR);
	if(s->u.wstat.mtime != ~0)
		e->mtime = s->u.wstat.mtime;
	errmsg = nil;
fail:
	logfsfreemem(cname);
	return errmsg;
}

static char *
replayblock(LogfsServer *server, LogSegment *seg, uchar *buf, long i, int *pagep, int disableerrors)
{
	int page;
	LogfsLowLevel *ll = server->ll;
	LogfsLowLevelReadResult llrr;
	ushort size;
	LogMessage s;
	int ppb = 1 << ll->l2pagesperblock;
	int pagesize = 1 << ll->l2pagesize;

	for(page = 0; page < ppb; page++) {
		uchar *p, *bufend;
		char *errmsg = (*ll->readpagerange)(ll, buf, seg->blockmap[i], page, 0,  pagesize, &llrr);
		if(errmsg)
			return errmsg;
		if(llrr != LogfsLowLevelReadResultOk)
			logfsserverreplacelogblock(server, seg, i);
			/* ignore failure to replace block */
		if(server->trace > 1)
			print("replaying seq %ld block %ld page %d\n", i, seg->blockmap[i], page);
		p = buf;
		if(*p == 0xff)
			break;
		bufend = p + pagesize;
		while(p < bufend) {
			int ok = 1;
			size = logfsconvM2S(p, bufend - p, &s);
			if(size == 0)
				return "parse failure";
			if(server->trace > 1) {
				print(">> ");
				logfsdumpS(&s);
				print("\n");
			}
			if(s.type == LogfsLogTend)
				break;
			switch(s.type) {
			case LogfsLogTstart:
				break;
			case LogfsLogTcreate:
				maxpath(server, s.path);
				maxpath(server, s.u.create.newpath);
				errmsg = recreate(server, &s, &ok);
				break;
			case LogfsLogTtrunc:
				maxpath(server, s.path);
				errmsg = retrunc(server, &s, &ok);
				break;
			case LogfsLogTremove:
				maxpath(server, s.path);
				errmsg = reremove(server, &s, &ok);
				break;
			case LogfsLogTwrite:
				maxpath(server, s.path);
				errmsg = rewrite(server, &s, &ok);
				break;
			case LogfsLogTwstat:
				maxpath(server, s.path);
				errmsg = rewstat(server, &s, &ok);
				break;
			default:
				return "bad tag in log page";
			}
			if(!ok)
				return errmsg;
			if(errmsg && !disableerrors){
				print("bad replay: %s\n", errmsg);
				print("on: "); logfsdumpS(&s); print("\n");
			}
			p += size;
		}
	}
	*pagep = page;
	return nil;
}

static int
map(void *magic, Extent *x, int hole)
{
	LogfsServer *server;
	LogfsLowLevel *ll;
	long seq;
	int page;
	int offset;
	u32int mask;
	DataBlock *db;

	if(hole || (x->flashaddr & LogAddr) != 0)
		return 1;
	server = magic;
	ll = server->ll;
	logfsflashaddr2spo(server, x->flashaddr, &seq, &page, &offset);
	if(seq >= server->ndatablocks || (db = server->datablock + seq)->block < 0) {
		print("huntfordata: seq %ld invalid\n", seq);
		return 1;
	}
	mask = logfsdatapagemask((x->max - x->min + offset + (1 << ll->l2pagesize) - 1) >> ll->l2pagesize, page);
//print("mask 0x%.8ux free 0x%.8ux dirty 0x%.8ux\n", mask, db->free, db->dirty);
	if((db->free & mask) != mask)
		print("huntfordata: data referenced more than once: block %ld(%ld) free 0x%.8ux mask 0x%.8ux\n",
			seq, db->block, db->free, mask);
	db->free &= ~mask;
	db->dirty |= mask;
	return 1;
}

static void
huntfordatainfile(LogfsServer *server, Entry *e)
{
	logfsextentlistwalk(e->u.file.extent, map, server);
}

static void
huntfordataindir(LogfsServer *server, Entry *pe)
{
	Entry *e;
	for(e = pe->u.dir.list; e; e = e->next)
		if(e->qid.type & QTDIR)
			huntfordataindir(server, e);
		else
			huntfordatainfile(server, e);
}

char *
logfsreplay(LogfsServer *server, LogSegment *seg, int disableerrorsforfirstblock)
{
	uchar *buf;
	long i;
	int page;
	char *errmsg;

	if(seg == nil || seg->curblockindex < 0)
		return nil;
	buf = logfsrealloc(nil, 1 << server->ll->l2pagesize);
	if(buf == nil)
		return Enomem;
	for(i = 0; i <= seg->curblockindex; i++) {
		errmsg = replayblock(server, seg, buf,  i, &page, disableerrorsforfirstblock);
		disableerrorsforfirstblock = 0;
		if(errmsg) {
			print("logfsreplay: error: %s\n", errmsg);
			goto fail;
		}
	}
	/*
	 * if the last block ended early, restart at the first free page
	 */
	if(page < (1 << server->ll->l2pagesperblock))
		seg->curpage = page;
	errmsg = nil;
fail:
	logfsfreemem(buf);
	return errmsg;
}

void
logfsreplayfinddata(LogfsServer *server)
{
	huntfordataindir(server, &server->root);
	if(server->trace > 0) {
		long i;
		DataBlock *db;
		for(i = 0, db = server->datablock; i < server->ndatablocks; i++, db++) {
			logfsfreeanddirtydatablockcheck(server, i);
			if(db->block >= 0)
				print("%4ld: free 0x%.8ux dirty 0x%.8ux\n", i, server->datablock[i].free, server->datablock[i].dirty);
		}
	}
}
