/*
 * OGLWindow.cpp
 * Jonathan Boldiga
 * 09/02/03
 *
 * Description: Implements windows handlers.
 *
*/

#define WIN32_MEAN_AND_LEAN
#define WIN32_EXTRA_LEAN

#include "OGLWindow.h"

bool leftMouseButton = false;			
bool rightMouseButton = false;			
LPARAM mouseDrag;					

//Setup the pixel format for openGL
void OGLWindow::SetupPixelFormat(){
	int pixelFormat;

	PIXELFORMATDESCRIPTOR pfd = {	
		sizeof(PIXELFORMATDESCRIPTOR),	// size
		1,								// version
		PFD_SUPPORT_OPENGL |
		PFD_DRAW_TO_WINDOW |
		PFD_DOUBLEBUFFER,				// support float-buffering
		PFD_TYPE_RGBA,					// color type
		16,								// prefered color depth
		0, 0, 0, 0, 0, 0,				// color bits (ignored)
		0,								// no alpha buffer
		0,								// alpha bits (ignored)
		0,								// no accumulation buffer
		0, 0, 0, 0,						// accum bits (ignored)
		16,								// depth buffer
		0,								// no stencil buffer
		0,								// no auxiliary buffers
		PFD_MAIN_PLANE,					// main layer
		0,								// reserved
		0, 0, 0,						// no layer, visible, damage masks
	};

	pixelFormat = ChoosePixelFormat(hDC, &pfd);

	SetPixelFormat(hDC, pixelFormat, &pfd);
}


//Setup a palette
void OGLWindow::SetupPalette(){

	int pixelFormat = GetPixelFormat(hDC);
	PIXELFORMATDESCRIPTOR pfd;
	LOGPALETTE* pPal;
	int paletteSize;

	int redMask, greenMask, blueMask;
	int i;

	DescribePixelFormat(hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

	if (pfd.dwFlags & PFD_NEED_PALETTE)
		paletteSize = 1 << pfd.cColorBits;
	else
		return;

	pPal = (LOGPALETTE*)LocalAlloc(LPTR, sizeof(LOGPALETTE) + paletteSize * sizeof(PALETTEENTRY));
	pPal->palVersion = 0x300;
	pPal->palNumEntries = (short)paletteSize;

	//build a simple RGB color palette
	redMask   = (1 << pfd.cRedBits)   - 1;
	greenMask = (1 << pfd.cGreenBits) - 1;
	blueMask  = (1 << pfd.cBlueBits)  - 1;

	for (i=0; i<paletteSize; ++i){
		pPal->palPalEntry[i].peRed = (BYTE)(
			(((i >> pfd.cRedShift) & redMask) * 255) / redMask);
		pPal->palPalEntry[i].peGreen = (BYTE)(
			(((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask);
		pPal->palPalEntry[i].peBlue = (BYTE)(
			(((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask);
		pPal->palPalEntry[i].peFlags = 0;
	}

	hPalette = CreatePalette(pPal);
	LocalFree(pPal);

	if (hPalette){
		DeleteObject(SelectPalette(hDC, hPalette, FALSE));
		RealizePalette(hDC);
	}
}


//Create a window
bool OGLWindow::Create(){
	hDC = GetDC(hWnd);
	SetupPixelFormat();
	SetupPalette();
	hGLRC = wglCreateContext(hDC);
	wglMakeCurrent(hDC, hGLRC);

	return !OnCreate();
}


//Destroy a window
void OGLWindow::Destroy(){
	if (hGLRC){	
		wglMakeCurrent(hDC, NULL);
		wglDeleteContext(hGLRC);
	}

	if (hPalette){	
		DeleteObject(hPalette);
	}

	SetWindowLong(hWnd, GWL_USERDATA, (LONG)NULL);

	DestroyWindow(hWnd);

	// if we're in fullscreen, get out of it
	EndFullScreen();
}


//Palette has changed
void OGLWindow::PaletteChanged(WPARAM wParam){
	if (hGLRC && hPalette && (HWND)wParam != hWnd){	
		wglMakeCurrent(hDC, hGLRC);
		UnrealizeObject(hPalette);
		SelectPalette(hDC, hPalette, FALSE);
		RealizePalette(hDC);
	}
}


//Query a new palette
BOOL OGLWindow::QueryNewPalette(){
	if (hGLRC && hPalette){	
		wglMakeCurrent(hDC, hGLRC);
		UnrealizeObject(hPalette);
		SelectPalette(hDC, hPalette, FALSE);
		RealizePalette(hDC);

		return TRUE;
	}
	return FALSE;
}


//Window paint
void OGLWindow::Paint(){
	PAINTSTRUCT ps;
	BeginPaint(hWnd, &ps);
	EndPaint(hWnd, &ps);
}


//Window resizing
void OGLWindow::Size(){
	if (width > height)
		aspect = width;
	else
		aspect = height;

	if (hGLRC){
		glViewport(0, 0, width, height);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();

		float nearClip = 1.0f / 100.0f;
		float farClip  = 256.0f;//127.0f;

		gluPerspective(54.0f, (float)width/(float)height, 0.1f, 200.0f);

		OnSize();

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}
}


//Get the x normalized position
float OGLWindow::GetNormalizedPosX(LPARAM lParam){	
	return mouseSensitivity * (float)((short)LOWORD(lParam) - width/2) / aspect;
}


//Get the y normalized position
float OGLWindow::GetNormalizedPosY(LPARAM lParam){
	return mouseSensitivity * (float)((short)HIWORD(lParam) - height/2) / aspect;
}


//Get the x mouse position (in window)
int OGLWindow::GetMouseX(LPARAM lParam){
	return LOWORD(lParam);
}


//Get the y mouse position (in window)
int OGLWindow::GetMouseY(LPARAM lParam){
	return HIWORD(lParam);
}


//Windows procedure for openGL
LRESULT APIENTRY WndProcOGL(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
	
	OGLWindow* glWindow = (OGLWindow*)GetWindowLong(hWnd, GWL_USERDATA);

	// make sure window has been created
	if ((glWindow == NULL) && (uMsg != WM_CREATE)){	
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	// dispatch messages
	switch (uMsg){	
		
		//window creation
		case WM_CREATE:{
			HINSTANCE hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
			glWindow = (OGLWindow*)(((LPCREATESTRUCT)lParam)->lpCreateParams);

			SetWindowLong(hWnd, GWL_USERDATA, (LONG)glWindow);
			glWindow->hWnd = hWnd;

			return glWindow->Create();
		}

		case WM_QUIT:
		
		//window close
		case WM_CLOSE:				
			glWindow->Destroy();
			PostQuitMessage(0);
			return 0;

		//window destroy
		case WM_DESTROY:			
			glWindow->Destroy();
			PostQuitMessage(0);
			return 0;

		//activate app
		case WM_ACTIVATEAPP:		
			
			if (wParam){	
				if (glWindow->fullscreen)
					glWindow->BeginFullScreen(glWindow->width, glWindow->height, glWindow->bits);

				ShowWindow(hWnd, SW_RESTORE);
				UpdateWindow(hWnd);
			}
			else{	
				ShowWindow(hWnd, SW_MINIMIZE);
				UpdateWindow(hWnd);
				
				if (glWindow->fullscreen)
					glWindow->EndFullScreen();
			}
			return 0;

		//palette change
		case WM_PALETTECHANGED:		
			glWindow->PaletteChanged(wParam);
			return 0;

		//new palette
		case WM_QUERYNEWPALETTE:		
			return glWindow->QueryNewPalette();

		//paint
		case WM_PAINT:				
			glWindow->Paint();
			return 0;

		//window size
		case WM_SIZE:				
			if (wParam != SIZE_MINIMIZED){	
				glWindow->width = LOWORD(lParam);
				glWindow->height= HIWORD(lParam);
				glWindow->Size();
			}
			return 0;

		//left mouse button
		case WM_LBUTTONDOWN:		
			if (!glWindow->useDirectInput){
				SetCapture(hWnd);
				mouseDrag = lParam;
				leftMouseButton = true;
				glWindow->OnMouseDownL(glWindow->GetNormalizedPosX(lParam), glWindow->GetNormalizedPosY(lParam));
			}
			break;

		//right mouse button
		case WM_RBUTTONDOWN:		
			if (!glWindow->useDirectInput){
				SetCapture(hWnd);
				mouseDrag = lParam;
				rightMouseButton = true;
				glWindow->OnMouseDownR(glWindow->GetNormalizedPosX(lParam), glWindow->GetNormalizedPosY(lParam));
			}
			break;

		//moust movement
		case WM_MOUSEMOVE:{	
			if (!glWindow->useDirectInput){
				int x  = glWindow->mouseX = glWindow->GetMouseX(lParam); 
				int y  = glWindow->mouseY = glWindow->GetMouseY(lParam);
				int dx = x - glWindow->GetMouseX(mouseDrag);
				int dy = y - glWindow->GetMouseY(mouseDrag);

				glWindow->OnMouseMove(x,y, glWindow->width, glWindow->height);

				if (GetCapture() == hWnd){
					//left mouse button
					if (leftMouseButton){
						glWindow->OnMousemouseDragL(x,y, dx,dy);
					}
					
					//right mouse button
					if (rightMouseButton){	
						glWindow->OnMousemouseDragR(x,y, dx,dy);
					}

					mouseDrag = lParam;
				}
			}
			break;
		}

		//left button release
		case WM_LBUTTONUP:			
			if (!glWindow->useDirectInput){
				if ((GetCapture() == hWnd) && !rightMouseButton){
					ReleaseCapture();
				}

				leftMouseButton = false;
				glWindow->OnMouseUpL();
			}
			break;

		//right button release
		case WM_RBUTTONUP:			
			if (!glWindow->useDirectInput){
				if ((GetCapture() == hWnd) && !leftMouseButton){
					ReleaseCapture();
				}
				rightMouseButton = false;
				glWindow->OnMouseUpR();
			}
			break;

		//keyboard key up
		case WM_KEYUP:
			if (!glWindow->useDirectInput){
				glWindow->OnKeyUp((int)wParam);
			}
			return 0;

		//keyboard keydown
		case WM_KEYDOWN:
			if (!glWindow->useDirectInput){
				glWindow->OnKeyDown((int)wParam);
			}
			return 0;

		default:
			break;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}


//Register the window class
bool OGLWindow::RegisterWindow(HINSTANCE hInst){
	WNDCLASS windowClass;		// the window class

	windowClass.style		 = 0;
	windowClass.lpfnWndProc	 = WndProcOGL;
	windowClass.cbClsExtra	 = 0;
	windowClass.cbWndExtra	 = 0;
	windowClass.hInstance	 = hInst;
	windowClass.hIcon		 = 0;
	windowClass.hCursor		 = LoadCursor(NULL, IDC_ARROW);
	windowClass.hbrBackground = NULL;
	windowClass.lpszMenuName	 = NULL;
	windowClass.lpszClassName = "Engine";

	// register the window class
	if (!RegisterClass(&windowClass))
		return false;

	return true;
}


//Begin full screen mode, save original resolution
void OGLWindow::BeginFullScreen(int width, int height, int bits){
	// Save current display state
	iPrevWidth = GetSystemMetrics(SM_CXSCREEN);
	iPrevHeight = GetSystemMetrics(SM_CYSCREEN);
	
	// Set new display state
	DEVMODE dm;
	memset(&dm, 0, sizeof(dm));
	dm.dmSize	= sizeof(DEVMODE);
	dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
	dm.dmPelsWidth	= width;
	dm.dmPelsHeight = height;
	dm.dmBitsPerPel = bits;
	ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
}


//Return to original resolution
void OGLWindow::EndFullScreen(){
	ChangeDisplaySettings(NULL, 0);
}

//Constructor
OGLWindow::OGLWindow(const char *szName, bool fscreen, int w, int h, int b){
	RECT windowRect;
	DWORD dwStyle;
	DWORD dwExStyle;

	fullscreen = fscreen;
	width = w;
	height = h;
	bits = b;

	windowRect.left = (long)0;
	windowRect.right = (long)width;
	windowRect.top = (long)0;
	windowRect.bottom = (long)height;

	if (fullscreen){
		dwExStyle = WS_EX_APPWINDOW;
		dwStyle = WS_POPUP;
	}
	
	else{
		dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
		dwStyle = WS_OVERLAPPEDWINDOW;
	}

	AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);
	
	// create the window
	if (fullscreen){
		BeginFullScreen(width, height, bits);
		hWnd = CreateWindowEx(NULL, "Engine", szName, dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
						  0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
						  NULL, NULL, (HINSTANCE)GetModuleHandle(NULL), (void*)this);
	}

	else{
		hWnd = CreateWindowEx(NULL, "Engine", szName, dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
						  0, 0, width, height, NULL, NULL, (HINSTANCE)GetModuleHandle(NULL),(void*)this);
	}

	if (hWnd == NULL) throw "ERROR: Creating OpenGL Window!";

	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	mouseSensitivity = 2.0f;

	inputSystem = new CInputSystem;
	useDirectInput = inputSystem->Initialize(hWnd, (HINSTANCE)GetModuleHandle(NULL), true, IS_USEKEYBOARD | IS_USEMOUSE);

	SetForegroundWindow(hWnd);
	SetCapture(hWnd);
	SetFocus(hWnd);

	if (useDirectInput){
		inputSystem->AcquireAll();
		inputSystem->Update();
	}

	ShowCursor(FALSE);
}

//Deconstructor
OGLWindow::~OGLWindow(){
	delete inputSystem;

	if (hGLRC)
		Destroy();

	if (fullscreen)
		EndFullScreen();

	ShowCursor(TRUE);
}
