//
// Asura_Windows.cpp
//
// Created by Kevin Lea - 02/07/2001 11:11:05
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// Handles basic Windows functionality: initialisation, message handling & shutdown.
#include "Core/Asura_Windows.h"

#include "Debugging/Asura_Assert.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_Direct3D_UserOptions.h"
#include "Input/Asura_PlatformInput.h"
#include "Sound/Asura_PlatformStreamSound.h"

#ifdef ASURA_TOOLS
#include "Tools_System.h"
#include "Texturing/Tools_Texturing_MaterialResponse.h"
#include "Movement/Tools_Movement.h"
#include "Misc/Tools_Render.h"
#endif

//#define DESTROY_WINDOW_ON_ALT_TAB

// KJL 25/02/00 17:29:41 - mouse wheel code
#include "zmouse.h"
#include "resource.h"

// SK 21/11/07 - This is required for the GetUserName() call in the Windows SDK.
#pragma comment( lib, "advapi32.lib")

#if defined ASURA_SUPPORT_MULTIPLE_MONITORS && WINVER < 0x0500
// COMPILE_MULTIMON_STUBS must only be defined in one code file, though <multimon.h> can be included anywhere
#define COMPILE_MULTIMON_STUBS
#include <multimon.h>
#endif

bool Asura_Windows::s_bFullScreen;
HWND* Asura_Windows::s_pxWindowHandles = NULL;
u_int Asura_Windows::s_uNumWindowHandles = 0;
HINSTANCE Asura_Windows::WindowhInstance;
int Asura_Windows::WinLeftX, Asura_Windows::WinTopY;
int Asura_Windows::s_iWindowedXPos = -9999, Asura_Windows::s_iWindowedYPos = -9999;
int Asura_Windows::s_iWindowedWidth = 0, Asura_Windows::s_iWindowedHeight = 0;
bool Asura_Windows::s_bSubwindowSpecifiedOnCommandLine = false;
bool Asura_Windows::s_bFullscreenSpecifiedOnCommandLine = false;
int Asura_Windows::WinWidth,Asura_Windows::WinHeight;
float Asura_Windows::WinScaleX,Asura_Windows::WinScaleY;
bool Asura_Windows::ApplicationIsActive;
char *Asura_Windows::ApplicationName;
unsigned char Asura_Windows::ToAsciiTable[256][256];
ASURA_KEYID Asura_Windows::s_aeExtendedKeyPressed[256][2];


// (AAM - 07.03.2006) - Don't want this on in retail builds - networking etc. requires updates even when minimised
// (RAR - 04.05.2007) - Default to off for all builds.
bool Asura_Windows::s_bPauseWhileMinimised = false;

#ifdef ASURA_TOOLS
//Starting off as false so that the inital tools loading is done at a higher priority. RAR 09/11/2007
bool Asura_Windows::s_bIdlePriorityWhileMinimised = false;
#else
//Defaulting to true (the old behaviour)in non-tools builds for now. RAR 09/11/2007
bool Asura_Windows::s_bIdlePriorityWhileMinimised = true;
#endif

DWORD Asura_Windows::s_uMainThreadID;
bool Asura_Windows::s_bInitialised = false;
Asura_String Asura_Windows::s_xTitle;
Asura_String Asura_Windows::s_xDefaultTitle;
bool Asura_Windows::s_bIsMaximised;
bool Asura_Windows::s_bMaximiseWindowRequested = false;
AsuraWindowsCommandLineInfo Asura_Windows::s_xCommandLineInfo = {false, false};

static bool g_bResizingWindow = false;
static bool g_bChangingFullscreenMode = false;
static bool g_bChangingFullscreenMode2 = false;
static bool g_bWindowClassRegistered = false;

#include "Rendering/Asura_Direct3D.h" // for alt-tabing
#include "Light/Asura_Light.h"


// mousewheel msg id
const UINT RWM_MOUSEWHEEL = RegisterWindowMessage(MSH_MOUSEWHEEL);

void Asura_Windows::Initialise(HINSTANCE hInstance, HINSTANCE hPrevInstance, const char* szCmdLine, int nCmdShow, const LPVOID guid, bool bFullScreen)
{
	u_int uWidth, uHeight;
	GetDesktopSize( &uWidth, &uHeight);
	Asura_Direct3D_UserOptions::GetCurrentOptions().m_uDefaultWidth = uWidth;
	Asura_Direct3D_UserOptions::GetCurrentOptions().m_uDefaultHeight = uHeight;

    SetRunningFullScreen(bFullScreen);

	WindowhInstance = hInstance;

//	ApplicationName
	InitialiseWindow(hInstance,nCmdShow);
	InitialiseToAsciiTable();
	ApplicationIsActive = true;

	SetPauseWhileMinimised( false );

	// assuming that this function is called by main thread...
	s_uMainThreadID = GetCurrentThreadId();
	s_bInitialised = true;
}

void Asura_Windows::LateInitialise()
{
	if(s_bMaximiseWindowRequested)
	{
		MaximiseWindow();
	}
}

void Asura_Windows::SetRunningFullScreen(bool bFullscreen)
{
	g_bChangingFullscreenMode = true;

	s_bFullScreen = bFullscreen;

	if (bFullscreen)
	{
		WinWidth = GetSystemMetrics(SM_CXSCREEN);
		WinHeight = GetSystemMetrics(SM_CYSCREEN);

		WinLeftX = 0;
		WinTopY = 0;
	}
	else
	{
		// JEB 17/04/2008 This does not work with all versions of Windows (specifically Vista).
		////force window to be 640x480 to avoid stretch blits.
		////
		//// DaveR 04/01/06 - This was incorrect, you need the window to be a bit bigger than 640x480, as 
		//// this is the window size *including* the frame. Fonts were noticably blurred by the slight scaling 
		//// down that this caused. Added the GetSystemMetrics to correct the window size. Not sure if I've
		//// got this 100% correct, but the size is now pixel-perfect on WinXP with default settings... 
		//// A little easier on the eyes....

		//WinWidth=Asura_Viewport::GetWidth()+
		//	((GetSystemMetrics(SM_CXBORDER)+
		//	GetSystemMetrics(SM_CXFIXEDFRAME))*2);

		//WinHeight=Asura_Viewport::GetHeight()+
		//	((GetSystemMetrics(SM_CYBORDER)+GetSystemMetrics(SM_CYFIXEDFRAME))*2)+
		//	GetSystemMetrics(SM_CYCAPTION);

		// JEB 17/04/2008 - The above method does not work on Windows Vista. The correct way
		// to determine a window rectangle based upon a desired client rectangle is to use
		// AdjustWindowRect (or AdjustWindowRectEx).

		// Get the window position.  Only store this in windowed mode, so it gets remembered between alt-enter switching.

		if((s_iWindowedXPos == -9999) || (s_iWindowedYPos == -9999))
		{
			s_iWindowedWidth = Asura_Direct3D_UserOptions::GetCurrentOptions().m_uDefaultWidth;
			s_iWindowedHeight = Asura_Direct3D_UserOptions::GetCurrentOptions().m_uDefaultHeight;

			RECT xRect = { 0, 0, s_iWindowedWidth, s_iWindowedWidth };

			AdjustWindowRect(&xRect, bFullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_THICKFRAME, FALSE);

			s_iWindowedXPos = 0;
			s_iWindowedYPos = 0;

			WinWidth = xRect.right - xRect.left;
			WinHeight = xRect.bottom - xRect.top;
		}
		else
		{
			RECT xRect = { 0, 0, s_iWindowedWidth, s_iWindowedHeight };
			AdjustWindowRect(&xRect, WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_THICKFRAME, FALSE);

			WinWidth = xRect.right - xRect.left;
			WinHeight = xRect.bottom - xRect.top;
		}

		WinLeftX = s_iWindowedXPos;
		WinTopY = s_iWindowedYPos;
	}
	
	g_bChangingFullscreenMode = false;
}

HWND Asura_Windows::GetWindowHandle(u_int uWindowIndex /* = 0 */)
{
	if(!s_pxWindowHandles) return NULL;
	return s_pxWindowHandles[uWindowIndex];
}

void Asura_Windows::Shutdown(void)
{
	// shut down and clean things up
	if(s_pxWindowHandles)
	{
		for(u_int uWindow = 0; uWindow < s_uNumWindowHandles; ++uWindow)
		{
			DestroyWindow(s_pxWindowHandles[uWindow]);
		}
	}
	delete[] s_pxWindowHandles;
	s_pxWindowHandles = NULL;
	s_uNumWindowHandles = 0;
}

#define IDI_MAIN_ICON                   101
#define IDR_MAIN_ACCEL                  113
#define IDR_MENU                        141
#define IDR_POPUP                       142
#define IDD_ABOUT                       143
#define IDD_CHANGEDEVICE                144
#define IDC_DEVICE_COMBO                1000
#define IDC_MODE_COMBO                  1001
#define IDC_WINDOWED_CHECKBOX           1012
#define IDC_STEREO_CHECKBOX             1013
#define IDC_FULLSCREEN_TEXT             1014
#define IDM_ABOUT                       40001
#define IDM_CHANGEDEVICE                40002
#define IDM_TOGGLEFULLSCREEN            40003
#define IDM_TOGGLESTART                 40004
#define IDM_SINGLESTEP                  40005
#define IDM_EXIT                        40006

bool Asura_Windows::InitialiseWindow(HINSTANCE hInstance, int nCmdShow)
{
#ifdef ASURA_SUPPORT_MULTIPLE_MONITORS
	// How many windows do we want? One per monitor should be enough.
	s_uNumWindowHandles = GetSystemMetrics(SM_CMONITORS);
#else
	s_uNumWindowHandles = 1;
#endif
	s_pxWindowHandles = new HWND[s_uNumWindowHandles];
	for(u_int i=0;i<s_uNumWindowHandles;i++) s_pxWindowHandles[i] = 0;

	if(!g_bWindowClassRegistered)
	{
		WNDCLASS xWc;

		// Set up and register window class
		// get double click messages from mouse if user double-clicks it 
		xWc.style = CS_DBLCLKS;

		// Name of window procedure (see above) 
		WNDPROC pfnWndProc = Asura_Windows::WindowsMessageHandler;
		xWc.lpfnWndProc = pfnWndProc;

		//Extra bytes for  obscure purposes bearing a sordid relationship to
		//dialog box conventions.  Zero for us.
		xWc.cbClsExtra = 0;
		xWc.cbWndExtra = 0;

		// Instance which window is within 
		xWc.hInstance = hInstance;

		//The main icon for any project should be called IDI_PROJECTICON
		//if this fails to load, the default application icon will be used.
		//Do not define IDI_PROJECTICON - this is done by Visual Studio
		//in making resource.h
	#ifdef IDI_PROJECTICON
		xWc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PROJECTICON));
	#else
		xWc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);	
	#endif

		// System cursor resource.  This one is generic.
		xWc.hCursor = LoadCursor(NULL, IDC_ARROW);
		
		//NULL background forces application to redraw
		//the background ITSELF when it receives a WM_ERASEBKGND
		//message, leaving graphical control with the engine
		xWc.hbrBackground = NULL;
		// Project name and class for windows menus etc.
		xWc.lpszMenuName = ASURA_PC_APPLICATION_NAME;
		xWc.lpszClassName = ASURA_PC_APPLICATION_NAME;
		
		//Register the class we have constructed as a valid window that
		//can then be created.   Return code indicates success or
		//failure.
		BOOL bClassRegistered = RegisterClass(&xWc);

		if (!bClassRegistered) return false;

		g_bWindowClassRegistered = true;
	}
	
	//If a title has been set , use that for the new window. RAR 14/02/2008
	const char* szTitle = s_xTitle.IsBlank() ? ASURA_PC_APPLICATION_TITLE : s_xTitle.GetString();
	
	for(u_int uWindow = 0; uWindow < s_uNumWindowHandles; ++uWindow)
	{

		if (!s_bFullScreen)
		{
			s_pxWindowHandles[uWindow] = CreateWindow
			( 
				ASURA_PC_APPLICATION_NAME,
				szTitle,
				WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_THICKFRAME | ((uWindow == 0) ? WS_VISIBLE : 0),
				s_iWindowedXPos,
				s_iWindowedYPos,
				WinWidth,
				WinHeight,
				0L,
				0L,
				hInstance,
				0L
			);
		}
		else
		{
			s_pxWindowHandles[uWindow] = CreateWindowEx
			(
				//WS_EX_TOPMOST forces this window to be topmost except
				//for other topmost	windows, even when deactivated.
				WS_EX_TOPMOST,
				//  Name of class (registered by RegisterClass call above) 
				ASURA_PC_APPLICATION_NAME, 
				// Name of window 
				szTitle, 
				// i.e. specify window is style pop up, i.e. non-application 
				((uWindow == 0) ? WS_VISIBLE : 0) | WS_POPUP, 
				//Initial horizontal and  vertical position.  For a pop-up window,
				//these are the coordinates of the upper left corner.
				WinLeftX,
				WinTopY,
				//Width and height of window. These are set to the current full
				//screen size in Initialise() above.

				//[DC - 30/1/09] I've changed this to a small resolution because a large number together with the WS_EX_TOPMOST
				// was causing CreateWindowEx to hang if this function was called certain areas of initialisation (I have no idea why!)
				// This resolution is largely irrelevant anyway in fullscreen mode, as directx deals with the resolution.
				10,
				10,
				// Parent window (null for a full screen game) 
				(uWindow == 0) ? 0 : s_pxWindowHandles[0],
				// Child/menu window (null for a full screen game) 
				NULL,
				// Handle for module associated with window 
				hInstance,
				// Parameter for associated structure (null in this case)
				NULL
			);
		}

		Asura_Assert(s_pxWindowHandles[uWindow], "Failed to initialise window");
		if (!s_pxWindowHandles[uWindow]) return false;

		// Set the window up to be displayed 
		//ShowWindow(WindowHandle, nCmdShow);
		//ShowWindow( WindowHandle, SW_SHOWNORMAL );
		// Update once (i.e. send WM_PAINT message to the window procedure) 
		UpdateWindow(s_pxWindowHandles[uWindow]);

	}

	return true;
}

void Asura_Windows::DestroyWindows()
{
	for(u_int uWindow = 0; uWindow < s_uNumWindowHandles; ++uWindow)
	{
		DestroyWindow(s_pxWindowHandles[uWindow]);
		s_pxWindowHandles[uWindow] = NULL;
	}
	delete[] s_pxWindowHandles;
	s_pxWindowHandles = NULL;
}

static u_int g_uInputBufferCharsUsed = 0;
static const u_int uMAXINPUTBUFFER = 100;
static Asura_UnicodeChar g_wszInputBuffer[uMAXINPUTBUFFER];

void Asura_Windows::CheckForWindowsMessages(void)
{
	MSG         msg;
	
	//Mouse.wheelStatus = 0;

    g_uInputBufferCharsUsed = 0;

	do
	{
		while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, 0, 0))
				return;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		
		// if the app is not active we should not return from the message lopp
		// until the app is re-activated
		
		if (s_bPauseWhileMinimised && !ApplicationIsActive)
		{
		//	ResetFrameCounter();
			Sleep(5);
		}
	}
	while (s_bPauseWhileMinimised && !ApplicationIsActive);
}

const Asura_UnicodeChar* Asura_Windows::GetInputBuffer()
{
    return g_wszInputBuffer;
}

u_int Asura_Windows::GetSizeOfInputBuffer()
{
    return g_uInputBufferCharsUsed;
}

inline LONG_PTR FAR PASCAL Asura_Windows::WindowsMessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC         hdc;
	RECT		NewWindCoord;

	// KJL 24/07/00 17:26:23 - have we got the window set up yet?
	//if (!WindowHandle) return 0;


//	const UINT RWM_MOUSEWHEEL = RegisterWindowMessage(MSH_MOUSEWHEEL);
 	
 	if (message==RWM_MOUSEWHEEL)
	{
		message = WM_MOUSEWHEEL;
		wParam <<= 16;
	}

	
	switch(message)
	{
		case WM_MOUSEWHEEL:
		{
			signed int w = wParam;
			w>>=16;
			// handle wheel stuff
			//Asura_UserInput_API_PC *userInputAPI = (Asura_UserInput_API_PC*)AsuraAPI->UserInput;
			Asura_PlatformInput::Mouse.WheelStatus = (float)w;
			return 0;
		}	
		
		
		case WM_CREATE:
		{
			break;
		}

		case WM_MOVE:
		{
			if (!s_bFullScreen)
			{
				if (hWnd)
				{
					GetWindowRect(hWnd, &NewWindCoord);
					WinLeftX = NewWindCoord.left;
					WinTopY = NewWindCoord.top;

					s_iWindowedXPos = WinLeftX;
					s_iWindowedYPos = WinTopY;
				}
			}
			break;
		}

		case WM_SIZE:
		{
			if(g_bChangingFullscreenMode) break;
			if(g_bChangingFullscreenMode2) break;

			switch(wParam)
			{
				case SIZE_MAXIMIZED:	s_bIsMaximised = true;		break;
				case SIZE_MINIMIZED:	s_bIsMaximised = false;		break;
				case SIZE_RESTORED:		s_bIsMaximised = false;		break;
			}

			if (!s_bFullScreen)
			{	
				if (hWnd)
				{
					RECT xClientRect;

					GetClientRect(hWnd, &xClientRect);
					GetWindowRect(hWnd, &NewWindCoord);
					WinLeftX = NewWindCoord.left;
					WinTopY = NewWindCoord.top;

					s_iWindowedXPos = WinLeftX;
					s_iWindowedYPos = WinTopY;

					s_iWindowedWidth = xClientRect.right - xClientRect.left;
					s_iWindowedHeight = xClientRect.bottom - xClientRect.top;

					#ifdef ASURA_TOOLS
					//Resize the viewport to match the new window size. RAR 23/08/2006
					//First time this gets called is before s_pxWindowHandles is filled in , and at that time
					//the window hasn't really been set to a sensible size anyway. So skip resizing the viewport
					//for that instance.
					//RAR 20/02/2008
					if(GetWindowHandle())
					{
						RECT xViewportRect;
						GetWindowViewportCoords(&xViewportRect);
						int iWidth = xViewportRect.right - xViewportRect.left;
						int iHeight = xViewportRect.bottom - xViewportRect.top;
						//Only update if the size has changed , and is still reasonably large.
						if(iWidth >= 100 && iHeight >= 100 && (iWidth != Asura_Viewport::GetWidth() || iHeight != Asura_Viewport::GetHeight()))
						{
							g_bResizingWindow = true;
							Tools_Render::SetVideoMode(iWidth,iHeight,false);
							g_bResizingWindow = false;
						}
					}
					#endif
				}
			}
			break;
		}

		// (AAM - 03.03.2006) This doesn't seem to be necessary here, and it causes the cursor
		// to disappear unexpectedly if error messages are up (e.g. unable to launch D3D)

//		case WM_SETCURSOR:
//		{
//			// KJL - 19-Apr-01 15:58:52 - turn off windows cursor
//			SetCursor(NULL);
//			return true;
//		}

		case WM_ERASEBKGND:
		{
			return true;
		}

		case WM_PAINT:
		{
			hdc = BeginPaint(hWnd, &ps);
			EndPaint(hWnd, &ps);
			return true;
		}

		case WM_DESTROY:
		{
			PostQuitMessage(0);
			break;
		}

		case WM_SYSCOMMAND:
		{
			switch( wParam)
			{
				// DLH - 21 March 2007 16:56:21 - Stops the Screen Saver or Monitor Power stuff happening and upsetting DirectX
				case SC_SCREENSAVE:
				case SC_MONITORPOWER:
				{
					return( 0);
				}
			}
			break;
		}

		case WM_ACTIVATEAPP:
		{
			// Only the primary (focus) window should handle this
			if(hWnd != Asura_Windows::GetWindowHandle(0)) break;

	        ApplicationIsActive = (wParam!=0);
	        
		    if (ApplicationIsActive)
	        {
	    		#if 0 // survive alttab
				if(AsuraD3DAPI)
				{
					extern bool ForceRedrawAll;
					AsuraD3DAPI->RestoreAllSurfaces();
///					ForceRedrawAll = true;
					Asura_Lighting_System::ForceLightingUpdate=true;
				}
				#endif
		
				Asura_UserInput::Reset();
				Asura_Mouse::IgnoreNextFramesInput();

				#ifdef ASURA_TOOLS
				Tools_System::ApplicationReactivated();
				#endif

				// TODO: This needs an audio system replacement
				#ifndef ASURA_USE_AUDIO_SYSTEM
				Asura_PlatformStreamSound::GainFocus_UnpauseSounds();
				#endif

				#if defined ASURA_TOOLS && defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
					// User might have checked a material in/out, update icon on overlay
					Tools_Texturing_MaterialResponse::UpdatePropertiesOverlay();
				#endif
			}
			#ifndef ASURA_USE_AUDIO_SYSTEM
			else
			{
				// TODO: This needs an audio system replacement
				Asura_PlatformStreamSound::LoseFocus_PauseAllSounds();
			}
			#endif

			//Change priority when going between active/inactive. RAR 09/11/2007
			if(ApplicationIsActive)
			{
				SetPriorityClass(GetCurrentProcess(),NORMAL_PRIORITY_CLASS);
			}
			else
			{
				SetPriorityClass(GetCurrentProcess(),s_bIdlePriorityWhileMinimised ? IDLE_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS);

			}
	
			return 0;
		}

		case WM_CHAR:				 
		{
            if( g_uInputBufferCharsUsed < uMAXINPUTBUFFER )
            {
                g_wszInputBuffer[g_uInputBufferCharsUsed++] = static_cast<Asura_UnicodeChar>( wParam );
            }
			return 0;
		}

		case WM_KEYDOWN: // it's intentional for this case to fall through to WM_SYSKEYDOWN
	    case WM_SYSKEYDOWN:
		{        
			int scancode = (lParam>>16)&255;
			unsigned char vkcode = (unsigned char)(wParam&255);

#ifdef ASURA_TOOLS
			const bool bAltDown = (lParam & (1 << 29)) != 0;
			const bool bPreviouslyDown = (lParam & (1 << 30)) != 0;
			if ( (message == WM_SYSKEYDOWN) && (vkcode == VK_RETURN) && bAltDown && !bPreviouslyDown && (Asura_Direct3D::GetNumSwapChains() < 2))
			{
				g_bChangingFullscreenMode2 = true;

				Asura_UserInput::Shutdown();

				#ifdef DESTROY_WINDOW_ON_ALT_TAB
				Asura_Windows::DestroyWindows();
				#endif

				Asura_Windows::SetRunningFullScreen(!Asura_Windows::RunningFullScreen());						

				#ifdef DESTROY_WINDOW_ON_ALT_TAB
				Asura_Windows::InitialiseWindow(Asura_Windows::GetWindowhInstance(), SW_SHOWNORMAL);
				#endif

				Asura_Direct3D::RecalcPresentParameters();
				Asura_Direct3D::RestoreAll(true);
				Asura_UserInput::Initialise();

				#ifndef DESTROY_WINDOW_ON_ALT_TAB
				// Change the window styles

				for(u_int uWindow = 0; uWindow < 1/*s_uNumWindowHandles*/; ++uWindow)
				{
					if(RunningFullScreen())
					{
						SetWindowLong(GetWindowHandle(), GWL_EXSTYLE, WS_EX_TOPMOST);
						SetWindowLong(GetWindowHandle(), GWL_STYLE, ((uWindow == 0) ? WS_VISIBLE : 0) | WS_POPUP);
						SetWindowPos(GetWindowHandle(), 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOREPOSITION | SWP_NOZORDER);
					}
					else
					{
						SetWindowLong(GetWindowHandle(), GWL_EXSTYLE, 0);
						SetWindowLong(GetWindowHandle(), GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_THICKFRAME | ((uWindow == 0) ? WS_VISIBLE : 0));

						SetWindowPos(GetWindowHandle(), HWND_NOTOPMOST, s_iWindowedXPos, s_iWindowedYPos, WinWidth, WinHeight, SWP_FRAMECHANGED);

						if(IsMaximised())
						{
							MaximiseWindow();
						}
					}
				}
				#endif

				SetForegroundWindow(Asura_Windows::GetWindowHandle());
				Tools_Movement::CancelToggledMMB();
				Tools_Movement::CancelToggledAlt();
				Tools_System::UpdateRestrictMouseToWindow();
				ApplicationIsActive = true;

				g_bChangingFullscreenMode2 = false;
			}
#endif

//			if (vkcode!=VK_CAPITAL && vkcode!=VK_SCROLL)
			{
				unsigned char ascii = ToAsciiTable[vkcode][scancode];
				//Unfortunately the above table maps the keys on the number pad to the ascii characters , 
				//scuppering attempts to distinguish between the number pad numbers and the numbers elsewhere
				//So , check for  the numberpad virtual keycodes , and ignore the ascii value if its one of those
				//-Richard
				if(vkcode>=VK_NUMPAD0 && vkcode<=VK_DIVIDE)
				{
					ascii = 0;
				}
				if (ascii)
				{
 			//		Asura_UserInput_API_PC *userInputAPI = (Asura_UserInput_API_PC*)AsuraAPI->UserInput;
					Asura_PlatformInput::KeyboardInput_AsciiKeyDown(ascii);
				}			 
				else
				{
					// NY - 14 February 2008 - need to filter out vkcodes that will clash with asura's internal code
					// these correspond to "launch browser" type buttons that we don't want to be supporting anyway
					if (vkcode + ASURA_NUMBER_OF_ASCII_KEYS < ASURA_KEYID_MOUSEBUTTON1)
					{
						Asura_PlatformInput::KeyboardInput_AsciiKeyDown(vkcode + ASURA_NUMBER_OF_ASCII_KEYS);
					}
				}

				// KOR - 14/07/2004 - added brackets to original     [ (lParam >> 24) & 255  > 0 ? 1 : 0], which may not have actually worked...
				// RAR - 15/07/2004 - the original worked , but for entirely the wrong reason. So try this version...
                ASURA_KEYID ePressed = s_aeExtendedKeyPressed[vkcode][((lParam >> 24) & 1)];
                if (ePressed != ASURA_KEYID_VOID)
                {
                    Asura_PlatformInput::KeyboardInput_AsciiKeyDown(ePressed);
                }

			}

			break;
		}

		case WM_SYSKEYUP:
		case WM_KEYUP:						
		{
			int scancode = (lParam>>16)&255;
			unsigned char vkcode = (unsigned char)(wParam&255);


			
//			if (vkcode!=VK_CAPITAL && vkcode!=VK_SCROLL)
			{
				unsigned char ascii = ToAsciiTable[vkcode][scancode];
				//Unfortunately the above table maps the keys on the number pad to the ascii characters , 
				//scuppering attempts to distinguish between the number pad numbers and the numbers elsewhere
				//So , check for  the numberpad virtual keycodes , and ignore the ascii value if its one of those
				//-Richard
				if(vkcode>=VK_NUMPAD0 && vkcode<=VK_DIVIDE)
				{
					ascii = 0;
				}

				if (ascii)
				{
		//			Asura_UserInput_API_PC *userInputAPI = (Asura_UserInput_API_PC*)AsuraAPI->UserInput;
					Asura_PlatformInput::KeyboardInput_AsciiKeyUp(ascii);
				}
				else
				{
					// NY - 14 February 2008 - need to filter out vkcodes that will clash with asura's internal code
					// these correspond to "launch browser" type buttons that we don't want to be supporting anyway
					if (vkcode + ASURA_NUMBER_OF_ASCII_KEYS < ASURA_KEYID_MOUSEBUTTON1)
					{
						Asura_PlatformInput::KeyboardInput_AsciiKeyUp(vkcode + ASURA_NUMBER_OF_ASCII_KEYS);
					}
				}
                    
				// KOR - 14/07/2004 - added brackets to original     [ (lParam >> 24) & 255  > 0 ? 1 : 0], which may not have actually worked...
				// RAR - 15/07/2004 - the original worked , but for entirely the wrong reason. So try this version...
                ASURA_KEYID ePressed = s_aeExtendedKeyPressed[vkcode][((lParam >> 24) & 1)];
                if (ePressed != ASURA_KEYID_VOID)
                {
                    Asura_PlatformInput::KeyboardInput_AsciiKeyUp(ePressed);
                }
			}				
			#if 0 // experiment to extract names...
			char buffer[100];
			if (vkcode!=VK_F5)
			{
				GetKeyNameText(lParam,buffer,100);
				int v2 = MapVirtualKey(scancode,3);
				return 0;
			}
			#endif
			return 0;
		}



		case WM_LBUTTONDBLCLK :
		case WM_RBUTTONDBLCLK :
			if(Asura_Mouse::ShouldAcceptButtonPresses())
			{
				bool bButton1 = Asura_Mouse::GetSwapMouseButtons() ? (message == WM_RBUTTONDBLCLK) : (message == WM_LBUTTONDBLCLK);

				//Setting both the double click and normal click ids as pressed , in case Asura_Mouse picks up
				//the button press a frame before/after the windows message
				//RAR 08/02/2008
				if(bButton1)
				{
					Asura_PlatformInput::SetKeyPressedThisFrame(ASURA_KEYID_DOUBLECLICK_MOUSEBUTTON1);
					Asura_PlatformInput::SetKeyPressedThisFrame(ASURA_KEYID_MOUSEBUTTON1);
				}
				else
				{
					Asura_PlatformInput::SetKeyPressedThisFrame(ASURA_KEYID_DOUBLECLICK_MOUSEBUTTON2);
					Asura_PlatformInput::SetKeyPressedThisFrame(ASURA_KEYID_MOUSEBUTTON2);
				}

			}
			return 0;



		case WM_INPUTLANGCHANGE:
		{
			// MGH 18/7/2005: Language and/or input method has changed, remake the ascii table.
			Asura_Windows::InitialiseToAsciiTable();
			break;
		}

#ifdef ASURA_USE_HANDLECOPYMESSAGE
		case WM_COPYDATA:
		{
			PCOPYDATASTRUCT pxCopyDataStruct = (PCOPYDATASTRUCT) lParam;
			char* pszCopyMessage = (char*)pxCopyDataStruct->lpData;
			if(Project_HandleCopyMessage(pszCopyMessage)) return true;
			break;
		}
#endif

        case WM_CLOSE:
#ifdef ASURA_TOOLS
			// If we're in tools (and not in quit-to-game) then this can be handled as requesting a quit to desktop
			if(Tools_System::IsActive())
			{
				Tools_System::RequestQuitToDesktop();
			}
#endif
			// we don't want Windows to shut us down
            return 0;

		case WM_SYSCHAR:
		{
			// [DC] I changed it so that on alt-enter, the main window doesn't get destroyed and recreated.  This caused the side effect
			// of causing a windows system beep when going from fullscreen mode to windowed mode.  It was beeping when DefWindowProc was
			// being called for the WM_SYSCHAR message.  Capturing this message stops the beep.  This may also stop annoying beeping on
			//other alt-<x> keyboard combinations.

			if(GetKeyState(VK_RETURN) & 0x80)
			{
				return 0;
			}

			break;
		}
	}

    return DefWindowProc(hWnd, message, wParam, lParam);
}

void Asura_Windows::InitialiseToAsciiTable(void)
{
	unsigned char ksarray[256];
	WORD output;

	//get the current keyboard state so we can get the stat of numlock,scroll lock and caps lock
	GetKeyboardState(ksarray);
	bool NumLockOn = (ksarray[VK_NUMLOCK] & 1) ? true : false;
	bool CapsLockOn = (ksarray[VK_CAPITAL] & 1) ? true : false;
	bool ScrollLockOn = (ksarray[VK_SCROLL] & 1) ? true : false;
	
	for (int k=0; k<=255; k++)

	{
		ksarray[k]=0;
	}

	for (int i=0; i<=255; i++)
	{
		for (int s=0; s<=255; s++)
		{
			if(ToAscii(i,s,&ksarray[0],&output,0)!=0)
			{
				ToAsciiTable[i][s] = (unsigned char)output;
			}
			else 
			{
				ToAsciiTable[i][s] = 0;
			}
		}
	}

	//Calling ToAscii above cause numlock etc to be turned off
	//Fill in the keyboard state array , and call ToAscii one last time to correct
	//the state of these keys
	if(NumLockOn) ksarray[VK_NUMLOCK] = 1;
	if(CapsLockOn) ksarray[VK_CAPITAL] = 1;
	if(ScrollLockOn) ksarray[VK_SCROLL] = 1;
	ToAscii(0,0,&ksarray[0],&output,0);

    for (u_int uKey = 0; uKey < 256; uKey++)
    {
        s_aeExtendedKeyPressed[uKey][0] = 
        s_aeExtendedKeyPressed[uKey][1] = ASURA_KEYID_VOID;
    }

    s_aeExtendedKeyPressed[VK_SHIFT][0] = ASURA_KEYID_LEFTSHIFT;
    s_aeExtendedKeyPressed[VK_SHIFT][1] = ASURA_KEYID_RIGHTSHIFT;
    s_aeExtendedKeyPressed[VK_CONTROL][0] = ASURA_KEYID_LEFTCTRL;
    s_aeExtendedKeyPressed[VK_CONTROL][1] = ASURA_KEYID_RIGHTCTRL;
    s_aeExtendedKeyPressed[VK_MENU][0] = ASURA_KEYID_LEFTALT;
    s_aeExtendedKeyPressed[VK_MENU][1] = ASURA_KEYID_RIGHTALT;
}					                          
#if 01
unsigned char *LoadFile(LPTSTR filename)
{
	unsigned char *bufferPtr;
	long int save_pos, size_of_file;
	FILE *fp;
	fp = fopen(filename,"rb");
	
	if (!fp) goto error;

	save_pos=ftell(fp);
	fseek(fp,0L,SEEK_END);
	size_of_file=ftell(fp);
	
	bufferPtr = new unsigned char [size_of_file];
	if (!bufferPtr)
	{
		goto error;
	}

	fseek(fp,save_pos,SEEK_SET);

	
	if (!fread(bufferPtr, size_of_file,1,fp))
	{
		fclose(fp);
		goto error;
	}
			
	fclose(fp);
	return bufferPtr;
	
error:
	{
		/* error whilst trying to load file */
	//	MessageBox(Asura_Windows::GetWindowHandle(), filename,"Error trying to load file:", MB_OK|MB_APPLMODAL );
		
		return 0;
	}
}
#endif

void Asura_Windows::SetWindowPosition(int iX, int iY)
{
	::SetWindowPos(GetWindowHandle(), NULL, iX, iY, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);

	WinLeftX = iX;
	WinTopY = iY;

	if(!s_bFullScreen)
	{
		s_iWindowedXPos = iX;
		s_iWindowedYPos = iY;
	}
}

void Asura_Windows::RequestMaximiseWindow()
{
	s_bMaximiseWindowRequested = true;
}

void Asura_Windows::MaximiseWindow()
{
	s_bMaximiseWindowRequested = false;

	ShowWindow(GetWindowHandle(), SW_MAXIMIZE);

	s_bIsMaximised = true;
}

void Asura_Windows::SetWindowSize(int iWidth,int iHeight)
{
	if(s_bFullScreen) return;

	//Ignore this if in the middle of processing a WM_SIZE message. RAR 23/08/2006
	if(g_bResizingWindow) return;

	//s_bIsMaximised = false;

	// JEB 17/04/2008 This does not work with all versions of Windows (specifically Vista).
	//// Correct the requested dimensions - add the border size
	//// DMR 23/01/06
	//iWidth  += ((GetSystemMetrics(SM_CXBORDER)+GetSystemMetrics(SM_CXFIXEDFRAME))*2);
	//iHeight += ((GetSystemMetrics(SM_CYBORDER)+GetSystemMetrics(SM_CYFIXEDFRAME))*2)+ GetSystemMetrics(SM_CYCAPTION);

	if(!s_bFullScreen)
	{
		s_iWindowedWidth = iWidth;
		s_iWindowedHeight = iHeight;
	}

	// JEB 17/04/2008 This is the correct way to get the viewport co-ordinates in screen space.
	RECT xRect = { 0, 0, iWidth, iHeight };
	AdjustWindowRect(&xRect, WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_THICKFRAME, FALSE);
	iWidth = xRect.right - xRect.left;
	iHeight = xRect.bottom - xRect.top;

	const int iScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
	const int iScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
	const int iScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
	const int iScreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
	
	//get the current window position
	GetWindowRect(GetWindowHandle(), &xRect);

	//try to choose the new rectanbgle so as to keep it on screen
	if((xRect.left+iWidth)>iScreenWidth)
	{
		if(iWidth<=iScreenWidth)
		{
			xRect.left=iScreenWidth-iWidth;
		}
		else
		{
			xRect.left=iScreenLeft;
		}
	}
	if((xRect.top+iHeight)>iScreenHeight)
	{
		if(iHeight<=iScreenHeight)
		{
			xRect.top=iScreenHeight-iHeight;
		}
		else
		{
			xRect.top=iScreenTop;
		}
	}
	
	xRect.right = xRect.left+iWidth;
	xRect.bottom = xRect.top+iHeight;
	
	MoveWindow(GetWindowHandle(),xRect.left,xRect.top,iWidth,iHeight,TRUE);

	WinLeftX = xRect.left;
	WinTopY = xRect.top;
	WinWidth = iWidth;
	WinHeight = iHeight;
}


bool Asura_Windows::IsMainThread(void)
{
	//Assume that this is the main thread if this is happening before Asura_Windows::Initialise()
	//has been called , to allow early asserts to work.
	//RAR 29/08/2006 
	return GetCurrentThreadId() == s_uMainThreadID || !s_bInitialised;
}

void Asura_Windows::GetWindowViewportCoords(RECT* pxRect)
{
	if(!s_bFullScreen)
	{
		// JEB 17/04/2008 This does not work with all versions of Windows (specifically Vista).
		//GetWindowRect(GetWindowHandle(), pxRect);
	
		//pxRect->left += GetSystemMetrics(SM_CXBORDER) + GetSystemMetrics(SM_CXFIXEDFRAME);
		//pxRect->right -= GetSystemMetrics(SM_CXBORDER) + GetSystemMetrics(SM_CXFIXEDFRAME);

		//pxRect->top += GetSystemMetrics(SM_CYBORDER) + GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION);
		//pxRect->bottom -= GetSystemMetrics(SM_CYBORDER) + GetSystemMetrics(SM_CYFIXEDFRAME);

		// JEB 17/04/2008 This is the correct way to get the viewport co-ordinates in screen space.
		GetClientRect(GetWindowHandle(), pxRect);

		POINT xPoint;

		xPoint.x = pxRect->left; xPoint.y = pxRect->top;
		ClientToScreen(GetWindowHandle(), &xPoint);
		pxRect->left = xPoint.x; pxRect->top = xPoint.y;

		xPoint.x = pxRect->right; xPoint.y = pxRect->bottom;
		ClientToScreen(GetWindowHandle(), &xPoint);
		pxRect->right = xPoint.x; pxRect->bottom = xPoint.y;
	}
	else
	{
		pxRect->left = 0;
		pxRect->right = WinWidth;
		pxRect->top = 0;
		pxRect->bottom = WinHeight;
	}
}


void Asura_Windows::UpdateWindowsCursorVisibility()
{
//	if(s_bFullScreen) return;

	static bool s_bWasVisible = true;

	bool bShouldBeVisible = false;

	if(!s_bFullScreen)
	{
		//make the cursor visible if outside of the viewport , so that it is visible while over the title bar.
		RECT xRect;
		GetWindowViewportCoords(&xRect);

		POINT xCursorPos;
		GetCursorPos(&xCursorPos);

		if(xCursorPos.x < xRect.left || xCursorPos.x >= xRect.right || xCursorPos.y < xRect.top || xCursorPos.y >= xRect.bottom)
		{
			bShouldBeVisible = true;
		}

		//Also make visible while over the window if not in the foreground
		if(GetForegroundWindow() != GetWindowHandle())
		{
			bShouldBeVisible = true;
		}
	}
	//or if we are trying to use the windows cursor instead of rendering the one from Asura_Cursor
	if(Asura_Mouse::GetUseWindowsCursor())
	{
		bShouldBeVisible = true;
	}


	if(bShouldBeVisible != s_bWasVisible)
	{
		//ShowCursor is reference counted ,so only call when the visibilty changes
		s_bWasVisible = bShouldBeVisible;
		ShowCursor(bShouldBeVisible);
	}
}



void Asura_Windows::PreShowDialogBox()
{
	if(Asura_Direct3D::Initialised)
	{
		if(!(Asura_Direct3D::GetPresentParameters(0).Flags & D3DPRESENTFLAG_LOCKABLE_BACKBUFFER) && RunningFullScreen())
		{
			// If we don't have a lockable backbuffer we can't use SetDialogBoxMode
			// We'll try just minimizing it to show the dialog. Irritating but unavoidable, and asserts aren't supposed to be fun anyway :-)
			SendMessage(Asura_Windows::GetWindowHandle(), WM_SYSCOMMAND, SC_MINIMIZE, NULL);
		}
	}

	//If in subwindow mode the windows cursor may be hidden , so try to show it.
	//(This is reference counted , so it doesn't matter if its not hidden)
	ShowCursor(TRUE);
}

void Asura_Windows::PostShowDialogBox()
{
	//Hide cursor again if needed
	ShowCursor(FALSE);


	if(Asura_Direct3D::Initialised)
	{

		// reset user input so keys don't get 'stuck down'
		Asura_UserInput::Reset();

		if(!(Asura_Direct3D::GetPresentParameters(0).Flags & D3DPRESENTFLAG_LOCKABLE_BACKBUFFER) && RunningFullScreen())
		{
			// Re-maximize
			SendMessage(Asura_Windows::GetWindowHandle(), WM_SYSCOMMAND, SC_MAXIMIZE, NULL);
		}
	}
}

void Asura_Windows::SetIdlePriorityWhileMinimised(bool bIdle)
{
	if(bIdle != s_bIdlePriorityWhileMinimised)
	{
		s_bIdlePriorityWhileMinimised = bIdle;

		if(!ApplicationIsActive)
		{
			SetPriorityClass(GetCurrentProcess(),s_bIdlePriorityWhileMinimised ? IDLE_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS);
		}
	}
}

u_int Asura_Windows::GetUserNameFromPC( Asura_Char* pszRetUserName, u_int uMaxUserNameLength)
{
	u_int uLength = uMaxUserNameLength;
	
	Asura_Assert( pszRetUserName, "Asura_Windows::GetUserNameFromPC - NULL char* passed.");
	if( pszRetUserName )
	{
		if( !::GetUserNameA( pszRetUserName, reinterpret_cast<LPDWORD>(&uLength)))
		{
			uLength = 0;
			pszRetUserName[0] = '\0';
		}
	}
	else
	{
		uLength = 0;
	}
	return uLength;
}

void Asura_Windows::SetWindowTitle(const char* szTitle)
{
	s_xTitle = szTitle;
	if(s_uNumWindowHandles && s_pxWindowHandles[0])
	{
		SetWindowText(s_pxWindowHandles[0], szTitle);
	}
}

bool Asura_Windows::GetDesktopSize( u_int* puWidth, u_int* puHeight)
{
	bool bSuccess = false;

	if( puWidth != NULL)
	{
		*puWidth = GetSystemMetrics(SM_CXSCREEN);
	}

	if( puHeight)
	{
		*puHeight = GetSystemMetrics(SM_CYSCREEN);
	}

		//HWND hwnd = ::GetDesktopWindow();
		//RECT rect, rect2;
		//::GetWindowRect( hwnd, &rect);
		//::GetClientRect( hwnd, &rect2);
	return bSuccess;
}

// copy the string to windows clipboard
bool Asura_Windows::CopyToClipboard( const char* const szString )
{
	if( !OpenClipboard( GetWindowHandle() ) ) 
	{
		return( false );
	}
	EmptyClipboard();

	const int iLength = ( szString == NULL ) ? 0 : strlen( szString );
	bool bOK = ( iLength == 0 );
	if( iLength > 0 )
	{
		HGLOBAL hCopy = GlobalAlloc( GMEM_MOVEABLE, ( iLength + 1 ) ); 
		if( hCopy != NULL )
		{ 
			char* const szCopy = (char*)GlobalLock( hCopy ); 
			if( szCopy != NULL )
			{
				strcpy( szCopy, szString );
				GlobalUnlock( hCopy );

				SetClipboardData( CF_TEXT, hCopy ); 
				bOK = true;
			}
		} 
	}
	CloseClipboard(); 
	return( bOK );
}

// copy the contents of windows clipboard to the specified buffer
bool Asura_Windows::CopyFromClipboard( char* const szBuffer, const int iBufferSize )
{
	if( !IsClipboardFormatAvailable( CF_TEXT ) ) 
	{
		return( false );
	}
	if( !OpenClipboard( GetWindowHandle() ) ) 
	{
		return( false );
	}

	bool bOK = false;
	HGLOBAL hClipboardData = GetClipboardData( CF_TEXT ); 
	if( hClipboardData != NULL )
	{ 
		char* szString = (char*)GlobalLock( hClipboardData ); 
		if( szString != NULL ) 
		{ 
			if( ( szBuffer != NULL ) && ( iBufferSize > 0 ) )
			{
				if( iBufferSize > 1 )
				{
					strncpy( szBuffer, szString, iBufferSize - 1 );
				}
				// make sure it is terminated
				szBuffer[ iBufferSize - 1 ] = 0;
			}

			GlobalUnlock( hClipboardData ); 
			bOK = true;
		} 
	} 
	CloseClipboard(); 
	return( bOK );
}

void Asura_Windows::SetCommandLineWindowPosition(int iXPos, int iYPos)
{
	s_xCommandLineInfo.bPositionSet = true;
	s_xCommandLineInfo.iWindowXPos = iXPos;
	s_xCommandLineInfo.iWindowYPos = iYPos;
}

bool Asura_Windows::GetCommandLineWindowPosition(int& iXPos, int& iYPos)
{
	if(s_xCommandLineInfo.bPositionSet)
	{
		iXPos = s_xCommandLineInfo.iWindowXPos;
		iYPos = s_xCommandLineInfo.iWindowYPos;

		return true;
	}

	return false;
}

void Asura_Windows::SetCommandLineWindowSize(int iXSize, int iYSize)
{
	s_xCommandLineInfo.bSizeSet = true;
	s_xCommandLineInfo.iWindowXSize = iXSize;
	s_xCommandLineInfo.iWindowYSize = iYSize;
}

bool Asura_Windows::GetCommandLineWindowSize(int& iXSize, int& iYSize)
{
	if(s_xCommandLineInfo.bSizeSet)
	{
		iXSize = s_xCommandLineInfo.iWindowXSize;
		iYSize = s_xCommandLineInfo.iWindowYSize;

		return true;
	}

	return false;
}
