/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source:
		win32 window

	revision history:
		May/23/2000 - Jukka Liimatta - initial revision
		Feb/04/2001 - Jukka Liimatta - renaissance build
*/
#include <prcore/prcore.hpp>
using namespace prcore;



//////////////////////////////////////////////////////
// win32 api                                       //
////////////////////////////////////////////////////

namespace
{
	const char WindowClassName[] = "TwilightWindowWrapperClassname_875443304495";
	bool DestroyWindowOnClose = true;


	int remap(int code)
	{
		switch ( code )
		{
			case VK_ESCAPE:		return KEYCODE_ESC;
			case '0':			return KEYCODE_0;
			case '1':			return KEYCODE_1;
			case '2':			return KEYCODE_2;
			case '3':			return KEYCODE_3;
			case '4':			return KEYCODE_4;
			case '5':			return KEYCODE_5;
			case '6':			return KEYCODE_6;
			case '7':			return KEYCODE_7;
			case '8':			return KEYCODE_8;
			case '9':			return KEYCODE_9;
			case 'A':			return KEYCODE_A;
			case 'B':			return KEYCODE_B;
			case 'C':			return KEYCODE_C;
			case 'D':			return KEYCODE_D;
			case 'E':			return KEYCODE_E;
			case 'F':			return KEYCODE_F;
			case 'G':			return KEYCODE_G;
			case 'H':			return KEYCODE_H;
			case 'I':			return KEYCODE_I;
			case 'J':			return KEYCODE_J;
			case 'K':			return KEYCODE_K;
			case 'L':			return KEYCODE_L;
			case 'M':			return KEYCODE_M;
			case 'N':			return KEYCODE_N;
			case 'O':			return KEYCODE_O;
			case 'P':			return KEYCODE_P;
			case 'Q':			return KEYCODE_Q;
			case 'R':			return KEYCODE_R;
			case 'S':			return KEYCODE_S;
			case 'T':			return KEYCODE_T;
			case 'U':			return KEYCODE_U;
			case 'V':			return KEYCODE_V;
			case 'W':			return KEYCODE_W;
			case 'X':			return KEYCODE_X;
			case 'Y':			return KEYCODE_Y;
			case 'Z':			return KEYCODE_Z;
			case VK_F1:			return KEYCODE_F1;
			case VK_F2:			return KEYCODE_F2;
			case VK_F3:			return KEYCODE_F3;
			case VK_F4:			return KEYCODE_F4;
			case VK_F5:			return KEYCODE_F5;
			case VK_F6:			return KEYCODE_F6;
			case VK_F7:			return KEYCODE_F7;
			case VK_F8:			return KEYCODE_F8;
			case VK_F9:			return KEYCODE_F9;
			case VK_F10:		return KEYCODE_F10;
			case VK_F11:		return KEYCODE_F11;
			case VK_F12:		return KEYCODE_F12;
			case VK_NUMPAD0:	return KEYCODE_NUMPAD0;
			case VK_NUMPAD1:	return KEYCODE_NUMPAD1;
			case VK_NUMPAD2:	return KEYCODE_NUMPAD2;
			case VK_NUMPAD3:	return KEYCODE_NUMPAD3;
			case VK_NUMPAD4:	return KEYCODE_NUMPAD4;
			case VK_NUMPAD5:	return KEYCODE_NUMPAD5;
			case VK_NUMPAD6:	return KEYCODE_NUMPAD6;
			case VK_NUMPAD7:	return KEYCODE_NUMPAD7;
			case VK_NUMPAD8:	return KEYCODE_NUMPAD8;
			case VK_NUMPAD9:	return KEYCODE_NUMPAD9;
			case VK_NUMLOCK:	return KEYCODE_NUMLOCK;
			case VK_DIVIDE:		return KEYCODE_DIVIDE;
			case VK_MULTIPLY:	return KEYCODE_MULTIPLY;
			case VK_SUBTRACT:	return KEYCODE_SUBTRACT;
			case VK_ADD:		return KEYCODE_ADDITION;
			case VK_DECIMAL:	return KEYCODE_DECIMAL;
			case VK_BACK:		return KEYCODE_BACKSPACE;
			case VK_TAB:		return KEYCODE_TAB;
			case VK_RETURN:		return KEYCODE_RETURN;
			case VK_LCONTROL:	return KEYCODE_LEFT_CTRL;
			case VK_RCONTROL:	return KEYCODE_RIGHT_CTRL;
			case VK_LSHIFT:		return KEYCODE_LEFT_SHIFT;
			case VK_RSHIFT:		return KEYCODE_RIGHT_SHIFT;
			case VK_SPACE:		return KEYCODE_SPACE;
			case VK_PRINT:		return KEYCODE_PRNT_SCRN;
			case VK_SCROLL:		return KEYCODE_SCROLL_LOCK;
			case VK_PRIOR:		return KEYCODE_PGUP;
			case VK_NEXT:		return KEYCODE_PGDN;
			case VK_INSERT:		return KEYCODE_INS;
			case VK_DELETE:		return KEYCODE_DEL;
			case VK_HOME:		return KEYCODE_HOME;
			case VK_END:		return KEYCODE_END;
			case VK_LEFT:		return KEYCODE_LEFT;
			case VK_RIGHT:		return KEYCODE_RIGHT;
			case VK_UP:			return KEYCODE_UP;
			case VK_DOWN:		return KEYCODE_DOWN;
			default:			return 0;
		}

		// the following cannot be handled by the EventKeyboard()
		// ----------------------------------------------------------------
		// KEYCODE_ENTER;
		// KEYCODE_LEFT_ALT;
		// KEYCODE_RIGHT_ALT;
		// KEYCODE_CAPSLOCK;
	}


	LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
	{
		WindowBase* window = (WindowBase*)GetWindowLong( hwnd, GWL_USERDATA );

		switch ( iMsg )
		{
			case WM_CREATE:
				break;

			case WM_TIMER:
				break;

			case WM_KEYDOWN:
			{
				int v = remap((int)wParam);
				if ( window )
					window->DispatchEventKeyboard(v,true);

				return 0;
			}

			case WM_KEYUP:
			{
				int v = remap((int)wParam);
				if ( window )
					window->DispatchEventKeyboard(v,false);

				return 0;
			}

			case WM_PAINT:
			{
				PAINTSTRUCT paint;
				BeginPaint( hwnd, &paint );

				if ( window )
					window->EventDraw();

				EndPaint( hwnd, &paint );
				ValidateRect( hwnd, NULL );
				return 0;
			}

			case WM_SIZE:
			{
				int width = LOWORD(lParam);
				int height = HIWORD(lParam);

				if ( window )
					window->EventSize( width, height );
				return 0;
			}

			case WM_ACTIVATE:
			{
				bool enter = (LOWORD(wParam) == WA_INACTIVE) ? false : true;

				if ( window )
					window->EventFocus( enter );
				return 0;
			}

			case WM_DESTROY:
			{
				if ( window )
				{
					DestroyWindowOnClose = false;
					window->Close();
					window->MainBreak();
					DestroyWindowOnClose = true;
				}

				SetWindowLong( hwnd, GWL_USERDATA, (LONG)NULL );

				return 0;
			}
		}
		return DefWindowProc(hwnd, iMsg, wParam, lParam);
	}


	void RegisterWindowClass(HINSTANCE hInstance, bool close)
	{
		WNDCLASSEX wndClass;

		wndClass.cbSize         = sizeof(wndClass);
		wndClass.style          = CS_HREDRAW | CS_VREDRAW ;
		wndClass.lpfnWndProc    = WndProc;
		wndClass.cbClsExtra     = 0;
		wndClass.cbWndExtra     = 0;
		wndClass.hInstance      = hInstance;
		wndClass.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
		wndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
		wndClass.hbrBackground  = NULL;
		wndClass.lpszMenuName   = NULL;
		wndClass.lpszClassName  = WindowClassName;
		wndClass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);

		if ( close==false )
			wndClass.style |= CS_NOCLOSE;

		RegisterClassEx( &wndClass );
	}
	
} // namespace


//////////////////////////////////////////////////////
// WindowBase                                      //
////////////////////////////////////////////////////

WindowBase::WindowBase()
{
	mWinHandle.hwnd = NULL;
	mMainBreak = false;
	
	for ( int i=0; i<256; i++ )
	{
		mKeyDown[i] = false;
	}
}


WindowBase::~WindowBase()
{
	Close();
}


bool WindowBase::Open(int width, int height, const char* name, int style)
{
	if ( IsOpen() )
		Close();

	uint32 mask = (style & WINDOW_FRAME) ? WS_OVERLAPPEDWINDOW : WS_POPUP;
	if ( !(style & WINDOW_TITLE) ) mask &= ~WS_CAPTION;
	if ( !(style & WINDOW_SIZE) ) mask &= ~WS_SIZEBOX;
	if ( !(style & WINDOW_MINIMIZE) ) mask &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
	//TODO: move, alwaysontop

	// register window class
	HINSTANCE hInstance = NULL;
	RegisterWindowClass( hInstance, (style & WINDOW_CLOSE) != 0 );

	RECT r;
	r.left 	 = 0;
	r.top 	 = 0;
	r.right  = width - 1;
	r.bottom = height - 1;
	AdjustWindowRect(&r,mask,FALSE);

	// create window
	mWinHandle.hwnd = CreateWindow(
		WindowClassName,name,mask,0,0,width,height,NULL,NULL,hInstance,NULL);

	// set win32 hwnd userdata to point to the window object
	SetWindowLong( mWinHandle.hwnd, GWL_USERDATA, (LONG)this );
	UpdateWindow( mWinHandle.hwnd );

	// default behaviour
	ShowWindow();
	ShowCursor();

	return true;
}


bool WindowBase::Close()
{
	if ( IsOpen() )
	{
		EventClose();

		if ( DestroyWindowOnClose )
			DestroyWindow( mWinHandle.hwnd );

		DestroyWindowOnClose = true;
		mWinHandle.hwnd = NULL;

		return true;
	}

	return false;
}


void WindowBase::Rename(const char* name)
{
	SetWindowText( mWinHandle.hwnd, name );
}


void WindowBase::Resize(int width, int height)
{
	SetWindowPos(
		mWinHandle.hwnd,HWND_TOP,0,0,width,height,SWP_NOMOVE|SWP_NOZORDER);
}


void WindowBase::Move(int x, int y)
{
	SetWindowPos(
		mWinHandle.hwnd,HWND_TOP,x,y,0,0,SWP_NOSIZE|SWP_NOZORDER);
}


void WindowBase::ShowCursor()
{
	while ( ::ShowCursor(TRUE) < 0 )
	{
	}
}


void WindowBase::HideCursor()
{
	while ( ::ShowCursor(FALSE) >= 0 )
	{
	}
}


void WindowBase::ShowWindow()
{
	::ShowWindow( mWinHandle.hwnd, SW_SHOW );
}


void WindowBase::HideWindow()
{
	::ShowWindow( mWinHandle.hwnd, SW_HIDE );
}


void WindowBase::MainLoop()
{
	// enter the message loop
	MSG msg;
	ZeroMemory(&msg,sizeof(msg));

	// message loop
	while( msg.message!=WM_QUIT && mMainBreak==false )
	{
		if ( PeekMessage(&msg,NULL,0U,0U,PM_REMOVE) )
		{
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
		else
		{
			if ( !EventMain() )
				break;
		}
	}
}


void WindowBase::MainBreak()
{
	mMainBreak = true;
}


bool WindowBase::EventMain()
{
	// default handler
	// -> keep running ( return true )
	return true;
}


void WindowBase::EventDraw()
{
	// default handler
	// -> do nothing
}


void WindowBase::EventSize(int width, int height)
{
	// default handler
	// -> do nothing
}


void WindowBase::EventKeyboard(int keycode, bool press)
{
	// default handler
	// -> do nothing
}


void WindowBase::EventFocus(bool enter)
{
	// default handler
	// -> do nothing
}


void WindowBase::EventClose()
{
	// default handler
	// -> do nothing
}


bool WindowBase::IsOpen() const
{
	return mWinHandle.hwnd ? true : false;
}


int WindowBase::GetStyle() const
{
	return mWinStyle;
}


WinHandle WindowBase::GetHandle() const
{
	return mWinHandle;
}


Rect WindowBase::GetRect() const
{
	RECT rect;
	GetWindowRect(mWinHandle.hwnd,&rect);
	
	return Rect(
		rect.left,rect.top,
		(rect.right - rect.left) + 1,
		(rect.bottom - rect.top) + 1);
}


float WindowBase::GetAspect() const
{
	Rect rect = GetRect();
	return rect.GetAspect();
}


bool WindowBase::IsDown(int keycode) const
{
	return mKeyDown[keycode&0xff];
}


void WindowBase::DispatchEventKeyboard(int keycode, bool press)
{
	// update keymap
	mKeyDown[keycode&0xff] = press;
	
	// tell the client key was pressed or released
	EventKeyboard(keycode,press);
}
