// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Utility module"
// For conditions of distribution and use, see copyright notice in Config.hpp

// Un grand merci à Laurent Gomila pour la SFML qui m'aura bien aidé à réaliser cette implémentation

#define OEMRESOURCE

#include <Nazara/Utility/Win32/WindowImpl.hpp>
#include <Nazara/Core/ConditionVariable.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/Mutex.hpp>
#include <Nazara/Core/Thread.hpp>
#include <Nazara/Utility/Config.hpp>
#include <Nazara/Utility/Cursor.hpp>
#include <Nazara/Utility/Image.hpp>
#include <Nazara/Utility/Icon.hpp>
#include <Nazara/Utility/Win32/CursorImpl.hpp>
#include <Nazara/Utility/Win32/IconImpl.hpp>
#include <cstdio>
#include <memory>
#include <windowsx.h>
#include <Nazara/Utility/Debug.hpp>

#ifdef _WIN64
	#define GCL_HCURSOR GCLP_HCURSOR
	#define GWL_USERDATA GWLP_USERDATA
#endif

// N'est pas défini avec MinGW
#ifndef MAPVK_VK_TO_VSC
    #define MAPVK_VK_TO_VSC 0
#endif

#undef IsMinimized // Conflit avec la méthode du même nom

namespace
{
	LPTSTR windowsCursors[] =
	{
		IDC_CROSS,       // nzWindowCursor_Crosshair
		IDC_ARROW,       // nzWindowCursor_Default
		IDC_HAND,        // nzWindowCursor_Hand
		IDC_HAND,        // nzWindowCursor_Pointer
		IDC_HELP,        // nzWindowCursor_Help
		IDC_SIZEALL,     // nzWindowCursor_Move
		nullptr,         // nzWindowCursor_None
		IDC_APPSTARTING, // nzWindowCursor_Progress
		IDC_SIZENS,      // nzWindowCursor_ResizeN
		IDC_SIZENS,      // nzWindowCursor_ResizeS
		IDC_SIZENWSE,    // nzWindowCursor_ResizeNW
		IDC_SIZENWSE,    // nzWindowCursor_ResizeSE
		IDC_SIZENESW,    // nzWindowCursor_ResizeNE
		IDC_SIZENESW,    // nzWindowCursor_ResizeSW
		IDC_SIZEWE,      // nzWindowCursor_ResizeE
		IDC_SIZEWE,      // nzWindowCursor_ResizeW
		IDC_IBEAM,       // nzWindowCursor_Text
		IDC_WAIT         // nzWindowCursor_Wait
	};

	static_assert(sizeof(windowsCursors)/sizeof(LPTSTR) == nzWindowCursor_Max+1, "Cursor type array is incomplete");

	const wchar_t* className = L"Nazara Window";
	NzWindowImpl* fullscreenWindow = nullptr;
}

NzWindowImpl::NzWindowImpl(NzWindow* parent) :
m_cursor(nullptr),
m_handle(nullptr),
m_callback(0),
m_style(0),
m_maxSize(-1),
m_minSize(-1),
m_parent(parent),
m_keyRepeat(true),
m_mouseInside(false),
m_smoothScrolling(false),
m_scrolling(0)
{
}

bool NzWindowImpl::Create(const NzVideoMode& mode, const NzString& title, nzUInt32 style)
{
	bool fullscreen = (style & nzWindowStyle_Fullscreen) != 0;
	DWORD win32Style, win32StyleEx;
	unsigned int x, y;
	unsigned int width = mode.width;
	unsigned int height = mode.height;
	if (fullscreen)
	{
		DEVMODE win32Mode;
		std::memset(&win32Mode, 0, sizeof(DEVMODE));
		win32Mode.dmBitsPerPel = mode.bitsPerPixel;
		win32Mode.dmPelsHeight = mode.height;
		win32Mode.dmPelsWidth  = mode.width;
		win32Mode.dmFields	   = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
		win32Mode.dmSize	   = sizeof(DEVMODE);

		if (ChangeDisplaySettings(&win32Mode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
		{
			// Situation extrêmement rare grâce à NzVideoMode::IsValid appelé par NzWindow
			NazaraError("Failed to change display settings for fullscreen, this video mode is not supported by your computer");
			fullscreen = false;
		}
	}

	// Testé une seconde fois car sa valeur peut changer
	if (fullscreen)
	{
		x = 0;
		y = 0;
		win32Style = WS_CLIPCHILDREN | WS_POPUP;

		// Pour cacher la barre des tâches
		// http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543(v=vs.85).aspx
		win32StyleEx = WS_EX_APPWINDOW;

		fullscreenWindow = this;
	}
	else
	{
		win32Style = WS_VISIBLE;
		if (style & nzWindowStyle_Titlebar)
		{
			win32Style |= WS_CAPTION | WS_MINIMIZEBOX;
			if (style & nzWindowStyle_Closable)
				win32Style |= WS_SYSMENU;

			if (style & nzWindowStyle_Resizable)
				win32Style |= WS_MAXIMIZEBOX | WS_SIZEBOX;
		}
		else
			win32Style |= WS_POPUP;

		win32StyleEx = 0;

		RECT rect = {0, 0, static_cast<LONG>(width), static_cast<LONG>(height)};
		AdjustWindowRect(&rect, win32Style, false);
		width = rect.right-rect.left;
		height = rect.bottom-rect.top;

		x = (GetSystemMetrics(SM_CXSCREEN) - width)/2;
		y = (GetSystemMetrics(SM_CYSCREEN) - height)/2;
	}

	m_callback = 0;

	#if NAZARA_UTILITY_THREADED_WINDOW
	NzMutex mutex;
	NzConditionVariable condition;
	m_threadActive = true;

	// On attend que la fenêtre soit créée
	mutex.Lock();
	m_thread = NzThread(WindowThread, &m_handle, win32StyleEx, title.GetWideString().data(), win32Style, x, y, width, height, this, &mutex, &condition);
	condition.Wait(&mutex);
	mutex.Unlock();
	#else
	m_handle = CreateWindowExW(win32StyleEx, className, title.GetWideString().data(), win32Style, x, y, width, height, nullptr, nullptr, GetModuleHandle(nullptr), this);
	#endif

	if (!m_handle)
	{
		NazaraError("Failed to create window: " + NzError::GetLastSystemError());
		return false;
	}

	if (fullscreen)
	{
		SetForegroundWindow(m_handle);
		ShowWindow(m_handle, SW_SHOW);
	}

	m_eventListener = true;
	m_ownsWindow = true;
	#if !NAZARA_UTILITY_THREADED_WINDOW
	m_sizemove = false;
	#endif
	m_style = style;

	// Récupération de la position/taille de la fenêtre (Après sa création)
	RECT clientRect, windowRect;
	GetClientRect(m_handle, &clientRect);
	GetWindowRect(m_handle, &windowRect);

	m_position.Set(windowRect.left, windowRect.top);
	m_size.Set(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);

	return true;
}

bool NzWindowImpl::Create(NzWindowHandle handle)
{
	m_handle = reinterpret_cast<HWND>(handle);

	if (!m_handle || !IsWindow(m_handle))
	{
		NazaraError("Invalid handle");
		return false;
	}

	m_eventListener = false;
	m_ownsWindow = false;
	#if !NAZARA_UTILITY_THREADED_WINDOW
	m_sizemove = false;
	#endif
	m_style = RetrieveStyle(m_handle);

	RECT clientRect, windowRect;
	GetClientRect(m_handle, &clientRect);
	GetWindowRect(m_handle, &windowRect);

	m_position.Set(windowRect.left, windowRect.top);
	m_size.Set(clientRect.right-clientRect.left, clientRect.bottom-clientRect.top);

	return true;
}

void NzWindowImpl::Destroy()
{
	if (m_ownsWindow)
	{
		#if NAZARA_UTILITY_THREADED_WINDOW
		if (m_thread.IsJoinable())
		{
			m_threadActive = false;
			PostMessageW(m_handle, WM_NULL, 0, 0); // Pour réveiller le thread

			m_thread.Join();
		}
		#else
		if (m_handle)
			DestroyWindow(m_handle);
		#endif
	}
	else
		SetEventListener(false);
}

void NzWindowImpl::EnableKeyRepeat(bool enable)
{
	m_keyRepeat = enable;
}

void NzWindowImpl::EnableSmoothScrolling(bool enable)
{
	m_smoothScrolling = enable;
}

NzWindowHandle NzWindowImpl::GetHandle() const
{
	return m_handle;
}

unsigned int NzWindowImpl::GetHeight() const
{
	return m_size.y;
}

NzVector2i NzWindowImpl::GetPosition() const
{
	return m_position;
}

NzVector2ui NzWindowImpl::GetSize() const
{
	return m_size;
}

nzUInt32 NzWindowImpl::GetStyle() const
{
	return m_style;
}

NzString NzWindowImpl::GetTitle() const
{
	unsigned int titleSize = GetWindowTextLengthW(m_handle);
	if (titleSize == 0)
		return NzString();

	titleSize++; // Caractère nul

	std::unique_ptr<wchar_t[]> wTitle(new wchar_t[titleSize]);
	GetWindowTextW(m_handle, wTitle.get(), titleSize);

	return NzString::Unicode(wTitle.get());
}

unsigned int NzWindowImpl::GetWidth() const
{
	return m_size.x;
}

bool NzWindowImpl::HasFocus() const
{
	return GetForegroundWindow() == m_handle;
}

void NzWindowImpl::IgnoreNextMouseEvent(int mouseX, int mouseY)
{
	// Petite astuce ...
	m_mousePos.x = mouseX;
	m_mousePos.y = mouseY;
}

bool NzWindowImpl::IsMinimized() const
{
	return IsIconic(m_handle) == TRUE;
}

bool NzWindowImpl::IsVisible() const
{
	return IsWindowVisible(m_handle) == TRUE;
}

void NzWindowImpl::ProcessEvents(bool block)
{
	if (m_ownsWindow)
	{
		if (block)
			WaitMessage();

		MSG message;
		while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&message);
			DispatchMessageW(&message);
		}
	}
}

void NzWindowImpl::SetCursor(nzWindowCursor cursor)
{
	#ifdef NAZARA_DEBUG
	if (cursor > nzWindowCursor_Max)
	{
		NazaraError("Window cursor out of enum");
		return;
	}
	#endif

	if (cursor != nzWindowCursor_None)
		m_cursor = reinterpret_cast<HCURSOR>(LoadImage(nullptr, windowsCursors[cursor], IMAGE_CURSOR, 0, 0, LR_SHARED));
	else
		m_cursor = nullptr;

	// Pas besoin de libérer le curseur par la suite s'il est partagé
	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
	::SetCursor(m_cursor);
}

void NzWindowImpl::SetCursor(const NzCursor& cursor)
{
	m_cursor = cursor.m_impl->GetCursor();

	::SetCursor(m_cursor);
}

void NzWindowImpl::SetEventListener(bool listener)
{
	if (m_ownsWindow)
		m_eventListener = listener;
	else if (listener != m_eventListener)
	{
		if (listener)
		{
			SetWindowLongPtr(m_handle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
			m_callback = SetWindowLongPtr(m_handle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(MessageHandler));
			m_eventListener = true;
		}
		else if (m_eventListener)
		{
			SetWindowLongPtr(m_handle, GWLP_WNDPROC, m_callback);
			m_eventListener = false;
		}
	}
}

void NzWindowImpl::SetFocus()
{
	SetForegroundWindow(m_handle);
}

void NzWindowImpl::SetIcon(const NzIcon& icon)
{
	HICON iconHandle = icon.m_impl->GetIcon();

	SendMessage(m_handle, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(iconHandle));
	SendMessage(m_handle, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(iconHandle));
}

void NzWindowImpl::SetMaximumSize(int width, int height)
{
	RECT rect = {0, 0, width, height};
	AdjustWindowRect(&rect, GetWindowLongPtr(m_handle, GWL_STYLE), false);

	if (width != -1)
		m_maxSize.x = rect.right-rect.left;
	else
		m_maxSize.x = -1;

	if (height != -1)
		m_maxSize.y = rect.bottom-rect.top;
	else
		m_maxSize.y = -1;
}

void NzWindowImpl::SetMinimumSize(int width, int height)
{
	RECT rect = {0, 0, width, height};
	AdjustWindowRect(&rect, GetWindowLongPtr(m_handle, GWL_STYLE), false);

	if (width != -1)
		m_minSize.x = rect.right-rect.left;
	else
		m_minSize.x = -1;

	if (height != -1)
		m_minSize.y = rect.bottom-rect.top;
	else
		m_minSize.y = -1;
}

void NzWindowImpl::SetPosition(int x, int y)
{
	SetWindowPos(m_handle, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

void NzWindowImpl::SetSize(unsigned int width, unsigned int height)
{
	// SetWindowPos demande la taille totale de la fenêtre
	RECT rect = {0, 0, static_cast<LONG>(width), static_cast<LONG>(height)};
	AdjustWindowRect(&rect, GetWindowLongPtr(m_handle, GWL_STYLE), false);

	SetWindowPos(m_handle, nullptr, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER);
}

void NzWindowImpl::SetStayOnTop(bool stayOnTop)
{
	if (stayOnTop)
		SetWindowPos(m_handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
	else
		SetWindowPos(m_handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}

void NzWindowImpl::SetTitle(const NzString& title)
{
	SetWindowTextW(m_handle, title.GetWideString().data());
}

void NzWindowImpl::SetVisible(bool visible)
{
	ShowWindow(m_handle, (visible) ? SW_SHOW : SW_HIDE);
}

bool NzWindowImpl::HandleMessage(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
	// Inutile de récupérer des évènements ne venant pas de notre fenêtre
	if (m_handle != window)
		return false;

	switch (message)
	{
		case WM_DESTROY:
			if (fullscreenWindow == this)
				ChangeDisplaySettings(nullptr, 0);

			break;

		case WM_SETCURSOR:
			// http://msdn.microsoft.com/en-us/library/windows/desktop/ms648382(v=vs.85).aspx
			if (LOWORD(lParam) == HTCLIENT)
				::SetCursor(m_cursor);

			break;

		case WM_WINDOWPOSCHANGING:
		{
			WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam);

			pos->cx = std::max(pos->cx, m_minSize.x);
			pos->cy = std::max(pos->cy, m_minSize.y);

			if (m_maxSize.x >= 0)
				pos->cx = std::min(pos->cx, m_maxSize.x);

			if (m_maxSize.y >= 0)
				pos->cy = std::min(pos->cy, m_maxSize.y);

			break;
		}

		default:
			break;
	}

	if (m_eventListener)
	{
		switch (message)
		{
			case WM_CHAR:
			{
				// http://msdn.microsoft.com/en-us/library/ms646267(VS.85).aspx
				bool repeated = ((HIWORD(lParam) & KF_REPEAT) != 0);
				if (m_keyRepeat || !repeated)
				{
					NzEvent event;
					event.type = nzEventType_TextEntered;
					event.text.character = static_cast<char32_t>(wParam);
					event.text.repeated = repeated;
					m_parent->PushEvent(event);
				}

				break;
			}

			case WM_CLOSE:
			{
				NzEvent event;
				event.type = nzEventType_Quit;
				m_parent->PushEvent(event);

				return true; // Afin que Windows ne ferme pas la fenêtre automatiquement
			}

			#if !NAZARA_UTILITY_THREADED_WINDOW
			case WM_ENTERSIZEMOVE:
			{
				m_sizemove = true;
				break;
			}

			case WM_EXITSIZEMOVE:
			{
				m_sizemove = false;

				// On vérifie ce qui a changé
				RECT clientRect, windowRect;
				GetClientRect(m_handle, &clientRect);
				GetWindowRect(m_handle, &windowRect);

				NzVector2i position(windowRect.left, windowRect.top);
				if (m_position != position)
				{
					m_position = position;

					NzEvent event;
					event.type = nzEventType_Moved;
					event.position.x = position.x;
					event.position.y = position.y;
					m_parent->PushEvent(event);
				}

				NzVector2ui size(clientRect.right-clientRect.left, clientRect.bottom-clientRect.top);
				if (m_size != size)
				{
					m_size = size;

					NzEvent event;
					event.type = nzEventType_Resized;
					event.size.width = size.x;
					event.size.height = size.y;
					m_parent->PushEvent(event);
				}
			}
			#endif

			case WM_KEYDOWN:
			case WM_SYSKEYDOWN:
			{
				// http://msdn.microsoft.com/en-us/library/ms646267(VS.85).aspx
				bool repeated = ((HIWORD(lParam) & KF_REPEAT) != 0);
				if (m_keyRepeat || !repeated)
				{
					NzEvent event;
					event.type = nzEventType_KeyPressed;
					event.key.code = ConvertVirtualKey(wParam, lParam);
					event.key.alt = ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0);
					event.key.control = ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0);
					event.key.repeated = repeated;
					event.key.shift = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
					event.key.system = (((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) || ((GetAsyncKeyState(VK_RWIN) & 0x8000) != 0));
					m_parent->PushEvent(event);
				}

				break;
			}

			case WM_KEYUP:
			case WM_SYSKEYUP:
			{
				// http://msdn.microsoft.com/en-us/library/ms646267(VS.85).aspx
				NzEvent event;
				event.type = nzEventType_KeyReleased;
				event.key.code = ConvertVirtualKey(wParam, lParam);
				event.key.alt = ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0);
				event.key.control = ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0);
				event.key.shift = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
				event.key.system = ((GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000));
				m_parent->PushEvent(event);

				break;
			}

			case WM_KILLFOCUS:
			{
				NzEvent event;
				event.type = nzEventType_LostFocus;
				m_parent->PushEvent(event);

				break;
			}

			case WM_LBUTTONDBLCLK:
			{
				// Cet évènement est généré à la place d'un WM_LBUTTONDOWN lors d'un double-clic.
				// Comme nous désirons quand même notifier chaque clic, nous envoyons les deux évènements.
				NzEvent event;
				event.mouseButton.button = NzMouse::Left;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);

				event.type = nzEventType_MouseButtonDoubleClicked;
				m_parent->PushEvent(event);

				event.type = nzEventType_MouseButtonPressed;
				m_parent->PushEvent(event);

				break;
			}

			case WM_LBUTTONDOWN:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonPressed;
				event.mouseButton.button = NzMouse::Left;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			case WM_LBUTTONUP:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonReleased;
				event.mouseButton.button = NzMouse::Left;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			case WM_MBUTTONDBLCLK:
			{
				NzEvent event;
				event.mouseButton.button = NzMouse::Middle;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);

				event.type = nzEventType_MouseButtonDoubleClicked;
				m_parent->PushEvent(event);

				event.type = nzEventType_MouseButtonPressed;
				m_parent->PushEvent(event);

				break;
			}

			case WM_MBUTTONDOWN:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonPressed;
				event.mouseButton.button = NzMouse::Middle;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			case WM_MBUTTONUP:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonReleased;
				event.mouseButton.button = NzMouse::Middle;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			// Nécessite un appel précédent à TrackMouseEvent (Fait dans WM_MOUSEMOVE)
			// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645615(v=vs.85).aspx
			case WM_MOUSELEAVE:
			{
				m_mouseInside = false;

				NzEvent event;
				event.type = nzEventType_MouseLeft;
				m_parent->PushEvent(event);
				break;
			}

			case WM_MOUSEMOVE:
			{
				int currentX = GET_X_LPARAM(lParam);
				int currentY = GET_Y_LPARAM(lParam);

				if (!m_mouseInside)
				{
					m_mouseInside = true;

					// On créé un évènement pour être informé de la sortie de la fenêtre
					TRACKMOUSEEVENT mouseEvent;
					mouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
					mouseEvent.dwFlags = TME_LEAVE;
					mouseEvent.hwndTrack = m_handle;

					TrackMouseEvent(&mouseEvent);

					NzEvent event;
					event.type = nzEventType_MouseEntered;
					m_parent->PushEvent(event);

					event.type = nzEventType_MouseMoved;

					// Le delta sera 0
					event.mouseMove.deltaX = 0;
					event.mouseMove.deltaY = 0;

					event.mouseMove.x = currentX;
					event.mouseMove.y = currentY;

					m_mousePos.x = currentX;
					m_mousePos.y = currentY;

					m_parent->PushEvent(event);
					break;
				}

				// Si la souris n'a pas bougé (Ou qu'on veut ignorer l'évènement)
				if (m_mousePos.x == currentX && m_mousePos.y == currentY)
					break;

				NzEvent event;
				event.type = nzEventType_MouseMoved;
				event.mouseMove.deltaX = currentX - m_mousePos.x;
				event.mouseMove.deltaY = currentY - m_mousePos.y;
				event.mouseMove.x = currentX;
				event.mouseMove.y = currentY;

				m_mousePos.x = currentX;
				m_mousePos.y = currentY;

				m_parent->PushEvent(event);
				break;
			}

			case WM_MOUSEWHEEL:
			{
				if (m_smoothScrolling)
				{
					NzEvent event;
					event.type = nzEventType_MouseWheelMoved;
					event.mouseWheel.delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wParam))/WHEEL_DELTA;
					m_parent->PushEvent(event);
				}
				else
				{
					m_scrolling += GET_WHEEL_DELTA_WPARAM(wParam);
					if (std::abs(m_scrolling) >= WHEEL_DELTA)
					{
						NzEvent event;
						event.type = nzEventType_MouseWheelMoved;
						event.mouseWheel.delta = static_cast<float>(m_scrolling/WHEEL_DELTA);
						m_parent->PushEvent(event);

						m_scrolling %= WHEEL_DELTA;
					}
				}
				break;
			}

			case WM_MOVE:
			{
				RECT windowRect;
				GetWindowRect(m_handle, &windowRect);

				NzEvent event;
				event.type = nzEventType_Moved;
				event.position.x = windowRect.left;
				event.position.y = windowRect.top;
				m_parent->PushEvent(event);

				break;
			}

			case WM_RBUTTONDBLCLK:
			{
				NzEvent event;
				event.mouseButton.button = NzMouse::Right;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);

				event.type = nzEventType_MouseButtonDoubleClicked;
				m_parent->PushEvent(event);

				event.type = nzEventType_MouseButtonPressed;
				m_parent->PushEvent(event);

				break;
			}

			case WM_RBUTTONDOWN:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonPressed;
				event.mouseButton.button = NzMouse::Right;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			case WM_RBUTTONUP:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonReleased;
				event.mouseButton.button = NzMouse::Right;
				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			case WM_SETFOCUS:
			{
				NzEvent event;
				event.type = nzEventType_GainedFocus;
				m_parent->PushEvent(event);

				break;
			}

			case WM_SIZE:
			{
				#if NAZARA_UTILITY_THREADED_WINDOW
				if (wParam != SIZE_MINIMIZED)
				#else
				if (!m_sizemove && wParam != SIZE_MINIMIZED)
				#endif
				{
					RECT rect;
					GetClientRect(m_handle, &rect);

					NzVector2ui size(rect.right-rect.left, rect.bottom-rect.top); // On récupère uniquement la taille de la zone client
					if (m_size == size)
						break;

					m_size = size;

					NzEvent event;
					event.type = nzEventType_Resized;
					event.size.width = size.x;
					event.size.height = size.y;
					m_parent->PushEvent(event);
				}
				break;
			}

			case WM_UNICHAR:
			{
				// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646288(v=vs.85).aspx
				if (wParam != UNICODE_NOCHAR)
				{
					bool repeated = ((HIWORD(lParam) & KF_REPEAT) != 0);
					if (m_keyRepeat || !repeated)
					{
						NzEvent event;
						event.type = nzEventType_TextEntered;
						event.text.character = static_cast<char32_t>(wParam);
						event.text.repeated = repeated;
						m_parent->PushEvent(event);
					}

					return true;
				}
			}

			case WM_XBUTTONDBLCLK:
			{
				NzEvent event;
				if (HIWORD(wParam) == XBUTTON1)
					event.mouseButton.button = NzMouse::XButton1;
				else
					event.mouseButton.button = NzMouse::XButton2;

				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);

				event.type = nzEventType_MouseButtonDoubleClicked;
				m_parent->PushEvent(event);

				event.type = nzEventType_MouseButtonPressed;
				m_parent->PushEvent(event);

				break;
			}

			case WM_XBUTTONDOWN:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonPressed;

				if (HIWORD(wParam) == XBUTTON1)
					event.mouseButton.button = NzMouse::XButton1;
				else
					event.mouseButton.button = NzMouse::XButton2;

				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			case WM_XBUTTONUP:
			{
				NzEvent event;
				event.type = nzEventType_MouseButtonReleased;

				if (HIWORD(wParam) == XBUTTON1)
					event.mouseButton.button = NzMouse::XButton1;
				else
					event.mouseButton.button = NzMouse::XButton2;

				event.mouseButton.x = GET_X_LPARAM(lParam);
				event.mouseButton.y = GET_Y_LPARAM(lParam);
				m_parent->PushEvent(event);

				break;
			}

			default:
				break;
		}
	}

	#if NAZARA_UTILITY_WINDOWS_DISABLE_MENU_KEYS
	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx
	if (message == WM_SYSCOMMAND && wParam == SC_KEYMENU)
		return true;
	#endif

	return false;
}

bool NzWindowImpl::Initialize()
{
	// Nous devons faire un type Unicode pour que la fenêtre le soit également
	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574(v=vs.85).aspx
	WNDCLASSW windowClass;
	windowClass.cbClsExtra = 0;
	windowClass.cbWndExtra = 0;
	windowClass.hbrBackground = nullptr;
	windowClass.hCursor = nullptr; // Le curseur est définit dynamiquement
	windowClass.hIcon = nullptr; // L'icône est définie dynamiquement
	windowClass.hInstance = GetModuleHandle(nullptr);
	windowClass.lpfnWndProc = MessageHandler;
	windowClass.lpszClassName = className;
	windowClass.lpszMenuName = nullptr;
	windowClass.style = CS_DBLCLKS; // Gestion du double-clic

	return RegisterClassW(&windowClass) != 0;
}

void NzWindowImpl::Uninitialize()
{
	UnregisterClassW(className, GetModuleHandle(nullptr));
}

NzKeyboard::Key NzWindowImpl::ConvertVirtualKey(WPARAM key, LPARAM flags)
{
	switch (key)
	{
		case VK_CONTROL:	return (HIWORD(flags) & KF_EXTENDED) ? NzKeyboard::RControl : NzKeyboard::LControl;
		case VK_MENU:		return (HIWORD(flags) & KF_EXTENDED) ? NzKeyboard::RAlt : NzKeyboard::LAlt;
		case VK_SHIFT:
		{
			static UINT scancode = MapVirtualKey(VK_SHIFT, MAPVK_VK_TO_VSC);
			return (((flags >> 16) & 0xFF) == scancode) ? NzKeyboard::LShift : NzKeyboard::RShift;
		}

		case 0x30:                 return NzKeyboard::Num0;
		case 0x31:                 return NzKeyboard::Num1;
		case 0x32:                 return NzKeyboard::Num2;
		case 0x33:                 return NzKeyboard::Num3;
		case 0x34:                 return NzKeyboard::Num4;
		case 0x35:                 return NzKeyboard::Num5;
		case 0x36:                 return NzKeyboard::Num6;
		case 0x37:                 return NzKeyboard::Num7;
		case 0x38:                 return NzKeyboard::Num8;
		case 0x39:                 return NzKeyboard::Num9;
		case 0x41:                 return NzKeyboard::A;
		case 0x42:                 return NzKeyboard::B;
		case 0x43:                 return NzKeyboard::C;
		case 0x44:                 return NzKeyboard::D;
		case 0x45:                 return NzKeyboard::E;
		case 0x46:                 return NzKeyboard::F;
		case 0x47:                 return NzKeyboard::G;
		case 0x48:                 return NzKeyboard::H;
		case 0x49:                 return NzKeyboard::I;
		case 0x4A:                 return NzKeyboard::J;
		case 0x4B:                 return NzKeyboard::K;
		case 0x4C:                 return NzKeyboard::L;
		case 0x4D:                 return NzKeyboard::M;
		case 0x4E:                 return NzKeyboard::N;
		case 0x4F:                 return NzKeyboard::O;
		case 0x50:                 return NzKeyboard::P;
		case 0x51:                 return NzKeyboard::Q;
		case 0x52:                 return NzKeyboard::R;
		case 0x53:                 return NzKeyboard::S;
		case 0x54:                 return NzKeyboard::T;
		case 0x55:                 return NzKeyboard::U;
		case 0x56:                 return NzKeyboard::V;
		case 0x57:                 return NzKeyboard::W;
		case 0x58:                 return NzKeyboard::X;
		case 0x59:                 return NzKeyboard::Y;
		case 0x5A:                 return NzKeyboard::Z;
		case VK_ADD:               return NzKeyboard::Add;
		case VK_BACK:              return NzKeyboard::Backspace;
		case VK_BROWSER_BACK:      return NzKeyboard::Browser_Back;
		case VK_BROWSER_FAVORITES: return NzKeyboard::Browser_Favorites;
		case VK_BROWSER_FORWARD:   return NzKeyboard::Browser_Forward;
		case VK_BROWSER_HOME:      return NzKeyboard::Browser_Home;
		case VK_BROWSER_REFRESH:   return NzKeyboard::Browser_Refresh;
		case VK_BROWSER_SEARCH:    return NzKeyboard::Browser_Search;
		case VK_BROWSER_STOP:      return NzKeyboard::Browser_Stop;
		case VK_CAPITAL:           return NzKeyboard::CapsLock;
		case VK_CLEAR:             return NzKeyboard::Clear;
		case VK_DECIMAL:           return NzKeyboard::Decimal;
		case VK_DELETE:            return NzKeyboard::Delete;
		case VK_DIVIDE:            return NzKeyboard::Divide;
		case VK_DOWN:              return NzKeyboard::Down;
		case VK_END:               return NzKeyboard::End;
		case VK_ESCAPE:            return NzKeyboard::Escape;
		case VK_F1:                return NzKeyboard::F1;
		case VK_F2:                return NzKeyboard::F2;
		case VK_F3:                return NzKeyboard::F3;
		case VK_F4:                return NzKeyboard::F4;
		case VK_F5:                return NzKeyboard::F5;
		case VK_F6:                return NzKeyboard::F6;
		case VK_F7:                return NzKeyboard::F7;
		case VK_F8:                return NzKeyboard::F8;
		case VK_F9:                return NzKeyboard::F9;
		case VK_F10:               return NzKeyboard::F10;
		case VK_F11:               return NzKeyboard::F11;
		case VK_F12:               return NzKeyboard::F12;
		case VK_F13:               return NzKeyboard::F13;
		case VK_F14:               return NzKeyboard::F14;
		case VK_F15:               return NzKeyboard::F15;
		case VK_HOME:              return NzKeyboard::Home;
		case VK_INSERT:            return NzKeyboard::Insert;
		case VK_LEFT:              return NzKeyboard::Left;
		case VK_LWIN:              return NzKeyboard::LSystem;
		case VK_MEDIA_NEXT_TRACK:  return NzKeyboard::Media_Next;
		case VK_MEDIA_PLAY_PAUSE:  return NzKeyboard::Media_Play;
		case VK_MEDIA_PREV_TRACK:  return NzKeyboard::Media_Previous;
		case VK_MEDIA_STOP:        return NzKeyboard::Media_Stop;
		case VK_MULTIPLY:          return NzKeyboard::Multiply;
		case VK_NEXT:              return NzKeyboard::PageDown;
		case VK_NUMPAD0:           return NzKeyboard::Numpad0;
		case VK_NUMPAD1:           return NzKeyboard::Numpad1;
		case VK_NUMPAD2:           return NzKeyboard::Numpad2;
		case VK_NUMPAD3:           return NzKeyboard::Numpad3;
		case VK_NUMPAD4:           return NzKeyboard::Numpad4;
		case VK_NUMPAD5:           return NzKeyboard::Numpad5;
		case VK_NUMPAD6:           return NzKeyboard::Numpad6;
		case VK_NUMPAD7:           return NzKeyboard::Numpad7;
		case VK_NUMPAD8:           return NzKeyboard::Numpad8;
		case VK_NUMPAD9:           return NzKeyboard::Numpad9;
		case VK_NUMLOCK:           return NzKeyboard::NumLock;
		case VK_OEM_1:             return NzKeyboard::Semicolon;
		case VK_OEM_2:             return NzKeyboard::Slash;
		case VK_OEM_3:             return NzKeyboard::Tilde;
		case VK_OEM_4:             return NzKeyboard::LBracket;
		case VK_OEM_5:             return NzKeyboard::Backslash;
		case VK_OEM_6:             return NzKeyboard::RBracket;
		case VK_OEM_7:             return NzKeyboard::Quote;
		case VK_OEM_COMMA:         return NzKeyboard::Comma;
		case VK_OEM_MINUS:         return NzKeyboard::Dash;
		case VK_OEM_PERIOD:        return NzKeyboard::Period;
		case VK_OEM_PLUS:          return NzKeyboard::Equal;
		case VK_RIGHT:             return NzKeyboard::Right;
		case VK_PRIOR:             return NzKeyboard::PageUp;
		case VK_PAUSE:             return NzKeyboard::Pause;
		case VK_PRINT:             return NzKeyboard::Print;
		case VK_SCROLL:            return NzKeyboard::ScrollLock;
		case VK_SNAPSHOT:          return NzKeyboard::PrintScreen;
		case VK_SUBTRACT:          return NzKeyboard::Subtract;
		case VK_RETURN:            return NzKeyboard::Return;
		case VK_RWIN:              return NzKeyboard::RSystem;
		case VK_SPACE:             return NzKeyboard::Space;
		case VK_TAB:               return NzKeyboard::Tab;
		case VK_UP:                return NzKeyboard::Up;
		case VK_VOLUME_DOWN:       return NzKeyboard::Volume_Down;
		case VK_VOLUME_MUTE:       return NzKeyboard::Volume_Mute;
		case VK_VOLUME_UP:         return NzKeyboard::Volume_Up;

		default:
			return NzKeyboard::Undefined;
	}
}

LRESULT CALLBACK NzWindowImpl::MessageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
	NzWindowImpl* me;
	if (message == WM_CREATE)
	{
		me = reinterpret_cast<NzWindowImpl*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
		SetWindowLongPtr(window, GWL_USERDATA, reinterpret_cast<LONG_PTR>(me));
	}
	else
		me = reinterpret_cast<NzWindowImpl*>(GetWindowLongPtr(window, GWL_USERDATA));

	if (me)
	{
		if (me->HandleMessage(window, message, wParam, lParam))
			return 0;
		else if (me->m_callback)
			return CallWindowProcW(reinterpret_cast<WNDPROC>(me->m_callback), window, message, wParam, lParam);
	}

	return DefWindowProcW(window, message, wParam, lParam);
}

nzUInt32 NzWindowImpl::RetrieveStyle(HWND handle)
{
	nzUInt32 style = 0;

	LONG_PTR winStyle = GetWindowLongPtr(handle, GWL_STYLE);

	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx
	if (winStyle & WS_CAPTION)
	{
		style |= nzWindowStyle_Titlebar;
		if (winStyle & WS_SYSMENU)
			style |= nzWindowStyle_Closable;

		if (winStyle & WS_MAXIMIZEBOX)
			style |= nzWindowStyle_Resizable;
	}

	if (winStyle & WS_SIZEBOX)
		style |= nzWindowStyle_Resizable;

	// Pour déterminer si la fenêtre est en plein écran, il suffit de vérifier si elle recouvre l'écran
	DEVMODE mode;
	mode.dmSize = sizeof(DEVMODE);
	EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &mode);

	RECT rect;
	if (GetWindowRect(handle, &rect))
	{
		if (static_cast<DWORD>(rect.right-rect.left) == mode.dmPelsWidth && static_cast<DWORD>(rect.bottom-rect.top) == mode.dmPelsHeight)
			style |= nzWindowStyle_Fullscreen;
	}

	return style;
}

#if NAZARA_UTILITY_THREADED_WINDOW
void NzWindowImpl::WindowThread(HWND* handle, DWORD styleEx, const wchar_t* title, DWORD style, unsigned int x, unsigned int y, unsigned int width, unsigned int height, NzWindowImpl* window, NzMutex* mutex, NzConditionVariable* condition)
{
	HWND& winHandle = *handle;
	winHandle = CreateWindowExW(styleEx, className, title, style, x, y, width, height, nullptr, nullptr, GetModuleHandle(nullptr), window);

	mutex->Lock();
	condition->Signal();
	mutex->Unlock(); // mutex et condition sont considérés invalides à partir d'ici

	if (!winHandle)
		return;

	while (window->m_threadActive)
		window->ProcessEvents(true);

	DestroyWindow(winHandle);
}
#endif
