/*
 * 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 "KeosD3D9Window.h"
#include "KeosD3D9Exception.h"
#include "KeosLogger.h"
#include "KeosD3D9RenderSystem.h"
#include "KeosConsole.h"

namespace Keos 
{

    //=======================================================================
    // CD3D9Window implementation
    //=======================================================================

    //-----------------------------------------------------------------------
    CD3D9Window::CD3D9Window(CD3D9* pD3D9)
	{
        m_pD3D9 = pD3D9;
		m_bIsFullScreen = false;
		m_HWnd = NULL;
		m_bActive = false;
		m_bSizing = false;
        m_bIsFullScreen = false;
		m_bReady = false;
		m_bClosed = false;
	}

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

    //-----------------------------------------------------------------------
    void CD3D9Window::Create(const String& strName, uint nWidth, uint nHeight, uint nColourDepth, bool bFullScreen)
	{
		
		HINSTANCE hInst = GetModuleHandle( KEOS_D3D9_LIB );
		
		m_FSAAType = D3DMULTISAMPLE_NONE;
		m_FSAAQuality = 0;
		m_bVSync = false;
		unsigned int displayFrequency = 0;
		String title = strName;
		unsigned int colourDepth = nColourDepth;
		int left = -1; // Defaults to screen center
		int top = -1; // Defaults to screen center
		bool depthBuffer = true;
		String border = "";
		bool outerSize = false;

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

		DWORD dwStyle = WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
		RECT rc;

		m_nWidth = nWidth;
		m_nHeight = nHeight;
		m_nTop = top;
		m_nLeft = left;

		if (!bFullScreen)
		{
			if (border == "none")
				dwStyle |= WS_POPUP;
			else if (border == "fixed")
				dwStyle |= WS_OVERLAPPED | WS_BORDER | WS_CAPTION |
				WS_SYSMENU | WS_MINIMIZEBOX;
			else
				dwStyle |= WS_OVERLAPPEDWINDOW;
			
			if (!outerSize)
			{
				// 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
				int screenw = GetSystemMetrics(SM_CXSCREEN);
				int screenh = GetSystemMetrics(SM_CYSCREEN);
				if ((int)m_nWidth > screenw)
					m_nWidth = screenw;
				if ((int)m_nHeight > screenh)
					m_nHeight = screenh;
				if (m_nLeft < 0)
					m_nLeft = (screenw - m_nWidth) / 2;
				if (m_nTop < 0)
					m_nTop = (screenh - m_nHeight) / 2;
			}
		}
		else
		{
			dwStyle |= WS_POPUP;
			m_nTop = m_nLeft = 0;
		}

		// Register the window class
		// NB allow 4 bytes of window data for D3D9RenderWindow pointer
		WNDCLASS wc = { 0, WndProc, 0, 0, hInst,
			LoadIcon(0, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW),
			(HBRUSH)GetStockObject(BLACK_BRUSH), 0, "KeosD3D9Wnd" };
		RegisterClass(&wc);

		// Create our main window
		// Pass pointer to self
		m_HWnd = CreateWindow("KeosD3D9Wnd", title.c_str(), dwStyle,
			m_nLeft, m_nTop, m_nWidth, m_nHeight, (HWND) NULL, 0, hInst, this);
	
		// top and left represent outer window coordinates
		GetWindowRect(m_HWnd, &rc);
		m_nTop = rc.top;
		m_nLeft = rc.left;
		// width and height represent interior drawable area
		GetClientRect(m_HWnd, &rc);
		m_nWidth = rc.right;
		m_nHeight = rc.bottom;

		m_strName = strName;
		//mIsDepthBuffered = depthBuffer;
		m_bIsFullScreen = bFullScreen;
		m_nColourDepth = colourDepth;

		ILogger::Log("Created CD3D9Window '%s' : %dx%d, %dbpp",
                     m_strName.c_str(), m_nWidth, m_nHeight, m_nColourDepth);

		CreateD3DResources();

		m_bActive = true;
	}

    void CD3D9Window::CreateD3DResources(void)
	{
		// Set up the presentation parameters
		HRESULT hr;
		LPDIRECT3D9 pD3D = m_pD3D9->pD3D;
		
        D3DDEVTYPE devType = D3DDEVTYPE_HAL;

		ZeroMemory( &m_d3dpp, sizeof(D3DPRESENT_PARAMETERS) );
	    m_d3dpp.Windowed				= !m_bIsFullScreen;
		m_d3dpp.SwapEffect				= D3DSWAPEFFECT_DISCARD;
		m_d3dpp.BackBufferCount			= 1;
		m_d3dpp.EnableAutoDepthStencil	= true;
		m_d3dpp.hDeviceWindow			= m_HWnd;
		m_d3dpp.BackBufferWidth			= m_nWidth;
		m_d3dpp.BackBufferHeight		= m_nHeight;  

		if (m_bVSync)
			m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
		else
			m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

		m_d3dpp.BackBufferFormat		= D3DFMT_R5G6B5;
		if( m_nColourDepth > 16 )
			m_d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;

		if (m_nColourDepth > 16 )
		{
			// Try to create a 32-bit depth, 8-bit stencil
			if( FAILED( pD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT,
				devType,  m_d3dpp.BackBufferFormat,  D3DUSAGE_DEPTHSTENCIL, 
				D3DRTYPE_SURFACE, D3DFMT_D24S8 )))
			{
				// Bugger, no 8-bit hardware stencil, just try 32-bit zbuffer 
				if( FAILED( pD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT,
					devType,  m_d3dpp.BackBufferFormat,  D3DUSAGE_DEPTHSTENCIL, 
					D3DRTYPE_SURFACE, D3DFMT_D32 )))
				{
					// Jeez, what a naff card. Fall back on 16-bit depth buffering
					m_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
				}
				else
					m_d3dpp.AutoDepthStencilFormat = D3DFMT_D32;
			}
			else
			{
				// Woohoo!
				if( SUCCEEDED( pD3D->CheckDepthStencilMatch( D3DADAPTER_DEFAULT, devType,
					m_d3dpp.BackBufferFormat, m_d3dpp.BackBufferFormat, D3DFMT_D24S8 ) ) )
				{
					m_d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; 
				} 
				else 
					m_d3dpp.AutoDepthStencilFormat = D3DFMT_D24X8; 
			}
		}
		else
			// 16-bit depth, software stencil
			m_d3dpp.AutoDepthStencilFormat	= D3DFMT_D16;

		m_d3dpp.MultiSampleType = m_FSAAType;
		m_d3dpp.MultiSampleQuality = (m_FSAAQuality == 0) ? 0 : m_FSAAQuality;


		if (!m_pD3D9->pD3DDevice)
		{
			// We haven't created the device yet, this must be the first time

			// Do we want to preserve the FPU mode? Might be useful for scientific apps
			DWORD extraFlags = 0;

			hr = pD3D->CreateDevice( D3DADAPTER_DEFAULT, devType, m_HWnd,
				D3DCREATE_HARDWARE_VERTEXPROCESSING | extraFlags, &m_d3dpp, &(m_pD3D9->pD3DDevice) );
			if( FAILED( hr ) )
			{
				hr = pD3D->CreateDevice( D3DADAPTER_DEFAULT, devType, m_HWnd,
					D3DCREATE_MIXED_VERTEXPROCESSING | extraFlags, &m_d3dpp, &(m_pD3D9->pD3DDevice) );
				if( FAILED( hr ) )
				{
					hr = pD3D->CreateDevice( D3DADAPTER_DEFAULT, devType, m_HWnd,
						D3DCREATE_SOFTWARE_VERTEXPROCESSING | extraFlags, &m_d3dpp, &(m_pD3D9->pD3DDevice) );
				}
			}
			if( FAILED( hr ) )
			{
				Destroy();
				D3D9_EXCEPT("Failed to create Direct3D9 Device", "CD3D9Window::CreateD3DResources");
			}
		}
		// Store references to buffers for convenience
		m_pD3D9->pD3DDevice->GetRenderTarget( 0, &m_pRenderSurface );
		m_pD3D9->pD3DDevice->GetDepthStencilSurface( &m_pRenderZBuffer );
		// release immediately so we don't hog them
		if (m_pRenderSurface)m_pRenderSurface->Release();
		if (m_pRenderZBuffer) m_pRenderZBuffer->Release();
    }

    //-----------------------------------------------------------------------
    void CD3D9Window::Destroy(void)
    {
		m_pRenderSurface = 0;
		m_pRenderZBuffer = 0;
		
		m_pD3D9->ReleaseDevice();
		
		DestroyWindow( m_HWnd );
		m_HWnd = 0;
        m_bActive = false;
    }

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

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

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

		// @todo resize window
    }

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

    //-----------------------------------------------------------------------
    void CD3D9Window::SwapBuffers(bool bWaitForVSync)
    {
        if( m_pD3D9->pD3DDevice )
		{
			HRESULT hr;
			hr = m_pD3D9->pD3DDevice->Present( NULL, NULL, NULL, NULL );	
			if( FAILED(hr) )
			{
				if (hr == D3DERR_DEVICELOST)
				{
					static_cast<CD3D9RenderSystem*>(
						CRoot::Instance().GetRenderSystem())->_NotifyDeviceLost();
					Sleep(500);
				}
				else D3D9_EXCEPT("Error Presenting surfaces", "CD3D9Window::SwapBuffers" );
			}
		}
    }

	//-----------------------------------------------------------------------
	void CD3D9Window::Update(void)
	{
		CD3D9RenderSystem* pRS = static_cast<CD3D9RenderSystem*>(
			CRoot::Instance().GetRenderSystem());
		if (pRS->IsDeviceLost())
		{
			// Test the cooperative mode first
			HRESULT hr = m_pD3D9->pD3DDevice->TestCooperativeLevel();
			if (hr == D3DERR_DEVICELOST)
			{
				// device lost, and we can't reset
				// can't do anything about it here, wait until we get 
				// D3DERR_DEVICENOTRESET; rendering calls will silently fail until 
				// then (except Present, but we ignore device lost there too)
				m_pRenderSurface = 0;
				m_pRenderZBuffer = 0;
				Sleep(500);
				return;
			}
			else if (hr == D3DERR_DEVICENOTRESET)
			{
				// device lost, and we can reset
				pRS->RestoreLostDevice();

				// Still lost?
				if (pRS->IsDeviceLost())
				{
					// Wait a while
					Sleep(500);
					return;
				}
			
				// re-qeuery buffers
				m_pD3D9->pD3DDevice->GetRenderTarget( 0, &m_pRenderSurface );
				m_pD3D9->pD3DDevice->GetDepthStencilSurface( &m_pRenderZBuffer );
				// release immediately so we don't hog them
				if (m_pRenderSurface) m_pRenderSurface->Release();
				if (m_pRenderZBuffer) m_pRenderZBuffer->Release();
			}
		}
		IRenderWindow::Update();
	}

	//-----------------------------------------------------------------------
	LRESULT CD3D9Window::WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
	{
		if (uMsg == WM_CREATE)
		{
			// copy CD3D9Window* from createwindow param to userdata slot
			SetWindowLong(hWnd, GWL_USERDATA,
				(LONG)(((LPCREATESTRUCT)lParam)->lpCreateParams));
			return 0;
		}

		CD3D9Window* win =
			(CD3D9Window*)GetWindowLong(hWnd, GWL_USERDATA);

		if (!win)
			return DefWindowProc(hWnd, uMsg, wParam, lParam);

		switch( uMsg )
		{
		case WM_ACTIVATE:
			win->m_bActive = (LOWORD(wParam) != WA_INACTIVE);
			break;

		case WM_ENTERSIZEMOVE:
			win->m_bSizing = true;
			break;

		case WM_EXITSIZEMOVE:
			win->WindowMovedOrResized();
			win->m_bSizing = false;
			break;

		case WM_MOVE:
		case WM_SIZE:
			if (!win->m_bSizing)
				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;

		case WM_CLOSE:
			win->Destroy(); // cleanup and call DestroyWindow
			win->m_bClosed = true;
			return 0;

		// 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;

		}

		return DefWindowProc( hWnd, uMsg, wParam, lParam );
	}

} // namespace Keos
