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

char *
nandfsopen(Nandfs *nandfs, long base, long limit, int trace, int xcount, long *xdata)
{
	NandfsBlockData *blockdata = nil;
	long u;
	ulong badones, goodones;
	char *errmsg;
	long possiblebaseblock, possiblesize;
	long baseblock, limitblock;
	int ppb;

	if (trace > 1)
		print("nandfsopen: base %ld limit %ld ppb %d\n", base, limit, 1 << nandfs->ll.l2pagesperblock);

	if (nandfs->blockdata)
		return Eperm;

	if (base % nandfs->rawblocksize)
		return Ebadarg;
	baseblock = base / nandfs->rawblocksize;

	if (limit == 0)
		limitblock = nandfs->limitblock;
	else if (limit % nandfs->rawblocksize)
		return Ebadarg;
	else
		limitblock = limit / nandfs->rawblocksize;

	if (trace > 1)
		print("nandfsopen: baseblock %ld limitblock %ld\n", baseblock, limitblock);

	possiblebaseblock = 0;
	possiblesize = 0;

	/*
	 * search for Tboot block which will reveal the parameters
	 */
	nandfs->baseblock = 0;

	for (u = baseblock; u < limitblock; u++) {
		NandfsTags tags;
		LogfsLowLevelReadResult e;
		int p;
		int lim;

		lim = xcount + 3;

		for (p = 0; p < lim; p++) {
			errmsg = nandfsreadpageauxiliary(nandfs, &tags, u, p, 1, &e);
			if (errmsg)
				goto error;
			if (e != LogfsLowLevelReadResultOk || tags.magic != LogfsMagic || tags.tag != LogfsTboot)
				break;
			if (trace > 1)
				print("block %lud/%d: 0x%.lux\n", u, p, tags.path);
			switch (p) {
			case 1:
				possiblebaseblock = tags.path;
				break;
			case 2:
				possiblesize = tags.path;
				break;
			default:
				xdata[p - 3] = tags.path;
				break;
			}
		}
		if (p == lim)
			break;
	}

	if (u >= limitblock) {
		errmsg = "no valid boot blocks found";
		goto error;
	}

	if (possiblebaseblock < baseblock
		|| possiblebaseblock >= limitblock
		|| possiblebaseblock + possiblesize > limitblock
		|| possiblesize == 0) {
		errmsg = "embedded parameters out of range";
		goto error;
	}

	baseblock = possiblebaseblock;
	limitblock = possiblebaseblock + possiblesize;

	if (trace > 0) {
		int x;
		print("nandfs filesystem detected: base %lud limit %lud",
			baseblock, limitblock);
		for (x = 0; x < xcount; x++)
			print(" data%d %ld", x, xdata[x]);
		print("\n");
	}

	blockdata = nandfsrealloc(nil, (limitblock - baseblock) * sizeof(NandfsBlockData));
	if (blockdata == nil) {
		errmsg = Enomem;
		goto error;
	}
	/*
	 * sanity check
	 * check the partition until 10 good blocks have been found
	 * check that bad blocks represent 10% or less
	 */

	badones = goodones = 0;
	ppb = 1 << nandfs->ll.l2pagesperblock;
	for (u = baseblock; u < limitblock; u++) {
		LogfsLowLevelReadResult firste, laste;
		NandfsTags firsttags, lasttags;
		errmsg = nandfsreadpageauxiliary(nandfs, &firsttags, u, 0, 1, &firste);
		if (errmsg)
			goto error;
		errmsg = nandfsreadpageauxiliary(nandfs, &lasttags, u, ppb - 1, 1, &laste);
		if (errmsg)
			goto error;
		if (firste == LogfsLowLevelReadResultBad || laste == LogfsLowLevelReadResultBad)
			continue;
		if (firste == LogfsLowLevelReadResultOk && laste == LogfsLowLevelReadResultOk && firsttags.magic == LogfsMagic &&
			lasttags.magic == LogfsMagic)
			goodones++;
		else
			badones++;
		if (badones == 0 && goodones >= 10)
			break;
	}

	if (badones * 10 > goodones) {
		errmsg = "most likely not a Log Filesystem";
		goto error;
	}

	for (u = baseblock; u < limitblock; u++) {
		int erased, partial;
		LogfsLowLevelReadResult firste, laste;
		NandfsTags firsttags, lasttags, newtags;
		int markedbad;
		errmsg = nandfsreadpageauxiliary(nandfs, &firsttags, u, 0, 1, &firste);
		if (errmsg)
			goto error;
		errmsg = nandfsreadpageauxiliary(nandfs, &lasttags, u, ppb - 1, 1, &laste);
		if (errmsg)
			goto error;
		if (trace > 1)
			print("%lud: ", u);
		if (firste == LogfsLowLevelReadResultBad || laste == LogfsLowLevelReadResultBad) {
			if (trace > 1)
				print("bad\n");
			blockdata[u - baseblock].tag = LogfsTbad;
			continue;
		}
		newtags = firsttags;
		erased = 0;
		partial = 0;
		if (firsttags.tag != lasttags.tag) {
			partial = 1;
			if (trace > 1)
				print("partially written\n");
			/*
			 * partially written block
			 * if Tboot, then it is either
			 * 	a failure during logfsformat() - well, we never got started, so give up
			 *	a failure during blocktransfer() - erase it as the transfer was not completed
			 *	tell the difference by the presence of another block with the same path
			 * if Tnone, then it's a no brainer
			 * if anything else, leave alone
			 */
			if (newtags.tag == LogfsTnone) {
				newtags.tag = LogfsTnone;
				newtags.path = NandfsPathMask;
				errmsg = nandfseraseblock(nandfs, u, nil, &markedbad);
				if (errmsg)
					goto error;
				if (markedbad) {
					blockdata[u - baseblock].tag = LogfsTbad;
					continue;
				}
				/* now erased */
				erased = 1;
				partial = 0;
			}
		}
		if (!erased && !partial && firste == LogfsLowLevelReadResultAllOnes) {
			if (trace > 1)
				print("probably erased");
			/*
			 * finding erased blocks at this stage is a rare event, so
			 * erase again just in case
			 */
			newtags.tag = LogfsTnone;
			newtags.path = NandfsPathMask;
			newtags.nerase = 1;		// what do I do here?
			errmsg = nandfseraseblock(nandfs, u, nil, &markedbad);
			if (errmsg)
				goto error;
			if (markedbad) {
				blockdata[u - baseblock].tag = LogfsTbad;
				continue;
			}
			erased = 1;
		}
		if (erased) {
			newtags.magic = 'V';
			errmsg = nandfseraseblock(nandfs, u, nil, &markedbad);
			if (errmsg)
				goto error;
			if (markedbad) {
				blockdata[u - baseblock].tag = LogfsTbad;
				continue;
			}
		}
		switch (newtags.tag) {
		case LogfsTboot:
		case LogfsTnone:
		case LogfsTdata:
		case LogfsTlog:
			blockdata[u - baseblock].path = newtags.path;
			blockdata[u - baseblock].tag = newtags.tag;
			blockdata[u - baseblock].nerase = newtags.nerase;
			blockdata[u - baseblock].partial = partial;
			if (trace > 1)
				print("%s 0x%.8lux %lud\n",
					logfstagname(blockdata[u - baseblock].tag),
					blockdata[u - baseblock].path,
					blockdata[u - baseblock].nerase);
			continue;
		}
		break;
	}
	nandfs->ll.blocks = u - baseblock;
	nandfs->baseblock = baseblock;
	nandfs->blockdata = nandfsrealloc(nil, nandfs->ll.blocks * sizeof(NandfsBlockData));
	if (nandfs->blockdata == nil) {
		errmsg = Enomem;
		goto error;
	}
	nandfs->trace = trace;
	memmove(nandfs->blockdata, blockdata, sizeof(*nandfs->blockdata) * nandfs->ll.blocks);
	nandfsfreemem(blockdata);
	if (trace > 0)
		print("nandfsopen: success\n");
	return nil;
error:
	nandfsfreemem(blockdata);
	return errmsg;
}
