#include <X11/keysym.h>
#include <stdio.h>
#include <string.h>
#include "input.h"
#include "image.h"
#include "main.h"
#include "win.h"
#include "keymap.h"

int delay(int i)
{
	struct timeval timeout;

	if (i>0)
	{
		timeout.tv_usec = i % (unsigned long) 1000000;
		timeout.tv_sec = i / (unsigned long) 1000000;
		select(0, NULL, NULL, NULL, &timeout);
	}
	return (i>0 ? i : 0);
}

int Win::create()
{
	char *window_name = "** could not get joy-driver name **";
	char *icon_name = "window";
	static XSizeHints size_hints;
	Window rootwin;
	unsigned long valuemask = 0;
	XGCValues values;
	num_images = 0;

	num_keys = (sizeof(key_mapping) / sizeof(struct _key_mapping));
	gettimeofday(&st, NULL);
	pause = 0;

	mousecallback = NULL;
	keycallback = NULL;
	joystickcallback = NULL;
	mousecb = NULL;
	keycb = NULL;
	joystickcb = NULL;

	joy.create();
	if (joy.name)
		window_name = joy.name;

	width=WINDOW_WIDTH;
	height=WINDOW_HEIGHT;
	display = XOpenDisplay(NULL);

	if (display == NULL)
	{
		fprintf(stderr, "Failed to open display\n");
		return 0;
	}

	screen = DefaultScreen(display);
	depth = DefaultDepth(display, screen);
	cmap = DefaultColormap(display, screen);
	rootwin = RootWindow(display, screen);
	win = XCreateSimpleWindow(display, rootwin, 10, 10, width, height, 5,
		BlackPixel(display, screen), BlackPixel(display, screen));

	XSetWindowColormap(display, win, cmap);

	private_colormap();

	gc = XCreateGC(display, win, valuemask, &values);
	copygc = XCreateGC(display, win, valuemask, &values);

	size_hints.flags = PSize | PMinSize | PMaxSize;
	size_hints.min_width = width;
	size_hints.max_width = width;
	size_hints.min_height = height;
	size_hints.max_height = height;

	XSetStandardProperties(display, win, window_name, icon_name, None,
		0, 0, &size_hints); 

	XSelectInput(display, win, ButtonPressMask | KeyPressMask | KeyReleaseMask | EnterWindowMask | LeaveWindowMask);
	XMapWindow(display, win);

	buffer = XCreatePixmap(display, win, WINDOW_WIDTH, WINDOW_HEIGHT, depth);

	font1 = XLoadQueryFont(display,
		"-*-helvetica-bold-r-*-*-14-*-*-*-*-*-iso8859-*");
	font2 = XLoadQueryFont(display,
		"-*-helvetica-bold-r-*-*-18-*-*-*-*-*-iso8859-*");
	font3 = XLoadQueryFont(display,
		"-*-helvetica-bold-r-*-*-24-*-*-*-*-*-iso8859-*");
}

int Win::time_diff()
{
	int diff;

	gettimeofday(&rt, NULL);
	diff = (1000000*(rt.tv_sec-st.tv_sec))+(rt.tv_usec-st.tv_usec);
	st = rt;
	return diff;
}

void Win::sync()
{
	gettimeofday(&rt, NULL);
	pause = delay(FRAME_LEN - time_diff() + pause);
	st = rt;
}

void Win::close()
{
	joy.finish();
	XAutoRepeatOn(display);
	XCloseDisplay(display);
}

void Win::alloc_color(int i, int r, int g, int b)
{
	XColor col;

	col.red =	r; col.green = g; col.blue = b;
	col.flags = DoRed | DoGreen | DoBlue;
	if (XAllocColor(display, cmap, &col))
	{
		idx[i].pixel = col.pixel;
	}else
	{
		if (cmap == DefaultColormap(display, screen))
		{
			cmap = XCopyColormapAndFree(display, cmap);
			XSetWindowColormap(display, win, cmap);
			col.red =	r; col.green = g; col.blue = b;
			col.flags = DoRed | DoGreen | DoBlue;
			if (XAllocColor(display, cmap, &col))
			{
				idx[i].pixel = col.pixel;
			}
		}
	}
}

void Win::private_colormap()
{
	int i, j, k;
	int r, g, b;
	int count = 0;

	for (i=0; i<256; i++) idx[i].pixel = 0;

	alloc_color(count++, 0, 0, 0);
	alloc_color(count++, 0, 0, 0);
	alloc_color(count++, 0, 0, 0);
	for (i=0; i<5; i++)
		for (j=0; j<5; j++)
			for (k=0; k<5; k++)
			{
				idx[count].r = 65535 - (i*16384); if(idx[count].r<0) idx[count].r=0;
				idx[count].g = 65535 - (j*16384); if(idx[count].g<0) idx[count].g=0;
				idx[count].b = 65535 - (k*16384); if(idx[count].b<0) idx[count].b=0;
				alloc_color(count++, idx[count].r, idx[count].g, idx[count].b);
			}

	for (i=0; i<4; i++)
		for (j=0; j<4; j++)
			for (k=0; k<4; k++)
			{
				idx[count].r = 60415 - (i*16384); if(idx[count].r<0) idx[count].r=0;
				idx[count].g = 60415 - (j*16384); if(idx[count].g<0) idx[count].g=0;
				idx[count].b = 60415 - (k*16384); if(idx[count].b<0) idx[count].b=0;
				alloc_color(count++, idx[count].r, idx[count].g, idx[count].b);
				idx[count].r = 55295 - (i*16384); if(idx[count].r<0) idx[count].r=0;
				idx[count].g = 55295 - (j*16384); if(idx[count].g<0) idx[count].g=0;
				idx[count].b = 55295 - (k*16384); if(idx[count].b<0) idx[count].b=0;
				alloc_color(count++, idx[count].r, idx[count].g, idx[count].b);
			}
}

int Win::get_color(char *col)
{
	int i, cindx;
	double rd, gd, bd, dist, mindist;
	XColor color;
	XColor def_colrs[256];

	// create a color from the input string
	XParseColor(display, cmap, col, &color);

	// find closest match
	cindx = -1;
	mindist = 196608.0;             // 256.0 * 256.0 * 3.0
	for (i=0; i<256; i++)
	{
		rd = (idx[i].r - color.red) / 256.0;
		gd = (idx[i].g - color.green) / 256.0;
		bd = (idx[i].b - color.blue) / 256.0;
		dist = (rd * rd) + (gd * gd) + (bd * bd);
		if (dist < mindist)
		{
			mindist = dist;
			cindx = idx[i].pixel;
			if (dist == 0.0) break;
		}
	}
	return cindx;
}

void Win::eventloop()
{
  XEvent xev;
  int i, num_events;

	XFlush(display);
	num_events = XPending(display);
	while((num_events != 0))
	{
		num_events--;
		XNextEvent(display, &xev);
		process_event(xev);
	}

	// check joystick
	joy.status();
	if (joystickcb != NULL)
		joystickcb(joystickcallback, joy.js.x, joy.js.y,
			joy.js.buttons);
}

void Win::process_event(XEvent report)
{
	int i, ms, ks = 0;
	KeySym key;

	switch(report.type)
	{
		case EnterNotify: XAutoRepeatOff(display); break;
		case LeaveNotify: XAutoRepeatOn(display); break;
		case KeyPress:
		case KeyRelease:
			if (report.type == KeyPress) ks = KEY_PRESSED; else ks = KEY_RELEASED;
			key = XLookupKeysym(&report.xkey, 0);
			if (keycb != NULL)
			{
				for (i=0; i<num_keys; i++)
				{
					if (key_mapping[i].xkey == key)
						keycb(keycallback, key_mapping[i].key, ks);
				}
			}
			break;

		case ButtonPressMask:
			ms = MOUSE_UNKNOWN;
			switch(report.xbutton.button)
			{
				case (1): ms = MOUSE_LEFT; break;
				case (2): ms = MOUSE_MIDDLE; break;
				case (3): ms = MOUSE_RIGHT; break;
				default: break;
			}
			if (mousecb != NULL) mousecb(mousecallback, ms, MOUSE_PRESSED, report.xbutton.x, report.xbutton.y);
			break;

		default:
			break;
	}
}

void Win::mcallback(MouseCB f, void *data)
{
	mousecallback = data;
	mousecb = f;
}

void Win::kcallback(KeyCB f, void *data)
{
	keycallback = data;
	keycb = f;
}

void Win::jcallback(JoystickCB f, void *data)
{
	joystickcallback = data;
	joystickcb = f;
}

/******************************************************************************

        Graphics Routines

 ******************************************************************************/

XFontStruct *Win::getFont(int fnt)
{
	XFontStruct *ft;

	switch (fnt)
	{
		case FONT_LARGE: ft = font3; break;
		case FONT_MEDIUM: ft = font2; break;
		default: ft = font1;
	}
	return ft;
}

int Win::StringWidth(char *str, int fnt)
{
	XFontStruct *ft = getFont(fnt);
	return (XTextWidth(ft, str, strlen(str)));
}

int Win::StringHeight(char *str, int fnt)
{
	XFontStruct *ft = getFont(fnt);
	return (ft->ascent + ft->descent);
}

void Win::DrawText(int x, int y, char *text, char *col, int fnt)
{
	XFontStruct *ft = getFont(fnt);
	XSetForeground(display, gc, get_color(col));
	XSetFont(display, gc, ft->fid);
	XDrawString(display, buffer, gc, x, y+ft->ascent, text, strlen(text));
}

void Win::FillRectangle(int x, int y, int w, int h, char *col)
{
	XSetForeground(display, gc, get_color(col));
	XFillRectangle(display, buffer, gc, x, y, w, h);
}

void Win::DrawRectangle(int x, int y, int w, int h, char *col)
{
	XSetForeground(display, gc, get_color(col));
	XDrawRectangle(display, buffer, gc, x, y, w, h);
}

void Win::DrawLine(int x, int y, int w, int h, char *col)
{
	XSetForeground(display, gc, get_color(col));
	XDrawLine(display, buffer, gc, x, y, w, h);
}

void Win::DrawWindow()
{
	XSetFillStyle(display, copygc, FillTiled);
	XSetTile(display, copygc, buffer);
	XFillRectangle(display, win, copygc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
}

int Win::LoadImage(char **data)
{
	if (num_images>=MAX_IMAGES) return -1;
	img[num_images] = new Image(this);
	img[num_images]->create(data);
	num_images++;
	return (num_images-1);
}

void Win::ClearImages()
{
	int i;
	for (i=0; i<num_images; i++)
		delete(img[i]);
	num_images = 0;
}

void Win::DrawImage(int x, int y, int im)
{
	img[im]->draw(buffer, x, y);
}


