#define STRICT

// Includes standard Windows
#include <windows.h>
#include <windowsx.h>
#include <time.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <stdio.h>

// Includes D3D
#define  D3D_OVERLOADS
#include <ddraw.h>
#include <d3d.h>
#include <d3dx.h>

// Includes utilitaires D3D
#include "d3dmath.h"
#include "d3dutil.h"
#include "D3DEnum.h"

// Ids Resources
//#include "resource.h"

// Constantes
#include "const.h"

// Types
#include "types.h"

// Variables globales projet
#include "vars.h"

// Prototypes fonctions autres modules
#include "proto.h"

// Macros
#include "macros.h"

//-----------------------------------------------------------------------------
// Name: EnumZBufferCallback()
// Desc: Enumeration function to report valid pixel formats for z-buffers.
//-----------------------------------------------------------------------------
#ifndef _AMIGA_
static HRESULT WINAPI hrEnumZBufferCallback( DDPIXELFORMAT* pddpf,
                                           VOID* pddpfDesired )
{
#ifndef NO3D
	// For this tutorial, we are only interested in z-buffers, so ignore any
	// other formats (e.g. DDPF_STENCILBUFFER) that get enumerated. An app
	// could also check the depth of the z-buffer (16-bit, etc,) and make a
	// choice based on that, as well. For this tutorial, we'll take the first
	// one we get.
    if( pddpf->dwFlags == DDPF_ZBUFFER )
    {
        memcpy( pddpfDesired, pddpf, sizeof(DDPIXELFORMAT) );
		vTrace("ZBuffer depth : %d bit", ((DDPIXELFORMAT *) pddpfDesired) -> dwZBufferBitDepth);

        // Return with D3DENUMRET_CANCEL to end the search.
		return D3DENUMRET_CANCEL;
    }
#endif
    // Return with D3DENUMRET_OK to continue the search.
    return D3DENUMRET_OK;
}
#endif

////////////////////////////////////////////////////////////////////////////
HRESULT hrPreInitD3D(void)
{
    HRESULT hr = S_OK;

#ifndef NO3D
	// Create DirectDraw interface.
	vTrace("Création interface DirectDraw7");
	hr = DirectDrawCreateEx (NULL, (void **)&lpDD, IID_IDirectDraw7, NULL); 
	if( FAILED( hr ) )
		return hr;

	// Get an IDirect3D7 interface
	vTrace("Définition interface Direct3D V7");
	hr = lpDD->QueryInterface (IID_IDirect3D7, (void **)&lpD3D);
#endif

	return hr;
}

////////////////////////////////////////////////////////////////////////////
HRESULT hrInitD3D(HWND hWnd, const GUID* pDeviceGUID)
{
    HRESULT hr = S_OK;
#ifndef NO3D

    // Set the Windows cooperative level. The flag DDSCL_NORMAL specifies windowed mode.
    vTrace("Demande mode fenêtré (mode coopératif)");
	hr = lpDD->SetCooperativeLevel( hWnd, DDSCL_NORMAL );
	if( FAILED( hr ) )
		return hr;

    // Initialize a surface description structure for the Render surface.
	vTrace("Initialisation du descripteur de surface Render");
	ZeroMemory( &ddsd, sizeof(DDSURFACEDESC2) );
    ddsd.dwSize         = sizeof(DDSURFACEDESC2);
    ddsd.dwFlags        = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    // Create the Render surface.
	vTrace("Création de la surface Render");
    hr = lpDD->CreateSurface( &ddsd, &lpddsRender, NULL );
    if( FAILED( hr ) )        return hr;

	// Setup a surface description to create a backbuffer with dimensions equal to our window size.
	vTrace("Initialisation du descripteur de surface BackBuffer");
	ddsd.dwFlags        = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
	ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;

	// Set the dimensions of the backbuffer. Note that if our window changes
	// size, we need to destroy this surface and create a new one.
	vTrace("Dimensionnement de la surface BackBuffer");
	GetClientRect( hWnd, &sRectangle );
	ClientToScreen( hWnd, (POINT*)&sRectangle.left );
	ClientToScreen( hWnd, (POINT*)&sRectangle.right );
	ddsd.dwWidth  = sRectangle.right - sRectangle.left;
	ddsd.dwHeight = sRectangle.bottom - sRectangle.top;

	// Create the backbuffer.
	vTrace("Création de la la surface BackBuffer");
	hr = lpDD->CreateSurface( &ddsd, &lpddsBackBuffer, NULL );
	if( FAILED( hr ) )
		return hr;

	// Create a clipper object which handles all our clipping for cases when
	// our window is partially obscured by other windows.
	vTrace("Création du clipper");
	LPDIRECTDRAWCLIPPER pcClipper;
	hr = lpDD->CreateClipper( 0, &pcClipper, NULL );
	if( FAILED( hr ) )
		return hr;

	// Associate the clipper with our window.
	vTrace("Association clipper <-> fenêtre");
	pcClipper->SetHWnd( 0, hWnd );
	lpddsRender->SetClipper( pcClipper );
	pcClipper->Release();
	
	// Create the z-buffer AFTER creating the backbuffer and BEFORE creating the d3ddevice.
	DDPIXELFORMAT ddpfZBuffer;
	lpD3D->EnumZBufferFormats( *pDeviceGUID, 
		                        hrEnumZBufferCallback, (VOID*)&ddpfZBuffer );

	// If we found a good zbuffer format, then the dwSize field will be
	// properly set during enumeration. Else, we have a problem and will exit.
    if( sizeof(DDPIXELFORMAT) != ddpfZBuffer.dwSize )
        return E_FAIL;

    // Get z-buffer dimensions from the render target
    // Setup the surface desc for the z-buffer.
    ddsd.dwFlags        = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT|DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
	ddsd.dwWidth        = sRectangle.right - sRectangle.left;
	ddsd.dwHeight       = sRectangle.bottom - sRectangle.top;
    memcpy( &ddsd.ddpfPixelFormat, &ddpfZBuffer, sizeof(DDPIXELFORMAT) );

	// For hardware devices, the z-buffer should be in video memory. For
	// software devices, create the z-buffer in system memory
	if( IsEqualIID( *pDeviceGUID, IID_IDirect3DHALDevice )  ||
	    IsEqualIID( *pDeviceGUID, IID_IDirect3DTnLHalDevice ) )
		ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
	else
		ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

    // Create and attach a z-buffer. Real apps should be able to handle an
	// error here (DDERR_OUTOFVIDEOMEMORY may be encountered). For this 
	// tutorial, though, we are simply going to exit ungracefully.
    if( FAILED( hr = lpDD->CreateSurface( &ddsd, &g_pddsZBuffer, NULL ) ) )
		return hr;

	// Attach the z-buffer to the back buffer.
    if( FAILED( hr = lpddsBackBuffer->AddAttachedSurface( g_pddsZBuffer ) ) )
		return hr;

	// Check that we are NOT in a palettized display.
	vTrace("Vérification true color");
	ddsd.dwSize = sizeof(DDSURFACEDESC2);
	lpDD->GetDisplayMode( &ddsd );
	if( ddsd.ddpfPixelFormat.dwRGBBitCount <= 8 )
		return DDERR_INVALIDMODE;

	// Create the device (hardware renderer).
	vTrace("Création device 3D");
    hr = lpD3D->CreateDevice( *pDeviceGUID, lpddsBackBuffer,
                               &lpd3dDevice );
	if( FAILED( hr ) )
	{
		vTrace("*** E0007 : Echec création device 3D.");
		return hr;
	}
	else
		vTrace("Succès création device 3D.");

	// Store device ID (to reuse later when resizing the window)
	gDeviceGUID = *pDeviceGUID;

    // Create the viewport
	vTrace("Création viewport");
	DWORD dwRenderWidth  = sRectangle.right - sRectangle.left;
	DWORD dwRenderHeight = sRectangle.bottom - sRectangle.top;
	D3DVIEWPORT7 vp = { 0, 0, dwRenderWidth, dwRenderHeight, 0.0f, 1.0f };
    hr = lpd3dDevice->SetViewport( &vp );
	if( FAILED( hr ) )
		return hr;

	vTrace("Initialisation D3D ok.");
#endif
	return hr;
}

HRESULT hrCloseD3D(BOOL bFull)
{
#ifndef NO3D

	// Release the DDraw and D3D objects used by the app
    if( g_pddsZBuffer )		g_pddsZBuffer->Release();
	if( lpddsBackBuffer )	lpddsBackBuffer->Release();
	if( lpddsRender )		lpddsRender->Release();

	// Do a safe check for releasing the D3DDEVICE. RefCount should be zero.
	if( lpd3dDevice )
		if( 0 < lpd3dDevice->Release() )
			return E_FAIL;

	g_pddsZBuffer   = NULL;
	lpddsBackBuffer = NULL;
	lpddsRender     = NULL;
	lpd3dDevice     = NULL;

    if (bFull)
	{
		D3DXUninitialize();
		if( lpD3D )
			lpD3D->Release();

		// Do a safe check for releasing DDRAW. RefCount should be zero.
		if( lpDD )
			if( 0 < lpDD->Release() )
				return E_FAIL;

		lpD3D           = NULL;
		lpDD            = NULL;
	}

#endif

	return 0;
}

// Show the frame on the Render surface
HRESULT hrShowFrame(void)
{
#ifndef NO3D
	if( NULL == lpddsRender )
		return E_FAIL;

    // Perform a blit from the backbuffer to the correct position on the Render surface
    return lpddsRender->Blt( &sRectangle, lpddsBackBuffer, 
                               NULL, DDBLT_WAIT, NULL );
#else
    return S_OK;
#endif
}

// Checks for lost surfaces and restores them if lost.
HRESULT hrRestoreSurfaces(void)
{
#ifndef NO3D
    // Check/restore the Render surface
    if( lpddsRender )
        if( lpddsRender->IsLost() )
            lpddsRender->Restore();
    
    // Check/restore the back buffer
    if( lpddsBackBuffer )
        if( lpddsBackBuffer->IsLost() )
            lpddsBackBuffer->Restore();
#endif
    return S_OK;
}

// Moves the screen rect for windowed renderers
void vOnMove(int x, int y)
{
	DWORD dwWidth  = sRectangle.right - sRectangle.left;
	DWORD dwHeight = sRectangle.bottom - sRectangle.top;
    SetRect( &sRectangle, x, y, x + dwWidth, y + dwHeight );
}

// Draws the scene. There are three steps here:
//       (1) Animate the scene
//       (2) Render the scene
//       (3) Show the frame (copy backbuffer contents to the Render).
HRESULT hrRender3DEnvironment(void)
{
#ifndef NO3D
    // Call the app specific function to render the scene
    hrRender( lpd3dDevice );
    
    // Show the frame on the Render surface.
    if( DDERR_SURFACELOST == hrShowFrame() )
		hrRestoreSurfaces();
#endif
    return S_OK;
}

