#include <QtCore/QCoreApplication>
#include "lib/framework/wzapp.h"
#include "lib/framework/input.h"
#include "lib/framework/utf.h"
#include "lib/framework/opengl.h"
#include "lib/ivis_opengl/pieclip.h"
#include "lib/gamelib/gtime.h"
#include "src/warzoneconfig.h"
#include <SDL.h>
#include <QtCore/QSize>
#include <QtCore/QString>
#include "scrap.h"
#include "wz2100icon.h"
#include "cursors_sdl.h"
#include <algorithm>

extern void mainLoop();
// used in crash reports & version info
const char *BACKEND="SDL";

int realmain(int argc, char *argv[]);
int main(int argc, char *argv[])
{
	return realmain(argc, argv);
}

unsigned                screenWidth = 0;   // Declared in frameint.h.
unsigned                screenHeight = 0;  // Declared in frameint.h.
static unsigned         screenDepth = 0;
static SDL_Surface *    screen = NULL;

QCoreApplication *appPtr;


/* The possible states for keys */
enum KEY_STATE
{
	KEY_UP,
	KEY_PRESSED,
	KEY_DOWN,
	KEY_RELEASED,
	KEY_PRESSRELEASE,	// When a key goes up and down in a frame
	KEY_DOUBLECLICK,	// Only used by mouse keys
	KEY_DRAG			// Only used by mouse keys
};

struct INPUT_STATE
{
	KEY_STATE state; /// Last key/mouse state
	UDWORD lastdown; /// last key/mouse button down timestamp
	Vector2i pressPos;    ///< Location of last mouse press event.
	Vector2i releasePos;  ///< Location of last mouse release event.
};

/// constant for the interval between 2 singleclicks for doubleclick event in ms
#define DOUBLE_CLICK_INTERVAL 250

/* The current state of the keyboard */
static INPUT_STATE aKeyState[KEY_MAXSCAN];

/* The current location of the mouse */
static Uint16 mouseXPos, mouseYPos;
static bool mouseInWindow = true;

/* How far the mouse has to move to start a drag */
#define DRAG_THRESHOLD	5

/* Which button is being used for a drag */
static MOUSE_KEY_CODE dragKey;

/* The start of a possible drag by the mouse */
static SDWORD dragX, dragY;

/* The current mouse button state */
static INPUT_STATE aMouseState[MOUSE_BAD];


/* The size of the input buffer */
#define INPUT_MAXSTR	512

/* The input string buffer */
struct InputKey
{
	UDWORD key;
	utf_32_char unicode;
};
static InputKey	pInputBuffer[INPUT_MAXSTR];
static InputKey	*pStartBuffer, *pEndBuffer;

/**************************/
/***     Misc support   ***/
/**************************/

#define WIDG_MAXSTR 80 // HACK, from widget.h

/* Put a character into a text buffer overwriting any text under the cursor */
QString wzGetSelection()
{
	QString retval;
	static char* scrap = NULL;
	int scraplen;

	get_scrap(T('T','E','X','T'), &scraplen, &scrap);
	if (scraplen > 0 && scraplen < WIDG_MAXSTR-2)
	{
		retval = QString::fromUtf8(scrap);
	}
	return retval;
}


#if defined(WZ_WS_X11)

#include <GL/glx.h> // GLXDrawable
// X11 polution
#ifdef Status
#undef Status
#endif // Status
#ifdef CursorShape
#undef CursorShape
#endif // CursorShape
#ifdef Bool
#undef Bool
#endif // Bool

#ifndef GLX_SWAP_INTERVAL_EXT
#define GLX_SWAP_INTERVAL_EXT 0x20F1
#endif // GLX_SWAP_INTERVAL_EXT

// Need this global for use case of only having glXSwapIntervalSGI
static int swapInterval = -1;

void wzSetSwapInterval(int interval)
{
	typedef void (* PFNGLXQUERYDRAWABLEPROC) (Display *, GLXDrawable, int, unsigned int *);
	typedef void ( * PFNGLXSWAPINTERVALEXTPROC) (Display*, GLXDrawable, int);
	typedef int (* PFNGLXGETSWAPINTERVALMESAPROC)(void);
	typedef int (* PFNGLXSWAPINTERVALMESAPROC)(unsigned);
	typedef int (* PFNGLXSWAPINTERVALSGIPROC) (int);
	PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
	PFNGLXQUERYDRAWABLEPROC glXQueryDrawable;
	PFNGLXGETSWAPINTERVALMESAPROC glXGetSwapIntervalMESA;
	PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
	PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;

	if (interval < 0)
			interval = 0;

#if GLX_VERSION_1_2
	// Hack-ish, but better than not supporting GLX_SWAP_INTERVAL_EXT?
	GLXDrawable drawable = glXGetCurrentDrawable();
	Display * display =  glXGetCurrentDisplay();
	glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) SDL_GL_GetProcAddress("glXSwapIntervalEXT");
	glXQueryDrawable = (PFNGLXQUERYDRAWABLEPROC) SDL_GL_GetProcAddress("glXQueryDrawable");

	if (glXSwapIntervalEXT && glXQueryDrawable && drawable)
	{
		unsigned clampedInterval;
		glXSwapIntervalEXT(display, drawable, interval);
		glXQueryDrawable(display, drawable, GLX_SWAP_INTERVAL_EXT, &clampedInterval);
		swapInterval = clampedInterval;
		return;
	}
#endif

	glXSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) SDL_GL_GetProcAddress("glXSwapIntervalMESA");
	glXGetSwapIntervalMESA = (PFNGLXGETSWAPINTERVALMESAPROC) SDL_GL_GetProcAddress("glXGetSwapIntervalMESA");
	if (glXSwapIntervalMESA && glXGetSwapIntervalMESA)
	{
		glXSwapIntervalMESA(interval);
		swapInterval = glXGetSwapIntervalMESA();
		return;
	}

	glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC) SDL_GL_GetProcAddress("glXSwapIntervalSGI");
	if (glXSwapIntervalSGI)
	{
		if (interval < 1)
			interval = 1;
		if (glXSwapIntervalSGI(interval))
		{
			// Error, revert to default
			swapInterval = 1;
			glXSwapIntervalSGI(1);
		}
		else
		{
			swapInterval = interval;
		}
		return;
	}
	swapInterval = 0;
}

int wzGetSwapInterval()
{
	if (swapInterval >= 0)
		return swapInterval;

	typedef void (* PFNGLXQUERYDRAWABLEPROC) (Display *, GLXDrawable, int, unsigned int *);
	typedef int (* PFNGLXGETSWAPINTERVALMESAPROC)(void);
	typedef int (* PFNGLXSWAPINTERVALSGIPROC) (int);
	PFNGLXQUERYDRAWABLEPROC glXQueryDrawable;
	PFNGLXGETSWAPINTERVALMESAPROC glXGetSwapIntervalMESA;
	PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;

#if GLX_VERSION_1_2
	// Hack-ish, but better than not supporting GLX_SWAP_INTERVAL_EXT?
	GLXDrawable drawable = glXGetCurrentDrawable();
	Display * display =  glXGetCurrentDisplay();
	glXQueryDrawable = (PFNGLXQUERYDRAWABLEPROC) SDL_GL_GetProcAddress("glXQueryDrawable");

	if (glXQueryDrawable && drawable)
	{
		unsigned interval;
		glXQueryDrawable(display, drawable, GLX_SWAP_INTERVAL_EXT, &interval);
		swapInterval = interval;
		return swapInterval;
	}
#endif

	glXGetSwapIntervalMESA = (PFNGLXGETSWAPINTERVALMESAPROC) SDL_GL_GetProcAddress("glXGetSwapIntervalMESA");
	if (glXGetSwapIntervalMESA)
	{
		swapInterval = glXGetSwapIntervalMESA();
		return swapInterval;
	}

	glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC) SDL_GL_GetProcAddress("glXSwapIntervalSGI");
	if (glXSwapIntervalSGI)
	{
		swapInterval = 1;
	}
	else
	{
		swapInterval = 0;
	}
	return swapInterval;
}

#elif defined(WZ_WS_WIN)

void wzSetSwapInterval(int interval)
{
	typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int);
	PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;

	if (interval < 0)
		interval = 0;

	wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) SDL_GL_GetProcAddress("wglSwapIntervalEXT");

	if (wglSwapIntervalEXT)
		wglSwapIntervalEXT(interval);
}

int wzGetSwapInterval()
{
	typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void);
	PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT;

	wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC) SDL_GL_GetProcAddress("wglGetSwapIntervalEXT");
	if (wglGetSwapIntervalEXT)
		return wglGetSwapIntervalEXT();
	return 0;
}

#elif !defined(WZ_OS_MAC)

void wzSetSwapInterval(int)
{
	return;
}

int wzGetSwapInterval()
{
	return 0;
}

#endif

QList<QSize> wzAvailableResolutions()
{
	QList<QSize> list;
	int count;
	SDL_Rect **modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE);
	for (count = 0; modes[count]; count++)
	{
		QSize s(modes[count]->w, modes[count]->h);
		list.push_back(s);
	}
	return list;
}

void wzShowMouse(bool visible)
{
	SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
}

int wzGetTicks()
{
	return SDL_GetTicks();
}

void wzFatalDialog(char const*)
{
	// no-op
}

void wzScreenFlip()
{
	SDL_GL_SwapBuffers();
}

void wzToggleFullscreen()
{
	war_setFullscreen(!war_getFullscreen());
	SDL_WM_ToggleFullScreen(screen);
}

void wzQuit()
{
	// Create a quit event to halt game loop.
	SDL_Event quitEvent;
	quitEvent.type = SDL_QUIT;
	SDL_PushEvent(&quitEvent);
}

void wzGrabMouse()
{
	SDL_WM_GrabInput(SDL_GRAB_ON);
}

void wzReleaseMouse()
{
	SDL_WM_GrabInput(SDL_GRAB_OFF);
}

/**************************/
/***    Thread support  ***/
/**************************/

WZ_THREAD *wzThreadCreate(int (*threadFunc)(void *), void *data)
{
	return (WZ_THREAD *)SDL_CreateThread(threadFunc, data);
}

int wzThreadJoin(WZ_THREAD *thread)
{
	int result;
	SDL_WaitThread((SDL_Thread *)thread, &result);
	return result;
}

void wzThreadStart(WZ_THREAD *thread)
{
	(void)thread; // no-op
}

void wzYieldCurrentThread()
{
	SDL_Delay(40);
}

WZ_MUTEX *wzMutexCreate()
{
	return (WZ_MUTEX *)SDL_CreateMutex();
}

void wzMutexDestroy(WZ_MUTEX *mutex)
{
	SDL_DestroyMutex((SDL_mutex *)mutex);
}

void wzMutexLock(WZ_MUTEX *mutex)
{
	SDL_LockMutex((SDL_mutex *)mutex);
}

void wzMutexUnlock(WZ_MUTEX *mutex)
{
	SDL_UnlockMutex((SDL_mutex *)mutex);
}

WZ_SEMAPHORE *wzSemaphoreCreate(int startValue)
{
	return (WZ_SEMAPHORE *)SDL_CreateSemaphore(startValue);
}

void wzSemaphoreDestroy(WZ_SEMAPHORE *semaphore)
{
	SDL_DestroySemaphore((SDL_sem *)semaphore);
}

void wzSemaphoreWait(WZ_SEMAPHORE *semaphore)
{
	SDL_SemWait((SDL_sem *)semaphore);
}

void wzSemaphorePost(WZ_SEMAPHORE *semaphore)
{
	SDL_SemPost((SDL_sem *)semaphore);
}

/**************************/
/***     Main support   ***/
/**************************/

std::pair<KEY_CODE, SDLKey> KEY_CODE_to_SDLKey[] =
{
	std::pair<KEY_CODE, SDLKey>(KEY_ESC, SDLK_ESCAPE),
	std::pair<KEY_CODE, SDLKey>(KEY_1, SDLK_1),
	std::pair<KEY_CODE, SDLKey>(KEY_2, SDLK_2),
	std::pair<KEY_CODE, SDLKey>(KEY_3, SDLK_3),
	std::pair<KEY_CODE, SDLKey>(KEY_4, SDLK_4),
	std::pair<KEY_CODE, SDLKey>(KEY_5, SDLK_5),
	std::pair<KEY_CODE, SDLKey>(KEY_6, SDLK_6),
	std::pair<KEY_CODE, SDLKey>(KEY_7, SDLK_7),
	std::pair<KEY_CODE, SDLKey>(KEY_8, SDLK_8),
	std::pair<KEY_CODE, SDLKey>(KEY_9, SDLK_9),
	std::pair<KEY_CODE, SDLKey>(KEY_0, SDLK_0),
	std::pair<KEY_CODE, SDLKey>(KEY_MINUS, SDLK_MINUS),
	std::pair<KEY_CODE, SDLKey>(KEY_EQUALS, SDLK_EQUALS),
	std::pair<KEY_CODE, SDLKey>(KEY_BACKSPACE, SDLK_BACKSPACE),
	std::pair<KEY_CODE, SDLKey>(KEY_TAB, SDLK_TAB),
	std::pair<KEY_CODE, SDLKey>(KEY_Q, SDLK_q),
	std::pair<KEY_CODE, SDLKey>(KEY_W, SDLK_w),
	std::pair<KEY_CODE, SDLKey>(KEY_E, SDLK_e),
	std::pair<KEY_CODE, SDLKey>(KEY_R, SDLK_r),
	std::pair<KEY_CODE, SDLKey>(KEY_T, SDLK_t),
	std::pair<KEY_CODE, SDLKey>(KEY_Y, SDLK_y),
	std::pair<KEY_CODE, SDLKey>(KEY_U, SDLK_u),
	std::pair<KEY_CODE, SDLKey>(KEY_I, SDLK_i),
	std::pair<KEY_CODE, SDLKey>(KEY_O, SDLK_o),
	std::pair<KEY_CODE, SDLKey>(KEY_P, SDLK_p),
	std::pair<KEY_CODE, SDLKey>(KEY_LBRACE, SDLK_LEFTBRACKET),
	std::pair<KEY_CODE, SDLKey>(KEY_RBRACE, SDLK_RIGHTBRACKET),
	std::pair<KEY_CODE, SDLKey>(KEY_RETURN, SDLK_RETURN),
	std::pair<KEY_CODE, SDLKey>(KEY_LCTRL, SDLK_LCTRL),
	std::pair<KEY_CODE, SDLKey>(KEY_A, SDLK_a),
	std::pair<KEY_CODE, SDLKey>(KEY_S, SDLK_s),
	std::pair<KEY_CODE, SDLKey>(KEY_D, SDLK_d),
	std::pair<KEY_CODE, SDLKey>(KEY_F, SDLK_f),
	std::pair<KEY_CODE, SDLKey>(KEY_G, SDLK_g),
	std::pair<KEY_CODE, SDLKey>(KEY_H, SDLK_h),
	std::pair<KEY_CODE, SDLKey>(KEY_J, SDLK_j),
	std::pair<KEY_CODE, SDLKey>(KEY_K, SDLK_k),
	std::pair<KEY_CODE, SDLKey>(KEY_L, SDLK_l),
	std::pair<KEY_CODE, SDLKey>(KEY_SEMICOLON, SDLK_SEMICOLON),
	std::pair<KEY_CODE, SDLKey>(KEY_QUOTE, SDLK_QUOTE),
	std::pair<KEY_CODE, SDLKey>(KEY_BACKQUOTE, SDLK_BACKQUOTE),
	std::pair<KEY_CODE, SDLKey>(KEY_LSHIFT, SDLK_LSHIFT),
	std::pair<KEY_CODE, SDLKey>(KEY_LMETA, SDLK_LMETA),
	std::pair<KEY_CODE, SDLKey>(KEY_LSUPER, SDLK_LSUPER),
	std::pair<KEY_CODE, SDLKey>(KEY_BACKSLASH, SDLK_BACKSLASH),
	std::pair<KEY_CODE, SDLKey>(KEY_Z, SDLK_z),
	std::pair<KEY_CODE, SDLKey>(KEY_X, SDLK_x),
	std::pair<KEY_CODE, SDLKey>(KEY_C, SDLK_c),
	std::pair<KEY_CODE, SDLKey>(KEY_V, SDLK_v),
	std::pair<KEY_CODE, SDLKey>(KEY_B, SDLK_b),
	std::pair<KEY_CODE, SDLKey>(KEY_N, SDLK_n),
	std::pair<KEY_CODE, SDLKey>(KEY_M, SDLK_m),
	std::pair<KEY_CODE, SDLKey>(KEY_COMMA, SDLK_COMMA),
	std::pair<KEY_CODE, SDLKey>(KEY_FULLSTOP, SDLK_PERIOD),
	std::pair<KEY_CODE, SDLKey>(KEY_FORWARDSLASH, SDLK_SLASH),
	std::pair<KEY_CODE, SDLKey>(KEY_RSHIFT, SDLK_RSHIFT),
	std::pair<KEY_CODE, SDLKey>(KEY_RMETA, SDLK_RMETA),
	std::pair<KEY_CODE, SDLKey>(KEY_RSUPER, SDLK_RSUPER),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_STAR, SDLK_KP_MULTIPLY),
	std::pair<KEY_CODE, SDLKey>(KEY_LALT, SDLK_LALT),
	std::pair<KEY_CODE, SDLKey>(KEY_SPACE, SDLK_SPACE),
	std::pair<KEY_CODE, SDLKey>(KEY_CAPSLOCK, SDLK_CAPSLOCK),
	std::pair<KEY_CODE, SDLKey>(KEY_F1, SDLK_F1),
	std::pair<KEY_CODE, SDLKey>(KEY_F2, SDLK_F2),
	std::pair<KEY_CODE, SDLKey>(KEY_F3, SDLK_F3),
	std::pair<KEY_CODE, SDLKey>(KEY_F4, SDLK_F4),
	std::pair<KEY_CODE, SDLKey>(KEY_F5, SDLK_F5),
	std::pair<KEY_CODE, SDLKey>(KEY_F6, SDLK_F6),
	std::pair<KEY_CODE, SDLKey>(KEY_F7, SDLK_F7),
	std::pair<KEY_CODE, SDLKey>(KEY_F8, SDLK_F8),
	std::pair<KEY_CODE, SDLKey>(KEY_F9, SDLK_F9),
	std::pair<KEY_CODE, SDLKey>(KEY_F10, SDLK_F10),
	std::pair<KEY_CODE, SDLKey>(KEY_NUMLOCK, SDLK_NUMLOCK),
	std::pair<KEY_CODE, SDLKey>(KEY_SCROLLLOCK, SDLK_SCROLLOCK),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_7, SDLK_KP7),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_8, SDLK_KP8),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_9, SDLK_KP9),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_MINUS, SDLK_KP_MINUS),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_4, SDLK_KP4),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_5, SDLK_KP5),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_6, SDLK_KP6),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_PLUS, SDLK_KP_PLUS),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_1, SDLK_KP1),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_2, SDLK_KP2),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_3, SDLK_KP3),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_0, SDLK_KP0),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_FULLSTOP, SDLK_KP_PERIOD),
	std::pair<KEY_CODE, SDLKey>(KEY_F11, SDLK_F11),
	std::pair<KEY_CODE, SDLKey>(KEY_F12, SDLK_F12),
	std::pair<KEY_CODE, SDLKey>(KEY_RCTRL, SDLK_RCTRL),
	std::pair<KEY_CODE, SDLKey>(KEY_KP_BACKSLASH, SDLK_KP_DIVIDE),
	std::pair<KEY_CODE, SDLKey>(KEY_RALT, SDLK_RALT),
	std::pair<KEY_CODE, SDLKey>(KEY_HOME, SDLK_HOME),
	std::pair<KEY_CODE, SDLKey>(KEY_UPARROW, SDLK_UP),
	std::pair<KEY_CODE, SDLKey>(KEY_PAGEUP, SDLK_PAGEUP),
	std::pair<KEY_CODE, SDLKey>(KEY_LEFTARROW, SDLK_LEFT),
	std::pair<KEY_CODE, SDLKey>(KEY_RIGHTARROW, SDLK_RIGHT),
	std::pair<KEY_CODE, SDLKey>(KEY_END, SDLK_END),
	std::pair<KEY_CODE, SDLKey>(KEY_DOWNARROW, SDLK_DOWN),
	std::pair<KEY_CODE, SDLKey>(KEY_PAGEDOWN, SDLK_PAGEDOWN),
	std::pair<KEY_CODE, SDLKey>(KEY_INSERT, SDLK_INSERT),
	std::pair<KEY_CODE, SDLKey>(KEY_DELETE, SDLK_DELETE),
	std::pair<KEY_CODE, SDLKey>(KEY_KPENTER, SDLK_KP_ENTER),
	std::pair<KEY_CODE, SDLKey>(KEY_IGNORE, SDLKey(5190)),
	std::pair<KEY_CODE, SDLKey>(KEY_MAXSCAN, SDLK_LAST),
};

std::pair<SDLKey, KEY_CODE> SDLKey_to_KEY_CODE[ARRAY_SIZE(KEY_CODE_to_SDLKey)];

static inline std::pair<SDLKey, KEY_CODE> swapit(std::pair<KEY_CODE, SDLKey> x)
{
	return  std::pair<SDLKey, KEY_CODE>(x.second, x.first);
}

static inline void initKeycodes()
{
	std::transform(KEY_CODE_to_SDLKey, KEY_CODE_to_SDLKey + ARRAY_SIZE(KEY_CODE_to_SDLKey), SDLKey_to_KEY_CODE, swapit);
	std::sort(KEY_CODE_to_SDLKey, KEY_CODE_to_SDLKey + ARRAY_SIZE(KEY_CODE_to_SDLKey));
	std::sort(SDLKey_to_KEY_CODE, SDLKey_to_KEY_CODE + ARRAY_SIZE(SDLKey_to_KEY_CODE));
}

static inline KEY_CODE sdlKeyToKeyCode(SDLKey key)
{
	std::pair<SDLKey, KEY_CODE> k(key, KEY_CODE(0));
	std::pair<SDLKey, KEY_CODE> const *found = std::lower_bound(SDLKey_to_KEY_CODE, SDLKey_to_KEY_CODE + ARRAY_SIZE(SDLKey_to_KEY_CODE), k);
	if (found != SDLKey_to_KEY_CODE + ARRAY_SIZE(SDLKey_to_KEY_CODE) && found->first == key)
	{
		return found->second;
	}

	return (KEY_CODE)key;
}

static inline SDLKey keyCodeToSDLKey(KEY_CODE code)
{
	std::pair<KEY_CODE, SDLKey> k(code, SDLKey(0));
	std::pair<KEY_CODE, SDLKey> const *found = std::lower_bound(KEY_CODE_to_SDLKey, KEY_CODE_to_SDLKey + ARRAY_SIZE(KEY_CODE_to_SDLKey), k);
	if (found != KEY_CODE_to_SDLKey + ARRAY_SIZE(KEY_CODE_to_SDLKey) && found->first == code)
	{
		return found->second;
	}

	return (SDLKey)code;
}

// Cyclic increment.
static InputKey *inputPointerNext(InputKey *p)
{
    return p + 1 == pInputBuffer + INPUT_MAXSTR ? pInputBuffer : p + 1;
}

/* add count copies of the characater code to the input buffer */
static void inputAddBuffer(UDWORD key, utf_32_char unicode)
{
	/* Calculate what pEndBuffer will be set to next */
	InputKey	*pNext = inputPointerNext(pEndBuffer);

	if (pNext == pStartBuffer)
	{
		return;  // Buffer full.
	}

	// Add key to buffer.
	pEndBuffer->key = key;
	pEndBuffer->unicode = unicode;
	pEndBuffer = pNext;
}


void keyScanToString(KEY_CODE code, char *ascii, UDWORD maxStringSize)
{
	if(keyCodeToSDLKey(code) == SDLK_LAST)
	{
		strcpy(ascii,"???");
		return;
	}
	else if (code == KEY_LCTRL)
	{
		// shortcuts with modifier keys work with either key.
		strcpy(ascii, "Ctrl");
		return;
	}
	else if (code == KEY_LSHIFT)
	{
		// shortcuts with modifier keys work with either key.
		strcpy(ascii, "Shift");
		return;
	}
	else if (code == KEY_LALT)
	{
		// shortcuts with modifier keys work with either key.
		strcpy(ascii, "Alt");
		return;
	}
	else if (code == KEY_LMETA)
	{
		// shortcuts with modifier keys work with either key.
#ifdef WZ_OS_MAC
		strcpy(ascii, "Cmd");
#else
		strcpy(ascii, "Meta");
#endif
		return;
	}
	ASSERT(keyCodeToSDLKey(code) < SDLK_LAST, "Invalid key code: %d", code);
	snprintf(ascii, maxStringSize, "%s", SDL_GetKeyName(keyCodeToSDLKey(code)));
	if (ascii[0] >= 'a' && ascii[0] <= 'z' && ascii[1] != 0)
	{
		// capitalize
		ascii[0] += 'A'-'a';
	}
}


/* Initialise the input module */
void inputInitialise(void)
{
	unsigned int i;

	for (i = 0; i < KEY_MAXSCAN; i++)
	{
		aKeyState[i].state = KEY_UP;
	}

	for (i = 0; i < MOUSE_BAD; i++)
	{
		aMouseState[i].state = KEY_UP;
	}

	pStartBuffer = pInputBuffer;
	pEndBuffer = pInputBuffer;

	dragX = mouseXPos = screenWidth/2;
	dragY = mouseYPos = screenHeight/2;
	dragKey = MOUSE_LMB;

	SDL_EnableUNICODE(1);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
}

/* Clear the input buffer */
void inputClearBuffer(void)
{
	pStartBuffer = pInputBuffer;
	pEndBuffer = pInputBuffer;
}

/* Return the next key press or 0 if no key in the buffer.
 * The key returned will have been remaped to the correct ascii code for the
 * windows key map.
 * All key presses are buffered up (including windows auto repeat).
 */
UDWORD inputGetKey(utf_32_char *unicode)
{
	UDWORD	retVal;

	if (pStartBuffer == pEndBuffer)
	{
	    return 0;  // Buffer empty.
	}

	retVal = pStartBuffer->key;
	if (unicode)
	{
	    *unicode = pStartBuffer->unicode;
	}
	if (!retVal)
	{
	    retVal = ' ';  // Don't return 0 if we got a virtual key, since that's interpreted as no input.
	}
	pStartBuffer = inputPointerNext(pStartBuffer);

	return retVal;
}


/*!
 * This is called once a frame so that the system can tell
 * whether a key was pressed this turn or held down from the last frame.
 */
void inputNewFrame(void)
{
	unsigned int i;

	/* Do the keyboard */
	for (i = 0; i < KEY_MAXSCAN; i++)
	{
		if (aKeyState[i].state == KEY_PRESSED)
		{
			aKeyState[i].state = KEY_DOWN;
		}
		else if ( aKeyState[i].state == KEY_RELEASED  ||
			  aKeyState[i].state == KEY_PRESSRELEASE )
		{
			aKeyState[i].state = KEY_UP;
		}
	}

	/* Do the mouse */
	for (i = 0; i < 6; i++)
	{
		if (aMouseState[i].state == KEY_PRESSED)
		{
			aMouseState[i].state = KEY_DOWN;
		}
		else if ( aMouseState[i].state == KEY_RELEASED
		       || aMouseState[i].state == KEY_DOUBLECLICK
		       || aMouseState[i].state == KEY_PRESSRELEASE )
		{
			aMouseState[i].state = KEY_UP;
		}
	}
}

/*!
 * Release all keys (and buttons) when we loose focus
 */
// FIXME This seems to be totally ignored! (Try switching focus while the dragbox is open)
void inputLoseFocus(void)
{
	unsigned int i;

	/* Lost the window focus, have to take this as a global key up */
	for(i = 0; i < KEY_MAXSCAN; i++)
	{
		aKeyState[i].state = KEY_UP;
	}
	for (i = 0; i < 6; i++)
	{
		aMouseState[i].state = KEY_UP;
	}
}

/* This returns true if the key is currently depressed */
bool keyDown(KEY_CODE code)
{
	ASSERT(keyCodeToSDLKey(code) < SDLK_LAST, "Invalid key code: %d", code);
	return (aKeyState[code].state != KEY_UP);
}

/* This returns true if the key went from being up to being down this frame */
bool keyPressed(KEY_CODE code)
{
	ASSERT(keyCodeToSDLKey(code) < SDLK_LAST, "Invalid key code: %d", code);
	return ((aKeyState[code].state == KEY_PRESSED) || (aKeyState[code].state == KEY_PRESSRELEASE));
}

/* This returns true if the key went from being down to being up this frame */
bool keyReleased(KEY_CODE code)
{
	ASSERT(keyCodeToSDLKey(code) < SDLK_LAST, "Invalid key code: %d", code);
	return ((aKeyState[code].state == KEY_RELEASED) || (aKeyState[code].state == KEY_PRESSRELEASE));
}

/* Return the X coordinate of the mouse */
Uint16 mouseX(void)
{
	return mouseXPos;
}

/* Return the Y coordinate of the mouse */
Uint16 mouseY(void)
{
	return mouseYPos;
}

bool wzMouseInWindow()
{
	return mouseInWindow;
}

Vector2i mousePressPos(MOUSE_KEY_CODE code)
{
	return aMouseState[code].pressPos;
}

Vector2i mouseReleasePos(MOUSE_KEY_CODE code)
{
	return aMouseState[code].releasePos;
}

/* This returns true if the mouse key is currently depressed */
bool mouseDown(MOUSE_KEY_CODE code)
{
	return (aMouseState[code].state != KEY_UP) ||

	       // holding down LMB and RMB counts as holding down MMB
	       (code == MOUSE_MMB && aMouseState[MOUSE_LMB].state != KEY_UP && aMouseState[MOUSE_RMB].state != KEY_UP);
}

/* This returns true if the mouse key was double clicked */
bool mouseDClicked(MOUSE_KEY_CODE code)
{
	return (aMouseState[code].state == KEY_DOUBLECLICK);
}

/* This returns true if the mouse key went from being up to being down this frame */
bool mousePressed(MOUSE_KEY_CODE code)
{
	return ((aMouseState[code].state == KEY_PRESSED) ||
			(aMouseState[code].state == KEY_DOUBLECLICK) ||
			(aMouseState[code].state == KEY_PRESSRELEASE));
}

/* This returns true if the mouse key went from being down to being up this frame */
bool mouseReleased(MOUSE_KEY_CODE code)
{
	return ((aMouseState[code].state == KEY_RELEASED) ||
			(aMouseState[code].state == KEY_DOUBLECLICK) ||
			(aMouseState[code].state == KEY_PRESSRELEASE));
}

/* Check for a mouse drag, return the drag start coords if dragging */
bool mouseDrag(MOUSE_KEY_CODE code, UDWORD *px, UDWORD *py)
{
	if ((aMouseState[code].state == KEY_DRAG) ||

	    // dragging LMB and RMB counts as dragging MMB
	    (code == MOUSE_MMB && ((aMouseState[MOUSE_LMB].state == KEY_DRAG && aMouseState[MOUSE_RMB].state != KEY_UP) ||
	     (aMouseState[MOUSE_LMB].state != KEY_UP && aMouseState[MOUSE_RMB].state == KEY_DRAG))))
	{
		*px = dragX;
		*py = dragY;
		return true;
	}

	return false;
}

// TODO: Rewrite this silly thing
void setMousePos(uint16_t x, uint16_t y)
{
	static int mousewarp = -1;

	if (mousewarp == -1)
	{
		int val;

		mousewarp = 1;
		if ((val = getMouseWarp()))
		{
			mousewarp = !val;
		}
	}
	if (mousewarp)
		SDL_WarpMouse(x, y);
}

/*!
 * Handle keyboard events
 */
static void inputHandleKeyEvent(SDL_KeyboardEvent * keyEvent)
{
	UDWORD code, vk;
	utf_32_char unicode = keyEvent->keysym.unicode;

	switch (keyEvent->type)
	{
		case SDL_KEYDOWN:
			switch (keyEvent->keysym.sym)
			{
				case SDLK_LEFT:
					vk = INPBUF_LEFT;
					break;
				case SDLK_RIGHT:
					vk = INPBUF_RIGHT;
					break;
				case SDLK_UP:
					vk = INPBUF_UP;
					break;
				case SDLK_DOWN:
					vk = INPBUF_DOWN;
					break;
				case SDLK_HOME:
					vk = INPBUF_HOME;
					break;
				case SDLK_END:
					vk = INPBUF_END;
					break;
				case SDLK_INSERT:
					vk = INPBUF_INS;
					break;
				case SDLK_DELETE:
					vk = INPBUF_DEL;
					break;
				case SDLK_PAGEUP:
					vk = INPBUF_PGUP;
					break;
				case SDLK_PAGEDOWN:
					vk = INPBUF_PGDN;
					break;
				default:
					vk = keyEvent->keysym.sym;
					break;
			}

			debug( LOG_INPUT, "Key Code (pressed): 0x%x, %d, [%c] SDLkey=[%s]", vk, vk, (vk < 128) && (vk > 31) ? (char) vk : '?' , SDL_GetKeyName(keyCodeToSDLKey((KEY_CODE)keyEvent->keysym.sym)));
			if (unicode < 32)
			{
				unicode = 0;
			}
			inputAddBuffer(vk, unicode);

			code = sdlKeyToKeyCode(keyEvent->keysym.sym);
			if ( aKeyState[code].state == KEY_UP ||
				 aKeyState[code].state == KEY_RELEASED ||
				 aKeyState[code].state == KEY_PRESSRELEASE )
			{
				//whether double key press or not
					aKeyState[code].state = KEY_PRESSED;
					aKeyState[code].lastdown = 0;
			}
			break;
		case SDL_KEYUP:
			code = sdlKeyToKeyCode(keyEvent->keysym.sym);
			if (aKeyState[code].state == KEY_PRESSED)
			{
				aKeyState[code].state = KEY_PRESSRELEASE;
			}
			else if (aKeyState[code].state == KEY_DOWN )
			{
				aKeyState[code].state = KEY_RELEASED;
			}
			break;
		default:
			break;
	}
}

/*!
 * Handle mousebutton events
 */
static void inputHandleMouseButtonEvent(SDL_MouseButtonEvent * buttonEvent)
{
	mouseXPos = buttonEvent->x;
	mouseYPos = buttonEvent->y;

	MOUSE_KEY_CODE mouseKeyCode;
	switch (buttonEvent->button)
	{
		default: return;  // Unknown button.
		case SDL_BUTTON_LEFT: mouseKeyCode = MOUSE_LMB; break;
		case SDL_BUTTON_MIDDLE: mouseKeyCode = MOUSE_MMB; break;
		case SDL_BUTTON_RIGHT: mouseKeyCode = MOUSE_RMB; break;
		case SDL_BUTTON_WHEELUP: mouseKeyCode = MOUSE_WUP; break;
		case SDL_BUTTON_WHEELDOWN: mouseKeyCode = MOUSE_WDN; break;
	}

	switch (buttonEvent->type)
	{
		case SDL_MOUSEBUTTONDOWN:
			aMouseState[mouseKeyCode].pressPos.x = mouseXPos;
			aMouseState[mouseKeyCode].pressPos.y = mouseYPos;
			if ( aMouseState[mouseKeyCode].state == KEY_UP
				|| aMouseState[mouseKeyCode].state == KEY_RELEASED
				|| aMouseState[mouseKeyCode].state == KEY_PRESSRELEASE )
			{
				if ( (buttonEvent->button != SDL_BUTTON_WHEELUP		//skip doubleclick check for wheel
					&& buttonEvent->button != SDL_BUTTON_WHEELDOWN))
				{
					// whether double click or not
					if ( realTime - aMouseState[mouseKeyCode].lastdown < DOUBLE_CLICK_INTERVAL )
					{
						aMouseState[mouseKeyCode].state = KEY_DOUBLECLICK;
						aMouseState[mouseKeyCode].lastdown = 0;
					}
					else
					{
						aMouseState[mouseKeyCode].state = KEY_PRESSED;
						aMouseState[mouseKeyCode].lastdown = realTime;
					}

				}
				else	//mouse wheel up/down was used, so notify.
				{
					aMouseState[mouseKeyCode].state = KEY_PRESSED;
					aMouseState[mouseKeyCode].lastdown = 0;
				}

				if (mouseKeyCode < 4) // Not the mousewheel
				{
					dragKey = mouseKeyCode;
					dragX = mouseXPos;
					dragY = mouseYPos;
				}
			}
			break;
		case SDL_MOUSEBUTTONUP:
			aMouseState[mouseKeyCode].releasePos.x = mouseXPos;
			aMouseState[mouseKeyCode].releasePos.y = mouseYPos;
			if (aMouseState[mouseKeyCode].state == KEY_PRESSED)
			{
				aMouseState[mouseKeyCode].state = KEY_PRESSRELEASE;
			}
			else if ( aMouseState[mouseKeyCode].state == KEY_DOWN
					|| aMouseState[mouseKeyCode].state == KEY_DRAG
					|| aMouseState[mouseKeyCode].state == KEY_DOUBLECLICK)
			{
				aMouseState[mouseKeyCode].state = KEY_RELEASED;
			}
			break;
		default:
			break;
	}
}


/*!
 * Handle mousemotion events
 */
static void inputHandleMouseMotionEvent(SDL_MouseMotionEvent * motionEvent)
{
	switch (motionEvent->type)
	{
		case SDL_MOUSEMOTION:
			/* store the current mouse position */
			mouseXPos = motionEvent->x;
			mouseYPos = motionEvent->y;

			/* now see if a drag has started */
			if ((aMouseState[dragKey].state == KEY_PRESSED ||
			     aMouseState[dragKey].state == KEY_DOWN) &&
			    (ABSDIF(dragX, mouseXPos) > DRAG_THRESHOLD ||
			     ABSDIF(dragY, mouseYPos) > DRAG_THRESHOLD))
			{
				aMouseState[dragKey].state = KEY_DRAG;
			}
			break;
		default:
			break;
	}
}

void wzMain(int &argc, char **argv)
{
	initKeycodes();

	appPtr = new QCoreApplication(argc, argv);  // For Qt-script.
}

bool wzMain2()
{
	//BEGIN **** Was in old frameInitialise. ****

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
	{
		debug( LOG_ERROR, "Error: Could not initialise SDL (%s).\n", SDL_GetError() );
		return false;
	}

#if !defined(WZ_OS_MAC) // Don't replace our 512 dock icon with a 32 one
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
	uint32_t rmask = 0xff000000;
	uint32_t gmask = 0x00ff0000;
	uint32_t bmask = 0x0000ff00;
	uint32_t amask = 0x000000ff;
#else
	uint32_t rmask = 0x000000ff;
	uint32_t gmask = 0x0000ff00;
	uint32_t bmask = 0x00ff0000;
	uint32_t amask = 0xff000000;
#endif

	SDL_WM_SetIcon(SDL_CreateRGBSurfaceFrom((void*)wz2100icon.pixel_data, wz2100icon.width, wz2100icon.height, wz2100icon.bytes_per_pixel * 8,
	                                        wz2100icon.width * wz2100icon.bytes_per_pixel, rmask, gmask, bmask, amask), NULL);
#endif
	SDL_WM_SetCaption(PACKAGE_NAME, NULL);

	/* initialise all cursors */
	sdlInitCursors();
	//END **** Was in old frameInitialise. ****

	// TODO Fall back to windowed mode, if fullscreen mode fails.
	//BEGIN **** Was in old screenInitialise. ****
	unsigned        width = pie_GetVideoBufferWidth();
	unsigned        height = pie_GetVideoBufferHeight();
	unsigned        bitDepth = pie_GetVideoBufferDepth();
	unsigned        fsaa = war_getFSAA();
	bool            fullScreen = war_getFullscreen();
	bool            vsync = war_GetVsync();

	// Fetch the video info.
	const SDL_VideoInfo* video_info = SDL_GetVideoInfo();

	if (video_info == NULL)
	{
		debug(LOG_ERROR, "SDL_GetVideoInfo failed.");
		return false;
	}

	if (width == 0 || height == 0)
	{
		pie_SetVideoBufferWidth(width = screenWidth = video_info->current_w);
		pie_SetVideoBufferHeight(height = screenHeight = video_info->current_h);
		pie_SetVideoBufferDepth(bitDepth = screenDepth = video_info->vfmt->BitsPerPixel);
	}
	else
	{
		screenWidth = width;
		screenHeight = height;
		screenDepth = bitDepth;
	}
	screenWidth = MAX(screenWidth, 640);
	screenHeight = MAX(screenHeight, 480);

	// The flags to pass to SDL_SetVideoMode.
	int video_flags  = SDL_OPENGL;    // Enable OpenGL in SDL.
	video_flags |= SDL_ANYFORMAT; // Don't emulate requested BPP if not available.

	if (fullScreen)
	{
		video_flags |= SDL_FULLSCREEN;
	}

	// Set the double buffer OpenGL attribute.
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	// Enable vsync if requested by the user
	SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, vsync);

	// Enable FSAA anti-aliasing if and at the level requested by the user
	if (fsaa)
	{
		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fsaa);
	}

	int bpp = SDL_VideoModeOK(width, height, bitDepth, video_flags);
	if (!bpp)
	{
		debug(LOG_ERROR, "Video mode %dx%d@%dbpp is not supported!", width, height, bitDepth);
		return false;
	}
	switch (bpp)
	{
		case 32:
		case 24:
			SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
			SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
			SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
			SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
			SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
			SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
			break;
		case 16:
			info("Using colour depth of %i instead of %i.", bpp, screenDepth);
			info("You will experience graphics glitches!");
			SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
			SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
			SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
			SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
			SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
			break;
		case 8:
			debug(LOG_FATAL, "You don't want to play Warzone with a bit depth of %i, do you?", bpp);
			exit(1);
			break;
		default:
			debug(LOG_FATAL, "Unsupported bit depth: %i", bpp);
			exit(1);
			break;
	}

	screen = SDL_SetVideoMode(width, height, bpp, video_flags);
	if (!screen)
	{
		debug(LOG_ERROR, "SDL_SetVideoMode failed (%s).", SDL_GetError());
		return false;
	}
	int value = 0;
	if (SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &value) == -1 || value == 0)
	{
		debug( LOG_FATAL, "OpenGL initialization did not give double buffering!" );
		debug( LOG_FATAL, "Double buffering is required for this game!");
		exit(1);
	}

	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0, width, height, 0, 1, -1);

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glCullFace(GL_FRONT);
	glEnable(GL_CULL_FACE);

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	//END **** Was in old screenInitialise. ****

	init_scrap();
	return true;
}

/*!
 * Activation (focus change) eventhandler
 */
static void handleActiveEvent(SDL_ActiveEvent * activeEvent)
{
	switch (activeEvent->state)
	{
		case SDL_APPMOUSEFOCUS:
			mouseInWindow = activeEvent->gain;
			break;
		default:
			break;
	}
}

// Actual mainloop
void wzMain3()
{
	SDL_Event event;

	while (true)
	{
		/* Deal with any windows messages */
		while (SDL_PollEvent(&event))
		{
			switch (event.type)
			{
			case SDL_KEYUP:
			case SDL_KEYDOWN:
				inputHandleKeyEvent(&event.key);
				break;
			case SDL_MOUSEBUTTONUP:
			case SDL_MOUSEBUTTONDOWN:
				inputHandleMouseButtonEvent(&event.button);
				break;
			case SDL_MOUSEMOTION:
				inputHandleMouseMotionEvent(&event.motion);
				break;
			case SDL_ACTIVEEVENT:
				handleActiveEvent(&event.active);
				break;
			case SDL_QUIT:
				//saveConfig();
				return;
			default:
				break;
			}
		}
		mainLoop();
		inputNewFrame();
	}
}

void wzShutdown()
{
	sdlFreeCursors();
	SDL_Quit();
	delete appPtr;
	appPtr = NULL;
}
