/*
 * IMPLEMENTATION NOTES.
 * 
 * 1. We allocate a fixed colors' look-up table (CLUT) with indices than can be
 * easily mapped back and forth to their corresponding RGB colors. We cannot
 * use too much colors because that would defeat the drawing optimizations
 * performed by Alib (as the plane moves, there would be too many differences
 * from a frame to the next just to draw the sky shades), resulting in a very
 * low frame rate. You may experiment this issue by increasing the number of
 * planes per color component below.
 */

// LINKER_OPTIONS -lm

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>

#include "../util/memory.h"
#include "../util/error.h"

#define gui_IMPORT
#include "gui.h"

#define RED_PLANES 17
#define GREEN_PLANES 17
#define BLUE_PLANES 17

#define CLUT_LEN (RED_PLANES * GREEN_PLANES * BLUE_PLANES)

static int inited;
static unsigned char compressed[256];
static unsigned char expanded[256];


static void gui_init()
{
	if( inited )
		return;
	inited = 1;
	int i;
	double p = 1.0;
	double k = 255.0 / pow(255, p);
	for(i = 0; i < 256; i++){
		compressed[i] = k * pow(i, p) + 0.5;
		expanded[i] = pow(i/k, 1/p) + 0.5;
		//printf("%d %d %d\n", i, compressed[i], expanded[i]);
	}
}


/**
 * Allocates a RGB color and returns the index to the CLUT.
 * Windows and X-Window will define their own specific implementation.
 * @param this
 * @param red
 * @param green
 * @param blue
 * @return Index to the allocated entry in the CLUT.
 */
static int gui_allocateColor(gui_Type *this, int red, int green, int blue);


/**
 * Maps CLUT index into RGB.
 * @param idx
 * @param red Red color component in the range [0,255].
 * @param green Green color component in the range [0,255].
 * @param blue Blue color component in the range [0,255].
 */
static void index_to_rgb(int idx, int *red, int *green, int *blue)
{
	assert(0 <= idx && idx < CLUT_LEN);
	*blue = 256 * (idx % BLUE_PLANES) / (BLUE_PLANES - 1);
	if( *blue > 255 )  *blue = 255;
	idx = idx / BLUE_PLANES;
	*green = 256 * (idx % GREEN_PLANES) / (GREEN_PLANES - 1);
	if( *green > 255 )  *green = 255;
	idx = idx / GREEN_PLANES;
	*red = 256 * idx  / (RED_PLANES - 1);
	if( *red > 255 )  *red = 255;
	gui_init();
	*red = compressed[*red];
	*green = compressed[*green];
	*blue = compressed[*blue];
}


/**
 * Allocates the CLUT.
 */
static void createCLUT(gui_Type *this)
{
	int idx, red, green, blue;
	for(idx = 0; idx < CLUT_LEN; idx++){
		index_to_rgb(idx, &red, &green, &blue);
		if( idx != gui_allocateColor(this, red, green, blue) )
			abort();
	}
}


int gui_getColorIndex(gui_Type *this, int red, int green, int blue)
{
	if( (red&255)!=red || (green&255)!=green || (blue&255)!=blue )
		error_internal("invalid RGB: %d,%d,%d", red, green, blue);
	gui_init();
	red = expanded[red];
	green = expanded[green];
	blue = expanded[blue];
	int r = ((RED_PLANES-1)*red + 128)/256;
	int g = ((GREEN_PLANES-1)*green + 128)/256;
	int b = ((BLUE_PLANES-1)*blue + 128)/256;
	int idx = (r*GREEN_PLANES + g)*BLUE_PLANES + b;
	assert(idx < CLUT_LEN);
	return idx;
}


int gui_getColorIndexString(gui_Type *this, char *s)
{
	int red, green, blue;
	if( ! gui_parseColorString(s, &red, &green, &blue) )
		error_internal("invalid color specification: %s", s);
	return gui_getColorIndex(this, red, green, blue);
}


// Color names currently used inside ACM and its objects collection, in no
// particular order.
// For more, check https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart
static char *ctab[][2] = {
	{"green", "#00ff00"},
	{"white", "#ffffff"},
	{"black", "#000000"},
	{"orange", "#FF8C00"},
	{"gray44", "#444444"},
	{"red",   "#ff0000"},
	{"gray40", "#404040"},
	{"gray77", "#777777"},
	{NULL, NULL}
};


int gui_parseColorString(char *s, int *red, int *green, int *blue)
{
	if( s == NULL ){
		return 0;
	} else if( s[0] == '#' ){
		int len = strlen(s);
		if( len == 4 ){
			char *end;
			// FIXME: strtol() also parses "0x..."
			long int x = strtol(s+1, &end, 16);
			if( *end != 0 )
				return 0;
			int n = x & 15;
			*blue = (n << 4) + n;
			n = (x >> 4) & 15;
			*green = (n << 4) + n;
			n = x >> 8;
			*red = (n << 4) + n;
			return 1;
			
		} else if( len == 7 ){
			char *end;
			long int x = strtol(s+1, &end, 16);
			if( *end != 0 )
				return 0;
			*red = x >> 16;
			*green = (x >> 8) & 255;
			*blue = x & 255;
			return 1;
			
		} else {
			return 0;
		}
	} else {
		int i = 0;
		do {
			if( ctab[i][0] == NULL )
				return 0;
			if( strcmp(ctab[i][0], s) == 0 )
				return gui_parseColorString(ctab[i][1], red, green, blue);
			i++;
		} while(1);
	}
}


#ifdef WINNT

/*
 * IMPLEMENTATION SPECIFIC FOR MICROSOFT WINDOWS
 * =============================================
 * 
 * IMPLEMENTATION NOTES.
 * 
 * 1. What makes the code a bit complicated is the events handling. In fact the
 * window procedure is a call-back that MS Windows calls quite randomly and
 * unpredictably, even outside the program's main events loop. In particular,
 * I had a problem with the focus events that went lost because they aren't
 * reported to the program's main events loop but were instead consumed by
 * MS Windows internally and never returned to the usual TranslateMessage()
 * and DispatchMessage() pair. To reproduce the exact same behavior of X-Win,
 * I had then to introduce a queue of messages.
 */

#include <tchar.h>
#include <windows.h>

#define EVENTS_QUEUE_CAPACITY 4

struct gui_Type {
	WNDCLASSEX wcex;
	HWND hWnd;
	HDC hdc;
	int showWindowInvoked;
	int has_focus;
	int is_visible;
	int width, height;
	// Current mouse pointer position in the content area.
	int pointer_x, pointer_y;
	// Indices to the beginning and to the end of the circular events queue.
	int queue_start, queue_end;
	// Tells if the queue is full.
	int queue_full;
	// Circular queue of events collected by the window procedure.
	gui_Event queue[EVENTS_QUEUE_CAPACITY];
	// Number of allocated pen colors.
	int colors_number;
	// Allocated pen colors.
	HPEN colors[CLUT_LEN];
};


// hInstance always NULL as stated in https://msdn.microsoft.com/en-us/library/windows/desktop/ms633559(v=vs.85).aspx
// FIXME: should I retrieve hInstance from WinMain() parameter?
static HINSTANCE hInstance;
// FIXME: should I retrieve nCommandShow from WinMain() parameter?
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms633559(v=vs.85).aspx
static int nCmdShow = SW_SHOWNORMAL;
// The main window class name.
static TCHAR szWindowClass[] = _T("win32app");


static int key_codes_map[][2] = {
	{VK_INSERT,   gui_KEY_INS},
	{VK_HOME,     gui_KEY_HOME},
	{VK_END,      gui_KEY_END},
	{VK_PRIOR,    gui_KEY_PGUP},
	{VK_NEXT,     gui_KEY_PGDW},
	{VK_DELETE,   gui_KEY_DEL},
	{VK_UP,       gui_KEY_UP},
	{VK_DOWN,     gui_KEY_DOWN},
	{VK_LEFT,     gui_KEY_LEFT},
	{VK_RIGHT,    gui_KEY_RIGHT},
	{VK_ADD,      gui_KEY_PAD_PLUS},
	{VK_SUBTRACT, gui_KEY_PAD_MINUS},
	{VK_MULTIPLY, gui_KEY_PAD_TIMES},
	{VK_DIVIDE,   gui_KEY_PAD_DIVIDE},
	{VK_NUMPAD0,  gui_KEY_PAD_0},
	{VK_NUMPAD1,  gui_KEY_PAD_1},
	{VK_NUMPAD2,  gui_KEY_PAD_2},
	{VK_NUMPAD3,  gui_KEY_PAD_3},
	{VK_NUMPAD4,  gui_KEY_PAD_4},
	{VK_NUMPAD5,  gui_KEY_PAD_5},
	{VK_NUMPAD6,  gui_KEY_PAD_6},
	{VK_NUMPAD7,  gui_KEY_PAD_7},
	{VK_NUMPAD8,  gui_KEY_PAD_8},
	{VK_NUMPAD9,  gui_KEY_PAD_9},
	{0, 0}
};


static char *errorCodeToString(int code)
{
	static char s[999];
	char win_err_descr[900];
	DWORD err = FormatMessageA(
		FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		code,
		LANG_SYSTEM_DEFAULT,
		win_err_descr,
		sizeof(win_err_descr),
		NULL
	);
	if( err > 0 ){
		snprintf(s, sizeof(s), "%s (Windows error code %d)", win_err_descr, code);
	} else {
		snprintf(s, sizeof(s), "error code %d (description not available: FormatMessageA() failed with code %lu)", code, err);
	}
	return s;
}


static int queueIsEmpty(gui_Type *this)
{
	return ! this->queue_full && this->queue_start == this->queue_end;
}


static void queueRemoveEvent(gui_Type *this, int i)
{
	assert( ! queueIsEmpty(this) );
	assert(
		(this->queue_start < this->queue_end && this->queue_start <= i && i < this->queue_end)
	|| (this->queue_end <= this->queue_start && !(this->queue_end <= i && i < this->queue_start)));
	do {
		// Replace event at index i with the next one.
		int j = (i + 1) % EVENTS_QUEUE_CAPACITY;
		if( j == this->queue_end ){
			this->queue_end = i;
			this->queue_full = 0;
			return;
		}
		i = j;
	} while(1);
}


static void queueRemoveEvents(gui_Type *this, int code)
{
	if( queueIsEmpty(this) )
		return;
	int i = this->queue_start;
	do {
		if( this->queue[i].code == code)
			queueRemoveEvent(this, i);
		else
			i = (i + 1) % EVENTS_QUEUE_CAPACITY;
		if( i == this->queue_end )
			return;
	} while(1);
}


/**
 * Returns the next free entry from the events queue of the given GUI. If the
 * queue is full, the oldest occupied entry is returned (then that old event
 * is lost, but this should never happen).
 * @param this
 * 
 * @return Next free entry in the events queue.
 */
static gui_Event * queueGetFreeEntry(gui_Type *this)
{
	gui_Event *free_entry;
	free_entry = &this->queue[this->queue_end];
	this->queue_end = (this->queue_end + 1) % EVENTS_QUEUE_CAPACITY;
	if( this->queue_full ){
		this->queue_start = this->queue_end;
		fprintf(stderr, "WARNING: event queue is full, some window event lost!\n");
		int i;
		for(i = 0; i < EVENTS_QUEUE_CAPACITY; i++){
			gui_Event *e = &this->queue[(this->queue_start + i) % EVENTS_QUEUE_CAPACITY];
			fprintf(stderr, "event no. %d: %d\n", i, e->code);
		}
	} else {
		this->queue_full = this->queue_start == this->queue_end;
	}
	return free_entry;
}


/**
 * Returns the next queued event from the given GUI.
 * @param this
 * @return Next available event, or null if queue is empty.
 */
static gui_Event * getAvailableQueueEntry(gui_Type *this)
{
	if( queueIsEmpty(this) )
		return NULL;
	gui_Event *avail = &this->queue[this->queue_start];
	this->queue_start = (this->queue_start + 1) % EVENTS_QUEUE_CAPACITY;
	this->queue_full = 0;
	return avail;
}


/**
 * Window event call-back. Meaningful events are added to the events queue.
 * @param hWnd
 * @param message
 * @param wParam
 * @param lParam
 * @return Zero if the event has been internally handled.
 */
static LRESULT CALLBACK gui_winProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam) {
	gui_Type *this = (gui_Type *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
	gui_Event *e;
	
	switch (message) {
	
	case WM_MOUSEMOVE:
		this->pointer_x = LOWORD(lParam);
		this->pointer_y = HIWORD(lParam);
		break;
		
	case WM_LBUTTONDOWN:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_LBUTTONDOWN;
		e->x = LOWORD(lParam);
		e->y = HIWORD(lParam);
		break;
		
	case WM_MBUTTONDOWN:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_MBUTTONDOWN;
		e->x = LOWORD(lParam);
		e->y = HIWORD(lParam);
		break;
		
	case WM_RBUTTONDOWN:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_RBUTTONDOWN;
		e->x = LOWORD(lParam);
		e->y = HIWORD(lParam);
		break;
		
	case WM_LBUTTONUP:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_LBUTTONUP;
		e->x = LOWORD(lParam);
		e->y = HIWORD(lParam);
		break;
		
	case WM_MBUTTONUP:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_MBUTTONUP;
		e->x = LOWORD(lParam);
		e->y = HIWORD(lParam);
		break;
		
	case WM_RBUTTONUP:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_RBUTTONUP;
		e->x = LOWORD(lParam);
		e->y = HIWORD(lParam);
		break;
		
	case WM_KEYDOWN:
		if( VK_F1 <= wParam && wParam <= VK_F12 ){
			e = queueGetFreeEntry(this);
			e->code = gui_EVENT_KEYDOWN;
			e->key = gui_KEY_F(wParam - VK_F1 + 1);
			return 0;
			
		} else {
			int i = 0;
			while(key_codes_map[i][0] != 0){
				if( key_codes_map[i][0] == wParam ){
					e = queueGetFreeEntry(this);
					e->code = gui_EVENT_KEYDOWN;
					e->key = key_codes_map[i][1];
					return 0;
				}
				i++;
			}
		}
		
		return DefWindowProc(hWnd, message, wParam, lParam);
		
		break;
		
	case WM_SYSCOMMAND:
		// F10 requires special handling otherwise it blocks the events loop!
		if( wParam == SC_KEYMENU ){
			e = queueGetFreeEntry(this);
			e->code = gui_EVENT_KEYDOWN;
			e->key = gui_KEY_F(10);
			
		} else {
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
		
	case WM_CHAR:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_KEYDOWN;
		e->key = wParam;
		break;

/*
	case WM_WINDOWPOSCHANGED:
	{
		unsigned int flags = ((PWINDOWPOS) lParam)->flags;
		if ((flags & 0x8000) && (flags & SWP_NOCOPYBITS) && (flags & SWP_FRAMECHANGED)) {
			active = !(flags & SWP_NOACTIVATE);
		}
		if ((flags & 0x1000) && (flags & 0x0800) && (flags & SWP_NOMOVE) && (flags & SWP_NOSIZE)) {
			active = 1;
		}
		break;
	}
*/
	
	case WM_ACTIVATE:
		e = queueGetFreeEntry(this);
		if( LOWORD(wParam) == 0 ){
			e->code = gui_EVENT_WINDOW_FOCUS_OUT;
			this->has_focus = 0;
		} else {
			e->code = gui_EVENT_WINDOW_FOCUS_IN;
			this->has_focus = 1;
		}
		this->is_visible = ! HIWORD(wParam);
		break;
			
	case WM_PAINT:
		queueRemoveEvents(this, gui_EVENT_WINDOW_EXPOSE);
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_WINDOW_EXPOSE;
		// MUST handle the event otherwise it keeps generating WM_PAINT!
		return DefWindowProc(hWnd, message, wParam, lParam);

	case WM_SIZE:
		queueRemoveEvents(this, gui_EVENT_WINDOW_SIZE_CHANGE);
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_WINDOW_SIZE_CHANGE;
		e->x = this->width = LOWORD(lParam);
		e->y = this->height = HIWORD(lParam);
		break;
		
	case WM_DESTROY:
		e = queueGetFreeEntry(this);
		e->code = gui_EVENT_WINDOW_CLOSE;
		break;
		
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}


static void gui_destruct(void *p)
{
	gui_Type *this = (gui_Type *) p;
	
	// Dispose pen colors:
	int i;
	for(i = this->colors_number - 1; i >= 0; i--)
		DeleteObject(this->colors[i]);
	this->colors_number = 0;
	
	// Dispose window:
	ReleaseDC(this->hWnd, this->hdc);
	DestroyWindow(this->hWnd);
}


gui_Type * gui_new(char *title, int width, int height)
{
	gui_Type * this;

	this = memory_allocate(sizeof(gui_Type), gui_destruct);
	this->showWindowInvoked = 0;
	this->is_visible = 0;
	this->has_focus = 0;
	this->width = width;
	this->height = height;
	this->pointer_x = 0;
	this->pointer_y = 0;
	this->has_focus = 0;
	this->queue_start = this->queue_end = 0;
	this->queue_full = 0;
	this->colors_number = 0;
	
	this->wcex.cbSize = sizeof (WNDCLASSEX);
	this->wcex.style = 0;
	this->wcex.lpfnWndProc = gui_winProc;
	this->wcex.cbClsExtra = 0;
	this->wcex.cbWndExtra = 0;
	this->wcex.hInstance = hInstance;
	this->wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
	this->wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	this->wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	this->wcex.lpszMenuName = NULL;
	this->wcex.lpszClassName = szWindowClass;
	this->wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
	if (!RegisterClassEx(&this->wcex))
		error_external("call to RegisterClassEx() failed");
	this->hWnd = CreateWindow(
		szWindowClass,
		title,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		width, height,
		NULL,
		NULL,
		hInstance,
		NULL
		);
	if (!this->hWnd)
		error_external("call to CreateWindow() failed");
	
	// Let the window proc a way to retrieve "this":
	SetWindowLongPtr(this->hWnd, GWLP_USERDATA, (LONG_PTR) this);
	
	this->hdc = GetDC(this->hWnd);
	
	createCLUT(this);
	
	return this;
}


static int gui_allocateColor(gui_Type *this, int red, int green, int blue)
{
	if( this->colors_number >= CLUT_LEN)
		error_internal("trying to allocate too many colors, %d maximum allowed",
			CLUT_LEN);
	if( (red&255)!=red || (green&255)!=green || (blue&255)!=blue )
		error_internal("invalid RGB: %d,%d,%d", red, green, blue);
	HPEN color = CreatePen(PS_SOLID, 0 /* 0 = 1 pixel */, RGB(red, green, blue));
	if (!color){
		int err = WSAGetLastError();
		error_internal("call to CreatePen() for CLUT entry no. %d failed: %s",
			this->colors_number, errorCodeToString(err));
	}
	this->colors[this->colors_number] = color;
	return this->colors_number++;
}


void gui_getDisplayDimensions(gui_Type *this, gui_DisplayDimensions *d)
{
	// FIXME: Win >= 8.1 has GetDpiForMonitor() that uses newer EIDI monitor
	// technology: try it once available under MinGW.
	// FIXME: BUG: this code gives 72x72 dpi for my screen, which is wrong
	// (actual is 117x122 dpi).
	d->widthPixels = GetDeviceCaps(this->hdc, HORZRES);
	d->widthMillimiters = GetDeviceCaps(this->hdc, HORZSIZE);
	d->heightPixels = GetDeviceCaps(this->hdc, VERTRES);
	d->heightMillimiters = GetDeviceCaps(this->hdc, VERTSIZE);
}


int gui_getEvent(gui_Type *this, gui_Event *e)
{
	// If events queue contains something, return it immediately.
	if( ! queueIsEmpty(this) ){
		*e = *getAvailableQueueEntry(this);
		return 1;
	}
	
	// First call to this event routine gives us the opportunity to initialize
	// and display the window. This also may trigger several calls to our window
	// procedure, but we are already prepared for that.
	if( ! this->showWindowInvoked ){
		this->showWindowInvoked = 1;
		ShowWindow(this->hWnd, nCmdShow);
		UpdateWindow(this->hWnd);
		if( ! queueIsEmpty(this) ){
			*e = *getAvailableQueueEntry(this);
			return 1;
		}
	}
	
	// Subsequent calls to this event routine require to first check if any
	// event is available to avoid blocking. Loop until we get at least one
	// meaningful event.
	// In some cases our window procedure gets called several times for a single
	// loop iteration, but this is not an issue because meaningful events are
	// added to the out events' queue.
	MSG msg;
	while( PeekMessage(&msg, this->hWnd, 0, 0, PM_REMOVE) ) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		if( ! queueIsEmpty(this) ){
			*e = *getAvailableQueueEntry(this);
			return 1;
		}
	}
	return 0;
}


void gui_getPointerPosition(gui_Type *this, int *x, int *y)
{
	*x = this->pointer_x;
	*y = this->pointer_y;
}


int gui_getWidth(gui_Type *this)
{
/*
	RECT r;
	GetWindowRect(g->hWnd, &r);
	return r.right - r.left;
*/
	return this->width;
}


int gui_getHeight(gui_Type *this)
{
/*
	RECT r;
	GetWindowRect(g->hWnd, &r);
	return r.bottom - r.top;
*/
	return this->height;
}


int gui_hasFocus(gui_Type *this)
{
	//return GetForegroundWindow() == this->hWnd;
	return this->has_focus;
}


int gui_isVisible(gui_Type *this)
{
	// FIXME: IsWindowVisible() returns true also when minimized
	//return IsWindowVisible(this->hWnd);
	return this->is_visible;
}


void gui_drawLine(gui_Type *this, int x1, int y1, int x2, int y2, int color)
{
	if( !(0 <= color && color < this->colors_number) )
		error_internal("color index out of the range: %d", color);
	
	// Ensure ending point be drawn:
	if( x1 < x2 )
		x2++;
	else if( x2 < x1 )
		x1++;
	if( y1 < y2 )
		y2++;
	else if( y2 < y1 )
		y1++;
	else if( x1 == x2 )
		x2++;
	
	SelectObject(this->hdc, this->colors[color]);
	MoveToEx(this->hdc, x1, y1, NULL);
	LineTo(this->hdc, x2, y2);
}


#else

/*
 * IMPLEMENTATION SPECIFIC FOR X-WINDOW
 * ====================================
 */

#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>


struct gui_Type {
	Display *dpy;
	Colormap cmap;
	Window   win;
	GC       gc;
	Atom     protocolsAtom;
	Atom     deleteWindowAtom;
	Atom     closedownAtom;
	int has_focus;
	int is_visible;
	int width, height;
	// Current mouse pointer position in the content area.
	int pointer_x, pointer_y;
	// Index of the current foreground color set in the GC. -1 = no color set.
	int curr_color;
	// Number of allocated pen colors.
	int colors_number;
	// Allocated pen colors.
	unsigned long colors[CLUT_LEN];
};


static void gui_destruct(void *p)
{
	gui_Type *this = (gui_Type *) p;
	
	// Dispose pen colors:
/*
	int i;
	for(i = this->colors_number - 1; i >= 0; i--)
		DeleteObject(this->colors[i]);
*/
	this->colors_number = 0;
	
	XDestroyWindow(this->dpy, this->win); // close window
	XCloseDisplay(this->dpy); // close connection
}


gui_Type * gui_new(char *title, int width, int height)
{
	gui_Type * this;
	int useDefaultVisual = 1;
	char *display;
	XSizeHints xsh;
	XWMHints   xwmh;
	Visual   *theVisual;
	int depth;
	char     *geomSpec = NULL;
	long      win_attr_mask;
	XSetWindowAttributes window_attributes;
	Cursor    cursor;
	Atom      atom[2];

	this = memory_allocate(sizeof(gui_Type), gui_destruct);
	
	this->has_focus = 0;
	this->is_visible = 0;
	
	this->pointer_x = 0;
	this->pointer_y = 0;
	
	this->curr_color = -1;

	display = getenv("DISPLAY");
	this->dpy = XOpenDisplay(display);
	if (this->dpy == NULL)
		error_external("XOpenDisplay(\"%s\") failed", display);
	int screen = DefaultScreen(this->dpy);

/*
 *  If the player has specified that he want the default Visual, simply
 *  give 'em that along with the default Colormap.
 */

	if (useDefaultVisual) {

		theVisual = DefaultVisual(this->dpy, screen);
		this->cmap = DefaultColormap(this->dpy, screen);
		depth = DisplayPlanes(this->dpy, screen);

	}
/*
 *  Look for a visual; if we can't find one,
 *  fall back to monochrome mode.
 */

	else {
		error_internal("not implemented", 0);
/*
		if ((theVisual = get_pseudo_visual(o->dpy, screen, &depth)) == NULL) {
			theVisual = DefaultVisual(o->dpy, screen);
			cmap = DefaultColormap(o->dpy, screen);
			depth = DefaultDepth(o->dpy, screen);
		}
		else {
			cmap = XCreateColormap(o->dpy, RootWindow(o->dpy, screen),
								   theVisual, AllocNone);
		}
*/
	}

/*
	If the defaults database doesn't contain a specification of the
	initial size, set the size leaving to the window manager to set
	the position:
*/
	
	char s[99];
	sprintf(s, "%dx%d", width, height);
	geomSpec = s;

	if (geomSpec == NULL) {
		xsh.flags = /* PPosition | */ PSize;
		xsh.height = DisplayHeight(this->dpy, screen) * 3 / 4;
		xsh.width = DisplayWidth(this->dpy, screen) * 3 / 4;
		xsh.x = 0; /* ignored */
		xsh.y = 0; /* ignored */
	}
	else {
		int       bitmask;

		memory_zero(&xsh);
		bitmask = XParseGeometry(geomSpec, &xsh.x, &xsh.y,
			 (unsigned int *) &xsh.width,
			 (unsigned int *) &xsh.height);
		if (bitmask & (XValue | YValue)) {
			xsh.flags |= USPosition;
		}
		if (bitmask & (WidthValue | HeightValue)) {
			xsh.flags |= USSize;
		}
	}

	width = xsh.width;
	height = xsh.height;

/*
 * Create the Window with the information in the XSizeHints, the
 * border width,  and the border & background pixels.
 */

	win_attr_mask = CWColormap | CWBitGravity | CWBackPixel;
	window_attributes.colormap = this->cmap;
	window_attributes.bit_gravity = NorthWestGravity;
	window_attributes.background_pixel = BlackPixel(this->dpy, screen);

	this->win = XCreateWindow(this->dpy, RootWindow(this->dpy, screen),
						   xsh.x, xsh.y, xsh.width, xsh.height, 0, depth,
			  InputOutput, theVisual, win_attr_mask, &window_attributes);

/*
 * Set the standard properties and hints for the window managers.
 */

	XSetStandardProperties(this->dpy, this->win, title, title, None,
		NULL /* argv */, 0 /* argc */, &xsh);
	xwmh.flags = InputHint | StateHint;
	xwmh.input = True;
	xwmh.initial_state = NormalState;
	XSetWMHints(this->dpy, this->win, &xwmh);
	cursor = XCreateFontCursor(this->dpy, XC_tcross);
	XDefineCursor(this->dpy, this->win, cursor);

/*
 *  Tell the window manager that we'd like to participate in the
 *  WM_CLOSEDOWN and WM_DELETE_WINDOW protocols.
 */

	this->protocolsAtom = XInternAtom(this->dpy, "WM_PROTOCOLS", False);
	atom[0] = this->closedownAtom = XInternAtom(this->dpy, "WM_CLOSEDOWN", False);
	atom[1] = this->deleteWindowAtom = XInternAtom(this->dpy, "WM_DELETE_WINDOW", False);
	XSetWMProtocols(this->dpy, this->win, atom, 2);
	
	XSelectInput(this->dpy, this->win, KeyPressMask | ButtonPressMask
		| StructureNotifyMask | ButtonReleaseMask
		| ExposureMask | FocusChangeMask);

	XMapWindow(this->dpy, this->win);
	
	XSetWindowBackground(this->dpy, this->win, BlackPixel(this->dpy, screen));
	
	XGCValues gcv;
	gcv.cap_style = CapButt;
	this->gc = XCreateGC(this->dpy, RootWindow(this->dpy, screen), GCCapStyle, &gcv);
	
	createCLUT(this);
	
	XFlush(this->dpy);
	
	return this;
}


static int gui_allocateColor(gui_Type *this, int red, int green, int blue)
{
	if( this->colors_number >= CLUT_LEN)
		error_internal("trying to allocate too many colors, %d maximum allowed",
			CLUT_LEN);
	if( (red&255)!=red || (green&255)!=green || (blue&255)!=blue )
		error_internal("invalid RGB: %d,%d,%d", red, green, blue);
	XColor color;
	color.red   = (red   << 8) + red;
	color.green = (green << 8) + green;
	color.blue  = (blue  << 8) + blue;
	if( ! XAllocColor(this->dpy, this->cmap, &color) )
		error_internal("failed allocating RGB color %d,%d,%d", red, green, blue);
	this->colors[this->colors_number] = color.pixel;
	return this->colors_number++;
}


void gui_getDisplayDimensions(gui_Type *this, gui_DisplayDimensions *d)
{
	d->widthPixels = DisplayWidth(this->dpy, DefaultScreen(this->dpy));
	d->widthMillimiters = DisplayWidthMM(this->dpy, DefaultScreen(this->dpy));
	d->heightPixels = DisplayHeight(this->dpy, DefaultScreen(this->dpy));
	d->heightMillimiters = DisplayHeightMM(this->dpy, DefaultScreen(this->dpy));
}


static int key_codes_map[][2] = {
	{XK_Insert,  gui_KEY_INS},
	{XK_Home,    gui_KEY_HOME},
	{XK_End,     gui_KEY_END},
	{XK_Prior,   gui_KEY_PGUP},
	{XK_Next,    gui_KEY_PGDW},
	{XK_Delete,  gui_KEY_DEL},
	{XK_Up,      gui_KEY_UP},
	{XK_Down,    gui_KEY_DOWN},
	{XK_Left,    gui_KEY_LEFT},
	{XK_Right,   gui_KEY_RIGHT},
	{XK_KP_Add,  gui_KEY_PAD_PLUS},
	{XK_KP_Subtract, gui_KEY_PAD_MINUS},
	{XK_KP_Multiply, gui_KEY_PAD_TIMES},
	{XK_KP_Divide, gui_KEY_PAD_DIVIDE},
	{XK_KP_0, gui_KEY_PAD_0},
	{XK_KP_1, gui_KEY_PAD_1},
	{XK_KP_2, gui_KEY_PAD_2},
	{XK_KP_3, gui_KEY_PAD_3},
	{XK_KP_4, gui_KEY_PAD_4},
	{XK_KP_5, gui_KEY_PAD_5},
	{XK_KP_6, gui_KEY_PAD_6},
	{XK_KP_7, gui_KEY_PAD_7},
	{XK_KP_8, gui_KEY_PAD_8},
	{XK_KP_9, gui_KEY_PAD_9},
	{0, 0}
};


int gui_getEvent(gui_Type *this, gui_Event *e)
{
	XEvent    ev;

	// If client is asking for events, certainly it has finished with drawings,
	// so it worths to flush the X Window commands queue:
	XFlush(this->dpy);
	
	// Loops until either a meaningful event to report is found or the queue
	// is empty:
	while(1){
		if ( XCheckWindowEvent(this->dpy, this->win, -1L, &ev) ) {

			switch (ev.type) {

			case MappingNotify:
				XRefreshKeyboardMapping(&ev.xmapping);
				break;

			case KeyPress:
				{
					XKeyEvent *ke = (XKeyEvent *) &ev;
					KeySym    keysym;
					XComposeStatus compose;
					char      translated[9];

					e->code = gui_EVENT_KEYDOWN;
					int len = XLookupString(ke, translated, sizeof(translated), &keysym, &compose);
					// Check function keys:
					if( XK_F1 <= keysym && keysym <= XK_F12 ){
						e->key = gui_KEY_F(keysym - XK_F1 + 1);
						return 1;
					}
					
					// Check special keys:
					int i = 0;
					while(key_codes_map[i][0] != 0){
						if( key_codes_map[i][0] == keysym ){
							e->key = key_codes_map[i][1];
							return 1;
						}
						i++;
					}
					
					// Checks ASCII:
					if( len == 1 && (translated[0]&127) == translated[0] ){
						e->key = translated[0];
						return 1;
					}
				}
				break;

			case ButtonPress:
				e->x = ev.xbutton.x;
				e->y = ev.xbutton.y;
				if (ev.xbutton.button == Button1) {
					e->code = gui_EVENT_LBUTTONDOWN;
					return 1;

				} else if (ev.xbutton.button == Button2) {
					e->code = gui_EVENT_MBUTTONDOWN;
					return 1;

				} else if (ev.xbutton.button == Button3) {
					e->code = gui_EVENT_RBUTTONDOWN;
					return 1;
				}
				break;

			case ButtonRelease:
				e->x = ev.xbutton.x;
				e->y = ev.xbutton.y;
				if (ev.xbutton.button == Button1) {
					e->code = gui_EVENT_LBUTTONUP;
					return 1;

				} else if (ev.xbutton.button == Button2) {
					e->code = gui_EVENT_MBUTTONUP;
					return 1;

				} else if (ev.xbutton.button == Button3) {
					e->code = gui_EVENT_RBUTTONUP;
					return 1;
				}
				break;
			
			case Expose:
				e->code = gui_EVENT_WINDOW_EXPOSE;
				return 1;

			case ConfigureNotify:
				e->code = gui_EVENT_WINDOW_SIZE_CHANGE;
				e->x = this->width = ev.xconfigure.width;
				e->y = this->height = ev.xconfigure.height;
				return 1;

			case FocusIn:
				this->has_focus = 1;
				e->code = gui_EVENT_WINDOW_FOCUS_IN;
				return 1;

			case FocusOut:
				this->has_focus = 0;
				e->code = gui_EVENT_WINDOW_FOCUS_OUT;
				return 1;
				
			case MapNotify:
				this->is_visible = 1;
				break;
				
			case UnmapNotify:
				this->is_visible = 0;
				break;

			default:
				break;
			}

		} else if( XCheckTypedEvent(this->dpy, ClientMessage, &ev) ) {

			if (ev.xclient.message_type == this->protocolsAtom
			&& (ev.xclient.data.l[0] == this->deleteWindowAtom
			|| ev.xclient.data.l[0] == this->closedownAtom) ){

				e->code = gui_EVENT_WINDOW_CLOSE;
				return 1;
			}

		} else {
			// Events queue is empty.
			return 0;
		}
	}
}


void gui_getPointerPosition(gui_Type *this, int *x, int *y)
{
	Window root, win;
	int root_x, root_y, win_x, win_y;
	unsigned int mask;
	if( XQueryPointer(this->dpy, this->win, &root, &win, &root_x, &root_y,
		&win_x, &win_y, &mask)
	&& (0 <= win_x && win_x < this->width && 0 <= win_y && win_y < this->height)
	){
		this->pointer_x = win_x;
		this->pointer_y = win_y;
	}
	*x = this->pointer_x;
	*y = this->pointer_y;
	if( *x >= this->width )
		*x = this->width - 1;
	if( *y >= this->height )
		*y = this->height - 1;
	if( *x < 0 )
		*x = 0;
	if( *y < 0 )
		*y = 0;
}


int gui_getWidth(gui_Type *this)
{
	return this->width;
}


int gui_getHeight(gui_Type *this)
{
	return this->height;
}


int gui_hasFocus(gui_Type *this)
{
	return this->has_focus;
}


int gui_isVisible(gui_Type *this)
{
	return this->is_visible;
}


void gui_drawLine(gui_Type *this, int x1, int y1, int x2, int y2, int color)
{
	if( !(0 <= color && color < this->colors_number) )
		error_internal("color index out of the range: %d", color);
	if( color != this->curr_color ){
		this->curr_color = color;
		XSetForeground(this->dpy, this->gc, this->colors[color]);
	}
	XDrawLine(this->dpy, this->win, this->gc, x1, y1, x2, y2);
}

#endif
