#include "u.h"
#include "lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"

static int
addbytes(char **dbuf, char *edbuf, char **sbuf, char *esbuf)
{
	int n;

	n = edbuf - *dbuf;
	if(n <= 0)
		return 0;
	if(n > esbuf - *sbuf)
		n = esbuf - *sbuf;
	if(n <= 0)
		return -1;

	memmove(*dbuf, *sbuf, n);
	*sbuf += n;
	*dbuf += n;
	return edbuf - *dbuf;
}

int
bootpass(Boot *b, void *vbuf, int nbuf)
{
	char *buf, *ebuf;
	Exec *ep;
	ulong entry, data, text, bss;

	if(b->state == FAILED)
		return FAIL;

	if(nbuf == 0)
		goto Endofinput;

	buf = vbuf;
	ebuf = buf+nbuf;
	while(addbytes(&b->wp, b->ep, &buf, ebuf) == 0) {
		switch(b->state) {
		case INITKERNEL:
			b->state = READEXEC;
			b->bp = (char*)&b->exec;
			b->wp = b->bp;
			b->ep = b->bp+sizeof(Exec);
			break;
		case READEXEC:
			ep = &b->exec;
			if(GLLONG(ep->magic) == I_MAGIC) {
				b->state = READTEXT;
				b->bp = (char*)PADDR(GLLONG(ep->entry));
				b->wp = b->bp;
				b->ep = b->wp+GLLONG(ep->text);
				print("%lud", GLLONG(ep->text));
				break;
			}

			/* check for gzipped kernel */
			if(b->bp[0] == 0x1F && (uchar)b->bp[1] == 0x8B && b->bp[2] == 0x08) {
				b->state = READGZIP;
				b->bp = (char*)malloc(1440*1024);
				b->wp = b->bp;
				b->ep = b->wp + 1440*1024;
				memmove(b->bp, &b->exec, sizeof(Exec));
				b->wp += sizeof(Exec);
				print("gz...");
				break;
			}

			print("bad kernel format\n");
			b->state = FAILED;
			return FAIL;

		case READTEXT:
			ep = &b->exec;
			b->state = READDATA;
			b->bp = (char*)PGROUND(GLLONG(ep->entry)+GLLONG(ep->text));
			b->wp = b->bp;
			b->ep = b->wp + GLLONG(ep->data);
			print("+%ld", GLLONG(ep->data));
			break;
	
		case READDATA:
			ep = &b->exec;
			bss = GLLONG(ep->bss);
			print("+%ld=%ld\n",
				bss, GLLONG(ep->text)+GLLONG(ep->data)+bss);
			b->state = TRYBOOT;
			return ENOUGH;

		case TRYBOOT:
		case READGZIP:
			return ENOUGH;

		case READ9LOAD:
		case INIT9LOAD:
			panic("9load");

		default:
			panic("bootstate");
		}
	}
	return MORE;


Endofinput:
	/* end of input */
	switch(b->state) {
	case INITKERNEL:
	case READEXEC:
	case READDATA:
	case READTEXT:
		print("premature EOF\n");
		b->state = FAILED;
		return FAIL;
	
	case TRYBOOT:
		entry = GLLONG(b->exec.entry);
		print("entry: 0x%lux\n", entry);
		warp9(PADDR(entry));
		b->state = FAILED;
		return FAIL;

	case READGZIP:
		ep = &b->exec;
		if(b->bp[0] != 0x1F || (uchar)b->bp[1] != 0x8B || b->bp[2] != 0x08)
			print("lost magic\n");

		print("%ld => ", b->wp - b->bp);
		if(gunzip((uchar*)ep, sizeof(*ep), (uchar*)b->bp, b->wp - b->bp) < sizeof(*ep)) {
			print("badly compressed kernel\n");
			return FAIL;
		}

		entry = GLLONG(ep->entry);
		text = GLLONG(ep->text);
		data = GLLONG(ep->data);
		bss = GLLONG(ep->bss);
		print("%lud+%lud+%lud=%lud\n", text, data, bss, text+data+bss);

		if(gunzip((uchar*)PADDR(entry)-sizeof(Exec), sizeof(Exec)+text+data, 
		     (uchar*)b->bp, b->wp-b->bp) < sizeof(Exec)+text+data) {
			print("error uncompressing kernel\n");
			return FAIL;
		}

		/* relocate data to start at page boundary */
		memmove((void*)PGROUND(entry+text), (void*)(entry+text), data);

		print("entry: %lux\n", entry);
		warp9(PADDR(entry));
		b->state = FAILED;
		return FAIL;

	case INIT9LOAD:
	case READ9LOAD:
		panic("end 9load");

	default:
		panic("bootdone");
	}
	b->state = FAILED;
	return FAIL;
}
