#include "shell/Shell.h"
#include "glxshell/GLXShell.h"
#include "glxshell/GLXTarget.h"

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "shell/ShellBatteryInfo.h"
#include "shell/ShellCallbacks.h"
#include "shell/ShellKeyCodes.h"
#include "shell/ShellThreads.h"
#include "utilities/UTFUtilities.h"

#include <GL/gl.h>
#define msleep(ms) usleep((ms) * 1000)

#define VSYNC_DEFAULT_WINDOW false
#define VSYNC_DEFAULT_FULLSCREEN true

static unsigned int timer1ID = UINT_MAX, timer2ID = UINT_MAX;
static bool syncFullscreen = VSYNC_DEFAULT_FULLSCREEN, syncWindow = VSYNC_DEFAULT_WINDOW;
static bool printMouseMoved = true;
static bool coalesceMouseEvents = false;
static bool allowQuit = true;
static bool sendActivationClicks = false;
static bool enableDisplaySleep = true;
static unsigned int unhideCursorThreshold;
static bool suppressHiddenMouseEvents = false;

static void registerShellCallbacks();
static void unregisterShellCallbacks();

static bool Target_draw(double referenceTime, double activeDrawDelta) {
	printf("Target_draw(%f, %f)\n", referenceTime, activeDrawDelta);
	glClearColor(0.0f, 0.25f, 0.5f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	return true;
}

static void Target_resized(unsigned int newWidth, unsigned int newHeight, double referenceTime) {
	printf("Target_resized(%d, %d, %f)\n", newWidth, newHeight, referenceTime);
	glViewport(0, 0, newWidth, newHeight);
}

static const char * keyboardConstantString(unsigned int keyCode) {
	switch (keyCode) {
#define KEY_CODE_PAIR(shell_key_code, cocoa_key_code) case shell_key_code: return #shell_key_code;
#include "glxshell/GLXShell_keyCodes.h"
#undef KEY_CODE_PAIR
	}
	return "Unknown key code!";
}

static void timerCallback(unsigned int timerID, void * context) {
	printf("timerCallback(timerID, \"%s\")\n", (char *) context);
	if (timerID == timer2ID) {
		timer2ID = UINT_MAX;
	}
}

static void mainThreadFunction(void * context) {
	printf("Main thread function invoked on thread %p at %f\n", Shell_getCurrentThread(), Shell_getCurrentTime());
}

static int threadFunc1(void * context) {
	printf("Secondary thread 1 %p begin\n", Shell_getCurrentThread());
	Shell_lockMutex(context);
	msleep(1000);
	Shell_unlockMutex(context);
	printf("Secondary thread 1 %p end\n", Shell_getCurrentThread());
	return 0;
}

static int threadFunc2(void * context) {
	printf("Secondary thread 2 %p begin\n", Shell_getCurrentThread());
	msleep(1000);
	Shell_postSemaphore(context);
	printf("Secondary thread 2 %p end\n", Shell_getCurrentThread());
	return 0;
}

static int threadFunc3(void * context) {
	printf("Secondary thread 3 %p begin\n", Shell_getCurrentThread());
	msleep(1000);
	printf("Secondary thread 3 %p exit\n", Shell_getCurrentThread());
	Shell_exitThread(2);
	printf("Secondary thread 3 %p end (bad!)\n", Shell_getCurrentThread());
	return 0;
}

static int threadFunc4(void * context) {
	msleep(500);
	printf("Calling Shell_executeOnMainThread() called from %p (secondary) at %f\n", Shell_getCurrentThread(), Shell_getCurrentTime());
	Shell_executeOnMainThread(mainThreadFunction, NULL);
	return 0;
}

static void restoreCallbacksTimer(unsigned int timerID, void * context) {
	registerShellCallbacks();
	printf("Restored event callbacks\n");
}

static bool isModifierKeyCode(unsigned int keyCode) {
	switch (keyCode) {
		case KEY_CODE_LEFT_SHIFT:
		case KEY_CODE_LEFT_CONTROL:
		case KEY_CODE_LEFT_ALT:
		case KEY_CODE_LEFT_GUI:
		case KEY_CODE_RIGHT_SHIFT:
		case KEY_CODE_RIGHT_CONTROL:
		case KEY_CODE_RIGHT_ALT:
		case KEY_CODE_RIGHT_GUI:
		case KEY_CODE_CAPS_LOCK:
			return true;
	}
	return false;
}

static void Target_keyDown(unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, double referenceTime) {
	static bool waitingForKeyLabel;
	if (waitingForKeyLabel && !isModifierKeyCode(keyCode)) {
		if (!isRepeat) {
			unsigned int utf32Character = Shell_getKeyLabel(keyCode, modifiers);
			uint8_t utf8String[UTF8_SEQUENCE_MAX + 1];
			size_t utf8Length = utf32CodepointToUTF8(utf8String, utf32Character, NULL);
			utf8String[utf8Length] = 0;
			printf("Shell_getKeyLabel(%s, 0x%X): 0x%X (%s)\n", keyboardConstantString(keyCode), modifiers, utf32Character, utf8String);
			waitingForKeyLabel = false;
		}
		return;
	}
	printf("Target_keyDown(%u, %u, 0x%X, %s, %f) (%s)\n", charCode, keyCode, modifiers, isRepeat ? "true" : "false", referenceTime, keyboardConstantString(keyCode));
	if (keyCode == KEY_CODE_Q) {
		if (modifiers & MODIFIER_CONTROL_BIT) {
			exit(EXIT_SUCCESS);
		}
		ShellThread thread;
		ShellMutex mutex;
		ShellSemaphore semaphore;
		bool success;
		int status;
		
		printf("Current thread: %p\n\n", Shell_getCurrentThread());
		
		mutex = Shell_createMutex();
		printf("Created mutex %p\n", mutex);
		success = Shell_tryLockMutex(mutex);
		printf("Lock acquired: %s (expected true)\n", success ? "true" : "false");
		success = Shell_tryLockMutex(mutex);
		printf("Lock acquired: %s (expected true)\n\n", success ? "true" : "false");
		Shell_unlockMutex(mutex);
		Shell_unlockMutex(mutex);
		
		thread = Shell_createThread(threadFunc1, mutex);
		Shell_detachThread(thread);
		printf("Created thread %p at %f\n", thread, Shell_getCurrentTime());
		msleep(500);
		Shell_lockMutex(mutex);
		printf("Acquired lock at %f\n\n", Shell_getCurrentTime());
		Shell_unlockMutex(mutex);
		
		semaphore = Shell_createSemaphore(0);
		printf("Try wait semaphore: %s (expected false)\n", Shell_tryWaitSemaphore(semaphore) ? "true" : "false");
		thread = Shell_createThread(threadFunc2, semaphore);
		Shell_detachThread(thread);
		printf("Created thread %p at %f\n", thread, Shell_getCurrentTime());
		printf("Try wait semaphore: %s (expected false)\n", Shell_tryWaitSemaphore(semaphore) ? "true" : "false");
		Shell_waitSemaphore(semaphore);
		printf("Acquired semaphore at %f\n\n", Shell_getCurrentTime());
		
		thread = Shell_createThread(threadFunc3, mutex);
		printf("Created thread %p at %f\n", thread, Shell_getCurrentTime());
		status = Shell_joinThread(thread);
		printf("Joined thread at %f, with status %d\n\n", Shell_getCurrentTime(), status);
		
		printf("Calling Shell_executeOnMainThread() called from %p (main) at %f\n", Shell_getCurrentThread(), Shell_getCurrentTime());
		Shell_executeOnMainThread(mainThreadFunction, NULL);
		thread = Shell_createThread(threadFunc4, NULL);
		printf("Created thread %p at %f\n", thread, Shell_getCurrentTime());
		
		Shell_disposeMutex(mutex);
		Shell_disposeSemaphore(semaphore);
		
	} else if (keyCode == KEY_CODE_T) {
		printf("Shell_getCurrentTime(): %f\n", Shell_getCurrentTime());
		
	} else if (keyCode == KEY_CODE_R) {
		printf("Shell_getResourcePath(): %s\n", Shell_getResourcePath());
		
	} else if (keyCode == KEY_CODE_Y) {
		printf("Shell_getSupportPath(NULL): %s\n", Shell_getSupportPath(NULL));
		printf("Shell_getSupportPath(\"glxshell\"): %s\n", Shell_getSupportPath("glxshell"));
		
	} else if (keyCode == KEY_CODE_U) {
		Shell_openURL("http://ludobloom.com/");
		
	} else if (keyCode == KEY_CODE_I) {
		sendActivationClicks = !sendActivationClicks;
		GLXShell_sendActivationClicks(sendActivationClicks);
		printf("GLXShell_sendActivationClicks(%s)\n", sendActivationClicks ? "true" : "false");
		
	} else if (keyCode == KEY_CODE_D) {
		Shell_redisplay();
		
	} else if (keyCode == KEY_CODE_F) {
		static bool documentEdited;
		documentEdited = !documentEdited;
		Shell_setWindowDocumentEdited(documentEdited);
		
	} else if (keyCode == KEY_CODE_E) {
		unsigned int displayIndex = Shell_getDisplayIndexFromWindow();
		printf("Shell_enterFullScreen(%u, %s): %s\n", displayIndex, (modifiers & MODIFIER_SHIFT_BIT) ? "true" : "false", Shell_enterFullScreen(displayIndex, (modifiers & MODIFIER_SHIFT_BIT) ? true : false) ? "true" : "false");
		
	} else if (keyCode == KEY_CODE_W) {
		Shell_exitFullScreen();
		
	} else if (keyCode == KEY_CODE_G) {
		printf("Shell_isFullScreen(): %s\n", Shell_isFullScreen() ? "true" : "false");
		
	} else if (keyCode == KEY_CODE_B) {
		printf("Shell_getBatteryState(): %d\n", Shell_getBatteryState());
		printf("Shell_getBatteryLevel(): %f\n", Shell_getBatteryLevel());
		
	} else if (keyCode == KEY_CODE_Z) {
		int x = 0, y = 0;
		unsigned int width = 0, height = 0;
		static unsigned int screenIndex;
		
		screenIndex %= Shell_getDisplayCount();
		Shell_getSafeWindowRect(screenIndex, &x, &y, &width, &height);
		printf("Shell_getSafeWindowRect(%u): %d, %d, %u, %u\n", screenIndex, x, y, width, height);
		printf("Shell_getDisplayRefreshRate(%u): %f\n", screenIndex, Shell_getDisplayRefreshRate(screenIndex));
		printf("Shell_getDisplayScaleFactor(%u): %f\n", screenIndex, Shell_getDisplayScaleFactor(screenIndex));
		printf("Shell_getDisplayDPI(%u): %f\n", screenIndex, Shell_getDisplayDPI(screenIndex));
		screenIndex++;
		
	} else if (keyCode == KEY_CODE_X) {
		int x = 0, y = 0;
		unsigned int width = 0, height = 0;
		static unsigned int screenIndex;
		
		screenIndex %= Shell_getDisplayCount();
		Shell_getDisplayBounds(screenIndex, &x, &y, &width, &height);
		printf("Shell_getDisplayBounds(%u): %d, %d, %u, %u\n", screenIndex, x, y, width, height);
		screenIndex++;
		
	} else if (keyCode == KEY_CODE_C && (modifiers & MODIFIER_CONTROL_BIT)) {
		printf("Shell_copyTextToClipboard(\"Pasteboard test text\"): %s\n", Shell_copyTextToClipboard("Pasteboard test text") ? "true" : "false");
		
	} else if (keyCode == KEY_CODE_V && (modifiers & MODIFIER_CONTROL_BIT)) {
		printf("Shell_getClipboardText(): \"%s\"\n", Shell_getClipboardText());
		
	} else if (keyCode == KEY_CODE_C) {
		printf("Shell_getDisplayCount(): %u\n", Shell_getDisplayCount());
		
	} else if (keyCode == KEY_CODE_V) {
		bool sync, fullscreen;
		
		fullscreen = Shell_isFullScreen();
		if (fullscreen) {
			syncFullscreen = !syncFullscreen;
			sync = syncFullscreen;
		} else {
			syncWindow = !syncWindow;
			sync = syncWindow;
		}
		Shell_setVSync(sync, fullscreen);
		printf("Shell_setVSync(%s, %s)\n", sync ? "true" : "false", fullscreen ? "true" : "false");
		
	} else if (keyCode == KEY_CODE_O) {
		const char * extensions1[] = {"png", "jpg", "jpeg"}, * extensions2[] = {"ogg", "wav"};
		struct ShellFileDialogFilter filters[] = {
			{"Image files", sizeof(extensions1) / sizeof(extensions1[0]), extensions1},
			{"Sound files", sizeof(extensions2) / sizeof(extensions2[0]), extensions2}
		};
		if (modifiers & MODIFIER_SHIFT_BIT) {
			unsigned int filePathCount = 0;
			const char ** filePaths = Shell_openFileDialogMultiple("Open multiple files", NULL, 0, NULL, &filePathCount, sizeof(filters) / sizeof(filters[0]), filters);
			if (filePaths != NULL) {
				printf("Shell_openFileDialogMultiple returned %u path%s:\n", filePathCount, filePathCount == 1 ? "" : "s");
				for (unsigned int fileIndex = 0; fileIndex < filePathCount; fileIndex++) {
					printf("  \"%s\"\n", filePaths[fileIndex]);
				}
			} else {
				printf("Shell_openFileDialogMultiple returned NULL\n");
			}
			
		} else if (modifiers & MODIFIER_CONTROL_BIT) {
			const char * filePath = Shell_chooseFolderDialog("Choose folder", NULL);
			if (filePath != NULL) {
				printf("Shell_chooseFolderDialog returned path \"%s\"\n", filePath);
			} else {
				printf("Shell_chooseFolderDialog returned NULL\n");
			}
			
		} else {
			const char * filePath = Shell_openFileDialog("Open file", NULL, NULL, sizeof(filters) / sizeof(filters[0]), filters);
			if (filePath != NULL) {
				printf("Shell_openFileDialog returned path \"%s\"\n", filePath);
			} else {
				printf("Shell_openFileDialog returned NULL\n");
			}
		}
		
	} else if (keyCode == KEY_CODE_P) {
		const char * extensions1[] = {"png", "jpg", "jpeg"}, * extensions2[] = {"ogg", "wav"};
		struct ShellFileDialogFilter filters[] = {
			{"Image files", sizeof(extensions1) / sizeof(extensions1[0]), extensions1},
			{"Sound files", sizeof(extensions2) / sizeof(extensions2[0]), extensions2}
		};
		const char * filePath = Shell_saveFileDialog("Save as...", NULL, "test", sizeof(filters) / sizeof(filters[0]), filters);
		if (filePath != NULL) {
			printf("Shell_saveFileDialog returned path \"%s\"\n", filePath);
		} else {
			printf("Shell_saveFileDialog returned NULL\n");
		}
		
	} else if (keyCode == KEY_CODE_COMMA) {
		if (timer1ID == UINT_MAX) {
			unsigned int timerFlags = TIMER_FLAG_REPEAT;
			if (modifiers & MODIFIER_SHIFT_BIT) {
				timerFlags |= TIMER_FLAG_RUN_IN_BACKGROUND;
			}
			if (modifiers & MODIFIER_ALT_BIT) {
				timerFlags |= TIMER_FLAG_RUN_DURING_FILE_DIALOGS;
			}
			timer1ID = Shell_setTimer(1.0, timerFlags, timerCallback, "Timer 1 context");
			printf("Shell_setTimer(1.0, 0x%X, %p, \"Timer 1 context\"): %u\n", timerFlags, timerCallback, timer1ID);
		} else {
			printf("Shell_cancelTimer(%u)\n", timer1ID);
			Shell_cancelTimer(timer1ID);
			timer1ID = UINT_MAX;
		}
		
	} else if (keyCode == KEY_CODE_PERIOD) {
		if (timer2ID == UINT_MAX) {
			timer2ID = Shell_setTimer(2.0, 0, timerCallback, "Timer 2 context");
			printf("Shell_setTimer(2.0, 0, %p, \"Timer 2 context\"): %u\n", timerCallback, timer2ID);
		} else {
			printf("Shell_cancelTimer(%u)\n", timer2ID);
			Shell_cancelTimer(timer2ID);
			timer2ID = UINT_MAX;
		}
		
	} else if (keyCode == KEY_CODE_SLASH) {
		float x, y;
		Shell_getMousePosition(&x, &y);
		printf("Shell_getMousePosition(): %f, %f\n", x, y);
		
	} else if (keyCode == KEY_CODE_SEMICOLON) {
		Shell_setMouseDeltaMode(!Shell_getMouseDeltaMode());
		printf("Shell_setMouseDeltaMode(%s)\n", Shell_getMouseDeltaMode() ? "true" : "false");
		
	} else if (keyCode == KEY_CODE_OPEN_BRACKET) {
		Shell_setMousePosition(400, 300, modifiers & MODIFIER_SHIFT_BIT);
		printf("Shell_setMousePosition(400, 300, %s)\n", (modifiers & MODIFIER_SHIFT_BIT) ? "true" : "false");
		
	} else if (keyCode == KEY_CODE_H) {
		Shell_setCursorVisible(false);
		
	} else if (keyCode == KEY_CODE_J) {
		if (modifiers & MODIFIER_SHIFT_BIT) {
			Shell_moveResizeWindow(300, 200, 800, 600);
		} else {
			Shell_moveWindow(300, 200);
		}
		
	} else if (keyCode == KEY_CODE_K) {
		Shell_resizeWindow(800, 600);
		
	} else if (keyCode == KEY_CODE_S) {
		Shell_setCursorVisible(true);
		
	} else if (keyCode == KEY_CODE_M) {
		if (modifiers & MODIFIER_SHIFT_BIT) {
			unhideCursorThreshold = (unhideCursorThreshold + 1) % 3;
			suppressHiddenMouseEvents = !suppressHiddenMouseEvents;
			printf("Shell_setUnhideCursorMovementThreshold(%f, %s)\n", (float) unhideCursorThreshold, suppressHiddenMouseEvents ? "true" : "false");
			Shell_setUnhideCursorMovementThreshold(unhideCursorThreshold, suppressHiddenMouseEvents);
		} else {
			printf("Shell_hideCursorUntilMouseMoves()\n");
			Shell_hideCursorUntilMouseMoves();
		}
		
	} else if (keyCode == KEY_CODE_N) {
		if (modifiers & MODIFIER_SHIFT_BIT) {
			coalesceMouseEvents = !coalesceMouseEvents;
			GLXShell_coalesceMouseMotionEvents(coalesceMouseEvents);
			printf("Mouse event coalesce %s\n", coalesceMouseEvents ? "enabled" : "disabled");
		} else {
			printMouseMoved = !printMouseMoved;
			printf("Mouse move messages %s\n", printMouseMoved ? "enabled" : "disabled");
		}
		
	} else if (keyCode == KEY_CODE_BACKSPACE) {
		unregisterShellCallbacks();
		printf("Removed all event callbacks for 5 seconds\n");
		Shell_setTimer(5.0, 0, restoreCallbacksTimer, NULL);
		
	} else if (keyCode == KEY_CODE_BACKSLASH) {
		allowQuit = !allowQuit;
		printf("Quitting %s\n", allowQuit ? "enabled" : "disabled");
		
	} else if (keyCode == KEY_CODE_SINGLE_QUOTE) {
		enableDisplaySleep = !enableDisplaySleep;
		Shell_setDisplaySleepEnabled(enableDisplaySleep);
		printf("Display sleep %s\n", enableDisplaySleep ? "enabled" : "disabled");
		
	} else if (keyCode == KEY_CODE_SPACE) {
		printf("Shell_systemBeep()\n");
		Shell_systemBeep();
		
	} else if (keyCode == KEY_CODE_L) {
		printf("Shell_getModifierKeys: 0x%X\n", Shell_getModifierKeys());
		
	} else if (keyCode == KEY_CODE_TAB) {
		if (modifiers & MODIFIER_SHIFT_BIT) {
			printf("Shell_getKeyboardLayoutName(): \"%s\"\n", Shell_getKeyboardLayoutName());
		} else {
			printf("Printing next key label\n");
			waitingForKeyLabel = true;
		}
		
	} else if (keyCode == KEY_CODE_0) {
		Shell_setCursor(ShellCursor_arrow);
		
	} else if (keyCode == KEY_CODE_1) {
		Shell_setCursor(ShellCursor_iBeam);
		
	} else if (keyCode == KEY_CODE_2) {
		Shell_setCursor(ShellCursor_crosshair);
		
	} else if (keyCode == KEY_CODE_3) {
		Shell_setCursor(ShellCursor_hand);
		
	} else if (keyCode == KEY_CODE_4) {
		Shell_setCursor(ShellCursor_wait);
		
	} else if (keyCode == KEY_CODE_BACKTICK) {
		static ShellCursorID customCursorID;
		if (customCursorID == 0) {
#define _ 0x00,0x00,0x00,0x00
#define B 0x00,0x00,0x00,0xFF
#define W 0xFF,0xFF,0xFF,0xFF
#define R 0xFF,0x00,0x00,0xFF
#define b 0x00,0x00,0x00,0x3F
#define w 0xFF,0xFF,0xFF,0x3F
			unsigned char cursor1x[] = {
				_,_,_,_,_,_,_,W,_,_,_,_,_,_,_,
				_,_,_,_,_,_,W,R,W,_,_,_,_,_,_,
				_,_,_,_,_,W,R,W,B,W,_,_,_,_,_,
				_,_,_,_,W,R,W,w,W,B,W,_,_,_,_,
				_,_,_,W,R,W,_,w,_,W,B,W,_,_,_,
				_,_,W,R,W,_,_,w,_,_,W,B,W,_,_,
				_,W,R,W,_,_,_,w,_,_,_,W,B,W,_,
				W,R,W,w,w,w,w,b,w,w,w,w,W,B,W,
				_,W,B,W,_,_,_,b,_,_,_,W,B,W,_,
				_,_,W,B,W,_,_,w,_,_,W,B,W,_,_,
				_,_,_,W,B,W,_,w,_,W,B,W,_,_,_,
				_,_,_,_,W,B,W,w,W,B,W,_,_,_,_,
				_,_,_,_,_,W,B,W,B,W,_,_,_,_,_,
				_,_,_,_,_,_,W,B,W,_,_,_,_,_,_,
				_,_,_,_,_,_,_,W,_,_,_,_,_,_,_
			};
			unsigned char cursor2x[] = {
				_,_,_,_,_,_,_,_,_,_,_,_,_,_,W,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,_,_,W,R,W,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,_,W,R,R,B,W,_,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,W,R,R,R,B,B,W,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,W,R,R,R,W,B,B,B,W,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,W,R,R,R,W,w,W,B,B,B,W,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,W,R,R,R,W,_,w,_,W,B,B,B,W,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,W,R,R,R,W,_,_,w,_,_,W,B,B,B,W,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,W,R,R,R,W,_,_,_,w,_,_,_,W,B,B,B,W,_,_,_,_,_,_,_,
				_,_,_,_,_,W,R,R,R,W,_,_,_,_,w,_,_,_,_,W,B,B,B,W,_,_,_,_,_,_,
				_,_,_,_,W,R,R,R,W,_,_,_,_,_,w,_,_,_,_,_,W,B,B,B,W,_,_,_,_,_,
				_,_,_,W,R,R,R,W,_,_,_,_,_,_,w,_,_,_,_,_,_,W,B,B,B,W,_,_,_,_,
				_,_,W,R,R,R,W,_,_,_,_,_,_,_,w,_,_,_,_,_,_,_,W,B,B,B,W,_,_,_,
				_,W,R,R,R,W,_,_,_,_,_,_,_,w,b,w,_,_,_,_,_,_,_,W,B,B,B,W,_,_,
				W,R,R,R,W,w,w,w,w,w,w,w,w,b,b,b,w,w,w,w,w,w,w,w,W,B,B,B,W,_,
				_,W,B,B,B,W,_,_,_,_,_,_,_,w,b,w,_,_,_,_,_,_,_,W,B,B,B,W,_,_,
				_,_,W,B,B,B,W,_,_,_,_,_,_,_,w,_,_,_,_,_,_,_,W,B,B,B,W,_,_,_,
				_,_,_,W,B,B,B,W,_,_,_,_,_,_,w,_,_,_,_,_,_,W,B,B,B,W,_,_,_,_,
				_,_,_,_,W,B,B,B,W,_,_,_,_,_,w,_,_,_,_,_,W,B,B,B,W,_,_,_,_,_,
				_,_,_,_,_,W,B,B,B,W,_,_,_,_,w,_,_,_,_,W,B,B,B,W,_,_,_,_,_,_,
				_,_,_,_,_,_,W,B,B,B,W,_,_,_,w,_,_,_,W,B,B,B,W,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,W,B,B,B,W,_,_,w,_,_,W,B,B,B,W,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,W,B,B,B,W,_,w,_,W,B,B,B,W,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,W,B,B,B,W,w,W,B,B,B,W,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,W,B,B,B,W,B,B,B,W,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,W,B,B,B,B,B,W,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,_,W,B,B,B,W,_,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,_,_,W,B,W,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,_,_,_,W,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
				_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_
			};
#undef _
#undef B
#undef W
			struct ShellCustomCursorImage images[2] = {
				{15, 15, cursor1x, 1.0f},
				{30, 30, cursor2x, 2.0f}
			};
			customCursorID = Shell_registerCustomCursor(sizeof(images) / sizeof(images[0]), images, 7, 7);
		}
		Shell_setCursor(customCursorID);
	}
}

static void Target_keyUp(unsigned int keyCode, unsigned int modifiers, double referenceTime) {
	printf("Target_keyUp(%u, 0x%X, %f) (%s)\n", keyCode, modifiers, referenceTime, keyboardConstantString(keyCode));
}

static void Target_keyModifiersChanged(unsigned int modifiers, unsigned int lastModifiers, double referenceTime) {
	printf("Target_keyModifiersChanged(0x%X, 0x%X, %f)\n", modifiers, lastModifiers, referenceTime);
}

static void Target_keyboardLayoutChanged(double referenceTime) {
	printf("Target_keyboardLayoutChanged(%f)\n", referenceTime);
}

static void Target_mouseDown(unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime) {
	printf("Target_mouseDown(%d, 0x%X, %f, %f, 0x%X, %f)\n", buttonNumber, buttonMask, x, y, modifiers, referenceTime);
}

static void Target_mouseUp(unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime) {
	printf("Target_mouseUp(%d, 0x%X, %f, %f, 0x%X, %f)\n", buttonNumber, buttonMask, x, y, modifiers, referenceTime);
}

static void Target_mouseMoved(float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime) {
	if (printMouseMoved) {
		printf("Target_mouseMoved(%f, %f, %f, %f, 0x%X, %f)\n", x, y, deltaX, deltaY, modifiers, referenceTime);
	}
}

static void Target_mouseDragged(unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime) {
	printf("Target_mouseDragged(0x%X, %f, %f, %f, %f, 0x%X, %f)\n", buttonMask, x, y, deltaX, deltaY, modifiers, referenceTime);
}

static void Target_mouseLeave(unsigned int modifiers, double referenceTime) {
	printf("Target_mouseLeave(0x%X, %f)\n", modifiers, referenceTime);
}

static void Target_cursorUnhidden(bool fromMouseMovement, double referenceTime) {
	printf("Target_cursorUnhidden(%s, %f)\n", fromMouseMovement ? "true" : "false", referenceTime);
}

static void Target_scrollWheel(float x, float y, int deltaX, int deltaY, unsigned int buttonMask, unsigned int modifiers, double referenceTime) {
	printf("Target_scrollWheel(%f, %f, %d, %d, 0x%X, 0x%X, %f)\n", x, y, deltaX, deltaY, buttonMask, modifiers, referenceTime);
}

static void Target_backgrounded(double referenceTime) {
	printf("Target_backgrounded(%f)\n", referenceTime);
}

static void Target_foregrounded(double referenceTime) {
	printf("Target_foregrounded(%f)\n", referenceTime);
}

static bool Target_confirmQuit(double referenceTime) {
	printf("Target_confirmQuit(%f) (returning %s)\n", referenceTime, allowQuit ? "true" : "false");
	return allowQuit;
}

static void registerShellCallbacks() {
	Shell_drawFunc(Target_draw);
	Shell_resizeFunc(Target_resized);
	Shell_keyDownFunc(Target_keyDown);
	Shell_keyUpFunc(Target_keyUp);
	Shell_keyModifiersChangedFunc(Target_keyModifiersChanged);
	Shell_keyboardLayoutChangedFunc(Target_keyboardLayoutChanged);
	Shell_mouseDownFunc(Target_mouseDown);
	Shell_mouseUpFunc(Target_mouseUp);
	Shell_mouseMovedFunc(Target_mouseMoved);
	Shell_mouseDraggedFunc(Target_mouseDragged);
	Shell_mouseLeaveFunc(Target_mouseLeave);
	Shell_cursorUnhiddenFunc(Target_cursorUnhidden);
	Shell_scrollWheelFunc(Target_scrollWheel);
	Shell_backgroundedFunc(Target_backgrounded);
	Shell_foregroundedFunc(Target_foregrounded);
	Shell_confirmQuitFunc(Target_confirmQuit);
}

static void unregisterShellCallbacks() {
	Shell_drawFunc(NULL);
	Shell_resizeFunc(NULL);
	Shell_keyDownFunc(NULL);
	Shell_keyUpFunc(NULL);
	Shell_keyModifiersChangedFunc(NULL);
	Shell_mouseDownFunc(NULL);
	Shell_mouseUpFunc(NULL);
	Shell_mouseMovedFunc(NULL);
	Shell_mouseDraggedFunc(NULL);
	Shell_mouseLeaveFunc(NULL);
	Shell_cursorUnhiddenFunc(NULL);
	Shell_scrollWheelFunc(NULL);
	Shell_backgroundedFunc(NULL);
	Shell_foregroundedFunc(NULL);
	Shell_confirmQuitFunc(NULL);
}

void GLXTarget_configure(int argc, const char ** argv, struct GLXShellConfiguration * configuration) {
	int argIndex;
	char workingDir[PATH_MAX];
	
	printf("GLXTarget_configure(%d", argc);
	for (argIndex = 0; argIndex < argc; argIndex++) {
		printf(", \"%s\"", argv[argIndex]);
	}
	printf(", %p)\n", configuration);
	
	printf("configuration->windowX: %d\n", configuration->windowX);
	printf("configuration->windowY: %d\n", configuration->windowY);
	printf("configuration->windowWidth: %d\n", configuration->windowWidth);
	printf("configuration->windowHeight: %d\n", configuration->windowHeight);
	printf("configuration->windowTitle: %s\n", configuration->windowTitle);
	printf("configuration->applicationName: %s\n", configuration->applicationName);
	printf("configuration->icon.data: %p\n", configuration->icon.data);
	printf("configuration->icon.size: %u\n", configuration->icon.size);
	printf("configuration->displayMode.doubleBuffer: %s\n", configuration->displayMode.doubleBuffer ? "true" : "false");
	printf("configuration->displayMode.depthBuffer: %s\n", configuration->displayMode.depthBuffer ? "true" : "false");
	printf("configuration->displayMode.depthSize: %u\n", configuration->displayMode.depthSize);
	printf("configuration->displayMode.stencilBuffer: %s\n", configuration->displayMode.stencilBuffer ? "true" : "false");
	printf("configuration->displayMode.stencilSize: %u\n", configuration->displayMode.stencilSize);
	printf("configuration->displayMode.multisample: %s\n", configuration->displayMode.multisample ? "true" : "false");
	
	configuration->windowTitle = "GLXShell Test Harness";
	printf("configuration->windowTitle = \"%s\"\n", configuration->windowTitle);
	
	configuration->applicationName = STEM_HUMAN_READABLE_TARGET_NAME;
	printf("configuration->applicationName = \"%s\"\n", configuration->applicationName);
	
#include "IconData_glxshell.h"
	configuration->icon.data = STATIC_iconData_glxshell;
	configuration->icon.size = sizeof(STATIC_iconData_glxshell) / sizeof(STATIC_iconData_glxshell[0]);
	printf("configuration->icon.data = %p\n", configuration->icon.data);
	printf("configuration->icon.data = %u\n", configuration->icon.size);
	
	for (argIndex = 0; argIndex < argc; argIndex++) {
		if (!strcmp(argv[argIndex], "--windowRect") && argIndex < argc - 4) {
			sscanf(argv[argIndex + 1], "%d", &configuration->windowX);
			sscanf(argv[argIndex + 2], "%d", &configuration->windowY);
			sscanf(argv[argIndex + 3], "%d", &configuration->windowWidth);
			sscanf(argv[argIndex + 4], "%d", &configuration->windowHeight);
			printf("configuration->windowX = %d\n", configuration->windowX);
			printf("configuration->windowY = %d\n", configuration->windowY);
			printf("configuration->windowWidth = %d\n", configuration->windowWidth);
			printf("configuration->windowHeight = %d\n", configuration->windowHeight);
			argIndex += 4;
		}
	}
	
	printf("getcwd(): %s\n", getcwd(workingDir, PATH_MAX));
	
	registerShellCallbacks();
}

void Target_init() {
	printf("Target_init()\n");
	Shell_mainLoop();
}
