#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>

#include "sokoban.h"

#define SOKOTREE "/sys/games/lib/sokoban/"

char *LEasy = SOKOTREE "levels/easy.slc";
char *LHard = SOKOTREE "levels/hard.slc";
char *levelfile;

#define SOKOIMG SOKOTREE "images/"

char	*GRImage =	SOKOIMG "right.bit";
char	*GLImage =	SOKOIMG "left.bit";
char	*WallImage =	SOKOIMG "wall.bit";
char	*EmptyImage =	SOKOIMG "empty.bit";
char	*CargoImage =	SOKOIMG "cargo.bit";
char	*GoalCargoImage= SOKOIMG "goalcargo.bit";
char	*GoalImage =	SOKOIMG "goal.bit";
char	*WinImage =	SOKOIMG "win.bit";

char *buttons[] = 
{
	"restart",
	"easy",
	"hard",
	"noanimate", /* this menu string initialized in main */
	"exit",
	0
};

char **levelnames;

Menu menu = 
{
	buttons,
};

Menu lmenu =
{
	levelnames,
};

void
buildmenu(void)
{
	int i;

	if (levelnames != nil) {
		for(i=0; levelnames[i] != 0; i++)
			free(levelnames[i]);
	}
	levelnames = realloc(levelnames, sizeof(char*)*(numlevels+1));
	if (levelnames == nil)
		sysfatal("cannot allocate levelnames");
	for(i=0; i < numlevels; i++)
		levelnames[i] = genlevels(i);
	levelnames[numlevels] = 0;
	lmenu.item = levelnames;
}

Image *
eallocimage(Rectangle r, int repl, uint color)
{
	Image *tmp;

	tmp = allocimage(display, r, screen->chan, repl, color);
	if(tmp == nil)
		sysfatal("cannot allocate buffer image: %r");

	return tmp;
}

Image *
eloadfile(char *path)
{
	Image *img;
	int fd;

	fd = open(path, OREAD);
	if(fd < 0) {
		fprint(2, "cannot open image file %s: %r\n", path);
		exits("image");
	}
	img = readimage(display, fd, 0);
	if(img == nil)
		sysfatal("cannot load image: %r");
	close(fd);
	
	return img;
}


void
allocimages(void)
{
	Rectangle one = Rect(0, 0, 1, 1);
	
	bg		= eallocimage(one, 1, DDarkyellow);
	text 		= eallocimage(one, 1, DBluegreen);

	gright = eloadfile(GRImage);
	gleft = eloadfile(GLImage);
	wall = eloadfile(WallImage);
	empty = eloadfile(EmptyImage);
	empty->repl = 1;
	goalcargo = eloadfile(GoalCargoImage);
	cargo = eloadfile(CargoImage);
	goal = eloadfile(GoalImage);
	win = eloadfile(WinImage);
}

int
key2move(int key)
{
	int k = 0;

	switch(key) {
	case 61454:
		k = Up;
		break;
	case 63488:
		k = Down;
		break;
	case 61457:
		k = Left;
		break;
	case 61458:
		k = Right;
		break;
	}

	return k;
}

static Route*
mouse2route(Mouse m)
{
	Point p, q;
	Route *r;

	p = subpt(m.xy, screen->r.min);
	p.x /= BoardX;
	p.y /= BoardY;

	q = subpt(p, level.glenda);
	// fprint(2, "x=%d y=%d\n", q.x, q.y);

	if (q.x == 0 && q.y ==  0)
		return nil;

	if (q.x == 0 || q.y ==  0) {
		if (q.x < 0)
			r = extend(nil, Left, -q.x, Pt(level.glenda.x, p.y));
		else if (q.x > 0)
			r = extend(nil, Right, q.x, Pt(level.glenda.x, p.y));
		else if (q.y < 0)
			r = extend(nil, Up, -q.y, level.glenda);
		else if (q.y > 0)
			r = extend(nil, Down, q.y, level.glenda);
		else
			r = nil;

		if (r != nil && isvalid(level.glenda, r, validpush))
			return r;
		freeroute(r);
	}

	return findroute(level.glenda, p);
}

char *
genlevels(int i)
{
	
	if(i >= numlevels)
		return 0;

	return smprint("level %d", i+1);
}


int
finished(void)
{
	int x, y;
	for(x = 0; x < MazeX; x++)
		for(y = 0; y < MazeY; y++)
			if(level.board[x][y] == Goal)
				return 0;

	return 1;
}

void
eresized(int new)
{
	Point p;

	if(new && getwindow(display, Refnone) < 0)
		sysfatal("can't reattach to window");
	
	p = Pt(Dx(screen->r), Dy(screen->r));

	if(!new || !eqpt(p, boardsize(level.max))) {
		drawlevel();
	}
	drawscreen();
}

void 
main(int argc, char **argv)
{
	Mouse m;
	Event ev;
	int e;
	Route *r;
	int timer;
	Animation a;
	int animate;


	if(argc == 2) 
		levelfile = argv[1];
	else
		levelfile = LEasy;
		
	if(! loadlevels(levelfile)) {
		fprint(2, "usage: %s [levelfile]\n", argv[0]);
		exits("usage");
	}
	buildmenu();

	animate = 0;
	buttons[3] = animate ? "noanimate" : "animate";

	if(initdraw(nil, nil, "sokoban") < 0)
		sysfatal("initdraw failed: %r");
	einit(Emouse|Ekeyboard);

	timer = etimer(0, 200);
	initanimation(&a);

	allocimages();
	glenda = gright;
	eresized(0);

	for(;;) {
		e = event(&ev);
		switch(e) {
		case Emouse:
			m = ev.mouse;
			if(m.buttons&1) {
				stopanimation(&a);
				r = mouse2route(m);
				if (r)
					setupanimation(&a, r);
				if (! animate) {
					while(onestep(&a))
						;
					drawscreen();
				}
			}
			if(m.buttons&2) {
				int l;
				/* levels start from 1 */
				lmenu.lasthit = level.index;
				l=emenuhit(2, &m, &lmenu);
				if(l>=0){
					stopanimation(&a);
					level = levels[l];
					drawlevel();
					drawscreen();
				}
			}
			if(m.buttons&4)
				switch(emenuhit(3, &m, &menu)) {
				case 0:
					stopanimation(&a);
					level = levels[level.index];
					drawlevel();
					drawscreen();
					break;
				case 1:
					stopanimation(&a);
					loadlevels(LEasy);
					buildmenu();
					drawlevel();
					drawscreen();
					break;
				case 2:
					stopanimation(&a);
					loadlevels(LHard);
					buildmenu();
					drawlevel();
					drawscreen();
					break;
				case 3:
					animate = !animate;
					buttons[3] = animate ? "noanimate" : "animate";
					break;
				case 4:
					exits(nil);
				}
			break;

		case Ekeyboard:
			if(level.done)
				break;

			stopanimation(&a);

			switch(ev.kbdc) {
			case 127:
			case 'q':
			case 'Q':
				exits(nil);
			case 'n':
			case 'N':
				if(level.index < numlevels - 1) {
					level = levels[++level.index];
					drawlevel();
					drawscreen();
				}
				break;
			case 'p':
			case 'P':
				if(level.index > 0) {
					level = levels[--level.index];
					drawlevel();
					drawscreen();
				}
				break;
			case 'r':
			case 'R':
				level = levels[level.index];
				drawlevel();
				drawscreen();
				break;
			case 61454:
			case 63488:
			case 61457:
			case 61458:
			case ' ':
				move(key2move(ev.kbdc));
				drawscreen();
				break;
			default:
				// fprint(2, "key: %d]\n", e.kbdc);
				break;
			}
			break;

		default:
			if (e == timer) {
				if (animate)
					onestep(&a);
				else
					while(onestep(&a))
						;
				drawscreen();
			}
			break;
		}

		if(finished()) {
			level.done = 1;
			drawwin();
			drawscreen();
			sleep(3000);
			if(level.index < numlevels - 1) {
				level = levels[++level.index];
				drawlevel();
				drawscreen();
			}
		}
	}
}
