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

void
logfsflashaddr2spo(LogfsServer *server, u32int flashaddr, long *seq, int *page, int *offset)
{
	LogfsLowLevel *ll = server->ll;
	flashaddr &= ~LogAddr;
	*offset = flashaddr & ((1 << ll->l2pagesize) - 1);
	flashaddr >>= ll->l2pagesize;
	*page = flashaddr & ((1 << ll->l2pagesperblock) - 1);
	flashaddr >>= ll->l2pagesperblock;
	*seq = flashaddr;
}

u32int
logfsspo2flashaddr(LogfsServer *server, long seq, int page, int offset)
{
//print("logfsspo2flashaddr(%ld, %d, %d)\n", seq, page, offset);
	return (((seq << server->ll->l2pagesperblock) + page) << server->ll->l2pagesize) + offset;
}

void
logfsflashaddr2o(LogfsServer *server, u32int flashaddr, int *offset)
{
	LogfsLowLevel *ll = server->ll;
	flashaddr &= ~LogAddr;
	*offset = flashaddr & ((1 << ll->l2pagesize) - 1);
}

char *
logfslogsegmentnew(LogfsServer *server, int gen, LogSegment **segp)
{
	LogSegment *seg;
	seg = logfsrealloc(nil, sizeof(LogSegment) + (server->ll->blocks - 1) * sizeof(long));
	if(seg == nil)
		return Enomem;
	seg->pagebuf = logfsrealloc(nil, 1 << server->ll->l2pagesize);
	if(seg->pagebuf == nil) {
		logfsfreemem(seg);
		return Enomem;
	}
	seg->curpage = -1;
	seg->curblockindex = -1;
	seg->gen = gen;
	*segp = seg;
	return nil;
}

void
logfslogsegmentfree(LogSegment **segp)
{
	LogSegment *seg = *segp;
	if(seg) {
		logfsfreemem(seg->pagebuf);
		logfsfreemem(seg);
		*segp = nil;
	}
}

char *
logfslogsegmentflush(LogfsServer *server, int active)
{
	LogSegment *seg;
	seg = active ? server->activelog : server->sweptlog;
	if(seg == nil)
		return nil;
	if(seg->curpage >= 0 && seg->nbytes) {
		char *errmsg;
		LogfsLowLevel *ll = server->ll;
		int pagesize = 1 << ll->l2pagesize;
//print("curblockindex %ld curpage %d nbytes %d\n", seg->curblockindex, seg->curpage, seg->nbytes);
		if(seg->nbytes < pagesize)
			seg->pagebuf[seg->nbytes++] = LogfsLogTend;
		memset(seg->pagebuf + seg->nbytes, 0xff, pagesize - seg->nbytes);
		for(;;) {
			errmsg = (*ll->writepage)(ll, seg->pagebuf,
				seg->blockmap[seg->curblockindex], seg->curpage);
			if(errmsg == nil)
				break;
			if(strcmp(errmsg, Eio) != 0)
				return errmsg;
			errmsg = logfsserverreplacelogblock(server, seg, seg->curblockindex);
			if(errmsg)
				return errmsg;
		}
		seg->curpage++;
		if(seg->curpage == (1 << ll->l2pagesperblock))
			seg->curpage = -1;
		seg->nbytes = 0;
	}
	return nil;
}

static char *
logspace(LogfsServer *server, int active, int takearisk, int nbytes, uchar **where, u32int *flashaddr)
{
	char *errmsg;
	LogfsLowLevel *ll = server->ll;
	int pagesize = 1 << ll->l2pagesize;
	LogSegment *seg;

	if(nbytes > pagesize)
		return logfselogmsgtoobig;
retry:
	seg = active ? server->activelog : server->sweptlog;
	for(;;) {
//print("curpage %d nbytes %d\n", seg->curpage, seg->nbytes);
		if(seg->curpage >= 0) {
			if(seg->nbytes + nbytes < pagesize)
				break;
			errmsg = logfslogsegmentflush(server, active);
			if(errmsg)
				return errmsg;
		}
		if(seg->curpage < 0) {
			long block;
			long path;
			block = logfsfindfreeblock(ll,
				active ? (takearisk ? AllocReasonLogExtend : AllocReasonDataExtend) : AllocReasonTransfer);
			if(block < 0) {
				if(active) {
					int didsomething;
					errmsg = logfsserverlogsweep(server, 0, &didsomething);
					if(errmsg)
						return errmsg;
					if(didsomething)
						goto retry;
				}
				return logfselogfull;
			}
			seg->blockmap[++seg->curblockindex] = block;
			path = mklogpath(seg->curblockindex, seg->gen, 0);
			(*ll->setblocktag)(ll, block, LogfsTlog);
			(*ll->setblockpath)(ll, block, path);
			seg->curpage = 0;
#ifdef FUTURE
			/* TODO - do we need one of these if the underlying system supports erase counting? */
			seg->pagebuf[0] = LogfsLogTstart;
			PBIT16(seg->pagebuf + 1, 8);
			PBIT32(seg->pagebuf + 3, path);	/* TODO duplicate information */
			PBIT32(seg->pagebuf + 7, 0);		/* TODO don't have this - discuss with forsyth */
			seg->nbytes = 11;
#else
			seg->nbytes = 0;
#endif
		}
	}
	*where = seg->pagebuf + seg->nbytes;
	if(flashaddr)
		*flashaddr = logfsspo2flashaddr(server, seg->curblockindex, seg->curpage, seg->nbytes);
	seg->nbytes += nbytes;
	return nil;
}

static void
logdirty(LogfsServer *server, int active)
{
	if(active)
		server->activelog->dirty = 1;
	else
		server->sweptlog->dirty = 1;
}

char *
logfslogbytes(LogfsServer *server, int active, uchar *msg, uint size)
{
	char *errmsg;
	uchar *p;

	errmsg = logspace(server, active, 0, size, &p, nil);
	if(errmsg)
		return errmsg;
	memcpy(p, msg, size);
	logdirty(server, active);
	return nil;
}

char *
logfslog(LogfsServer *server, int active, LogMessage *s)
{
	uint size = logfssizeS2M(s);
	char *errmsg;
	uchar *p;
	int takearisk;

	if(server->trace > 1) {
		print("%c<< ", active ? 'A' : 'S');
		logfsdumpS(s);
		print("\n");
	}
	if(active) {
		switch(s->type) {
		case LogfsLogTremove:
		case LogfsLogTtrunc:
			takearisk = 1;
			break;
		default:
			takearisk = 0;
		}
	}
	else
		takearisk = 0;
	errmsg = logspace(server, active, takearisk, size, &p, nil);
	if(errmsg)
		return errmsg;
	if(logfsconvS2M(s, p, size) != size)
		return "bad conversion";
	logdirty(server, active);
	return nil;
}

int
lognicesizeforwrite(LogfsServer *server, int active, u32int count, int muidlen)
{
	int rawspace;
	LogSegment *seg;
	if(count > LogDataLimit)
		return 0;
	seg = active ? server->activelog : server->sweptlog;
	if(seg->curpage < 0)
		return LogDataLimit;
	rawspace = (1 << server->ll->l2pagesize) - seg->nbytes;
	if(rawspace < 5 * 4 + 2 + muidlen + 1)
		return LogDataLimit;
	return 5 * 4 + 2 + muidlen - rawspace;
}

char *
logfslogwrite(LogfsServer *server, int active, u32int path, u32int offset, int count, u32int mtime, u32int cvers,
	char *muid, uchar *data, u32int *flashaddr)
{
	/* 'w' size[2] path[4] offset[4] count[2] mtime[4] cvers[4] muid[s] flashaddr[4] [data[n]] */
	LogMessage s;
	uint size;
	char *errmsg;
	uchar *p;
	u32int faddr;
	uint asize;

	s.type = LogfsLogTwrite;
	s.path = path;
	s.u.write.offset = offset;
	s.u.write.count = count;
	s.u.write.mtime = mtime;
	s.u.write.cvers = cvers;
	s.u.write.muid = muid;
	s.u.write.data = data;
	size = logfssizeS2M(&s);
	errmsg = logspace(server, active, 0, size, &p, &faddr);
	if(errmsg)
		return errmsg;
	if(data)
		*flashaddr = (faddr + size - count) | LogAddr;
	s.u.write.flashaddr = *flashaddr;
	if(server->trace > 1) {
		print("%c<< ", active ? 'A' : 'S');
		logfsdumpS(&s);
		print("\n");
	}
	if((asize = logfsconvS2M(&s, p, size)) != size) {
		print("expected %d actual %d\n", size, asize);
		return "bad conversion";
	}
	logdirty(server, active);
	return nil;
}

