#include "logfsos.h"
#include "logfs.h"
#include "local.h"

typedef struct AllocState AllocState;
struct AllocState {
	long oldblock;
	int markbad;
};

Pageset
logfsdatapagemask(int pages, int base)
{
	if(pages == BITSPERSET)
		return ~(Pageset)0;
	return (((Pageset)1 << pages) - 1) << (BITSPERSET - base - pages);
}

static Pageset
fastgap(Pageset w, uint n)
{
	Pageset s;
//print("fastgap(0x%.8ux, %d)\n", w, n);
	if(w == 0 || n < 1 || n > BITSPERSET)
		return 0;
/*
#	unroll the following loop 5 times:
#		while(n > 1){
#			s := n >> 1;
#			w &= w<<s;
#			n -= s;
#		}
*/
	s = n >> 1;
	w &= w << s;
	n -= s;
	s = n >> 1;
	w &= w << s;
	n -= s;
	s = n >> 1;
	w &= w << s;
	n -= s;
	s = n >> 1;
	w &= w << s;
	n -= s;
	s = n >> 1;
	if(BITSPERSET == 64){	/* extra time if 64 bits */
		w &= w << s;
		n -= s;
		s = n >> 1;
	}
	return w & (w << s);
}

static int
nlz(Pageset x)
{
	int n, c;

	if(x == 0)
		return BITSPERSET;
	if(x & PAGETOP)
		return 0;
	n = BITSPERSET;
	c = BITSPERSET/2;
	do {
		Pageset y;
		y = x >> c;
		if(y != 0) {
			n -= c;
			x = y;
		}
	} while((c >>= 1) != 0);
	return n - x;
}

static Pageset
findgap(Pageset w, uint n)
{
	Pageset m;

	do {
		m  = fastgap(w, n);
		if(m)
			break;
		n--;
	} while(n);
	if(n == 0)
		return 0;
	return logfsdatapagemask(n, nlz(m));
}

static int
bitcount(Pageset mask)
{
	Pageset m;
	int rv;

	rv = 0;
	for(m = PAGETOP; m != 0; m >>= 1)
		if(mask & m)
			rv++;
	return rv;
}

static char *
allocdatapages(LogfsServer *server, u32int count, int *countp, long *blockindexp, int *pagep, u32int *flashaddr, AllocState *state)
{
	LogfsLowLevel *ll = server->ll;
	long b, blockindex;
	DataBlock *db;
	int pagebase;
	u32int pages = (count + (1 << ll->l2pagesize) - 1) >> ll->l2pagesize;
	u32int gapmask;
	long bestfreeblockindex;
	int bestfree;
	int pagesperblock = 1 << ll->l2pagesperblock;
	int apages;
	char *errmsg;
	int didsomething;

	state->oldblock = -1;
	state->markbad = 0;
	if(pages > pagesperblock)
		pages = pagesperblock;
	/*
	 * fill in gaps first
	 */
	bestfreeblockindex = -1;
	bestfree = 0;
	for(blockindex = 0; blockindex < server->ndatablocks; blockindex++) {
		db = server->datablock + blockindex;
		if(db->block < 0)
			continue;
		gapmask = findgap(db->free & ~db->dirty, pages);
//print("blockindex %ld free 0x%.8ux dirty 0x%.8ux gapmask %.8ux\n", blockindex, db->free, db->dirty, gapmask);
		if(gapmask != 0) {
			/*
			 * this is free and !dirty
			 */
			b = db->block;
			USED(b);
			goto done;
		}
		else {
			int free = bitcount(db->free & logfsdatapagemask(pagesperblock, 0));
			if(free > 0 && (bestfreeblockindex < 0 || free > bestfree)) {
				bestfreeblockindex = blockindex;
				bestfree = free;
			}
		}
	}
//print("out of space - need to clean up a data block\n");
	if(bestfreeblockindex >= 0) {
//print("best block index %ld (%ld) %d bits\n", bestfreeblockindex, server->datablock[bestfreeblockindex].block, bestfree);
		/*
		 * clean up data block
		 */
		b = logfsfindfreeblock(ll, AllocReasonTransfer);
		while(b >= 0) {
			char *errmsg;
			LogfsLowLevelReadResult llrr;
			long oldblock;
			int markedbad;

			db = server->datablock + bestfreeblockindex;
			oldblock = db->block;
			errmsg = logfsservercopyactivedata(server, b, bestfreeblockindex, 0, &llrr, &markedbad);
			if(errmsg) {
				if(!markedbad)
					return errmsg;
				b = logfsfindfreeblock(ll, AllocReasonTransfer);
			}
			else {
				Pageset available;
				/*
				 * if page0 is free, then we must ensure that we use it otherwise
				 * in tagged storage such as nand, the block tag is not written
				 * in all cases, it is safer to erase the block afterwards to
				 * preserve the data for as long as possible (we could choose
				 * to erase the old block now if page0 has already been copied)
				 */
				blockindex = bestfreeblockindex;
				state->oldblock = oldblock;
				state->markbad = llrr != LogfsLowLevelReadResultOk;
				available = db->free & ~db->dirty;
				if(available & PAGETOP)
					available = logfsdatapagemask(nlz(~available), 0);
				gapmask = findgap(available, pages);
				goto done;
			}
		}
	}
	/*
	 * use already erased blocks, so long as there are a few free
	 */
	b = logfsfindfreeblock(ll, AllocReasonDataExtend);
	if(b >= 0) {
useerased:
		for(blockindex = 0, db = server->datablock; blockindex < server->ndatablocks; blockindex++, db++)
			if(db->block < 0)
				break;
		if(blockindex == server->ndatablocks)
			server->ndatablocks++;
		db->path = mkdatapath(blockindex, 0);
		db->block = b;
		(*ll->setblocktag)(ll, b, LogfsTdata);
		(*ll->setblockpath)(ll, b, db->path);
		db->free = logfsdatapagemask(pagesperblock, 0);
		db->dirty = 0;
		gapmask = db->free;
		goto done;
	}
	/*
	 * last resort; try to steal from log
	 */
//print("last resort\n");
	errmsg = logfsserverlogsweep(server, 0, &didsomething);
	if(errmsg)
		return errmsg;
	if(didsomething) {
		/*
		 * this can only create whole free blocks, so...
		 */
//print("findfree after last resort\n");
		b = logfsfindfreeblock(ll, AllocReasonDataExtend);
		if(b >= 0) {
//print("*********************************************************\n");
			goto useerased;
		}
	}
	*countp = 0;
	return nil;
done:
	/*
	 * common finish - needs gapmask, blockindex, db
	 */
	apages = bitcount(gapmask);
	pagebase = nlz(gapmask);
	if(apages > pages)
		apages = pages;
	gapmask = logfsdatapagemask(apages, pagebase);
	if(server->trace > 1)
		print("allocdatapages: block %ld(%ld) pages %d mask 0x%.8ux pagebase %d apages %d\n",
			blockindex, db->block, pages, gapmask, pagebase, apages);
	db->free &= ~gapmask;
	db->dirty |= gapmask;
	*pagep = pagebase;
	*blockindexp = blockindex;
	*flashaddr = logfsspo2flashaddr(server, blockindex, pagebase, 0);
	*countp = apages << ll->l2pagesize;
	if(*countp > count)
		*countp = count;
	return nil;
}

typedef struct Page {
	u32int pageaddr;
	int ref;
} Page;

typedef struct DataStructure {
	LogfsServer *server;
	int nentries;
	int maxentries;
	Page *array;
} DataStructure;

static int
deltapage(DataStructure *ds, u32int pageaddr, int add, int delta)
{
	int i;
	for(i = 0; i < ds->nentries; i++)
		if(ds->array[i].pageaddr == pageaddr) {
			ds->array[i].ref += delta;
			return 1;
		}
	if(!add)
		return 1;
	if(ds->maxentries == 0) {
		ds->array = logfsrealloc(nil, sizeof(Page) * 100);
		if(ds->array == nil)
			return 0;
		ds->maxentries = 100;
	}
	else if(ds->nentries >= ds->maxentries) {
		void *a = logfsrealloc(ds->array, ds->maxentries * 2 * sizeof(Page));
		if(a == nil)
			return 0;
		ds->array = a;
		ds->maxentries *= 2;
	}
	ds->array[ds->nentries].pageaddr = pageaddr;
	ds->array[ds->nentries++].ref = delta;
	return 1;
}

/*
 * only called for data addresses
 */
static int
deltapages(DataStructure *ds, LogfsLowLevel *ll, u32int baseflashaddr, int range, int add, int delta)
{
	long seq;
	int page, offset;
	int pages;
	u32int pageaddr;
	int x;

//print("deltapages(%ud, %ud, %d, %d)\n", baseflashaddr, limitflashaddr, add, delta);
	logfsflashaddr2spo(ds->server, baseflashaddr, &seq, &page, &offset);
	pages = (offset + range + (1 << ll->l2pagesize) - 1) >> ll->l2pagesize;
	pageaddr = (seq << ll->l2pagesperblock) + page;
 	for(x = 0; x < pages; x++, pageaddr++)
		if(!deltapage(ds, pageaddr, add, delta))
			return 0;
	return 1;
}

static int
findpageset(void *magic, u32int baseoffset, u32int limitoffset, Extent *e, u32int extentoffset)
{
	DataStructure *ds = magic;
	LogfsLowLevel *ll;
	u32int flashaddr;
	u32int range;
	u32int residue;

	if(e == nil || (e->flashaddr & LogAddr) != 0)
		return 1;
	ll = ds->server->ll;
//print("baseoffset %ud limitoffset %ud min %ud max %ud\n", baseoffset, limitoffset, e->min, e->max);
	flashaddr = e->flashaddr;
	if(extentoffset)
		if(!deltapages(ds, ll, flashaddr, extentoffset, 1, 1))
			return -1;
	flashaddr += extentoffset;
	range = limitoffset - baseoffset;
	if(!deltapages(ds, ll, flashaddr, range, 1, -1))
		return -1;
	flashaddr += range;
	residue = e->max - e->min - (extentoffset + range);
	if(residue)
		if(!deltapages(ds, ll, flashaddr, residue, 1, 1))
			return -1;
	return 1;
}

static int
addpagereferences(void *magic, Extent *e, int hole)
{
	DataStructure *ds = magic;

	if(hole || (e->flashaddr & LogAddr) != 0)
		return 1;
	return deltapages(ds, ds->server->ll, e->flashaddr, e->max - e->min, 0, 1) ? 1 : -1;
}

static char *
zappages(LogfsServer *server, Entry *e, u32int min, u32int max)
{
	DataStructure ds;
	long seq;
	int x, rv, page;
	Page *p;

	if(min >= e->u.file.length)
		return nil;		/* no checks necessary */
	if(min == 0 && max >= e->u.file.length) {
		/* replacing entire file */
		logfsextentlistwalk(e->u.file.extent, logfsunconditionallymarkfreeanddirty, server);
		return nil;
	}
	/* hard after that - this will need to be improved */
	/*
	 * current algorithm
	 * build a list of all pages referenced by the extents being removed, and count the
 	 * number of references
	 * then subtract the number of references to each page in entire file
	 * any pages with a reference count == 0 can be removed
	 */
	ds.server = server;
	ds.nentries = 0;
	ds.maxentries = 0;
	ds.array = nil;
	rv = logfsextentlistwalkrange(e->u.file.extent, findpageset, &ds, min, max);
	if(rv < 0 || ds.nentries == 0)
		goto Out;
	if(server->trace > 1){
		print("pass 1\n");
		for(x = 0; x < ds.nentries; x++){
			p = &ds.array[x];
			seq = p->pageaddr >> server->ll->l2pagesperblock;
			page = p->pageaddr & ((1 << server->ll->l2pagesperblock) - 1);
			print("block %lud page %ud ref %d\n", seq, page, p->ref);
		}
		print("pass 2\n");
	}
	rv = logfsextentlistwalk(e->u.file.extent, addpagereferences, &ds);
	if(rv >= 0){
		for(x = 0; x < ds.nentries; x++){
			p = &ds.array[x];
			seq = p->pageaddr >> server->ll->l2pagesperblock;
			page = p->pageaddr & ((1 << server->ll->l2pagesperblock) - 1);
			if(server->trace > 1)
				print("block %lud page %ud ref %d\n", seq, page, p->ref);
			if(p->ref == 0)
				logfsfreedatapages(server, seq, logfsdatapagemask(1, page));
		}
	}
Out:
	logfsfreemem(ds.array);
	return rv < 0 ? Enomem : nil;
}

static void
disposeofoldblock(LogfsServer *server, AllocState *state)
{
	if(state->oldblock >= 0) {
		if(server->testflags & LogfsTestDontFettleDataBlock) {
			/* take the block out of commission */
			(*server->ll->setblocktag)(server->ll, state->oldblock, LogfsTworse);
			server->testflags &= ~LogfsTestDontFettleDataBlock;
		}
		else {
			if(state->markbad)
				(*server->ll->markblockbad)(server->ll, state->oldblock);
			else
				logfsbootfettleblock(server->lb, state->oldblock, LogfsTnone, ~0, nil);
		}
		state->oldblock = -1;
	}
}

char *
logfsserverwrite(LogfsServer *server, u32int fid, u32int offset, u32int count, uchar *buf, u32int *rcount)
{
	Fid *f;
	Entry *e;
	u32int now;
	char *muid;
	int muidlen;
	LogfsLowLevel *ll = server->ll;

	if(server->trace > 1)
		print("logfsserverwrite(%ud, %ud, %ud)\n", fid, offset, count);
	f = logfsfidmapfindentry(server->fidmap, fid);
	if(f == nil)
		return logfsebadfid;
	if(f->openmode < 0)
		return logfsefidnotopen;
	if((f->openmode & 3) == OREAD)
		return logfseaccess;
	e = f->entry;
	if(e->deadandgone)
		return Eio;
	if(e->qid.type & QTDIR)
		return Eperm;
	if(e->perm & DMAPPEND)
		offset = e->u.file.length;
	now = logfsnow();
	*rcount = count;
	muid = logfsisfindidfromname(server->is, f->uname);
	muidlen = strlen(muid);
	while(count) {
		Extent extent;
		int thistime;
		char *errmsg;
		thistime = lognicesizeforwrite(server, 1, count, muidlen);
		if(thistime == 0) {
			int p;
			u32int n;
			long blockindex;
			int pagebase;
			AllocState state;
			int pagesize = 1 << ll->l2pagesize;
		reallocate:
			errmsg = allocdatapages(server, count, &thistime, &blockindex, &pagebase, &extent.flashaddr, &state);
			if(errmsg)
				return errmsg;
			if(thistime == 0)
				return logfselogfull;
			for(p = pagebase, n = 0; n < thistime; p++, n += pagesize) {
				u32int mask;
				DataBlock *db = server->datablock + blockindex;
				errmsg = (*ll->writepage)(ll, buf + n, db->block, p);
				if(errmsg) {
					if(strcmp(errmsg, Eio) != 0) {
						/*
						 * something horrid happened down below
						 * recover without writing any more than we have to
						 */
						if(p != 0) {
							/*
							 * page 0 was either written already, or has been written in this loop
							 * thus the block referenced is valid on the media. all we need to do
							 * is lose the old block, mark the written pages as free (so they can
							 * be scavenged), and don't bother with the log message
							 */
							disposeofoldblock(server, &state);
							mask = logfsdatapagemask(p - pagebase - 1, pagebase);
							db->free |= mask;
							db->dirty |= mask;
							return errmsg;
						}
						/*
						 * page 0 failed to write (so nothing written at all)
						 * this is either an entirely free block (no erased block in savestate),
						 * or a copy of a scavenged block (erased block in savestate)
						 */
						if(state.oldblock < 0) {
							/*
							 * newly selected erased block (blockindex == server->ndatablocks - 1)
							 * mark it bad, lose it from the datablock table
							 */
							(*ll->markblockbad)(ll, db->block);
							db->block = -1;
							if(blockindex == server->ndatablocks - 1)
								server->ndatablocks--;
							return errmsg;
						}
						/*
						 * page 0 of a data scavenge copy
						 * mark it bad, restore state (old block)
						 */
						(*ll->markblockbad)(ll, db->block);
						db->block = state.oldblock;
						return errmsg;
					}
					/*
					 * write error on target block
					 *
					 * if it is a replacement (state saved)
					 *	mark the new block bad, restore state and try again
					 *
					 * if it is not replaced (no state saved)
					 *	replace block, and try again
					 */
					if(state.oldblock >= 0) {
						(*ll->markblockbad)(ll, db->block);
						db->block = state.oldblock;
					}
					else {
						errmsg = logfsserverreplacedatablock(server, blockindex);
						if(errmsg)
							return errmsg;
					}
					goto reallocate;
				}
				mask = logfsdatapagemask(1, p);
				db->free &= ~mask;
				db->dirty |= mask;
			}
			/* well, we managed to write the data out */
			errmsg = logfslogwrite(server, 1, e->qid.path, offset, thistime, now, e->u.file.cvers,
				muid, nil, &extent.flashaddr);
			/*
			 * now we can dispose of the original data block, if any
			 * this is regardless of whether we succeeded in writing a log message, as
			 * if this block is not erased, there will be a duplicate
			 */
			disposeofoldblock(server, &state);
		}
		else {
			if(thistime > count)
				thistime = count;
			errmsg = logfslogwrite(server, 1, e->qid.path, offset, thistime, now, e->u.file.cvers,
				muid, buf, &extent.flashaddr);
		}
		/*
		 * here if we failed to write the log message
		 */
		if(errmsg)
			return errmsg;
		if(server->trace > 1)
			print("logfsserverwrite: %d bytes at flashaddr 0x%.8ux\n", thistime, extent.flashaddr);
		extent.min = offset;
		extent.max = offset + thistime;
		errmsg = zappages(server, e, extent.min, extent.max);
		if(errmsg)
			return errmsg;
		errmsg = logfsextentlistinsert(e->u.file.extent, &extent, nil);
		if(errmsg)
			return errmsg;
		e->muid = muid;
		e->mtime = now;
		offset += thistime;	
		if(e->u.file.length < offset)
			e->u.file.length = offset;
		count -= thistime;
		buf += thistime;
		e->qid.vers++;
	}
	return nil;
}
