/******************************************************************************
 * ATI 3D RAGE PRO SDK sample code.                                           *
 *                                                                            *
 * RPexpl3.c - RAGE PRO 32 Bit (8888) Windowed Explosion Example.             *
 *                                                                            *
 * Copyright (c) 1997 ATI Technologies Inc. All rights reserved.              *
 *																			  *
 * Commented versions of this code exist elsewhere.							  *
 *																			  *
 ******************************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ddraw.h>
#include <stdio.h>			

#include "ati3dcif.h"

#pragma pack (push)
#pragma pack (1)	

typedef struct _TGAHeaderInfo
{
	BYTE	idlen;		
	BYTE	cmtype;		
	BYTE	imtype;		
	WORD 	cmorg;		
	WORD	cmcnt;		
	BYTE	cmsize;		
	WORD	imxorg;		
	WORD	imyorg;		
	WORD	imwidth;	
	WORD	imheight;	
	BYTE	imdepth;	
	BYTE	imdesc;		
}TGAHeaderInfo;

#pragma pack (pop)

typedef struct _Texture
{
    LPDIRECTDRAWSURFACE lpDDSTex;
    DDSURFACEDESC       ddsd;
    C3D_HTX             hTX;
} Texture;


static LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static BOOL InitDirectDraw (void);
static void CloseDirectDraw (void);
static BOOL InitATI3DCIF (void);
static void CloseATI3DCIF (void);
static BOOL SetATI3DCIFInitialState (void);
static BOOL LoadTargaTextures (void);
static void UnloadTargaTextures (void);
static BOOL LoadTexture (const char *pszTGAFile, Texture *pTex);
static void UnloadTexture (Texture *pTex);
static BOOL CopyTextureData (TGAHeaderInfo *TGAh, unsigned char *pdata, Texture *pTex);
static BOOL InitApp (void);
static void CloseApp (void);
static void DrawFrame (void);
static void RenderScene (void);
static C3D_UINT32 IsValidTexDimension (WORD num);
static BOOL IsDisplay32Bit (void);
static void ErrBox(const char *errStr, ... );
static void _Assert (const char *file, int line, const char *msg);

#define ASSERT(x)   if( !(x) )  _Assert( __FILE__, __LINE__, #x)

#define RELEASE(x)	{												\
						if (x == NULL) 								\
							ErrBox ("Tried to free Null pointer");	\
						else										\
						{											\
							x->Release(); 							\
							x = NULL;								\
						}											\
					}

#define OUR_WIDTH			320			
#define OUR_HEIGHT			200			

#define COLORDEPTH_IN_BYTES	4			
#define IMAGEDEPTH_IN_BYTES	4			
#define FILL_COLOR			0x7F7F7Fl	

#define NTEXS_IN_SEQUENCE	7			
#define NFRAMES_PER_LEVEL	32			

Texture gTex[NTEXS_IN_SEQUENCE];		

const char *gTGAFile[] = {{"Ex51118.tga"},
						  {"Ex51119.tga"},
						  {"Ex51120.tga"},
						  {"Ex51121.tga"},
						  {"Ex51122.tga"},
						  {"Ex51123.tga"},
						  {"Ex51124.tga"}};


LPDIRECTDRAW        glpDD = NULL;			
LPDIRECTDRAWSURFACE glpDDSPrimary = NULL;	
LPDIRECTDRAWSURFACE glpDDSBack = NULL;		
LPDIRECTDRAWCLIPPER glpDDClip = NULL;		
C3D_HRC             ghRC = FALSE;			
BOOL                gbCIFInit = FALSE;		

char                gszErrMsg[64];			
HWND				ghWnd;					

C3D_VTCF gvtcfPrim1TriList[6]= {
    { 100.0f,  40.0f, 0.0f, 0.0f, 1.0f, 1.0f, 255.0f, 255.0f, 255.0f, 0.0f}, 
    { 228.0f,  40.0f, 0.0f, 1.0f, 1.0f, 1.0f,   0.0f,   0.0f,   0.0f, 0.0f},   
    { 100.0f, 168.0f, 0.0f, 0.0f, 0.0f, 1.0f, 255.0f, 255.0f, 255.0f, 0.0f}, 
    { 228.0f, 168.0f, 0.0f, 1.0f, 0.0f, 1.0f,   0.0f,   0.0f,   0.0f, 0.0f}, 
    { 100.0f, 168.0f, 0.0f, 0.0f, 0.0f, 1.0f, 255.0f, 255.0f, 255.0f, 0.0f}, 
    { 228.0f,  40.0f, 0.0f, 1.0f, 1.0f, 1.0f,   0.0f,   0.0f,   0.0f, 0.0f}   
};

C3D_PVTCF gvlstPrim1List[6];
 

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
										LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg;
    WNDCLASSEX wndclass;
	RECT rc;
	DWORD dwStyle;
	const char *pszClassName = "RAGE PRO Windowed 8888 Explosion Sequence";			    
	const char *pszWindowCaption = "RAGE PRO Windowed 8888 Explosion Sequence";

	memset (&wndclass, 0, sizeof(WNDCLASSEX));
	wndclass.cbSize = sizeof(WNDCLASSEX);
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon (hInstance, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH) GetStockObject (GRAY_BRUSH);
    wndclass.lpszMenuName = pszClassName;
    wndclass.lpszClassName = pszClassName;
	wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

    RegisterClassEx (&wndclass);

	dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;

   	rc.top = rc.left = 0;
	rc.bottom = OUR_HEIGHT;
	rc.right = OUR_WIDTH;

	AdjustWindowRectEx (&rc, dwStyle, FALSE, WS_EX_APPWINDOW);

	ghWnd = CreateWindowEx (WS_EX_APPWINDOW,
							pszClassName,
   							pszWindowCaption,
							dwStyle,
							50,
							50,
							rc.right - rc.left,
							rc.bottom - rc.top,
							NULL,
							NULL,
							hInstance,
							NULL);

    if (!ghWnd) return FALSE;

    if (!InitApp ()) return FALSE;

	ShowWindow (ghWnd, nCmdShow);
    UpdateWindow (ghWnd);
	
    while (TRUE)
    {
        if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
				break;
             
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        } 
		else
		{
			RenderScene ();
		}
    } 

    return msg.wParam;
} 


LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_KEYDOWN:
            switch (wParam)
            {
                case VK_ESCAPE:
                    DestroyWindow (hWnd);
                    return 0;
            } 
            return 0;

		case WM_KILLFOCUS:	 	  
			PostQuitMessage (0);
			return 0;
			
		case WM_DESTROY:
            CloseApp ();
            PostQuitMessage (0);
            return 0;
    } 

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


static BOOL InitApp (void)
{
	if (InitDirectDraw ())
	{
		if (InitATI3DCIF ())
		{
			if(SetATI3DCIFInitialState ())
			{
				if (LoadTargaTextures ())
				{
					for (int i = 0 ; i < 6; i++)
						gvlstPrim1List[i] = &(gvtcfPrim1TriList[i]);

					return TRUE;

					//Reminder for future expansion...
					//UnloadTargaTextures ();
				}
				else
					ErrBox ("Could not load all textures"); 
			}
			else
				ErrBox ("Could not set ATI3DCIF initial state"); 
			CloseATI3DCIF ();
		}
		else
			ErrBox ("ATI3DCIF not initialized");
		CloseDirectDraw ();
	}
	else
		ErrBox ("DirectDraw not initialized"); 
	return FALSE;
}


static void CloseApp (void)
{
	UnloadTargaTextures ();
	CloseATI3DCIF ();
	CloseDirectDraw ();
} 


static BOOL SetATI3DCIFInitialState (void)
{
	BOOL bTMap = TRUE;							
	C3D_ETEXOP etexop = C3D_ETEXOP_ALPHA;		
	C3D_EASRC easrc = C3D_EASRC_SRCALPHA;		
	C3D_EADST eadst = C3D_EADST_INVSRCALPHA;	

	if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_TMAP_EN, &bTMap) == C3D_EC_OK)
	{
		if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_TMAP_TEXOP, &etexop) == C3D_EC_OK)
		{
			if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_ALPHA_SRC, &easrc) == C3D_EC_OK)
			{
				if (ATI3DCIF_ContextSetState (ghRC, C3D_ERS_ALPHA_DST, &eadst) == C3D_EC_OK)
					return TRUE;
				else
				   	ErrBox ("Couldn't set alpha destination");
			}
			else
			   	ErrBox ("Couldn't set alpha source");
		}
		else
		   	ErrBox ("Couldn't set texture operation");
	}
	else
        ErrBox ("Couldn't enable texture mapping.");
	return FALSE;
}


static BOOL InitDirectDraw (void)
{
	if (DirectDrawCreate (NULL, &glpDD, NULL) == DD_OK)
	{
		if (IsDisplay32Bit ())
		{
			if (glpDD->SetCooperativeLevel (ghWnd,DDSCL_NORMAL) == DD_OK)
			{
				DDSURFACEDESC ddsd;
				memset (&ddsd, 0, sizeof (ddsd));
				ddsd.dwSize = sizeof (ddsd);
				ddsd.dwFlags = DDSD_CAPS;
				ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY;

				if (glpDD->CreateSurface (&ddsd, &glpDDSPrimary, NULL) == DD_OK)
				{
					memset (&ddsd, 0, sizeof (ddsd));
					ddsd.dwSize = sizeof (ddsd);
					ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
					ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
					ddsd.dwHeight = OUR_HEIGHT;
					ddsd.dwWidth = OUR_WIDTH;

					if (glpDD->CreateSurface (&ddsd, &glpDDSBack, NULL) == DD_OK)
					{
						if (glpDD->CreateClipper (0, &glpDDClip, NULL) == DD_OK)
						{
							if (glpDDClip->SetHWnd (0, ghWnd) == DD_OK)
							{
								if (glpDDSPrimary->SetClipper (glpDDClip) == DD_OK)
									return TRUE;
								else
									ErrBox ("Could not set clipper to front buffer");
							}
							else
								ErrBox ("Could not set clipper to window");
							RELEASE (glpDDClip);
						}
						else
							ErrBox ("Could not create clipper");
						RELEASE (glpDDSBack);
					}
					else
						ErrBox ("Could not create back buffer");
					RELEASE (glpDDSPrimary);
				}
				else
					ErrBox ("Could not create the primary surface");
				glpDD->SetCooperativeLevel (NULL, DDSCL_NORMAL);
			}
			else
				ErrBox ("Could not set the cooperative level");
		}
		else
			ErrBox ("Please reset to 32 bit display mode");
		RELEASE (glpDD);
	}
	else
        ErrBox ("Could not create DirectDraw object");
    return FALSE;
}     


static void CloseDirectDraw (void)
{
	RELEASE (glpDDClip);
	RELEASE (glpDDSBack);
	RELEASE (glpDDSPrimary);
	glpDD->SetCooperativeLevel (NULL, DDSCL_NORMAL);
	RELEASE (glpDD);
} 


static BOOL InitATI3DCIF (void)
{
	gbCIFInit = FALSE;

	if (ATI3DCIF_Init () == C3D_EC_OK)					
    {
		if ( (ghRC = ATI3DCIF_ContextCreate ()) != NULL)
		{
			gbCIFInit = TRUE;		
			
			return TRUE;

			//Reminder for future expansion...
			//ATI3DCIF_ContextDestroy (ghRC);
		}
		else
	        ErrBox ("Could not create 3D rendering context");
		ATI3DCIF_Term ();
	}
	else
        ErrBox ("Could not initialize ATI3DCIF driver interface");

	return FALSE;
}


static void CloseATI3DCIF (void)
{
    ASSERT (ghRC);
	ASSERT (gbCIFInit);

	ATI3DCIF_ContextDestroy (ghRC);
    ghRC = NULL;
    
    ATI3DCIF_Term ();
    gbCIFInit = FALSE;
} 
 

void RenderScene (void)
{
	DrawFrame ();

	RECT rcFront;
	GetClientRect (ghWnd, &rcFront);

	POINT pt = {0,0};				
	ClientToScreen (ghWnd, &pt);	
	OffsetRect (&rcFront, pt.x, pt.y); 	 

	glpDDSPrimary->Blt (&rcFront, glpDDSBack, NULL, DDBLT_WAIT, NULL);
}


static void DrawFrame (void)
{
	static int nFrames = 0;
	static int curTex = 0;
    DDSURFACEDESC ddsd;
	DDBLTFX ddbltfx;
	DWORD pitch;
    
    memset (&ddbltfx, 0, sizeof (ddbltfx));
    ddbltfx.dwSize = sizeof (ddbltfx);
    ddbltfx.dwFillColor = FILL_COLOR; 
    glpDDSBack->Blt (NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);

    memset (&ddsd, 0, sizeof (ddsd));
    ddsd.dwSize = sizeof (ddsd);
    if (glpDDSBack->Lock (NULL, &ddsd, DDLOCK_WAIT, NULL) != DD_OK) return;
	
	glpDDSBack->Unlock (ddsd.lpSurface);

	ATI3DCIF_ContextSetState (ghRC, C3D_ERS_SURF_DRAW_PTR, (C3D_PRSDATA) &(ddsd.lpSurface));

	pitch = ddsd.lPitch / COLORDEPTH_IN_BYTES;
	ATI3DCIF_ContextSetState (ghRC, C3D_ERS_SURF_DRAW_PITCH, (C3D_PRSDATA) &pitch);

	nFrames++;
	if (nFrames > NFRAMES_PER_LEVEL)	
	{
		curTex++;
		if (curTex > (NTEXS_IN_SEQUENCE-1)) curTex = 0;

		ATI3DCIF_ContextSetState (ghRC, C3D_ERS_TMAP_SELECT, &gTex[curTex].hTX);
		
		nFrames = 0;		
	}

	if (ATI3DCIF_RenderBegin (ghRC) != C3D_EC_OK) return;

	ATI3DCIF_RenderPrimList ((C3D_VSTRIP *) gvlstPrim1List, 6);

	ATI3DCIF_RenderEnd ();
} 


static BOOL LoadTargaTextures (void)
{
 	int nTexsLoaded = 0;
	int i;

	for (i=0; i<NTEXS_IN_SEQUENCE; i++)
	{
		if (!LoadTexture (gTGAFile[i], &gTex[i])) 
		{
			ErrBox ("Texture %d in sequence from %s not loaded", i, gTGAFile[i]);
	        break;
		}
		nTexsLoaded++;
	}
	
	if (nTexsLoaded == NTEXS_IN_SEQUENCE)
		return TRUE;
	else
		ErrBox ("Couldn't load all textures.");

	for (i=nTexsLoaded-1; i>=0; i--)
		UnloadTexture (&gTex[i]);
	
	return FALSE;
} 


static void UnloadTargaTextures (void)
{
	for (int i=NTEXS_IN_SEQUENCE-1; i>=0; i--)
	{
		ASSERT (gTex[i].lpDDSTex && gTex[i].hTX);
		UnloadTexture (&gTex[i]);
	}
}


static BOOL LoadTexture (const char* pszTGAFile, Texture *pTex)
{
    C3D_UINT32          log2X = 0L;
    C3D_UINT32          log2Y = 0L;					  
    C3D_TMAP            TMap;
    FILE				*fh;
	TGAHeaderInfo		TGAHeader;
	DWORD				imageSize;
	unsigned char       *ptmap;
	DWORD               bytesRead;
 
	ASSERT (pTex);

    if ((fh = fopen (pszTGAFile, "rb")) != NULL)
	{
	    if ((bytesRead = fread (&TGAHeader, sizeof (unsigned char), sizeof (TGAHeader), fh)) ==
																			sizeof (TGAHeader))
		{		  
			if (fseek (fh, TGAHeader.idlen, SEEK_CUR) == 0)
			{
				log2X = IsValidTexDimension (TGAHeader.imwidth);
				log2Y = IsValidTexDimension (TGAHeader.imheight);
				if ((log2X <= cu32MAX_TMAP_LEV) && (log2Y <= cu32MAX_TMAP_LEV))
				{
					imageSize = TGAHeader.imheight * TGAHeader.imwidth * IMAGEDEPTH_IN_BYTES;
					ptmap = (unsigned char *) malloc (imageSize * sizeof (unsigned char));
					if (ptmap) 
					{
						if ((bytesRead = fread (ptmap, sizeof (unsigned char), imageSize, fh)) ==
																						imageSize)
						{
							if (CopyTextureData (&TGAHeader, ptmap, pTex) == TRUE)
							{
								memset (&TMap, 0, sizeof (TMap));
								TMap.u32Size = sizeof (TMap);
	
								TMap.apvLevels[0] = pTex->ddsd.lpSurface;
								TMap.bMipMap = FALSE;
								TMap.u32MaxMapXSizeLg2 = log2X;
								TMap.u32MaxMapYSizeLg2 = log2Y;
								TMap.eTexFormat = C3D_ETF_RGB8888;

								if (ATI3DCIF_TextureReg (&TMap, &(pTex->hTX)) == C3D_EC_OK)
								{
									free (ptmap);
									fclose (fh);

									return TRUE;

									//Reminder for future expansion...
									//ATI3DCIF_TextureUnreg(pTex->hTX);
								}
								else
									ErrBox ("Error registering texture %s", pszTGAFile);
							}
							else
								ErrBox ("Error copying texture data to video memory surface");
						}
						else
							ErrBox ("Error reading bitmap data");
						free (ptmap);
					} 
					else
						ErrBox ("Could not allocate memory for texture map data");
				}
				else
					ErrBox ("Texture %s dimension greater than 1024 or not a power of 2", 
																					pszTGAFile);
			}
			else
		        ErrBox ("Error setting file pointer");
		}
		else
	        ErrBox ("Error reading file header");
		fclose (fh);
	}
	else
        ErrBox ("Could not open texture map %s", pszTGAFile);
    return FALSE;
} 


static void UnloadTexture (Texture *pTex)
{
	ASSERT (pTex);
    ASSERT (pTex->hTX);
    
    ATI3DCIF_TextureUnreg (pTex->hTX);
	RELEASE (pTex->lpDDSTex);
}


static BOOL CopyTextureData (TGAHeaderInfo *TGAh, unsigned char *pdata, Texture *pTex)
{
    HRESULT             ddrval;

	memset (&(pTex->ddsd), 0, sizeof (pTex->ddsd));
	pTex->ddsd.dwSize = sizeof (pTex->ddsd);
	pTex->ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
	pTex->ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
	pTex->ddsd.dwWidth =  TGAh->imwidth;
	pTex->ddsd.dwHeight =  TGAh->imheight;

	ddrval = glpDD->CreateSurface (&(pTex->ddsd), &pTex->lpDDSTex, NULL);
	if (ddrval == DD_OK)
	{
		memset (&(pTex->ddsd), 0, sizeof (pTex->ddsd));
		pTex->ddsd.dwSize = sizeof (pTex->ddsd);

		if (pTex->lpDDSTex->Lock (NULL, &(pTex->ddsd),	DDLOCK_WAIT, NULL) == DD_OK)
		{
			pTex->lpDDSTex->Unlock (pTex->ddsd.lpSurface);

			unsigned char *texSurfBase;
			unsigned char *oldpdata;
			DWORD rowBytes;

			rowBytes = TGAh->imwidth*COLORDEPTH_IN_BYTES;
			texSurfBase = (unsigned char *)pTex->ddsd.lpSurface;

			oldpdata = pdata;		

			for (int y=0; y<TGAh->imheight; y++)
			{
				memcpy (texSurfBase, pdata, rowBytes);
				pdata += rowBytes;
				texSurfBase += pTex->ddsd.lPitch;
			}

			pdata = oldpdata;		

			return TRUE;
		}
		else
			ErrBox ("Surface Lock failed");
		RELEASE (pTex->lpDDSTex);
	}
	else
	{
		if (ddrval == DDERR_OUTOFVIDEOMEMORY)
			ErrBox ("Out of video memory for texture");
		else
			ErrBox ("Could not create texture surface in video memory");
	}

	return FALSE;
}


static BOOL IsDisplay32Bit (void)
{
    DDSURFACEDESC ddsd;
	memset (&ddsd, 0, sizeof (ddsd));
	ddsd.dwSize = sizeof (ddsd);
	glpDD->GetDisplayMode (&ddsd);

	if (ddsd.ddpfPixelFormat.dwRGBBitCount == 32)
		return TRUE;

	return FALSE;
}


static C3D_UINT32 IsValidTexDimension (WORD num)
{
    C3D_UINT32 log2val = 0;
	WORD dimension = 1;

	while (log2val <= cu32MAX_TMAP_LEV)
    {
		if (dimension == num) break;
		dimension <<= 1;	
        log2val++;
    } 

	return log2val;
}

static void ErrBox(const char *errStr, ... )
{	
	va_list vl;

	va_start (vl, errStr);
	wvsprintf (gszErrMsg, errStr, vl);				
	MessageBox (ghWnd, gszErrMsg, NULL, MB_OK);	
	va_end (vl);
}

static void _Assert (const char *file, int line, const char *msg)
{
	int result;	
	static char buf[1024];

	sprintf (buf, "Assertion Failed %s at %d:  %s", file, line, msg);

	result = MessageBox (NULL, buf, "Assertion Failure", MB_OKCANCEL | 
												MB_APPLMODAL | MB_ICONERROR);

	if (result == IDCANCEL)
		PostQuitMessage (0);
}



