#include	<stdio.h>

#include	<X11/Xlib.h>
#include	<X11/Xutil.h>
#include	<X11/keysym.h>

#include	"atari.h"

#define	FALSE	0
#define	TRUE	1

static Display	*display;
static Screen	*screen;
static Window	window;
static Pixmap	pixmap;
static Visual	*visual;

static GC	gc;
static GC	gc_colour[256];

static XComposeStatus	keyboard_status;

static int	SHIFT = 0x00;
static int	CONTROL = 0x00;
static UBYTE	*image_data;
static int	modified;
static UBYTE	*scanline_ptr;

extern int	colortable[128];

void Atari_Initialise ()
{
	XSetWindowAttributes	xswda;

	XGCValues	xgcvl;

	int	colours[256];
	int	depth;
	int	i;

	display = XOpenDisplay (NULL);
	if (!display)
	{
		printf ("Failed to open display\n");
		exit (1);
	}

	screen = XDefaultScreenOfDisplay (display);
	if (!screen)
	{
		printf ("Unable to get screen\n");
		exit (1);
	}

	depth = XDefaultDepthOfScreen (screen);

	xswda.event_mask = KeyPressMask | KeyReleaseMask | ExposureMask;

#ifdef DOUBLE_SIZE
	window = XCreateWindow (display,
			XRootWindowOfScreen(screen),
			50, 50,
			ATARI_WIDTH*2, ATARI_HEIGHT*2, 3, depth,
			InputOutput, visual,
			CWEventMask | CWBackPixel,
			&xswda);

	pixmap = XCreatePixmap (display, window, ATARI_WIDTH*2, ATARI_HEIGHT*2, depth);
#else
	window = XCreateWindow (display,
			XRootWindowOfScreen(screen),
			50, 50,
			ATARI_WIDTH, ATARI_HEIGHT, 3, depth,
			InputOutput, visual,
			CWEventMask | CWBackPixel,
			&xswda);

	pixmap = XCreatePixmap (display, window, ATARI_WIDTH, ATARI_HEIGHT, depth);
#endif

	XStoreName (display, window, ATARI_TITLE);

	Atari800_ReadColourTable ();

	for (i=0;i<128;i++)
	{
		XColor	colour;

		int	rgb = colortable[i];
		int	status;

		colour.red = (rgb & 0x00ff0000) >> 8;
		colour.green = (rgb & 0x0000ff00);
		colour.blue = (rgb & 0x000000ff) << 8;

		status = XAllocColor (display,
				XDefaultColormapOfScreen(screen),
				&colour);

		colours[i+i] = colour.pixel;
		colours[i+i+1] = colour.pixel;
	}

	for (i=0;i<256;i++)
	{
		xgcvl.background = colours[0];
		xgcvl.foreground = colours[i];

		gc_colour[i] = XCreateGC (display, window,
				GCForeground | GCBackground,
				&xgcvl);
	}

	xgcvl.background = colours[0];
	xgcvl.foreground = colours[0];

	gc = XCreateGC (display, window,
		GCForeground | GCBackground,
		&xgcvl);

#ifdef DOUBLE_SIZE
	XFillRectangle (display, pixmap, gc, 0, 0, ATARI_WIDTH*2, ATARI_HEIGHT*2);
#else
	XFillRectangle (display, pixmap, gc, 0, 0, ATARI_WIDTH, ATARI_HEIGHT);
#endif

	XMapWindow (display, window);

	XSync (display, False);
/*
	============================
	Storage for Atari 800 Screen
	============================
*/
	image_data = (UBYTE*) malloc (ATARI_WIDTH * ATARI_HEIGHT);
	if (!image_data)
	{
		printf ("Failed to allocate space for image\n");
		exit (1);
	}
}

void Atari_Exit ()
{
	free (image_data);

	XSync (display, True);

	XFreePixmap (display, pixmap);
	XUnmapWindow (display, window);
	XDestroyWindow (display, window);
	XCloseDisplay (display);
}

void Atari_PreUpdate ()
{
	scanline_ptr = image_data;
	modified = FALSE;
}

void Atari_PostUpdate ()
{
	if (modified)
	{
#ifdef DOUBLE_SIZE
		XCopyArea (display, pixmap, window, gc, 0, 0, ATARI_WIDTH*2, ATARI_HEIGHT*2, 0, 0);
#else
		XCopyArea (display, pixmap, window, gc, 0, 0, ATARI_WIDTH, ATARI_HEIGHT, 0, 0);
#endif
	}
}

extern UWORD	scanline[ATARI_WIDTH];
extern int	ypos;

static int	last_colour = -1;

#define	NPOINTS	(4096/4)
#define	NRECTS	(4096/4)

static XPoint		points[NPOINTS];
static XRectangle	rectangles[NRECTS];

#ifdef DOUBLE_SIZE
static int	nrects = 0;
#else
static int	npoints = 0;
#endif

void Atari_ScanLine_Flush ()
{
#ifdef DOUBLE_SIZE
	if (nrects != 0)
	{
		XFillRectangles (display, pixmap, gc_colour[last_colour], rectangles, nrects);
		nrects = 0;
		modified = TRUE;
	}
#else
	if (npoints != 0)
	{
		XDrawPoints (display, pixmap, gc_colour[last_colour], points, npoints, CoordModeOrigin);
		npoints = 0;
		modified = TRUE;
	}
#endif

	last_colour = -1;
}

void Atari_ScanLine ()
{
	UWORD	*pixel_ptr;
	int	xpos;

	UBYTE	temp[16];

	temp[0] = COLBK;
	temp[1] = COLPF0;
	temp[2] = COLPF1;
	temp[3] = COLBK;
	temp[4] = COLPF2;
	temp[5] = COLBK;
	temp[6] = COLBK;
	temp[7] = COLBK;
	temp[8] = COLPF3;
	temp[9] = COLBK;
	temp[10] = COLBK;
	temp[11] = COLBK;
	temp[12] = COLBK;
	temp[13] = COLBK;
	temp[14] = COLBK;
	temp[15] = COLBK;

	pixel_ptr = scanline;
	for (xpos=0;xpos<ATARI_WIDTH;xpos++)
	{
		UWORD	pixel;
		UBYTE	playfield;
		UBYTE	colour;

		pixel = *pixel_ptr++;
		playfield = pixel & 0x000f;
		colour = temp[playfield];

		if (pixel & 0xff00)
		{
			UBYTE	player;

			player = (pixel >> 8) & 0x000f;

			if (pixel & 0xf000)
			{
				if (pixel & 0x8000)
				{
					M3PF |= playfield;
					M3PL |= player;
					colour = COLPM3;
				}

				if (pixel & 0x4000)
				{
					M2PF |= playfield;
					M2PL |= player;
					colour = COLPM2;
				}

				if (pixel & 0x2000)
				{
					M1PF |= playfield;
					M1PL |= player;
					colour = COLPM1;
				}

				if (pixel & 0x1000)
				{
					M0PF |= playfield;
					M0PL |= player;
					colour = COLPM0;
				}
/*
	==========================
	Handle fifth player enable
	==========================
*/
				if (PRIOR & 0x10)
				{
					colour = COLPF3;
				}
			}

			if (pixel & 0x0f00)
			{
				if (pixel & 0x0800)
				{
					P3PF |= playfield;
					P3PL |= player;
					colour = COLPM3;
				}

				if (pixel & 0x0400)
				{
					P2PF |= playfield;
					P2PL |= player;
					colour = COLPM2;
				}

				if (pixel & 0x0200)
				{
					P1PF |= playfield;
					P1PL |= player;
					colour = COLPM1;
				}

				if (pixel & 0x0100)
				{
					P0PF |= playfield;
					P0PL |= player;
					colour = COLPM0;
				}
			}
		}

		if (colour != *scanline_ptr)
		{
			int	flush = FALSE;

#ifdef DOUBLE_SIZE
			if (nrects == NRECTS) flush = TRUE;
#else
			if (npoints == NPOINTS) flush = TRUE;
#endif

			if (colour != last_colour) flush = TRUE;
			if (flush)
			{
				Atari_ScanLine_Flush ();
				last_colour = colour;
			}

#ifdef DOUBLE_SIZE
			rectangles[nrects].x = xpos << 1;
			rectangles[nrects].y = ypos << 1;
			rectangles[nrects].width = 2;
			rectangles[nrects].height = 2;
			nrects++;
#else
			points[npoints].x = xpos;
			points[npoints].y = ypos;
			npoints++;
#endif
			*scanline_ptr++ = colour;
		}
		else
		{
			scanline_ptr++;
		}
	}

	Atari_ScanLine_Flush ();
}

int Atari_Keyboard (void)
{
	int	keycode = AKEY_NONE;

	if (XEventsQueued (display, QueuedAfterFlush) > 0)
	{
		XEvent	event;
		KeySym	keysym;
		char	buffer[128];

		XNextEvent (display, &event);
/*
		keysym = XLookupKeysym ((XKeyEvent*)&event, 0);
*/
		XLookupString ((XKeyEvent*)&event, buffer, 128, &keysym, &keyboard_status);

		switch (event.type)
		{
			case Expose :
#ifdef DOUBLE_SIZE
				XCopyArea (display, pixmap, window, gc, 0, 0, ATARI_WIDTH*2, ATARI_HEIGHT*2, 0, 0);
#else
				XCopyArea (display, pixmap, window, gc, 0, 0, ATARI_WIDTH, ATARI_HEIGHT, 0, 0);
#endif
				break;
			case KeyPress :
				switch (keysym)
				{
					case XK_Shift_L :
					case XK_Shift_R :
						SHIFT = 0x40;
						break;
					case XK_Control_L :
					case XK_Control_R :
						CONTROL = 0x80;
						break;
					case XK_Caps_Lock :
						if (SHIFT)
							keycode = 0x7c;
						else
							keycode = 0x3c;
						printf ("XK_Caps_Lock\n");
						keycode = AKEY_CAPSTOGGLE;
						break;
					case XK_Shift_Lock :
						break;
					case XK_Alt_L :
					case XK_Alt_R :
						keycode = AKEY_ATARI;
						break;
					case XK_F1 :
						keycode = AKEY_WARMSTART;
						break;
					case XK_F2 :
						keycode = AKEY_OPTION;
						break;
					case XK_F3 :
						keycode = AKEY_SELECT;
						break;
					case XK_F4 :
						keycode = AKEY_START;
						break;
					case XK_F5 :
						keycode = AKEY_COLDSTART;
						break;
					case XK_F6 :
						keycode = AKEY_PIL;
						break;
					case XK_F7 :
						keycode = AKEY_BREAK;
						break;
					case XK_F8 :
						keycode = AKEY_DISKCHANGE;
						break;
					case XK_F9 :
						keycode = AKEY_EXIT;
						break;
					case XK_Home :
						keycode = 0x76;
						break;
					case XK_Insert :
						if (SHIFT)
							keycode = 0x77;
						else
							keycode = 0xb7;
						break;
					case XK_Delete :
						if (SHIFT)
							keycode = 0x74;
						else
							keycode = 0x34;
						break;
					case XK_Left :
						keycode = AKEY_LEFT;
						break;
					case XK_Up :
						keycode = AKEY_UP;
						break;
					case XK_Right :
						keycode = AKEY_RIGHT;
						break;
					case XK_Down :
						keycode = AKEY_DOWN;
						break;
					case XK_Escape :
						keycode = AKEY_ESCAPE;
						break;
					case XK_Tab :
						if (SHIFT)
							keycode = 0x6c;
						else
							keycode = 0x2c;
						break;
					case XK_exclam :
						keycode = '!';
						break;
					case XK_quotedbl :
						keycode = '"';
						break;
					case XK_numbersign :
						keycode = '#';
						break;
					case XK_dollar :
						keycode = '$';
						break;
					case XK_percent :
						keycode = '%';
						break;
					case XK_ampersand :
						keycode = '&';
						break;
					case XK_quoteright :
						keycode = '\'';
						break;
					case XK_at :
						keycode = '@';
						break;
					case XK_parenleft :
						keycode = '(';
						break;
					case XK_parenright :
						keycode = ')';
						break;
					case XK_less :
						keycode = '<';
						break;
					case XK_greater :
						keycode = '>';
						break;
					case XK_equal :
						keycode = '=';
						break;
					case XK_question :
						keycode = '?';
						break;
					case XK_minus :
						keycode = '-';
						break;
					case XK_plus :
						keycode = '+';
						break;
					case XK_asterisk :
						keycode = '*';
						break;
					case XK_slash :
						keycode = '/';
						break;
					case XK_colon :
						keycode = ':';
						break;
					case XK_semicolon :
						keycode = ';';
						break;
					case XK_comma :
						keycode = ',';
						break;
					case XK_period :
						keycode = '.';
						break;
					case XK_underscore :
						keycode = '_';
						break;
					case XK_bracketleft :
						keycode = '[';
						break;
					case XK_bracketright :
						keycode = ']';
						break;
					case XK_asciicircum :
						keycode = '^';
						break;
					case XK_backslash :
						keycode = '\\';
						break;
					case XK_bar :
						keycode = '|';
						break;
					case XK_space :
						keycode = ' ';
						break;
					case XK_Return :
						keycode = AKEY_RETURN;
						break;
					case XK_0 :
						if (CONTROL)
							keycode = AKEY_CTRL_0;
						else
							keycode = '0';
						break;
					case XK_1 :
						if (CONTROL)
							keycode = AKEY_CTRL_1;
						else
							keycode = '1';
						break;
					case XK_2 :
						if (CONTROL)
							keycode = AKEY_CTRL_2;
						else
							keycode = '2';
						break;
					case XK_3 :
						if (CONTROL)
							keycode = AKEY_CTRL_3;
						else
							keycode = '3';
						break;
					case XK_4 :
						if (CONTROL)
							keycode = AKEY_CTRL_4;
						else
							keycode = '4';
						break;
					case XK_5 :
						if (CONTROL)
							keycode = AKEY_CTRL_5;
						else
							keycode = '5';
						break;
					case XK_6 :
						if (CONTROL)
							keycode = AKEY_CTRL_6;
						else
							keycode = '6';
						break;
					case XK_7 :
						if (CONTROL)
							keycode = AKEY_CTRL_7;
						else
							keycode = '7';
						break;
					case XK_8 :
						if (CONTROL)
							keycode = AKEY_CTRL_8;
						else
							keycode = '8';
						break;
					case XK_9 :
						if (CONTROL)
							keycode = AKEY_CTRL_9;
						else
							keycode = '9';
						break;
					case XK_a :
						if (CONTROL)
							keycode = AKEY_CTRL_A;
						else
							keycode = 'a';
						break;
					case XK_b :
						if (CONTROL)
							keycode = AKEY_CTRL_B;
						else
							keycode = 'b';
						break;
					case XK_c :
						if (CONTROL)
							keycode = AKEY_CTRL_C;
						else
							keycode = 'c';
						break;
					case XK_d :
						if (CONTROL)
							keycode = AKEY_CTRL_D;
						else
							keycode = 'd';
						break;
					case XK_e :
						if (CONTROL)
							keycode = AKEY_CTRL_E;
						else
							keycode = 'e';
						break;
					case XK_f :
						if (CONTROL)
							keycode = AKEY_CTRL_F;
						else
							keycode = 'f';
						break;
					case XK_g :
						if (CONTROL)
							keycode = AKEY_CTRL_G;
						else
							keycode = 'g';
						break;
					case XK_h :
						if (CONTROL)
							keycode = AKEY_CTRL_H;
						else
							keycode = 'h';
						break;
					case XK_i :
						if (CONTROL)
							keycode = AKEY_CTRL_I;
						else
							keycode = 'i';
						break;
					case XK_j :
						if (CONTROL)
							keycode = AKEY_CTRL_J;
						else
							keycode = 'j';
						break;
					case XK_k :
						if (CONTROL)
							keycode = AKEY_CTRL_K;
						else
							keycode = 'k';
						break;
					case XK_l :
						if (CONTROL)
							keycode = AKEY_CTRL_L;
						else
							keycode = 'l';
						break;
					case XK_m :
						if (CONTROL)
							keycode = AKEY_CTRL_M;
						else
							keycode = 'm';
						break;
					case XK_n :
						if (CONTROL)
							keycode = AKEY_CTRL_N;
						else
							keycode = 'n';
						break;
					case XK_o :
						if (CONTROL)
							keycode = AKEY_CTRL_O;
						else
							keycode = 'o';
						break;
					case XK_p :
						if (CONTROL)
							keycode = AKEY_CTRL_P;
						else
							keycode = 'p';
						break;
					case XK_q :
						if (CONTROL)
							keycode = AKEY_CTRL_Q;
						else
							keycode = 'q';
						break;
					case XK_r :
						if (CONTROL)
							keycode = AKEY_CTRL_R;
						else
							keycode = 'r';
						break;
					case XK_s :
						if (CONTROL)
							keycode = AKEY_CTRL_S;
						else
							keycode = 's';
						break;
					case XK_t :
						if (CONTROL)
							keycode = AKEY_CTRL_T;
						else
							keycode = 't';
						break;
					case XK_u :
						if (CONTROL)
							keycode = AKEY_CTRL_U;
						else
							keycode = 'u';
						break;
					case XK_v :
						if (CONTROL)
							keycode = AKEY_CTRL_V;
						else
							keycode = 'v';
						break;
					case XK_w :
						if (CONTROL)
							keycode = AKEY_CTRL_W;
						else
							keycode = 'w';
						break;
					case XK_x :
						if (CONTROL)
							keycode = AKEY_CTRL_X;
						else
							keycode = 'x';
						break;
					case XK_y :
						if (CONTROL)
							keycode = AKEY_CTRL_Y;
						else
							keycode = 'y';
						break;
					case XK_z :
						if (CONTROL)
							keycode = AKEY_CTRL_Z;
						else
							keycode = 'z';
						break;
					case XK_A :
						if (CONTROL)
							keycode = AKEY_CTRL_A;
						else
							keycode = 'A';
						break;
					case XK_B :
						if (CONTROL)
							keycode = AKEY_CTRL_B;
						else
							keycode = 'B';
						break;
					case XK_C :
						if (CONTROL)
							keycode = AKEY_CTRL_C;
						else
							keycode = 'C';
						break;
					case XK_D :
						if (CONTROL)
							keycode = AKEY_CTRL_D;
						else
							keycode = 'D';
						break;
					case XK_E :
						if (CONTROL)
							keycode = AKEY_CTRL_E;
						else
							keycode = 'E';
						break;
					case XK_F :
						if (CONTROL)
							keycode = AKEY_CTRL_F;
						else
							keycode = 'F';
						break;
					case XK_G :
						if (CONTROL)
							keycode = AKEY_CTRL_G;
						else
							keycode = 'G';
						break;
					case XK_H :
						if (CONTROL)
							keycode = AKEY_CTRL_H;
						else
							keycode = 'H';
						break;
					case XK_I :
						if (CONTROL)
							keycode = AKEY_CTRL_I;
						else
							keycode = 'I';
						break;
					case XK_J :
						if (CONTROL)
							keycode = AKEY_CTRL_J;
						else
							keycode = 'J';
						break;
					case XK_K :
						if (CONTROL)
							keycode = AKEY_CTRL_K;
						else
							keycode = 'K';
						break;
					case XK_L :
						if (CONTROL)
							keycode = AKEY_CTRL_L;
						else
							keycode = 'L';
						break;
					case XK_M :
						if (CONTROL)
							keycode = AKEY_CTRL_M;
						else
							keycode = 'M';
						break;
					case XK_N :
						if (CONTROL)
							keycode = AKEY_CTRL_N;
						else
							keycode = 'N';
						break;
					case XK_O :
						if (CONTROL)
							keycode = AKEY_CTRL_O;
						else
							keycode = 'O';
						break;
					case XK_P :
						if (CONTROL)
							keycode = AKEY_CTRL_P;
						else
							keycode = 'P';
						break;
					case XK_Q :
						if (CONTROL)
							keycode = AKEY_CTRL_Q;
						else
							keycode = 'Q';
						break;
					case XK_R :
						if (CONTROL)
							keycode = AKEY_CTRL_R;
						else
							keycode = 'R';
						break;
					case XK_S :
						if (CONTROL)
							keycode = AKEY_CTRL_S;
						else
							keycode = 'S';
						break;
					case XK_T :
						if (CONTROL)
							keycode = AKEY_CTRL_T;
						else
							keycode = 'T';
						break;
					case XK_U :
						if (CONTROL)
							keycode = AKEY_CTRL_U;
						else
							keycode = 'U';
						break;
					case XK_V :
						if (CONTROL)
							keycode = AKEY_CTRL_V;
						else
							keycode = 'V';
						break;
					case XK_W :
						if (CONTROL)
							keycode = AKEY_CTRL_W;
						else
							keycode = 'W';
						break;
					case XK_X :
						if (CONTROL)
							keycode = AKEY_CTRL_X;
						else
							keycode = 'X';
						break;
					case XK_Y :
						if (CONTROL)
							keycode = AKEY_CTRL_Y;
						else
							keycode = 'Y';
						break;
					case XK_Z :
						if (CONTROL)
							keycode = AKEY_CTRL_Z;
						else
							keycode = 'Z';
						break;
					default :
						keycode = AKEY_NONE;
						printf ("Pressed Keysym = %x\n", (int)keysym);
						break;
				}
				break;
			case KeyRelease :
				switch (keysym)
				{
					case XK_Shift_L :
					case XK_Shift_R :
						SHIFT = 0x00;
						break;
					case XK_Control_L :
					case XK_Control_R :
						CONTROL = 0x00;
						break;
					default :
						break;
				}
				break;
		}
	}

	return keycode;
}

int Atari_Joystick (int num)
{
	int	stick = 0x1f;	/* Central + Trigger not pressed */

	if (num == 0)
	{
		Window	root_return;
		Window	child_return;
		int	root_x_return;
		int	root_y_return;
		int	win_x_return;
		int	win_y_return;
		int	mask_return;

		if (XQueryPointer (display, window, &root_return, &child_return, &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask_return))
		{
#ifdef DOUBLE_SIZE
			int	center_x = (ATARI_WIDTH * 2) / 2;
			int	center_y = (ATARI_HEIGHT * 2 ) / 2;
			int	threshold = 64;
#else
			int	center_x = ATARI_WIDTH / 2;
			int	center_y = ATARI_HEIGHT / 2;
			int	threshold = 32;
#endif
			if (win_x_return < (center_x - threshold))
				stick &= 0xfb;
			if (win_x_return > (center_x + threshold))
				stick &= 0xf7;
			if (win_y_return < (center_y - threshold))
				stick &= 0xfe;
			if (win_y_return > (center_y + threshold))
				stick &= 0xfd;

			if (mask_return) stick &= 0xef;
		}
	}

	return stick;
}

/*
	Return value:

	Bits
	0-7	Paddle Postion
	8	Paddle Button State (1 == not pressed)
	-1	Unsupported Paddle
*/

int Atari_Paddle (int num)
{
	int	pot;

	if (num == 0)
	{
		Window	root_return;
		Window	child_return;
		int	root_x_return;
		int	root_y_return;
		int	win_x_return;
		int	win_y_return;
		int	mask_return;

		if (XQueryPointer (display, window, &root_return, &child_return, &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask_return))
		{
#ifdef DOUBLE_SIZE
			pot = ((float)((ATARI_WIDTH * 2) - win_x_return) / (float)(ATARI_WIDTH * 2)) * 228;
#else
			pot = ((float)((ATARI_WIDTH) - win_x_return) / (float)(ATARI_WIDTH)) * 228;
#endif
			if (mask_return)
			{
				pot |= 0x100;
			}
		}
	}
	else
	{
		pot = 0x100;
	}

	return pot;
}
