/*
 * This source file is part of KEOS (Free 3D Engine)
 * For the latest info, see http://www.keosengine.org/
 * E-mails : thierry.vouriot@keosengine.org, yeri@keosengine.org
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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, or go to
 * http://www.gnu.org/copyleft/lesser.txt.
 *
 */

#include "KeosWin32Window.h"
#include "KeosOGLException.h"
#include "KeosLogger.h"
#include "KeosConsole.h"

namespace Keos 
{

    //=======================================================================
    // CWin32Window implementation
    //=======================================================================

    //-----------------------------------------------------------------------
	CWin32Window::CWin32Window()		
    {
		m_bIsFullScreen = false;
		m_HWnd = 0;
		m_bActive = false;
		m_bReady = false;
		m_bClosed = false;
    }

    //-----------------------------------------------------------------------
    CWin32Window::~CWin32Window()
    {
		Destroy();
    }

    //-----------------------------------------------------------------------
	void CWin32Window::Create(const String& strName, uint nWidth, uint nHeight, uint nColourDepth, bool bFullScreen)
    {
        HWND parentHWnd = 0;
		HWND externalHandle = 0;
		HINSTANCE hInst = GetModuleHandle(KEOS_OGL_LIB);
		bool bVsync = false;
		uint nDisplayFrequency = 0;
		String strTitle = strName;
		uint nLeft = 0; // Defaults to screen center
		uint nTop = 0; // Defaults to screen center
		bool bDepthBuffer = true;

		// Destroy current window if any
		if( m_HWnd )
			Destroy();

        if (bFullScreen)
        {
			m_nColourDepth = nColourDepth;
        }
		else 
		{
			// Get colour depth from display
			m_nColourDepth = GetDeviceCaps(GetDC(0), BITSPIXEL);
		}

		if (!externalHandle) 
        {
			DWORD dwStyle = (bFullScreen ? WS_POPUP : WS_OVERLAPPEDWINDOW) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
			RECT rc;

			m_nWidth = nWidth;
			m_nHeight = nHeight;

			if (!bFullScreen)
			{
				// Calculate window dimensions required to get the requested client area
				SetRect(&rc, 0, 0, m_nWidth, m_nHeight);
				AdjustWindowRect(&rc, dwStyle, false);
				m_nWidth = rc.right - rc.left;
				m_nHeight = rc.bottom - rc.top;

				// Clamp width and height to the desktop dimensions
				if (m_nWidth > (unsigned)GetSystemMetrics(SM_CXSCREEN))
					m_nWidth = (unsigned)GetSystemMetrics(SM_CXSCREEN);
				if (m_nHeight > (unsigned)GetSystemMetrics(SM_CYSCREEN))
					m_nHeight = (unsigned)GetSystemMetrics(SM_CYSCREEN);

				if (!nLeft)
                {
					m_nLeft = (GetSystemMetrics(SM_CXSCREEN) / 2) - (m_nWidth / 2);
                }
				else
                {
					m_nLeft = nLeft;
                }
				if (!nTop)
                {
					m_nTop = (GetSystemMetrics(SM_CYSCREEN) / 2) - (m_nHeight / 2);
                }
				else
                {
                    m_nTop = nTop;
                }
			}
			else
            {
				m_nTop = m_nLeft = 0;
            }

			// Register the window class
			WNDCLASS wndClass = { CS_HREDRAW | CS_VREDRAW, WndProc, 0, 4, hInst,
				LoadIcon( NULL, IDI_APPLICATION ),
				LoadCursor( NULL, IDC_ARROW ),
				(HBRUSH)GetStockObject( BLACK_BRUSH ), NULL,
				TEXT(strTitle.c_str()) };
			RegisterClass( &wndClass );

			// Create our main window
			// Pass pointer to self
			HWND hWnd = CreateWindowEx(bFullScreen?WS_EX_TOPMOST:0, TEXT(strTitle.c_str()), TEXT(strTitle.c_str()),
				dwStyle, m_nLeft, m_nTop, m_nWidth, m_nHeight, 0L, 0L, hInst, this);
			m_HWnd = hWnd;

			GetClientRect(m_HWnd,&rc);
			m_nWidth = rc.right;
			m_nHeight = rc.bottom;

            if (bFullScreen) 
            {
			    DEVMODE DevMode;
			    DevMode.dmSize = sizeof(DevMode);
			    DevMode.dmBitsPerPel = m_nColourDepth;
			    DevMode.dmPelsWidth = m_nWidth;
			    DevMode.dmPelsHeight = m_nHeight;
			    DevMode.dmDisplayFrequency = nDisplayFrequency;
			    DevMode.dmFields = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;
			    if (ChangeDisplaySettings(&DevMode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
				    ILogger::Log("WARNING - ChangeDisplaySettings error");
		    }
		}
		else 
        {
			m_HWnd = externalHandle;
			RECT rc;
			GetClientRect(m_HWnd, &rc);
			m_nWidth = rc.right;
			m_nHeight = rc.bottom;
			m_nLeft = rc.left;
			m_nTop = rc.top;
		}

		ShowWindow(m_HWnd, SW_SHOWNORMAL);
		UpdateWindow(m_HWnd);

		m_strName = strName;
		m_bIsDepthBuffered = bDepthBuffer;
		m_bIsFullScreen = bFullScreen;

		HDC hdc = GetDC(m_HWnd);

        ILogger::Log("Created CWin32Window '%s' : %dx%d, %dbpp",
                     m_strName.c_str(), m_nWidth, m_nHeight, m_nColourDepth);
		
        PIXELFORMATDESCRIPTOR pfd = {
			sizeof(PIXELFORMATDESCRIPTOR),
			1,
			PFD_DRAW_TO_WINDOW |
			PFD_SUPPORT_OPENGL |
			PFD_DOUBLEBUFFER,
			PFD_TYPE_RGBA,
			m_nColourDepth,
			0, 0, 0, 0, 0, 0,
			0,
			0,
			0,
			0, 0, 0, 0,
			32, 			     // 32-bit depth-buffer (will be emulated in 16-bit colour mode)
			8,				     // 8-bit stencil buffer
			0,
			PFD_MAIN_PLANE,
			0,
			0, 0, 0};

		int nPixelFormat = ChoosePixelFormat(hdc, &pfd);
		if (!nPixelFormat)
            OGL_EXCEPT("ChoosePixelFormat", "CWin32Window::Create");

		if (!SetPixelFormat(hdc, nPixelFormat, &pfd))
            OGL_EXCEPT("SetPixelFormat", "CWin32Window::Create");

		HGLRC glrc = wglCreateContext(hdc);
		if (!glrc)
			OGL_EXCEPT("wglCreateContext", "CWin32Window::Create");
		if (!wglMakeCurrent(hdc, glrc))
			OGL_EXCEPT("wglMakeCurrent", "CWin32Window::Create");
		
		m_Glrc = glrc;
		m_HDC = hdc;

        //@todo Faire fonctionner
		//m_nOldSwapIntervall = wglGetSwapIntervalEXT();
		//if (bVsync) 
		//	wglSwapIntervalEXT(1);
		//else
		//	wglSwapIntervalEXT(0);

		m_bReady = true;
    }

    //-----------------------------------------------------------------------
    void CWin32Window::Destroy(void)
    {
        //wglSwapIntervalEXT(m_nOldSwapIntervall);

		if (m_Glrc) {
			wglMakeCurrent(NULL, NULL);
			wglDeleteContext(m_Glrc);
			m_Glrc = NULL;
		}
		if (m_HDC) {
			ReleaseDC(m_HWnd, m_HDC);
			m_HDC = NULL;
		}
		if (m_bIsFullScreen)
		{
			ChangeDisplaySettings(NULL, 0);
		}
	    DestroyWindow(m_HWnd);
        m_bActive = false;
    }

    //-----------------------------------------------------------------------
    bool CWin32Window::IsClosed() const
    {
        return m_bClosed;
    }

    //-----------------------------------------------------------------------
    void CWin32Window::Reposition(int left, int top)
    {
        // @todo
    }

    //-----------------------------------------------------------------------
    void CWin32Window::Resize(uint nWidth, uint nHeight)
    {
		m_nWidth = nWidth;
		m_nHeight = nHeight;

		// @todo resize window
    }

    //-----------------------------------------------------------------------
	void CWin32Window::WindowMovedOrResized()
	{
		RECT temprect;
		::GetClientRect(GetWindowHandle(),&temprect);
		Resize(temprect.right-temprect.left,temprect.bottom-temprect.top);
	}

    //-----------------------------------------------------------------------
    void CWin32Window::SwapBuffers(bool bWaitForVSync)
    {
        ::SwapBuffers((HDC)m_HDC);
    }

	//-----------------------------------------------------------------------
	LRESULT CWin32Window::WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
	{
		CWin32Window* win;

		// look up window instance
		if( WM_CREATE != uMsg )
			win = (CWin32Window*)GetWindowLong( hWnd, 0 );

		switch( uMsg )
		{
		case WM_ACTIVATE:
			if( WA_INACTIVE == LOWORD( wParam ) )
				win->m_bActive = false;
			else
				win->m_bActive = true;
			break;

		case WM_CREATE: {
			// Log the new window
			// Get CREATESTRUCT
			LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
			win = (CWin32Window*)(lpcs->lpCreateParams);
			// Store pointer in window user data area
			SetWindowLong( hWnd, 0, (long)win );
			win->m_bActive = true;

			return 0; }
			break;

		case WM_PAINT:
			// If we get WM_PAINT messges, it usually means our window was
			// comvered up, so we need to refresh it by re-showing the contents
			// of the current frame.
			if( win->m_bActive && win->m_bReady )
				win->Update();
			break;

		case WM_MOVE:
			// Move messages need to be tracked to update the screen rects
			// used for blitting the backbuffer to the primary
			// *** This doesn't need to be used to Direct3D9 ***
			break;

		case WM_ENTERSIZEMOVE:
			// Previent rendering while moving / sizing
			win->m_bReady = false;
			break;

		case WM_EXITSIZEMOVE:
			win->WindowMovedOrResized();
			win->m_bReady = true;
			break;

		case WM_SIZE:
			// Check to see if we are losing or gaining our window.  Set the 
			// active flag to match
			if( SIZE_MAXHIDE == wParam || SIZE_MINIMIZED == wParam )
				win->m_bActive = false;
			else
			{
				win->m_bActive = true;
				if( win->m_bReady )
					win->WindowMovedOrResized();
			}
			break;

		case WM_GETMINMAXINFO:
			// Prevent the window from going smaller than some minimu size
			((MINMAXINFO*)lParam)->ptMinTrackSize.x = 100;
			((MINMAXINFO*)lParam)->ptMinTrackSize.y = 100;
			break;
        // On arrete le rendu lorsque l'on appuie sur Echap
        // Provisoire en attendant une gestion plus fine des "Input"
		case WM_KEYDOWN:	
            switch (wParam)
            {
			case VK_ESCAPE :win->m_bClosed = true; break;
			case VK_F1 :     CConsole::Instance().Enable(true);  break; // F1 = activer la console
            case VK_F2 :     CConsole::Instance().Enable(false); break; // F2 = désactiver la console
            }
                    break;
        // Caractère = envoi à la console
        case WM_CHAR :
            CConsole::Instance().SendChar(static_cast<char>(LOWORD(wParam)));
            return 0;

		case WM_CLOSE:
			DestroyWindow( win->m_HWnd );
			win->m_bClosed = true;
			return 0;
		}

		return DefWindowProc( hWnd, uMsg, wParam, lParam );
	}
	
} // namespace Keos
