//-----------------------------------------------------------------------------------------------
// Application_Win32.cpp
//
#include "GypsyPrecompiledHeader.hpp"

//-----------------------------------------------------------------------------------------------
#include "Engine/Core/Application_Win32.hpp"
#include "Engine/Core/GameInterface.hpp"
#include "Engine/Core/SystemEvent.hpp"


//-----------------------------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR pCmdLine, int nCmdShow )
{
	UNUSED( nCmdShow );

	try
	{
		new Application_Win32( hInstance ); // self-registering as "theApplication"

		std::string commandLineString( pCmdLine );
		CommandLineCommands commandLineCommands( commandLineString );
		theApplication->Main( commandLineCommands );
		delete theApplication;
		theApplication = nullptr;
	}

	catch( std::exception& exception )
	{
		FATAL_ERROR( exception.what() );
	}
	catch( ... )
	{
		FATAL_ERROR( "Unknown exception!" );
	}
}


//-----------------------------------------------------------------------------------------------
KeyButton GetGypsyKeyButtonForWindowsVKCode( unsigned char win32_vk_code, long lParam )
{
	// Many Gypsy KeyButton codes are numerically identical to windows VK_ codes
	if( (win32_vk_code >= 'A' && win32_vk_code <= 'Z') || (win32_vk_code >= '0' && win32_vk_code <= '9') )
	{
		return (KeyButton) win32_vk_code;
	}
	else if( win32_vk_code == VK_SHIFT )
	{
		bool isRightShift = ((lParam & BIT( 20 )) != 0);
		return isRightShift ? BUTTON_KEYBOARD_RIGHT_SHIFT : BUTTON_KEYBOARD_LEFT_SHIFT;
	}
	else if( win32_vk_code == VK_CONTROL )
	{
		bool isRightCtrl = ((lParam & BIT( 24 )) != 0);
		return isRightCtrl ? BUTTON_KEYBOARD_RIGHT_CTRL : BUTTON_KEYBOARD_LEFT_CTRL;
	}

	switch( win32_vk_code )
	{
		case VK_BACK:		return BUTTON_KEYBOARD_BACKSPACE;
		case VK_DELETE:		return BUTTON_KEYBOARD_DELETE;
		case VK_RETURN:		return BUTTON_KEYBOARD_ENTER;
// 		case VK_SHIFT:		return BUTTON_KEYBOARD_SHIFT;
// 		case VK_CONTROL:	return BUTTON_KEYBOARD_CONTROL;
		case VK_ESCAPE:		return BUTTON_KEYBOARD_ESCAPE;
		case VK_SPACE:		return BUTTON_KEYBOARD_SPACE;
		case VK_OEM_PLUS:	return BUTTON_KEYBOARD_PLUS;
		case VK_OEM_COMMA:	return BUTTON_KEYBOARD_COMMA;
		case VK_OEM_MINUS:	return BUTTON_KEYBOARD_MINUS;
		case VK_OEM_PERIOD:	return BUTTON_KEYBOARD_PERIOD;
		case VK_OEM_2:		return BUTTON_KEYBOARD_SLASH;
		case VK_OEM_1:		return BUTTON_KEYBOARD_SEMICOLON;
		case VK_LEFT:		return BUTTON_KEYBOARD_LEFTARROW;
		case VK_UP:			return BUTTON_KEYBOARD_UPARROW;
		case VK_RIGHT:		return BUTTON_KEYBOARD_RIGHTARROW;
		case VK_DOWN:		return BUTTON_KEYBOARD_DOWNARROW;
		case VK_HOME:		return BUTTON_KEYBOARD_HOME;
		case VK_PRIOR:		return BUTTON_KEYBOARD_PGUP;
		case VK_NEXT:		return BUTTON_KEYBOARD_PGDN;
		case VK_END:		return BUTTON_KEYBOARD_END;
		case VK_OEM_4:		return BUTTON_KEYBOARD_LEFTBRACKET;
		case VK_OEM_5:		return BUTTON_KEYBOARD_BACKSLASH;
		case VK_OEM_6:		return BUTTON_KEYBOARD_RIGHTBRACKET;
		case VK_CLEAR:		return BUTTON_KEYBOARD_NUMPAD5; // FIXME: VK_CLEAR is what a typical keyboard numpad returns for the '5' key when numlock is OFF.  What should we convert it to?
		case VK_NUMPAD0:	return BUTTON_KEYBOARD_NUMPAD0;
		case VK_NUMPAD1:	return BUTTON_KEYBOARD_NUMPAD1;
		case VK_NUMPAD2:	return BUTTON_KEYBOARD_NUMPAD2;
		case VK_NUMPAD3:	return BUTTON_KEYBOARD_NUMPAD3;
		case VK_NUMPAD4:	return BUTTON_KEYBOARD_NUMPAD4;
		case VK_NUMPAD5:	return BUTTON_KEYBOARD_NUMPAD5;
		case VK_NUMPAD6:	return BUTTON_KEYBOARD_NUMPAD6;
		case VK_NUMPAD7:	return BUTTON_KEYBOARD_NUMPAD7;
		case VK_NUMPAD8:	return BUTTON_KEYBOARD_NUMPAD8;
		case VK_NUMPAD9:	return BUTTON_KEYBOARD_NUMPAD9;
		case VK_F1:			return BUTTON_KEYBOARD_F1;
		case VK_F2:			return BUTTON_KEYBOARD_F2;
		case VK_F3:			return BUTTON_KEYBOARD_F3;
		case VK_F4:			return BUTTON_KEYBOARD_F4;
		case VK_F5:			return BUTTON_KEYBOARD_F5;
		case VK_F6:			return BUTTON_KEYBOARD_F6;
		case VK_F7:			return BUTTON_KEYBOARD_F7;
		case VK_F8:			return BUTTON_KEYBOARD_F8;
		case VK_F9:			return BUTTON_KEYBOARD_F9;
		case VK_F10:		return BUTTON_KEYBOARD_F10;
		case VK_F11:		return BUTTON_KEYBOARD_F11;
		case VK_F12:		return BUTTON_KEYBOARD_F12;
		case VK_F13:		return BUTTON_KEYBOARD_F13;
		case VK_F14:		return BUTTON_KEYBOARD_F14;
		case VK_F15:		return BUTTON_KEYBOARD_F15;
		case VK_F16:		return BUTTON_KEYBOARD_F16;
		case VK_F17:		return BUTTON_KEYBOARD_F17;
		case VK_F18:		return BUTTON_KEYBOARD_F18;
		case VK_F19:		return BUTTON_KEYBOARD_F19;
		case VK_F20:		return BUTTON_KEYBOARD_F20;
		case VK_F21:		return BUTTON_KEYBOARD_F21;
		case VK_F22:		return BUTTON_KEYBOARD_F22;
		case VK_F23:		return BUTTON_KEYBOARD_F23;
		case VK_F24:		return BUTTON_KEYBOARD_F24;
		case VK_OEM_3:		return BUTTON_KEYBOARD_TILDE;
	}

	return BUTTON_NONE;
}


//-----------------------------------------------------------------------------------------------
const char* GetNameForWindowsMessage( UINT wmMessageCode )
{					
	switch( wmMessageCode )
	{
		case WM_NULL:							return "WM_NULL";
		case WM_CREATE:							return "WM_CREATE";
		case WM_DESTROY:						return "WM_DESTROY";
		case WM_MOVE:							return "WM_MOVE";
		case WM_SIZE:							return "WM_SIZE";
		case WM_ACTIVATE:						return "WM_ACTIVATE";
		case WM_SETFOCUS:						return "WM_SETFOCUS";
		case WM_KILLFOCUS:						return "WM_KILLFOCUS";
		case WM_ENABLE:							return "WM_ENABLE";
		case WM_SETREDRAW:						return "WM_SETREDRAW";
		case WM_SETTEXT:						return "WM_SETTEXT";
		case WM_GETTEXT:						return "WM_GETTEXT";
		case WM_GETTEXTLENGTH:					return "WM_GETTEXTLENGTH";
		case WM_PAINT:							return "WM_PAINT";
		case WM_CLOSE:							return "WM_CLOSE";
		case WM_QUERYENDSESSION:				return "WM_QUERYENDSESSION";
		case WM_QUERYOPEN:						return "WM_QUERYOPEN";
		case WM_ENDSESSION:						return "WM_ENDSESSION";
		case WM_QUIT:							return "WM_QUIT";
		case WM_ERASEBKGND:						return "WM_ERASEBKGND";
		case WM_SYSCOLORCHANGE:					return "WM_SYSCOLORCHANGE";
		case WM_SHOWWINDOW:						return "WM_SHOWWINDOW";
		case WM_WININICHANGE:					return "WM_WININICHANGE or WM_SETTINGCHANGE";
		case WM_DEVMODECHANGE:					return "WM_DEVMODECHANGE";
		case WM_ACTIVATEAPP:					return "WM_ACTIVATEAPP";
		case WM_FONTCHANGE:						return "WM_FONTCHANGE";
		case WM_TIMECHANGE:						return "WM_TIMECHANGE";
		case WM_CANCELMODE:						return "WM_CANCELMODE";
		case WM_SETCURSOR:						return "WM_SETCURSOR";
		case WM_MOUSEACTIVATE:					return "WM_MOUSEACTIVATE";
		case WM_CHILDACTIVATE:					return "WM_CHILDACTIVATE";
		case WM_QUEUESYNC:						return "WM_QUEUESYNC";
		case WM_GETMINMAXINFO:					return "WM_GETMINMAXINFO";
		case WM_PAINTICON:						return "WM_PAINTICON";
		case WM_ICONERASEBKGND:					return "WM_ICONERASEBKGND";
		case WM_NEXTDLGCTL:						return "WM_NEXTDLGCTL";
		case WM_SPOOLERSTATUS:					return "WM_SPOOLERSTATUS";
		case WM_DRAWITEM:						return "WM_DRAWITEM";
		case WM_MEASUREITEM:					return "WM_MEASUREITEM";
		case WM_DELETEITEM:						return "WM_DELETEITEM";
		case WM_VKEYTOITEM:						return "WM_VKEYTOITEM";
		case WM_CHARTOITEM:						return "WM_CHARTOITEM";
		case WM_SETFONT:						return "WM_SETFONT";
		case WM_GETFONT:						return "WM_GETFONT";
		case WM_SETHOTKEY:						return "WM_SETHOTKEY";
		case WM_GETHOTKEY:						return "WM_GETHOTKEY";
		case WM_QUERYDRAGICON:					return "WM_QUERYDRAGICON";
		case WM_COMPAREITEM:					return "WM_COMPAREITEM";
		case WM_GETOBJECT:						return "WM_GETOBJECT";
		case WM_COMPACTING:						return "WM_COMPACTING";
		case WM_COMMNOTIFY:						return "WM_COMMNOTIFY";
		case WM_WINDOWPOSCHANGING:				return "WM_WINDOWPOSCHANGING";
		case WM_WINDOWPOSCHANGED:				return "WM_WINDOWPOSCHANGED";
		case WM_POWER:							return "WM_POWER";
		case WM_COPYDATA:						return "WM_COPYDATA";
		case WM_CANCELJOURNAL:					return "WM_CANCELJOURNAL";
		case WM_NOTIFY:							return "WM_NOTIFY";
		case WM_INPUTLANGCHANGEREQUEST:			return "WM_INPUTLANGCHANGEREQUEST";
		case WM_INPUTLANGCHANGE:				return "WM_INPUTLANGCHANGE";
		case WM_TCARD:							return "WM_TCARD";
		case WM_HELP:							return "WM_HELP";
		case WM_USERCHANGED:					return "WM_USERCHANGED";
		case WM_NOTIFYFORMAT:					return "WM_NOTIFYFORMAT";
		case WM_CONTEXTMENU:					return "WM_CONTEXTMENU";
		case WM_STYLECHANGING:					return "WM_STYLECHANGING";
		case WM_STYLECHANGED:					return "WM_STYLECHANGED";
		case WM_DISPLAYCHANGE:					return "WM_DISPLAYCHANGE";
		case WM_GETICON:						return "WM_GETICON";
		case WM_SETICON:						return "WM_SETICON";
		case WM_NCCREATE:						return "WM_NCCREATE";
		case WM_NCDESTROY:						return "WM_NCDESTROY";
		case WM_NCCALCSIZE:						return "WM_NCCALCSIZE";
		case WM_NCHITTEST:						return "WM_NCHITTEST";
		case WM_NCPAINT:						return "WM_NCPAINT";
		case WM_NCACTIVATE:						return "WM_NCACTIVATE";
		case WM_GETDLGCODE:						return "WM_GETDLGCODE";
		case WM_SYNCPAINT:						return "WM_SYNCPAINT";
		case WM_NCMOUSEMOVE:					return "WM_NCMOUSEMOVE";
		case WM_NCLBUTTONDOWN:					return "WM_NCLBUTTONDOWN";
		case WM_NCLBUTTONUP:					return "WM_NCLBUTTONUP";
		case WM_NCLBUTTONDBLCLK:				return "WM_NCLBUTTONDBLCLK";
		case WM_NCRBUTTONDOWN:					return "WM_NCRBUTTONDOWN";
		case WM_NCRBUTTONUP:					return "WM_NCRBUTTONUP";
		case WM_NCRBUTTONDBLCLK:				return "WM_NCRBUTTONDBLCLK";
		case WM_NCMBUTTONDOWN:					return "WM_NCMBUTTONDOWN";
		case WM_NCMBUTTONUP:					return "WM_NCMBUTTONUP";
		case WM_NCMBUTTONDBLCLK:				return "WM_NCMBUTTONDBLCLK";
		case WM_NCXBUTTONDOWN:					return "WM_NCXBUTTONDOWN";
		case WM_NCXBUTTONUP:					return "WM_NCXBUTTONUP";
		case WM_NCXBUTTONDBLCLK:				return "WM_NCXBUTTONDBLCLK";
		case WM_INPUT_DEVICE_CHANGE:			return "WM_INPUT_DEVICE_CHANGE";
		case WM_INPUT:							return "WM_INPUT";
		case WM_KEYDOWN:						return "WM_KEYDOWN";
		case WM_KEYUP:							return "WM_KEYUP";
		case WM_CHAR:							return "WM_CHAR";
		case WM_DEADCHAR:						return "WM_DEADCHAR";
		case WM_SYSKEYDOWN:						return "WM_SYSKEYDOWN";
		case WM_SYSKEYUP:						return "WM_SYSKEYUP";
		case WM_SYSCHAR:						return "WM_SYSCHAR";
		case WM_SYSDEADCHAR:					return "WM_SYSDEADCHAR";
		case WM_UNICHAR:						return "WM_UNICHAR";
		case WM_IME_STARTCOMPOSITION:			return "WM_IME_STARTCOMPOSITION";
		case WM_IME_ENDCOMPOSITION:				return "WM_IME_ENDCOMPOSITION";
		case WM_IME_COMPOSITION:				return "WM_IME_COMPOSITION";
		case WM_INITDIALOG:						return "WM_INITDIALOG";
		case WM_COMMAND:						return "WM_COMMAND";
		case WM_SYSCOMMAND:						return "WM_SYSCOMMAND";
		case WM_TIMER:							return "WM_TIMER";
		case WM_HSCROLL:						return "WM_HSCROLL";
		case WM_VSCROLL:						return "WM_VSCROLL";
		case WM_INITMENU:						return "WM_INITMENU";
		case WM_INITMENUPOPUP:					return "WM_INITMENUPOPUP";
		case WM_GESTURE:						return "WM_GESTURE";
		case WM_GESTURENOTIFY:					return "WM_GESTURENOTIFY";
		case WM_MENUSELECT:						return "WM_MENUSELECT";
		case WM_MENUCHAR:						return "WM_MENUCHAR";
		case WM_ENTERIDLE:						return "WM_ENTERIDLE";
		case WM_MENURBUTTONUP:					return "WM_MENURBUTTONUP";
		case WM_MENUDRAG:						return "WM_MENUDRAG";
		case WM_MENUGETOBJECT:					return "WM_MENUGETOBJECT";
		case WM_UNINITMENUPOPUP:				return "WM_UNINITMENUPOPUP";
		case WM_MENUCOMMAND:					return "WM_MENUCOMMAND";
		case WM_CHANGEUISTATE:					return "WM_CHANGEUISTATE";
		case WM_UPDATEUISTATE:					return "WM_UPDATEUISTATE";
		case WM_QUERYUISTATE:					return "WM_QUERYUISTATE";
		case WM_CTLCOLORMSGBOX:					return "WM_CTLCOLORMSGBOX";
		case WM_CTLCOLOREDIT:					return "WM_CTLCOLOREDIT";
		case WM_CTLCOLORLISTBOX:				return "WM_CTLCOLORLISTBOX";
		case WM_CTLCOLORBTN:					return "WM_CTLCOLORBTN";
		case WM_CTLCOLORDLG:					return "WM_CTLCOLORDLG";
		case WM_CTLCOLORSCROLLBAR:				return "WM_CTLCOLORSCROLLBAR";
		case WM_CTLCOLORSTATIC:					return "WM_CTLCOLORSTATIC";
		case WM_MOUSEMOVE:						return "WM_MOUSEMOVE";
		case WM_LBUTTONDOWN:					return "WM_LBUTTONDOWN";
		case WM_LBUTTONUP:						return "WM_LBUTTONUP";
		case WM_LBUTTONDBLCLK:					return "WM_LBUTTONDBLCLK";
		case WM_RBUTTONDOWN:					return "WM_RBUTTONDOWN";
		case WM_RBUTTONUP:						return "WM_RBUTTONUP";
		case WM_RBUTTONDBLCLK:					return "WM_RBUTTONDBLCLK";
		case WM_MBUTTONDOWN:					return "WM_MBUTTONDOWN";
		case WM_MBUTTONUP:						return "WM_MBUTTONUP";
		case WM_MBUTTONDBLCLK:					return "WM_MBUTTONDBLCLK";
		case WM_MOUSEWHEEL:						return "WM_MOUSEWHEEL";
		case WM_XBUTTONDOWN:					return "WM_XBUTTONDOWN";
		case WM_XBUTTONUP:						return "WM_XBUTTONUP";
		case WM_XBUTTONDBLCLK:					return "WM_XBUTTONDBLCLK";
		case WM_MOUSEHWHEEL:					return "WM_MOUSEHWHEEL";
		case WM_PARENTNOTIFY:					return "WM_PARENTNOTIFY";
		case WM_ENTERMENULOOP:					return "WM_ENTERMENULOOP";
		case WM_EXITMENULOOP:					return "WM_EXITMENULOOP";
		case WM_NEXTMENU:						return "WM_NEXTMENU";
		case WM_SIZING:							return "WM_SIZING";
		case WM_CAPTURECHANGED:					return "WM_CAPTURECHANGED";
		case WM_MOVING:							return "WM_MOVING";
		case WM_POWERBROADCAST:					return "WM_POWERBROADCAST";
		case WM_DEVICECHANGE:					return "WM_DEVICECHANGE";
		case WM_MDICREATE:						return "WM_MDICREATE";
		case WM_MDIDESTROY:						return "WM_MDIDESTROY";
		case WM_MDIACTIVATE:					return "WM_MDIACTIVATE";
		case WM_MDIRESTORE:						return "WM_MDIRESTORE";
		case WM_MDINEXT:						return "WM_MDINEXT";
		case WM_MDIMAXIMIZE:					return "WM_MDIMAXIMIZE";
		case WM_MDITILE:						return "WM_MDITILE";
		case WM_MDICASCADE:						return "WM_MDICASCADE";
		case WM_MDIICONARRANGE:					return "WM_MDIICONARRANGE";
		case WM_MDIGETACTIVE:					return "WM_MDIGETACTIVE";
		case WM_MDISETMENU:						return "WM_MDISETMENU";
		case WM_ENTERSIZEMOVE:					return "WM_ENTERSIZEMOVE";
		case WM_EXITSIZEMOVE:					return "WM_EXITSIZEMOVE";
		case WM_DROPFILES:						return "WM_DROPFILES";
		case WM_MDIREFRESHMENU:					return "WM_MDIREFRESHMENU";
		case WM_TOUCH:							return "WM_TOUCH";
		case WM_IME_SETCONTEXT:					return "WM_IME_SETCONTEXT";
		case WM_IME_NOTIFY:						return "WM_IME_NOTIFY";
		case WM_IME_CONTROL:					return "WM_IME_CONTROL";
		case WM_IME_COMPOSITIONFULL:			return "WM_IME_COMPOSITIONFULL";
		case WM_IME_SELECT:						return "WM_IME_SELECT";
		case WM_IME_CHAR:						return "WM_IME_CHAR";
		case WM_IME_REQUEST:					return "WM_IME_REQUEST";
		case WM_IME_KEYDOWN:					return "WM_IME_KEYDOWN";
		case WM_IME_KEYUP:						return "WM_IME_KEYUP";
		case WM_MOUSEHOVER:						return "WM_MOUSEHOVER";
		case WM_MOUSELEAVE:						return "WM_MOUSELEAVE";
		case WM_NCMOUSEHOVER:					return "WM_NCMOUSEHOVER";
		case WM_NCMOUSELEAVE:					return "WM_NCMOUSELEAVE";
		case WM_WTSSESSION_CHANGE:				return "WM_WTSSESSION_CHANGE";
		case WM_TABLET_FIRST:					return "WM_TABLET_FIRST";
		case WM_TABLET_LAST:					return "WM_TABLET_LAST";
		case WM_CUT:							return "WM_CUT";
		case WM_COPY:							return "WM_COPY";
		case WM_PASTE:							return "WM_PASTE";
		case WM_CLEAR:							return "WM_CLEAR";
		case WM_UNDO:							return "WM_UNDO";
		case WM_RENDERFORMAT:					return "WM_RENDERFORMAT";
		case WM_RENDERALLFORMATS:				return "WM_RENDERALLFORMATS";
		case WM_DESTROYCLIPBOARD:				return "WM_DESTROYCLIPBOARD";
		case WM_DRAWCLIPBOARD:					return "WM_DRAWCLIPBOARD";
		case WM_PAINTCLIPBOARD:					return "WM_PAINTCLIPBOARD";
		case WM_VSCROLLCLIPBOARD:				return "WM_VSCROLLCLIPBOARD";
		case WM_SIZECLIPBOARD:					return "WM_SIZECLIPBOARD";
		case WM_ASKCBFORMATNAME:				return "WM_ASKCBFORMATNAME";
		case WM_CHANGECBCHAIN:					return "WM_CHANGECBCHAIN";
		case WM_HSCROLLCLIPBOARD:				return "WM_HSCROLLCLIPBOARD";
		case WM_QUERYNEWPALETTE:				return "WM_QUERYNEWPALETTE";
		case WM_PALETTEISCHANGING:				return "WM_PALETTEISCHANGING";
		case WM_PALETTECHANGED:					return "WM_PALETTECHANGED";
		case WM_HOTKEY:							return "WM_HOTKEY";
		case WM_PRINT:							return "WM_PRINT";
		case WM_PRINTCLIENT:					return "WM_PRINTCLIENT";
		case WM_APPCOMMAND:						return "WM_APPCOMMAND";
		case WM_THEMECHANGED:					return "WM_THEMECHANGED";
		case WM_CLIPBOARDUPDATE:				return "WM_CLIPBOARDUPDATE";
		case WM_DWMCOMPOSITIONCHANGED:			return "WM_DWMCOMPOSITIONCHANGED";
		case WM_DWMNCRENDERINGCHANGED:			return "WM_DWMNCRENDERINGCHANGED";
		case WM_DWMCOLORIZATIONCOLORCHANGED:	return "WM_DWMCOLORIZATIONCOLORCHANGED";
		case WM_DWMWINDOWMAXIMIZEDCHANGE:		return "WM_DWMWINDOWMAXIMIZEDCHANGE";
		case WM_DWMSENDICONICTHUMBNAIL:			return "WM_DWMSENDICONICTHUMBNAIL";
		case WM_DWMSENDICONICLIVEPREVIEWBITMAP:	return "WM_DWMSENDICONICLIVEPREVIEWBITMAP";
		case WM_GETTITLEBARINFOEX:				return "WM_GETTITLEBARINFOEX";
		case WM_HANDHELDFIRST:					return "WM_HANDHELDFIRST";
		case WM_HANDHELDLAST:					return "WM_HANDHELDLAST";
		case WM_AFXFIRST:						return "WM_AFXFIRST";
		case WM_AFXLAST:						return "WM_AFXLAST";
		case WM_PENWINFIRST:					return "WM_PENWINFIRST";
		case WM_PENWINLAST:						return "WM_PENWINLAST";
		case WM_APP:							return "WM_APP";
		case WM_USER:							return "WM_USER";
		default:								return "UNKNOWN WM_ WINDOWS MESSAGE!";
	}
}


//-----------------------------------------------------------------------------------------------
void ConvertWindowsMessageToGypsySystemEvent( UINT wmMessageCode, WPARAM wParam, LPARAM lParam, SystemEvent& out_systemEvent )
{
	static int pcIndex = GetProfileCategoryIndexForName( "Input.Msg.Convert", Rgba( 255, 255, 0 ) );
	ScopedProfileSection profileSection( pcIndex );

	UNUSED( lParam );
	out_systemEvent.m_eventType = EVENT_TYPE_UNRECOGNIZED;
	out_systemEvent.m_keyButtonCode = BUTTON_NONE;
	out_systemEvent.m_utf32TextCharacter = 0;

	if( wmMessageCode == WM_KEYDOWN )
	{
		bool wasKeyRepeat = (lParam & BIT( 30 )) != 0;
		out_systemEvent.m_eventType = wasKeyRepeat ? EVENT_TYPE_BUTTON_REPEATED : EVENT_TYPE_BUTTON_PRESSED;
		unsigned char win32_vk_code = (unsigned char) wParam;
		out_systemEvent.m_keyButtonCode = GetGypsyKeyButtonForWindowsVKCode( win32_vk_code, lParam );
	}
	else if( wmMessageCode == WM_KEYUP )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_RELEASED;
		unsigned char win32_vk_code = (unsigned char) wParam;
		out_systemEvent.m_keyButtonCode = GetGypsyKeyButtonForWindowsVKCode( win32_vk_code, lParam );
	}
	else if( wmMessageCode == WM_SYSKEYDOWN )
	{
		bool wasKeyRepeat = (lParam & BIT( 30 )) != 0;
		bool isRightAlt = (lParam & BIT( 24 )) != 0;

		out_systemEvent.m_eventType = wasKeyRepeat ? EVENT_TYPE_BUTTON_REPEATED : EVENT_TYPE_BUTTON_PRESSED;
		out_systemEvent.m_keyButtonCode = isRightAlt ? BUTTON_KEYBOARD_RIGHT_ALT : BUTTON_KEYBOARD_LEFT_ALT;
// 		if( wParam == VK_MENU )
// 			DebuggerPrintf( "%s Alt was pressed\n", isRightAlt ? "Right" : "Left" );
// 		else
// 			DebuggerPrintf( "Alt-#%i(0x%04x) key combination pressed\n", wParam, wParam );
//		DebuggerPrintf( "WM_SYSKEYDOWN received; lParam = %i (0x%08x), wParam = %i (0x%08x)\n", lParam, lParam, wParam, wParam );
	}
	else if( wmMessageCode == WM_SYSKEYUP )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_RELEASED;
		bool isRightAlt = (lParam & BIT( 24 )) != 0;
		out_systemEvent.m_keyButtonCode = isRightAlt ? BUTTON_KEYBOARD_RIGHT_ALT : BUTTON_KEYBOARD_LEFT_ALT;
	}
	else if( wmMessageCode == WM_LBUTTONDOWN )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_PRESSED;
		out_systemEvent.m_keyButtonCode = BUTTON_MOUSE_LEFT_BUTTON;
	}
	else if( wmMessageCode == WM_LBUTTONUP )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_RELEASED;
		out_systemEvent.m_keyButtonCode = BUTTON_MOUSE_LEFT_BUTTON;
	}
	else if( wmMessageCode == WM_RBUTTONDOWN )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_PRESSED;
		out_systemEvent.m_keyButtonCode = BUTTON_MOUSE_RIGHT_BUTTON;
	}
	else if( wmMessageCode == WM_RBUTTONUP )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_RELEASED;
		out_systemEvent.m_keyButtonCode = BUTTON_MOUSE_RIGHT_BUTTON;
	}
	else if( wmMessageCode == WM_MBUTTONDOWN )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_PRESSED;
		out_systemEvent.m_keyButtonCode = BUTTON_MOUSE_MIDDLE_BUTTON;
	}
	else if( wmMessageCode == WM_MBUTTONUP )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_BUTTON_RELEASED;
		out_systemEvent.m_keyButtonCode = BUTTON_MOUSE_MIDDLE_BUTTON;
	}
	else if( wmMessageCode == WM_MOUSEWHEEL )
	{
		short wheelDelta = GET_WHEEL_DELTA_WPARAM( wParam );
		int numMouseWheelPositions = wheelDelta / WHEEL_DELTA;
		out_systemEvent.m_eventType = EVENT_TYPE_MOUSE_WHEEL_MOVED;
		out_systemEvent.m_miscData = numMouseWheelPositions;
	}
	else if( wmMessageCode == WM_QUIT || wmMessageCode == WM_CLOSE || wmMessageCode == WM_DESTROY )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_APP_CLOSE;
	}
	else if( wmMessageCode == WM_ACTIVATEAPP )
	{
// 		int status = (wParam & 0x0000FFFF); // Low word is activation status
// 		if( status == WA_INACTIVE )
// 			out_systemEvent.m_eventType = EVENT_TYPE_APP_LOST_FOCUS;
// 		else
// 			out_systemEvent.m_eventType = EVENT_TYPE_APP_GAINED_FOCUS;
		bool isNowActive = (wParam == TRUE);
		if( isNowActive )
			out_systemEvent.m_eventType = EVENT_TYPE_APP_GAINED_FOCUS;
		else
			out_systemEvent.m_eventType = EVENT_TYPE_APP_LOST_FOCUS;
	}
	else if( wmMessageCode == WM_CHAR )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_TEXT_CHARACTER_TYPED;
		out_systemEvent.m_utf32TextCharacter = wParam;
	}
	/*
	else if( wmMessageCode == WM_PAINT || wmMessageCode == WM_NCHITTEST || wmMessageCode == WM_SETCURSOR || wmMessageCode == WM_MOUSEFIRST )
	{
		out_systemEvent.m_eventType = EVENT_TYPE_IGNORE_AND_PERMIT;
	}
	else if( wmMessageCode == WM_NCACTIVATE )
	{
		ConsolePrintf( "Received WM_NCACTIVATE; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_SYSKEYDOWN )
	{
		ConsolePrintf( "Received WM_SYSKEYDOWN; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_GETICON )
	{
		ConsolePrintf( "Received WM_GETICON; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_ACTIVATE )
	{
		ConsolePrintf( "Received WM_ACTIVATE; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_KILLFOCUS )
	{
		ConsolePrintf( "Received WM_KILLFOCUS; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_ACTIVATEAPP )
	{
		ConsolePrintf( "Received WM_ACTIVATEAPP; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_IME_SETCONTEXT )
	{
		ConsolePrintf( "Received WM_IME_SETCONTEXT; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_IME_NOTIFY )
	{
		ConsolePrintf( "Received WM_IME_NOTIFY; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_SYSKEYUP )
	{
		ConsolePrintf( "Received WM_SYSKEYUP; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_SYSCOMMAND )
	{
		ConsolePrintf( "Received WM_SYSCOMMAND; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_INITMENU )
	{
		ConsolePrintf( "Received WM_INITMENU; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_ENTERMENULOOP )
	{
		ConsolePrintf( "Received WM_ENTERMENULOOP; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_MENUSELECT )
	{
		ConsolePrintf( "Received WM_MENUSELECT; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_CAPTURECHANGED )
	{
		ConsolePrintf( "Received WM_CAPTURECHANGED; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_EXITMENULOOP )
	{
		ConsolePrintf( "Received WM_EXITMENULOOP; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_CREATE )
	{
		ConsolePrintf( "Received WM_CREATE; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_SHOWWINDOW )
	{
		ConsolePrintf( "Received WM_SHOWWINDOW; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_GETMINMAXINFO )
	{
		ConsolePrintf( "Received WM_GETMINMAXINFO; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_WINDOWPOSCHANGING )
	{
		ConsolePrintf( "Received WM_WINDOWPOSCHANGING; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_NCCREATE )
	{
		ConsolePrintf( "Received WM_NCCREATE; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
	else if( wmMessageCode == WM_NCCALCSIZE )
	{
		ConsolePrintf( "Received WM_NCCALCSIZE; wParam=%d (0x%08x), lParam=%d (0x%08x)",
			wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );
	}
*/
// 	else if( wmMessageCode == WM_ENTERMENULOOP )
// 	{
// 		out_systemEvent.m_eventType = EVENT_TYPE_IGNORE_AND_CONSUME;
// 		const char* wmMessageName = GetNameForWindowsMessage( wmMessageCode );
// 		ConsolePrintf( "CONSUMING %s; wParam=%d (0x%08x), lParam=%d (0x%08x)",
// 			wmMessageName, wParam, wParam, lParam, lParam );
// 	}
	else
	{
//		const char* wmMessageName = GetNameForWindowsMessage( wmMessageCode );
//		DebuggerPrintf( "Received %s; wParam=%d (0x%08x), lParam=%d (0x%08x)\n",
//			wmMessageName, wParam, wParam, lParam, lParam );
	}
/*
CONSOLE: Received unknown WM_ message code #3 (0x00000003); wParam=0 (0x00000000), lParam=1966240 (0x001e00a0)
CONSOLE: Received unknown WM_ message code #5 (0x00000005); wParam=0 (0x00000000), lParam=58984000 (0x03840640)
CONSOLE: Received unknown WM_ message code #7 (0x00000007); wParam=0 (0x00000000), lParam=0 (0x00000000)
CONSOLE: Received unknown WM_ message code #20 (0x00000014); wParam=302065380 (0x120126e4), lParam=0 (0x00000000)
CONSOLE: Received unknown WM_ message code #71 (0x00000047); wParam=0 (0x00000000), lParam=1372456 (0x0014f128)
CONSOLE: Received unknown WM_ message code #133 (0x00000085); wParam=1 (0x00000001), lParam=0 (0x00000000)
CONSOLE: Received unknown WM_ message code #289 (0x00000121); wParam=2 (0x00000002), lParam=0 (0x00000000)
CONSOLE: Received unknown WM_ message code #799 (0x0000031f); wParam=1 (0x00000001), lParam=0 (0x00000000)
CONSOLE: Received unknown WM_ message code #49393 (0x0000c0f1); wParam=0 (0x00000000), lParam=0 (0x00000000)
*/
}


//-----------------------------------------------------------------------------------------------
LRESULT CALLBACK WindowsMessageHandlingProcedure( HWND windowHandle, UINT wmMessageCode, WPARAM wParam, LPARAM lParam )
{
	static int pcIndex = GetProfileCategoryIndexForName( "Input.Msg", Rgba( 255, 255, 0 ) );
	ScopedProfileSection profileSection( pcIndex );

//	DebuggerPrintf( "WinProc: windowHandle=0x%08x, wmMsgCode=%d (0x%04x), wParam=%d (0x%08x), lParam=%d (0x%08x)",
//		windowHandle, wmMessageCode, wmMessageCode, wParam, wParam, lParam, lParam );

	if( wmMessageCode == WM_CREATE || wmMessageCode == WM_NCCREATE )
	{
		return DefWindowProc( windowHandle, wmMessageCode, wParam, lParam );
	}

	SystemEvent systemEvent;
	ConvertWindowsMessageToGypsySystemEvent( wmMessageCode, wParam, lParam, systemEvent );
	bool wasMessageConsumed = false;
	if( systemEvent.m_eventType != EVENT_TYPE_NONE )
	{
		wasMessageConsumed = theApplication->ProcessSystemEventPreGame( systemEvent );
		if( !wasMessageConsumed )
		{
			GameInterface& game = GameInterface::GetInstance();
			wasMessageConsumed = game.ProcessSystemEvent( systemEvent );
		}

		if( !wasMessageConsumed )
		{
			wasMessageConsumed = theApplication->ProcessSystemEventPostGame( systemEvent );
		}
	}

	if( wasMessageConsumed )
	{
		return 0;
	}
	else
	{
		return DefWindowProc( windowHandle, wmMessageCode, wParam, lParam );
	}
}


/////////////////////////////////////////////////////////////////////////////////////////////////


//-----------------------------------------------------------------------------------------------
Application_Win32::Application_Win32( HINSTANCE applicationInstanceHandle )
	: m_applicationInstanceHandle( applicationInstanceHandle )
	, m_windowHandle( nullptr )
	, m_clientAreaOffset( 0, 0 )
	, m_hasFocus( false )
	, m_defaultCursor( nullptr )
	, m_customCursor( nullptr )
{
	m_clientRect.left = 0;
	m_clientRect.right = 0;
	m_clientRect.bottom = 0;
	m_clientRect.top = 0;
	m_windowRect.left = 0;
	m_windowRect.right = 0;
	m_windowRect.bottom = 0;
	m_windowRect.top = 0;
	
	SetProcessDPIAware();
}


//-----------------------------------------------------------------------------------------------
Application_Win32::~Application_Win32()
{
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Application_Win32::BeginFrame()
{
	Application::BeginFrame();
	RunWin32MessagePump();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Application_Win32::ShutDown()
{
	DestroyOSWindow();
	Application::ShutDown();
}


//-----------------------------------------------------------------------------------------------
// Returns the last Win32 error, in string format. Returns an empty string if there is no error.
//
std::string GetLastErrorAsString()
{
	//Get the error message, if any.
	DWORD errorMessageID = ::GetLastError();
	if(errorMessageID == 0)
		return "No error message has been recorded";

	LPSTR messageBuffer = nullptr;
	size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);

	std::string message(messageBuffer, size);

	//Free the buffer.
	LocalFree(messageBuffer);

	return message;
}


//-----------------------------------------------------------------------------------------------
void Application_Win32::CreateOSWindow( bool isFullscreen, const IntVector2& clientAreaSize )
{
	if( isFullscreen )
	{
		DEVMODE fullscreenMode; // fullscreen device mode
		memset( &fullscreenMode, 0, sizeof( fullscreenMode ) );
		fullscreenMode.dmSize = sizeof( fullscreenMode );
		fullscreenMode.dmPelsWidth	= clientAreaSize.x;
		fullscreenMode.dmPelsHeight	= clientAreaSize.y;
		fullscreenMode.dmBitsPerPel	= 32;
		fullscreenMode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
		ChangeDisplaySettings( &fullscreenMode, CDS_FULLSCREEN ); // TODO: Validate success
	}

	// Set up window class, style, and properties
	WNDCLASSEX windowClassDescription;
	PopulateWindowClassDescription( windowClassDescription );
//	ATOM windowClassAtom = RegisterClassEx( &windowClassDescription ); // TODO: validate success
//	char* windowClassAtomAsPointer = (char*) windowClassAtom;
	RegisterClassEx( &windowClassDescription ); // TODO: validate success
	DWORD windowStyleFlags, windowStyleExFlags;
	PopulateWindowStyleFlags( isFullscreen, windowStyleFlags, windowStyleExFlags );
	PopulateClientAndWindowRect( clientAreaSize, windowStyleFlags, windowStyleExFlags );

	// Determine the title of the window
	WCHAR windowTitle[ 1024 ];
	int windowTitleLength = sizeof(windowTitle)/sizeof(windowTitle[0]);
	MultiByteToWideChar( GetACP(), 0, m_appTitle.c_str(), -1, windowTitle, windowTitleLength );

	// Create the window
	m_windowHandle = CreateWindowExW( // TODO: Validate success
		windowStyleExFlags,
		windowClassDescription.lpszClassName,
		windowTitle,
		windowStyleFlags,
		m_windowRect.left,
		m_windowRect.top,
		m_windowRect.right - m_windowRect.left,
		m_windowRect.bottom - m_windowRect.top,
		nullptr, nullptr,
		m_applicationInstanceHandle,
		nullptr );

	if( m_windowHandle == nullptr )
	{
		std::string errorMessage = GetLastErrorAsString();
		FATAL_ERROR( Stringf( "ERROR: CreateWindow() FAILED!\n\nError message is:\n\n%s\n", errorMessage.c_str() ) );
	}

	// Set the window to be visible, to be in the foreground, and to have focus
	ShowWindow( m_windowHandle, SW_SHOW );
	SetForegroundWindow( m_windowHandle );
	SetFocus( m_windowHandle );

	m_defaultCursor = LoadCursor( nullptr, IDC_ARROW );
	m_customCursor	= nullptr;

	//// TEMP HACK
//	char buffer[ 1024 ];
// 	GetCurrentDirectoryA( 1000, buffer );
// 	HWND hWnd = GetActiveWindow();
// 	MessageBoxA( hWnd, buffer, buffer, MB_OK );
	//// TEMP HACK
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Application_Win32::DestroyOSWindow()
{
	if( m_windowHandle )
		DestroyWindow( m_windowHandle );

	m_windowHandle = nullptr;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL IntVector2 Application_Win32::GetDesktopResolution() const
{
	HWND desktopWindowHandle = GetDesktopWindow();
	RECT desktopClientArea;
	GetClientRect( desktopWindowHandle, &desktopClientArea );
	return IntVector2( desktopClientArea.right, desktopClientArea.bottom );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Application_Win32::SleepSeconds( double secondsToSleep )
{
	unsigned int millisecondsToSleep = static_cast< unsigned int >( 1000.0 * secondsToSleep );
	Sleep( millisecondsToSleep );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Application_Win32::UpdateCursorNormalizedCoords()
{
	POINT cursorPos;
	RECT clientRect;
	GetCursorPos( &cursorPos );
	ScreenToClient( m_windowHandle, &cursorPos );
	GetClientRect( m_windowHandle, &clientRect );
	cursorPos.y = clientRect.bottom - cursorPos.y;
	Vector2 clientMouseCoords( static_cast< float >( cursorPos.x ), static_cast< float >( cursorPos.y ) );
	Vector2 clientRectSize( static_cast< float >( clientRect.right - clientRect.left ), static_cast< float >( clientRect.bottom - clientRect.top ) );
	m_cursorNormalizedCoords = clientMouseCoords.GetInverseScaledNonUniform( clientRectSize );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Application_Win32::SetNativeCursorVisibility( bool isVisible )
{
	if( isVisible )
		ShowCursor( TRUE );
	else
		ShowCursor( FALSE );
}


//-----------------------------------------------------------------------------------------------
// Note: Subclasses should call this base version in order to make sure the internal bookkeeping
//	for last known cursor position isn't delayed by a frame, etc.
//
VIRTUAL void Application_Win32::SnapCursorToNormalizedCoords( const Vector2& newNormalizedCoords )
{
	RECT clientRect;
	GetClientRect( m_windowHandle, &clientRect );
	AABB2 clientBounds( static_cast< float >( clientRect.left ),
						static_cast< float >( clientRect.top ),
						static_cast< float >( clientRect.right ),
						static_cast< float >( clientRect.bottom ) );
	Vector2 clientPosition = clientBounds.GetPointAtNormalizedPositionWithinBox( newNormalizedCoords );
	int cursorPosX = static_cast< LONG >( floor( clientPosition.x + 0.5f ) );
	int cursorPosY = static_cast< LONG >( floor( clientPosition.y + 0.5f ) );
	cursorPosY = clientRect.bottom - cursorPosY;
	SetCursorPos( cursorPosX, cursorPosY );

	Application::SnapCursorToNormalizedCoords( newNormalizedCoords );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL IntVector2 Application_Win32::GetCursorPhysicalClientCoords() const
{
	POINT cursorPos;
	GetCursorPos( &cursorPos ); // #ToDo: Take window offset into account here (these are screen coords, not client coords)
	return IntVector2( cursorPos.x, cursorPos.y );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Application_Win32::SnapCursorToPhysicalClientCoords( const IntVector2& newCursorClientCoords )
{
	SetCursorPos( newCursorClientCoords.x, newCursorClientCoords.y ); // #ToDo: Take window offset into account here (these are screen coords, not client coords)
}


//-----------------------------------------------------------------------------------------------
void Application_Win32::PopulateWindowClassDescription( WNDCLASSEX& out_windowClassDescription )
{
	memset( &out_windowClassDescription, 0, sizeof( out_windowClassDescription ) );
	out_windowClassDescription.cbSize = sizeof( out_windowClassDescription );
	out_windowClassDescription.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraw on move, request own Display Context
	out_windowClassDescription.lpfnWndProc = (WNDPROC) WindowsMessageHandlingProcedure; // Assign a win32 message-handling function
	out_windowClassDescription.hInstance = GetModuleHandle( nullptr );
	out_windowClassDescription.hIcon = nullptr; // TODO: Support custom game icon
	out_windowClassDescription.hCursor = nullptr ;//LoadCursor( nullptr, IDC_ARROW ); // Default cursor; TODO: Support custom cursor
	out_windowClassDescription.lpszClassName = TEXT( "Gypsy Game Engine Window Class" );
}


//-----------------------------------------------------------------------------------------------
void Application_Win32::PopulateWindowStyleFlags( bool isFullscreen, DWORD& out_windowStyleFlags, DWORD& out_windowStyleExFlags )
{
	if( isFullscreen )
	{
		out_windowStyleFlags	= WS_POPUP;
		out_windowStyleExFlags	= WS_EX_APPWINDOW | WS_EX_TOPMOST;
	}
	else
	{
		out_windowStyleFlags	= WS_CAPTION | WS_BORDER | WS_THICKFRAME | WS_SYSMENU | WS_OVERLAPPED;// | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
		out_windowStyleExFlags	= WS_EX_APPWINDOW;
	}
}


//-----------------------------------------------------------------------------------------------
void Application_Win32::PopulateClientAndWindowRect( const IntVector2& clientAreaSize, DWORD windowStyleFlags, DWORD windowStyleExFlags )
{
	const RECT desktopRect = GetWindowsDesktopRect();
	const IntVector2 desktopSize( desktopRect.right - desktopRect.left, desktopRect.bottom - desktopRect.top );
	const IntVector2 desktopCenter( (desktopRect.right + desktopRect.left) / 2, (desktopRect.bottom + desktopRect.top) / 2 );

	m_clientRect.left = desktopCenter.x - (clientAreaSize.x / 2);
	m_clientRect.top = 0; // desktopCenter.y - (clientAreaSize.y / 2);
	m_clientRect.right = m_clientRect.left + clientAreaSize.x;
	m_clientRect.bottom = m_clientRect.top + clientAreaSize.y;
	m_windowRect = m_clientRect;

	m_clientAreaOffset.x = m_clientRect.left;
	m_clientAreaOffset.y = m_clientRect.top;

	AdjustWindowRectEx( &m_windowRect, windowStyleFlags, FALSE, windowStyleExFlags );
	ClampWindowRectToDesktop( false );
}


//-----------------------------------------------------------------------------------------------
RECT Application_Win32::GetWindowsDesktopRect() const
{
	RECT desktopRect;
	HWND desktopHWnd = GetDesktopWindow();
	GetClientRect( desktopHWnd, &desktopRect );
	return desktopRect;
}


//-----------------------------------------------------------------------------------------------
void Application_Win32::ClampWindowRectToDesktop( const bool forceResize )
{
	const RECT desktopRect = GetWindowsDesktopRect();
	RECT windowRectBeforeMoving = m_windowRect;

	// Move the window up so that the bottom fits
	if( m_windowRect.bottom > desktopRect.bottom )
	{
		m_windowRect.top -= ( m_windowRect.bottom - desktopRect.bottom );
		m_windowRect.bottom = desktopRect.bottom;
	}

	// Move the window left so that the right fits
	if( m_windowRect.right > desktopRect.right )
	{
		m_windowRect.left -= ( m_windowRect.right - desktopRect.right );
		m_windowRect.right = desktopRect.right;
	}

	// Move the window down so that the top fits
	if( m_windowRect.top < desktopRect.top )
	{
		if( !forceResize )
			m_windowRect.bottom += ( desktopRect.top - m_windowRect.top );
		m_windowRect.top = desktopRect.top;
	}

	// Move the window right so that the left fits
	if( m_windowRect.left < desktopRect.left )
	{
		if( !forceResize )
			m_windowRect.right += ( desktopRect.left - m_windowRect.left );
		m_windowRect.left = desktopRect.left;
	}

	const int deltaX = (m_windowRect.left - windowRectBeforeMoving.left);
	const int deltaY = (m_windowRect.top - windowRectBeforeMoving.top);

	m_clientRect.left += deltaX;
	m_clientRect.right += deltaX;
	m_clientRect.top += deltaY;
	m_clientRect.bottom += deltaY;

	m_clientAreaOffset.x += deltaX;
	m_clientAreaOffset.y += deltaY;
}


//-----------------------------------------------------------------------------------------------
void Application_Win32::RunWin32MessagePump()
{
	static int pcIndex = GetProfileCategoryIndexForName( "Input", Rgba( 255, 255, 0 ) );
	ScopedProfileSection profileSection( pcIndex );

	MSG win32Msg; // Windows message structure
	memset( &win32Msg, 0, sizeof( win32Msg ) );

	for( ;; )
	{
		BOOL isMessagePresent = PeekMessage( &win32Msg, m_windowHandle, 0, 0, PM_REMOVE );
		if( !isMessagePresent )
			break;

		TranslateMessage( &win32Msg );
		DispatchMessage( &win32Msg );
	}
}


