
#include <Windows.h>


#include <Spectre/Display/Display.h>
#include <Spectre/System/SystemEvent.h>
#include <Spectre/System/Log.h>
#include "Win32Application.h"
#include "Win32Internal.h"
#include "Win32Display.h"

#define WND_CLASSNAME "SPECTRE_WIN32_WNDCLASS"

static bool firstTime = true;

Win32Display::Win32Display() :
m_handle		(NULL),
m_icon			(0),
m_cursor		(0),
m_inResizeModalLoop	(false),
m_minSize		(200, 200)
{
}

Win32Display::~Win32Display()
{
	if (m_icon) {
		DestroyIcon(m_icon);
	}

	if (m_handle) {
		DestroyWindow(m_handle);
	}
}

bool Win32Display::create(DisplayDescription description)
{
	DWORD flags = getWin32Flags(description.decoration);
	int x, y;

	if (firstTime) {
		registerClass();
		firstTime = false;
	}

	centerWindow(x, y, description.mode.width, description.mode.height);

	// Create window.
	m_handle = CreateWindowExA(0, WND_CLASSNAME, "", flags, 
		x, y, description.mode.width, description.mode.height, 
		NULL, NULL, GetModuleHandle(NULL), (LPVOID) this);

	if (!m_handle) {
		log("Win32 - Could not create window: %s", Win32GetMessage(GetLastError()));
		return false;
	}

	setSize(description.mode.width, description.mode.height);

	// Store the size for use later.
	m_size = getSize();

	return true;
}

void Win32Display::destroy()
{
	if (m_handle) {
		DestroyWindow(m_handle);
		m_handle = NULL;
	}
}

void* Win32Display::getHandle() const
{
	return m_handle;
}

bool Win32Display::isValid()
{
	return m_handle != NULL;
}

void Win32Display::setSize(unsigned int width, unsigned int height)
{
	int w, h;
	RECT rect = {0, 0, width, height};
	AdjustWindowRect(&rect, GetWindowLong(m_handle, GWL_STYLE), false);

	w = rect.right - rect.left;
	h = rect.bottom - rect.top;

	::SetWindowPos(m_handle, NULL, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER);
}

Vector2u Win32Display::getSize() const
{
	RECT rect;
	Vector2u size;

	if (GetClientRect(m_handle, &rect)) {
		size.x = rect.right - rect.left;
		size.y = rect.bottom - rect.top;
	}
	return size;
}

void Win32Display::setPosition(unsigned int x, unsigned int y)
{
	::SetWindowPos(m_handle, NULL, x, y, 0, 0, SWP_NOSIZE);
}

void Win32Display::setCaption(const std::string& caption)
{
	::SetWindowText(m_handle, caption.c_str());
}

void Win32Display::showCursor(bool value)
{
	if (value) {
		m_cursor = ::LoadCursor(NULL, IDC_ARROW);
	} else {
		m_cursor = 0;
	}

	::SetCursor(m_cursor);
}

void Win32Display::setIcon(const std::string& icon)
{
	HICON hIcon = (HICON) ::LoadImage(0, icon.c_str(), IMAGE_ICON, 
		0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);

	if (hIcon) {

		if (m_icon) {
			DestroyIcon(m_icon);
		}
		m_icon = hIcon;

		::SendMessage(m_handle, WM_SETICON, ICON_SMALL, (LPARAM) hIcon);
		::SendMessage(m_handle, WM_SETICON, ICON_BIG, (LPARAM) hIcon);
	}
}

void Win32Display::registerClass()
{
	WNDCLASSA wndcl;

	ZeroMemory(&wndcl, sizeof(wndcl));

	wndcl.style		= CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	wndcl.lpfnWndProc	= Win32Display::staticWndProc;
	wndcl.hInstance		= ::GetModuleHandle(NULL);
	wndcl.lpszClassName	= WND_CLASSNAME;

	::RegisterClass(&wndcl);
}

DWORD Win32Display::getWin32Flags(unsigned int flags)
{
	DWORD win32_flags = WS_VISIBLE;

	if (flags == DisplayDecorate::None) {
		win32_flags |= WS_POPUP;
	} else {
		if (flags & DisplayDecorate::Menu) {
			win32_flags |= WS_CAPTION | WS_MINIMIZEBOX;
		}

		if (flags & DisplayDecorate::Resize) {
			win32_flags |= WS_THICKFRAME | WS_MAXIMIZEBOX;
		}

		if (flags & DisplayDecorate::Close) {
			win32_flags |= WS_SYSMENU;
		}
	}
	return win32_flags;
}

void Win32Display::centerWindow(int &x, int &y, int width, int height)
{
	x = (::GetSystemMetrics(SM_CXSCREEN) - width) / 2;
	y = (::GetSystemMetrics(SM_CYSCREEN) - height) / 2;
}

void Win32Display::processResizeMessage(const Vector2u& new_size)
{
	// Check if the size has actually changed.
	if (m_size != new_size) {

		// Update the size and notify the higher layer.
		m_size = new_size;
		onReshape(m_size.x, m_size.y);
	}
}

void Win32Display::processMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message) {
	case WM_SETCURSOR :
		if (LOWORD(lParam) == HTCLIENT) {
			::SetCursor(m_cursor);
		}
		break;
	case WM_DESTROY :
		destroy();
		PostQuitMessage(0);
		break;
	case WM_SETFOCUS :
		OutputDebugString("WM_SETFOCUS\n");
		break;
	case WM_KILLFOCUS :
		OutputDebugString("WM_KILLFOCUS\n");
		break;
	case WM_SIZE :

		if (!m_inResizeModalLoop && (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)) {
			processResizeMessage(getSize());
		}
		break;
	case WM_GETMINMAXINFO :
	{
		MINMAXINFO* info = (MINMAXINFO*)lParam;
		info->ptMinTrackSize.x = m_minSize.x;
		info->ptMinTrackSize.y = m_minSize.y;
		info->ptMaxTrackSize.x = 99999;
		info->ptMaxTrackSize.y = 99999;
		break;
	}
	case WM_ENTERSIZEMOVE :
		m_inResizeModalLoop = true;
		break;
	case WM_EXITSIZEMOVE :
		m_inResizeModalLoop = false;
		processResizeMessage(getSize());
		break;
	}
}

LRESULT CALLBACK Win32Display::staticWndProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) 
{
	Win32Display *display;

	if (message == WM_NCCREATE) {

		LONG_PTR ptr = (LONG_PTR) ((LPCREATESTRUCT)lParam)->lpCreateParams;
		::SetWindowLong(handle, GWL_USERDATA, ptr);

		display = (Win32Display*) ptr;
	} else {
		display = (Win32Display*) ::GetWindowLong(handle, GWL_USERDATA);
	}

	if (display) {
		display->processMessage(message, wParam, lParam);
	}
	return DefWindowProc(handle, message, wParam, lParam);
}
