#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <keyboard.h>
#include "../eui.h"
#include "dat.h"
#include "fns.h"

extern uchar ppuram[16384];
int nprg, nchr, map, chrram;
uchar *prg, *chr;
int clock, ppuclock, apuclock, dmcclock, dmcfreq, sampclock, msgclock, saveclock;
int oflag, savefd = -1;
int mirr;

void
message(char *fmt, ...)
{
	va_list va;
	static char buf[512];
	
	va_start(va, fmt);
	vsnprint(buf, sizeof buf, fmt, va);
	string(screen, Pt(10, 10), display->black, ZP, display->defaultfont, buf);
	msgclock = FREQ;
	va_end(va);
}

void
flushram(void)
{
	if(savefd >= 0)
		pwrite(savefd, mem + 0x6000, 0x2000, 0);
	saveclock = 0;
}

void
loadrom(char *file, int sflag)
{
	int fd;
	int nes20;
	char *s;
	static uchar header[16];
	static u32int flags;
	static char buf[512];
	
	fd = open(file, OREAD);
	if(fd < 0)
		sysfatal("open: %r");
	if(readn(fd, header, sizeof(header)) < sizeof(header))
		sysfatal("read: %r");
	if(memcmp(header, "NES\x1a", 4) != 0)
		sysfatal("not a ROM");
	if(header[15] != 0)
		memset(header + 7, 0, 9);
	flags = header[6] | header[7] << 8;
	nes20 = (flags & FLNES20M) == FLNES20V;
	if(flags & (FLVS | FLPC10))
		sysfatal("ROM not supported");
	nprg = header[HPRG];
	if(nes20)
		nprg |= (header[HROMH] & 0xf) << 8;
	if(nprg == 0)
		sysfatal("invalid ROM");
	nchr = header[HCHR];
	if(nes20)
		nchr |= (header[HROMH] & 0xf0) << 4;
	map = (flags >> FLMAPPERL) & 0x0f | (((flags >> FLMAPPERH) & 0x0f) << 4);
	if(nes20)
		map |= (header[8] & 0x0f) << 8;
	if(map >= 256 || mapper[map] == nil)
		sysfatal("unimplemented mapper %d", map);

	memset(mem, 0, sizeof(mem));
	if((flags & FLTRAINER) != 0 && readn(fd, mem + 0x7000, 512) < 512)
			sysfatal("read: %r");
	prg = malloc(nprg * PRGSZ);
	if(prg == nil)
		sysfatal("malloc: %r");
	if(readn(fd, prg, nprg * PRGSZ) < nprg * PRGSZ)
		sysfatal("read: %r");
	chrram = nchr == 0;
	if(nchr != 0){
		chr = malloc(nchr * CHRSZ);
		if(chr == nil)
			sysfatal("malloc: %r");
		if(readn(fd, chr, nchr * CHRSZ) < 1)
			sysfatal("read: %r");
	}else{
		nchr = 1;
		chr = malloc(nchr * CHRSZ);
		if(chr == nil)
			sysfatal("malloc: %r");
	}
	if((flags & FLFOUR) != 0)
		mirr = MFOUR;
	else if((flags & FLMIRROR) != 0)
		mirr = MVERT;
	else
		mirr = MHORZ;
	if(sflag){
		strncpy(buf, file, sizeof buf - 5);
		s = buf + strlen(buf) - 4;
		if(s < buf || strcmp(s, ".nes") != 0)
			s += 4;
		strcpy(s, ".sav");
		savefd = create(buf, ORDWR | OEXCL, 0666);
		if(savefd < 0)
			savefd = open(buf, ORDWR);
		if(savefd < 0)
			message("open: %r");
		else
			readn(savefd, mem + 0x6000, 0x2000);
		atexit(flushram);
	}
	mapper[map](INIT, 0);
}

void
usage(void)
{
	fprint(2, "usage: %s [-aos] [-x scale] rom\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char **argv)
{
	int t, sflag;

	sflag = 0;
	ARGBEGIN {
	case 'a':
		initaudio();
		break;
	case 'o':
		oflag = 1;
		break;
	case 's':
		sflag = 1;
		break;
	case 'x':
		fixscale = strtol(EARGF(usage()), nil, 0);
		break;
	default:
		usage();
	} ARGEND;
	if(argc < 1)
		usage();
	loadrom(argv[0], sflag);
	initemu(256, 240 - oflag * 16, 4, XRGB32, 1, nil);
	regkey("b", 'z', 1<<1);
	regkey("a", 'x', 1<<0);
	regkey("control", Kshift, 1<<2);
	regkey("start", '\n', 1<<3);
	regkey("up", Kup, 1<<4);
	regkey("down", Kdown, 1<<5);
	regkey("left", Kleft, 1<<6);
	regkey("right", Kright, 1<<7);

	pc = memread(0xFFFC) | memread(0xFFFD) << 8;
	rP = FLAGI;
	dmcfreq = 12 * 428;
	for(;;){
		if(savereq){
			savestate("nes.save");
			savereq = 0;
		}
		if(loadreq){
			loadstate("nes.save");
			loadreq = 0;
		}
		if(paused){
			qlock(&pauselock);
			qunlock(&pauselock);
		}
		t = step() * 12;
		clock += t;
		ppuclock += t;
		apuclock += t;
		sampclock += t;
		dmcclock += t;
		while(ppuclock >= 4){
			ppustep();
			ppuclock -= 4;
		}
		if(apuclock >= APUDIV){
			apustep();
			apuclock -= APUDIV;
		}
		if(sampclock >= SAMPDIV){
			audiosample();
			sampclock -= SAMPDIV;
		}
		if(dmcclock >= dmcfreq){
			dmcstep();
			dmcclock -= dmcfreq;
		}
		if(msgclock > 0){
			msgclock -= t;
			if(msgclock <= 0){
				extern Image *bg;
				draw(screen, screen->r, bg, nil, ZP);	
				msgclock = 0;
			}
		}
		if(saveclock > 0){
			saveclock -= t;
			if(saveclock <= 0)
				flushram();
		}
	}
}
