#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>

// Includes utilitaires D3D
#include "d3dmath.h"
#include "d3dutil.h"
#include "D3DEnum.h"

#include <d3dx.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"

static W3D_Context *hWC3D;
static HBITMAP hbDBuf3D;
static DC dcDBuf3D, *hdcDBuf3D = &dcDBuf3D;
static bZBuf = FALSE;
static int iW, iH;
static long lError;
static BOOL bImage;

extern W3D_Texture *tex;

void vShow3DFrame(void)
{
    if (!bImage) return;

    int iLeftEdge = hWndPersp -> BorderLeft,
        iTopEdge = hWndPersp -> BorderTop,
        iW = hWndPersp -> Width - iLeftEdge - hWndPersp -> BorderRight,
        iH = hWndPersp -> Height - iTopEdge - hWndPersp -> BorderBottom;

    ClipBlit(hdcDBuf3D, iLeftEdge, iTopEdge, hWndPersp -> RPort, iLeftEdge, iTopEdge, iW, iH, 0xc0);
}

void vRender3DEnvironment(void)
{
    bImage = FALSE;

    // Verrouiller le bitmap en VRAM
    lError = TAG_DONE;
#if 0
    APTR hBM = LockBitMapTagList(hbDBuf3D,(struct TagItem *) &lError);
    if (!hBM)
    {
         vTrace("*** E0077 : vue 3D : lock bitmap (contexte @%08lX, code %ld)",  hWC3D, lError);
         return;
    }
#endif
    // Verrouiller le hardware 3D
    if (!hWC3D || !hbDBuf3D || W3D_SUCCESS != (lError = W3D_LockHardware(hWC3D)))
    {
#if 0
         // Déverrouiller le bitmap
         UnLockBitMap(hBM);
#endif
         vTrace("*** E0067 : vue 3D : lock hardware (contexte @%08lX, code %ld, bm %08lX)", hWC3D, lError, hbDBuf3D);
         return;
    }

    W3D_Triangle sTriangle;
    W3D_Double dZdepth = 1.;
    W3D_Float   fLeftEdge = hWndPersp -> BorderLeft,
                fTopEdge = hWndPersp -> BorderTop,
                fW = hWndPersp -> Width - fLeftEdge - hWndPersp -> BorderRight,
                fH = hWndPersp -> Height - fTopEdge - hWndPersp -> BorderBottom,
                fXMed = fLeftEdge + fW / 2.,
                fYMed = fTopEdge + fH / 2.;
    W3D_Vertex sV[4];
    W3D_Triangles sTs; sTs.vertexcount = 4; sTs.v = sV;
    W3D_Color sC = {0., 0., 0., 1.};

    // Clearer le zbuf
    if (bZBuf) W3D_ClearZBuffer(hWC3D, &dZdepth);

	// Set Drawing region
	sScissor.left   = (int) fLeftEdge;
    sScissor.top    = (int) fTopEdge;
    sScissor.width  = (int) fW;
    sScissor.height = (int) fH;
    W3D_SetDrawRegion(hWC3D, NULL, 0, &sScissor);

    // Calculer le produit des matrices view / proj
    D3DMath_MatrixMultiply(matWorld, matView, matProj);

    // Gouraud shading & zbuffer off
    W3D_SetState(hWC3D, W3D_GOURAUD, W3D_DISABLE);
    W3D_SetState(hWC3D, W3D_ZBUFFER, W3D_DISABLE);

	// Remplir le fond
    WSetPen(hWC3D, cBack);
    WRectFill(hWC3D, 0, 0, fW - 1, fH - 1);

    // Gouraud shading & zBuffer on
    W3D_SetState(hWC3D, W3D_GOURAUD, W3D_ENABLE);
    if (bZBuf) W3D_SetState(hWC3D, W3D_ZBUFFER, W3D_ENABLE);

    // Calculer le vecteur normalisé observateur / cible (pour le lighting simpliste)
    D3DVECTOR vObs = Normalize(Observer - Target);

    // Dessiner les triangles
    for (register int iTriangles = 0 ; iTriangles <= iTriaLastUsed ; iTriangles++)
    {
        register gTri *hTri = &Triangles[iTriangles];

        if (!hTri -> bEnabled) continue;
        if (hTri -> bHidden)   continue;
        D3DVECTOR
            // Déréférencer les 3 sommets du triangle
            v0_UnT = Vertices[hTri -> iSommets[0]].vPoint,
            v1_UnT = Vertices[hTri -> iSommets[1]].vPoint,
            v2_UnT = Vertices[hTri -> iSommets[2]].vPoint,
            // Transformés normalisés des 3 vecteurs ci-dessus
            v0, v1, v2;

        // TRANSFORM, step 1 : Transformation coordonnées objets vers [-1..+1] en utilisant la concaténation des matrices View et Proj
        D3DMath_VectorMatrixMultiply(v0, v0_UnT, matWorld);
        D3DMath_VectorMatrixMultiply(v1, v1_UnT, matWorld);
        D3DMath_VectorMatrixMultiply(v2, v2_UnT, matWorld);

        // TRANSFORM, step 2 : ajustement du span X/Y dans fenêtre 3D
        sTriangle.v1.x = fXMed + v0.x * fW;
        sTriangle.v1.y = fYMed + v0.y * fH;
        sTriangle.v2.x = fXMed + v1.x * fW;
        sTriangle.v2.y = fYMed + v1.y * fH;
        sTriangle.v3.x = fXMed + v2.x * fW;
        sTriangle.v3.y = fYMed + v2.y * fH;
        sTriangle.v1.z = v0.z;
        sTriangle.v2.z = v1.z;
        sTriangle.v3.z = v2.z;

        // LIGHT, step 1 : calculer les vecteurs normaux sur les trois sommets et les facteurs de correction
        D3DVECTOR
			v01 = v1_UnT - v0_UnT,
			v02 = v2_UnT - v0_UnT,
			v12 = v2_UnT - v1_UnT,
			vn0 = Normalize(CrossProduct(v01, v02)),
            vn1 = Normalize(CrossProduct(v01, v12)),
            vn2 = Normalize(CrossProduct(v02, v12));
        FLOAT
            f0 = fabs(DotProduct(vObs, vn0)),
            f1 = fabs(DotProduct(vObs, vn1)),
            f2 = fabs(DotProduct(vObs, vn2));

        // LIGHT, step 2 : moduler la couleur en fonction orientation
        D3DMATERIAL7 *hMtrl = &Materials[hTri -> iMtrl].mtrl;
        sC.r = hMtrl -> diffuse.r;
        sC.g = hMtrl -> diffuse.g;
        sC.b = hMtrl -> diffuse.b;
        sTriangle.v1.color.r = sC.r * f0; sTriangle.v1.color.g = sC.g * f0; sTriangle.v1.color.b = sC.b * f0;
        sTriangle.v2.color.r = sC.r * f1; sTriangle.v2.color.g = sC.g * f1; sTriangle.v2.color.b = sC.b * f1;
        sTriangle.v3.color.r = sC.r * f2; sTriangle.v3.color.g = sC.g * f2; sTriangle.v3.color.b = sC.b * f2;

        if (Materials[hTri -> iMtrl].bTextured)
        {
        	// Set Texture mapping on
        	W3D_SetState(hWC3D, W3D_TEXMAPPING, W3D_ENABLE);

            // Affecter les u & v
            sTriangle.v1.u = hTri -> u[0];  sTriangle.v1.v = hTri -> v[0];
            sTriangle.v2.u = hTri -> u[1];  sTriangle.v2.v = hTri -> v[1];
            sTriangle.v3.u = hTri -> u[2];  sTriangle.v3.v = hTri -> v[2];

            // Affecter la texture
            sTriangle.tex = Materials[hTri -> iMtrl].hTexture;
/********/  sTriangle.tex = tex;

            // Dessiner le triangle
            W3D_DrawTriangle(hWC3D, &sTriangle);

        	// Set Texture mapping off
        	W3D_SetState(hWC3D, W3D_TEXMAPPING, W3D_DISABLE);
        }
        else
            W3D_DrawTriangle(hWC3D, &sTriangle);
    }

    // Déverrouiller le hardware 3D
    W3D_UnLockHardware(hWC3D);

#if 0
    // Déverrouiller le bitmap
    UnLockBitMap(hBM);
#endif

    // Mémoriser qu'on a bien créé une image dans le double buffer
    bImage = TRUE;

    // Blitter le double dans la fenêtre
    vShow3DFrame();
}

void vForce3DRefresh(BOOL bFull)
{
	if (bFull == XDC_MODE_COMPLET)
		vRender3DEnvironment();
	else
		vShow3DFrame();
}

LRESULT CALLBACK lrPerspWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch( uMsg )
    {
        case WM_PAINT:
			// If we get WM_PAINT messages, it usually means our window was
			// covered up, so we need to refresh it by re-showing the contents
			// of the current frame.
			vForce3DRefresh(XDC_MODE_PARTIEL);
            break;

        case WM_CREATE:
            InitRastPort(hdcDBuf3D);
        case WM_SIZE:
#ifndef _AMIGA_
            // Check to see if we are losing or gaining our window. Set the
			// active flag to match.
            if( SIZE_MAXHIDE==wParam || SIZE_MINIMIZED==wParam )
                bActive = FALSE;
			else bActive = TRUE;
            // A new window size will require a new backbuffer size. The
			// easiest way to achieve this is to release and re-create
			// everything.
            if( bActive && bReady )
#endif
			{
				bReady = FALSE;
#ifndef _AMIGA_
				if( FAILED( hrCloseD3D(FALSE) ) )
					DestroyWindow( hWnd );

				if( FAILED( hrInitD3D( hWnd, &gDeviceGUID ) ) )
					DestroyWindow( hWnd );

				if (FAILED(hrInitWorld( lpd3dDevice )))
				{
					hrCloseD3D(TRUE);
					DestroyWindow( hWnd );
				}

				// Mettre à jour les variables d'état du pipe D3D
				vSetD3DState();

				// Remettre en service les lampes
				for (int iCnt = 0 ; iCnt <= iLampLastUsed ; iCnt++)
					if (Lampes[iCnt].bEnabled)
						bUpdateLamp(iCnt);
				// Redessiner la scène
#else
                // Si la taille n'a pas changé, ne rien faire
                if (iW == hWnd -> Width && iH == hWnd -> Height)
                    break;
                iW = hWnd -> Width;
                iH = hWnd -> Height;

                // Supprimer le contexte 3D s'il est déjà alloué
                if (hWC3D)
                {
                    if (bZBuf)
                    {
                        W3D_FreeZBuffer(hWC3D);
                        bZBuf = FALSE;
                    }
                    vCloseTextures(hWC3D);
                    W3D_DestroyContext(hWC3D);
                    hWC3D = NULL;
                }

                // Supprimer le bitmap offscreen s'il est déjà alloué
                if (hbDBuf3D)
                    FreeBitMap(hbDBuf3D);

                // Créer le bitmap offscreen
                if (!(hbDBuf3D = AllocBitMap(hWnd -> Width, hWnd -> Height, 8, BMF_MINPLANES | BMF_DISPLAYABLE, hWnd -> RPort -> BitMap)))
                {
                    vTrace("*** E0068 : vue 3D : allocation double buffer");
                    break;
                }

                // Attacher le bitmap au rastport
                dcDBuf3D.BitMap = hbDBuf3D;

            	// Créer le contexte W3D
                extern ULONG lModeID, lError;
                if (!(hWC3D = W3D_CreateContextTags(&lError,
                    W3D_CC_MODEID,      lModeID,
            		W3D_CC_BITMAP,      (ULONG) hbDBuf3D,
            		W3D_CC_YOFFSET,     0,
            		W3D_CC_DRIVERTYPE,  W3D_DRIVER_BEST,
            		W3D_CC_FAST,        TRUE,
            	    TAG_DONE)))
                {
                    vTrace("*** E0069 : vue 3D : crétion contexte Warp3D (code %ld)", lError);
                    FreeBitMap(hbDBuf3D); hbDBuf3D = NULL;
                    break;
                }

            	// Set dithering. This will look considerably better on 15 bit displays.
            	W3D_SetState(hWC3D, W3D_DITHERING, W3D_ENABLE);

                // Activer le clipping
                W3D_SetState(hWC3D, W3D_SCISSOR, W3D_ENABLE);

            	// Allouer le ZBuffer
           	    if (W3D_SUCCESS != W3D_AllocZBuffer(hWC3D))
            	{
                    bZBuf = FALSE;
                	vTrace("*** E0047 : vue 3D : allocation ZBuffer -> pas de suppression faces cachées");
                }
                else
                {
                    W3D_SetZCompareMode(hWC3D, W3D_Z_LESS);
                    W3D_SetState(hWC3D, W3D_ZBUFFERUPDATE, W3D_ENABLE);
                    bZBuf = TRUE;
                    vTrace("Vue 3D : allocation double buffer / contexte Warp3D / z-buffer OK");
                }
                vInitTextures(hWC3D);

#endif
				// Redessiner la 3D dans le double buffer, Intuition va de de toute façon nous envoyer un REFRESH qui nous donnera un PAINT qui nous fera blitter le double buffer dans la fenêtre
                vRender3DEnvironment();

				bReady = TRUE;
			}
            break;

		case WM_COMMAND: // Rediriger les frappes clavier et chois menus vers la fenêtre menus
		case WM_CHAR :
			PostMessage(hWndMenu, uMsg, wParam, lParam );
			break;

        case WM_DESTROY:
            // Supprimer le contexte 3D
            if (hWC3D)
            {
                if (bZBuf)
                {
                    W3D_FreeZBuffer(hWC3D);
                    bZBuf = FALSE;
                }
                vCloseTextures(hWC3D);
                W3D_DestroyContext(hWC3D);
                hWC3D = NULL;
            }

            // Supprimer le bitmap offscreen
            if (hbDBuf3D) FreeBitMap(hbDBuf3D);

            PostQuitMessage(0);
            return 0L;
    }
#ifndef _AMIGA_
    return DefWindowProc( hWnd, uMsg, wParam, lParam );
#else
    return 0;
#endif
}

