/* 
 * Copyright (c) 2002 Light Weight Java Game Library Project
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are 
 * met:
 * 
 * * Redistributions of source code must retain the above copyright 
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Light Weight Java Game Library' nor the names of 
 *   its contributors may be used to endorse or promote products derived 
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
/**
 * $Id$
 *
 * Base Win32 window
 *
 * @author cix_foo <cix_foo@users.sourceforge.net>
 * @version $Revision$
 */

#define _PRIVATE_WINDOW_H_
#include "Window.h"
#include "org_lwjgl_opengl_Window.h"

bool				oneShotInitialised = false;			// Registers the LWJGL window class
HWND				hwnd = NULL;						// Handle to the window
HDC					hdc = NULL;							// Device context
HGLRC				hglrc = NULL;						// OpenGL context
LPDIRECTINPUT		lpdi = NULL;						// DirectInput
bool				isFullScreen = false;				// Whether we're fullscreen or not
bool				isMinimized = false;				// Whether we're minimized or not
/*JNIEnv *			environment = NULL;					// Cached environment
jclass				window;								// Cached Java Window class*/
extern HINSTANCE	dll_handle;							// Handle to the LWJGL dll
RECT clientSize;

static bool closerequested;
static bool minimized;
static bool focused;
static bool dirty;

//CAS: commented these out as no longer used
//extern	void			tempRestoreDisplayMode();
//extern	void			tempResetDisplayMode();

#define WINDOWCLASSNAME "LWJGL"

/*
 * Utility function to throw an Exception
 */
void throwException(JNIEnv * env, const char * err)
{
	jclass cls = env->FindClass("java/lang/Exception");
	env->ThrowNew(cls, err);
	env->DeleteLocalRef(cls);
}

/*
 * Utility function to throw a RuntimeException
 */
void throwRuntimeException(JNIEnv * env, const char * err)
{
	jclass cls = env->FindClass("java/lang/RuntimeException");
	env->ThrowNew(cls, err);
	env->DeleteLocalRef(cls);
}

/*
 * Find an appropriate pixel format
 */
static int findPixelFormat(JNIEnv *env, unsigned int flags, int bpp, int alpha, int depth, int stencil)
{
	PIXELFORMATDESCRIPTOR pfd = { 
		sizeof(PIXELFORMATDESCRIPTOR),   // size of this pfd 
		1,                     // version number 
		flags,         // RGBA type 
		PFD_TYPE_RGBA,
		(BYTE)bpp,       
		0, 0, 0, 0, 0, 0,      // color bits ignored 
		(BYTE)alpha,       
		0,                     // shift bit ignored 
		0,                     // no accumulation buffer 
		0, 0, 0, 0,            // accum bits ignored 
		(BYTE)depth,       
		(BYTE)stencil,     
		0,                     // No auxiliary buffer 
		PFD_MAIN_PLANE,        // main layer
		0,                     // reserved 
		0, 0, 0                // layer masks ignored
	};

	// get the best available match of pixel format for the device context  
	int iPixelFormat = ChoosePixelFormat(hdc, &pfd);
	if (iPixelFormat == 0) {
		throwException(env, "Failed to choose pixel format");
		return -1;
	}

#ifdef _DEBUG
	printf("Pixel format is %d\n", iPixelFormat);
#endif

	// make that the pixel format of the device context 
	if (SetPixelFormat(hdc, iPixelFormat, &pfd) == FALSE) {
		printf("Failed to set pixel format\n");
		throwException(env, "Failed to choose pixel format");
		return -1;
	}

	// 3. Check the chosen format matches or exceeds our specifications
	PIXELFORMATDESCRIPTOR desc;
	if (DescribePixelFormat(hdc, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &desc) == 0) {
		throwException(env, "Could not describe pixel format");
		return -1;
	}

	if (desc.cColorBits < bpp) {
		throwException(env, "This application requires a greater colour depth");
		return -1;
	}

	if (desc.cAlphaBits < alpha) {
		throwException(env, "This application requires a greater alpha depth");
		return -1;
	}

	if (desc.cStencilBits < stencil) {
		throwException(env, "This application requires a greater stencil depth");
		return -1;
	}

	if (desc.cDepthBits < depth) {
		throwException(env, "This application requires a greater depth buffer depth");
		return -1;
	}

	if ((desc.dwFlags & PFD_GENERIC_FORMAT) != 0 || (desc.dwFlags & PFD_GENERIC_ACCELERATED) != 0) {
		throwException(env, "Mode not supported by hardware");
		return -1;
	}

	if ((desc.dwFlags & flags) != flags) {
		throwException(env, "Capabilities not supported");
		return -1;
	}

	// 4. Initialise other things now
	if (extgl_Open() != 0) {
		throwException(env, "Failed to open extgl");
		return -1;
	}
	return iPixelFormat;
}
/*
 * Create DirectInput.
 * Returns true for success, or false for failure
 */
static bool createDirectInput()
{
	// Create input
	HRESULT ret = DirectInputCreate(dll_handle, DIRECTINPUT_VERSION, &lpdi, NULL);
	if (ret != DI_OK && ret != DIERR_BETADIRECTINPUTVERSION ) {
		printf("Failed to create directinput");
		switch (ret) {
			case DIERR_INVALIDPARAM :
				printf(" - Invalid parameter\n");
				break;
			case DIERR_OLDDIRECTINPUTVERSION :
				printf(" - Old Version\n");
				break;
			case DIERR_OUTOFMEMORY :
				printf(" - Out Of Memory\n");
				break;
			default:
				printf(" - Unknown failure\n");
		}
		return false;
	} else {
		return true;
	}
}

/*
 * Close the window
 */
static void closeWindow()
{
	// Release DirectInput
	if (lpdi != NULL) {
#ifdef _DEBUG
		printf("Destroying directinput\n");
#endif
		lpdi->Release();
		lpdi = NULL;
	}

	// Release device context
	if (hdc != NULL && hwnd != NULL) {
#ifdef _DEBUG
		printf("Releasing DC\n");
#endif
		ReleaseDC(hwnd, hdc);
	}

	// Close the window
	if (hwnd != NULL) {
#ifdef _DEBUG
		printf("Destroy window\n");
#endif
		// Vape the window
		DestroyWindow(hwnd);
#ifdef _DEBUG
		printf("Destroyed window\n");
#endif
		hwnd = NULL;
	}
}

/*
 * Called when the application is alt-tabbed to or from
 */
static void appActivate(bool active)
{
//	if (!active) {
//		tempResetDisplayMode();
//	}
	if (active) {
		SetForegroundWindow(hwnd);
		ShowWindow(hwnd, SW_RESTORE);
	} else if (isFullScreen) {
		ShowWindow(hwnd, SW_MINIMIZE);
	}
//	if (active) {
//		tempRestoreDisplayMode();
//	}
}

/*
 *	WindowProc for the GL window.
 */
LRESULT CALLBACK lwjglWindowProc(HWND hWnd,
							     UINT msg,
							     WPARAM wParam,
							     LPARAM lParam)
{
	switch (msg) {
		// disable screen saver and monitor power down messages which wreak havoc
		case WM_SYSCOMMAND:
		{
			switch (wParam) {
			case SC_SCREENSAVE:
			case SC_MONITORPOWER:
				return 0L;
			case SC_MINIMIZE:
				minimized = true;
				appActivate(false);
				break;
			case SC_RESTORE:
				minimized = false;
				appActivate(true);
				break;
			case SC_CLOSE:
				closerequested = true;
				//don't continue processing this command since this 
				//would shutdown the window, which the application might not want to
				return 0L;
			}
		}
		break;
		case WM_ACTIVATE:
		{
			switch(LOWORD(wParam)) {
			case WA_ACTIVE:
			case WA_CLICKACTIVE:
				minimized = false;
				isMinimized = false;

				break;
			case WA_INACTIVE:
				minimized = true;
				isMinimized = true;

				break;
			}
			appActivate(!isMinimized);
		}
		break;
		case WM_QUIT:
		{
			closerequested = true;
			return 0L;
		}
		case WM_PAINT:
		{
			dirty = true;
		}
	}

	// default action
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

/*
 * Register the LWJGL window class.
 * Returns true for success, or false for failure
 */
static bool registerWindow()
{
	if (!oneShotInitialised) {
		WNDCLASS windowClass;

		windowClass.style = CS_GLOBALCLASS | CS_OWNDC;
		windowClass.lpfnWndProc = lwjglWindowProc;
		windowClass.cbClsExtra = 0;
		windowClass.cbWndExtra = 0;
		windowClass.hInstance = dll_handle;
		windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
		windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
		windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
		windowClass.lpszMenuName = NULL;
		windowClass.lpszClassName = WINDOWCLASSNAME;

		if (RegisterClass(&windowClass) == 0) {
			printf("Failed to register window class\n");
			return false;
		}
#ifdef _DEBUG
		printf("Window registered\n");
#endif
		oneShotInitialised = true;
	}

	return true;
}

/*
 * Handle native Win32 messages
 */
static void handleMessages(JNIEnv * env, jclass clazz)
{
	/*
	 * Now's our chance to deal with Windows messages that are
	 * otherwise just piling up and causing everything not to
	 * work properly
	 */
	MSG msg;
	while (PeekMessage(
		&msg,         // message information
		hwnd,           // handle to window
		0,  // first message
		0,  // last message
		PM_REMOVE      // removal options
		))
	{
		TranslateMessage(&msg);
      	DispatchMessage(&msg);
	};
}

/*
 * Create a window with the specified title, position, size, and
 * fullscreen attribute. The window will have DirectInput associated
 * with it.
 * 
 * Returns true for success, or false for failure
 */
static bool createWindow(const char * title, int x, int y, int width, int height, bool fullscreen)
{
	// 1. Register window class if necessary
	if (!registerWindow())
		return false;

	// 2. Create the window
	int exstyle, windowflags;

	if (fullscreen) {
		exstyle = WS_EX_APPWINDOW | WS_EX_TOPMOST;
		windowflags = WS_POPUP | WS_VISIBLE;
	} else {
		exstyle = WS_EX_APPWINDOW;
		windowflags = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU;
	}

	// If we're not a fullscreen window, adjust the height to account for the
	// height of the title bar:
	clientSize.bottom = height;
	clientSize.left = 0;
	clientSize.right = width;
	clientSize.top = 0;
	
	AdjustWindowRectEx(
	  &clientSize,    // client-rectangle structure
	  windowflags,    // window styles
	  FALSE,       // menu-present option
	  exstyle   // extended window style
	);

	// Create the window now, using that class:
	hwnd = CreateWindowEx (
		 exstyle, 
		 WINDOWCLASSNAME,
		 title,
		 windowflags,
		 x, y, clientSize.right - clientSize.left, clientSize.bottom - clientSize.top,
		 NULL,
		 NULL,
		 dll_handle,
		 NULL);

	if (hwnd == NULL) {
		printf("Failed to create window\n");
		return false;
	}

#ifdef _DEBUG
	printf("Created window\n");
#endif

//	ShowWindow(hwnd, SW_SHOWNORMAL);
	ShowWindow(hwnd, SW_SHOW);
	UpdateWindow(hwnd);
	SetForegroundWindow(hwnd);
	SetFocus(hwnd);

	hdc = GetWindowDC(hwnd);  

	// Success! Now you need to initialize a GL object, which creates a GL rendering context;
	// and then to issue commands to it, you need to call gl::makeCurrent().

	// 3. Hide the mouse if necessary
	isFullScreen = fullscreen == JNI_TRUE;

	// 4. Create DirectInput
	if (!createDirectInput()) {
		// Close the window
		closeWindow();
		return false;

	}

	return true;
}

/*
 * Class:     org_lwjgl_Window
 * Method:    nSetTitle
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_lwjgl_opengl_Window_nSetTitle
  (JNIEnv * env, jclass clazz, jstring title_obj)
{
	const char * title = env->GetStringUTFChars(title_obj, NULL);
	SetWindowText(hwnd, title);
	env->ReleaseStringUTFChars(title_obj, title);
}

/*
 * Class:     org_lwjgl_Window
 * Method:    update
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_lwjgl_opengl_Window_update
  (JNIEnv * env, jclass clazz)
{
	handleMessages(env, clazz);
}


/*
 * Class:     org_lwjgl_Window
 * Method:    minimize
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_lwjgl_opengl_Window_minimize
  (JNIEnv * env, jclass clazz)
{
	if (isMinimized)
		return;
	ShowWindow(hwnd, SW_MINIMIZE);
}

/*
 * Class:     org_lwjgl_Window
 * Method:    minimize
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_lwjgl_opengl_Window_restore
  (JNIEnv * env, jclass clazz)
{
	if (!isMinimized)
		return;

	ShowWindow(hwnd, SW_RESTORE);
}

/*
 * Class:     org_lwjgl_Window
 * Method:    swapBuffers
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_lwjgl_opengl_Window_swapBuffers
  (JNIEnv * env, jclass clazz)
{
	dirty = false;
	wglSwapLayerBuffers(hdc, WGL_SWAP_MAIN_PLANE);
}

/*
 * Class:     org_lwjgl_Window
 * Method:    nCreate
 * Signature: (Ljava/lang/String;IIIIZIIII)V
 */
JNIEXPORT void JNICALL Java_org_lwjgl_opengl_Window_nCreate
  (JNIEnv * env, jclass clazz, jstring title, jint x, jint y, jint width, jint height, jboolean fullscreen, jint bpp, jint alpha, jint depth, jint stencil)
{
	closerequested = false;
	minimized = false;
	focused = true;
	dirty = true;

	// 1. Create a window
	const char * titleString = env->GetStringUTFChars(title, NULL);
	if (!createWindow(titleString, x, y, width, height, fullscreen == JNI_TRUE ? true : false)) {
		env->ReleaseStringUTFChars((jstring) title, titleString);
		closeWindow();
		throwException(env, "Failed to create the window.");
		return;
	}
	env->ReleaseStringUTFChars(title, titleString);

	// 2. Choose a pixel format and set it
	unsigned int flags = PFD_DRAW_TO_WINDOW |   // support window 
		PFD_SUPPORT_OPENGL |   // support OpenGL 
		PFD_DOUBLEBUFFER;      // double buffered 

	int iPixelFormat = findPixelFormat(env, flags, bpp, alpha, depth, stencil);
	if (iPixelFormat == -1) {
		closeWindow();
		return;
	}
	// Create a rendering context
	hglrc = wglCreateContext(hdc);
	if (hglrc == NULL) {
		throwException(env, "Failed to create OpenGL rendering context");
		closeWindow();
		return;
	}

	// Automatically make it the current context
	wglMakeCurrent(hdc, hglrc);

	// Initialise GL extensions
	if (extgl_Initialize() != 0) {
		closeWindow();
		throwException(env, "Failed to initialize GL extensions");
		return;
	}
}

/*
 * Class:     org_lwjgl_Window
 * Method:    doDestroy
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_lwjgl_opengl_Window_nDestroy
  (JNIEnv * env, jclass clazz)
{
	wglMakeCurrent(NULL, NULL);

	// Delete the rendering context
	if (hglrc != NULL) {
#ifdef _DEBUG
		printf("Delete GL context\n");
#endif
		wglDeleteContext(hglrc); 
		hglrc = NULL;
	}
	closeWindow();
	extgl_Close();
}

/*
 * Class:     org_lwjgl_opengl_Window
 * Method:    nIsDirty
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_org_lwjgl_opengl_Window_nIsDirty
  (JNIEnv *env, jclass clazz) {
	return dirty;
}

/*
 * Class:     org_lwjgl_opengl_Window
 * Method:    nIsMinimized
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_org_lwjgl_opengl_Window_nIsMinimized
  (JNIEnv *env, jclass clazz) {
	return minimized;
}

/*
 * Class:     org_lwjgl_opengl_Window
 * Method:    nIsCloseRequested
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_org_lwjgl_opengl_Window_nIsCloseRequested
  (JNIEnv *, jclass) {
	bool saved = closerequested;
	closerequested = false;
	return saved;
}

/*
 * Class:     org_lwjgl_opengl_Window
 * Method:    nIsFocused
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_org_lwjgl_opengl_Window_nIsFocused
  (JNIEnv *env, jclass clazz) {
	return focused;
}
