// ================================================================
// Filename: Terrain.cpp
// Description: A terrain engine
// ================================================================


#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d8.h>
#include <d3dx8.h>
#include <dxerr8.h>
#include <mmsystem.h>

#include "D3DFuncs.h"
#include "cd3drasterfont.h"
#include "cd3dtimer.h"

#include "TerrainEngine.h"
#include "Skybox.h"

// Function declarations
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

HRESULT GameInit();
HRESULT GameLoop();
HRESULT GameShutDown();
HRESULT Render();


// Globals
static char strAppname[]="Simple Terrain demo by 32Bits.co.uk";
LPDIRECT3D8 g_pD3D;
LPDIRECT3DDEVICE8 g_pDevice;
LPDIRECT3DSURFACE8 g_pBackSurface;
HWND g_hWnd;

D3DCURRENTSETTINGS g_D3DSettings;

CD3DRasterFont g_FPSFont;
CD3DRasterFont g_32BitsFont;
CD3DTimer g_TimerGlobal;

// Globals specifically for this source

CTerrainEngine g_Terrain;
CSkyBox	g_SkyBox;

//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: The application's entry point
//-----------------------------------------------------------------------------

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
	
	ZeroMemory(&wc, sizeof(WNDCLASSEX));
	wc.cbSize=sizeof(WNDCLASSEX);						// size of the window struct in bytes
	wc.style=CS_HREDRAW | CS_VREDRAW | CS_OWNDC;		// window styles to use
	wc.lpfnWndProc=MsgProc;								// function name of event handler
	wc.hInstance=hInstance;								// handle to this apps instance
	wc.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);// background colour of window
	wc.hIcon= LoadIcon(NULL, IDI_APPLICATION);			// icon for the app window
	wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);			// icon when minimized to taskbar
	wc.hCursor=LoadCursor(NULL, IDC_ARROW);				// cursor to use for this window
	wc.lpszClassName=strAppname;						// name for this class

    // Register the window class
    RegisterClassEx( &wc );

	g_D3DSettings.m_nDeviceWidth=800;
	g_D3DSettings.m_nDeviceHeight=600;
	
    // Create the application's window
    g_hWnd = CreateWindow(strAppname, strAppname, WS_OVERLAPPEDWINDOW, 10, 10,
						  g_D3DSettings.m_nDeviceWidth, g_D3DSettings.m_nDeviceHeight,
						  NULL, NULL, wc.hInstance, NULL );

	// Show the window
	ShowWindow(g_hWnd, nCmdShow);
	UpdateWindow(g_hWnd);
	
	if(FAILED(GameInit()))
	{
		UnregisterClass( strAppname, wc.hInstance );
		return -1;
	}

	// Enter the message loop
	MSG msg;
	ZeroMemory( &msg, sizeof(msg) );
	int count=0;
	while( msg.message!=WM_QUIT )
	{
		if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
		{
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
		else
		{
			GameLoop();
		}
	}

	GameShutDown();
	UnregisterClass( strAppname, wc.hInstance );
    return 0;
}


//-----------------------------------------------------------------------------
// Name: MsgProc()
// Desc: The window's message handler
//-----------------------------------------------------------------------------
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	HDC hDC;
	PAINTSTRUCT PaintStruct;


    switch( msg )
    {
	case WM_PAINT:
		{
			hDC=BeginPaint(hWnd, &PaintStruct);		// Tell windows we want to update the window

			// Do GDI drawing here

			EndPaint(hWnd, &PaintStruct);
			return 0;
		}
	case WM_KEYDOWN:
		{
			switch(wParam)
			{
				
			case VK_SPACE:
				{
					PostQuitMessage( 0 );
					return 0;			
				}
			}
		}
		
	case WM_DESTROY:
		{
			PostQuitMessage( 0 );
			return 0;
		}
		
	default:	
		return DefWindowProc( hWnd, msg, wParam, lParam );
    }
	
}


// =====================================================================================
//  High level functions for initialization, loop and shutdown
// =====================================================================================

HRESULT GameInit()
{
	HRESULT rslt=0;

	g_pD3D=Direct3DCreate8(D3D_SDK_VERSION);
	if(g_pD3D==NULL)
	{
		return E_FAIL;
	}


	// Populate our struct with how we want to set up D3D...
	g_D3DSettings.m_bWindowed=TRUE;
	g_D3DSettings.m_bMultiSampling=FALSE;
	g_D3DSettings.m_D3DFormat=D3DFMT_X8R8G8B8;

	// ...and pass it to our function to create the device!
	rslt=InitDirect3DDevice(g_hWnd, g_D3DSettings, g_pD3D, &g_pDevice);
	if(FAILED(rslt))
	{
		return rslt;
	}
	
	// Set our culling renderstate, turn off lighting, and enable ZBuffering
	g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	g_pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
	g_pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

	// Use bilinear filtering to smooth out the texture
	g_pDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
	g_pDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
	g_pDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);


	// Set the viewport
	D3DVIEWPORT8 ViewPort;
	ZeroMemory(&ViewPort, sizeof(D3DVIEWPORT8));
	ViewPort.Height=g_D3DSettings.m_nDeviceHeight;
	ViewPort.Width=g_D3DSettings.m_nDeviceWidth;
	ViewPort.MaxZ=1.0f;
	g_pDevice->SetViewport(&ViewPort);

	
	// Set the projection matrix
	D3DXMATRIX ProjectionMatrix;
	ZeroMemory(&ProjectionMatrix, sizeof(ProjectionMatrix));

	float ScreenAspect=(float)g_D3DSettings.m_nDeviceWidth / (float)g_D3DSettings.m_nDeviceHeight;
	float FOV = D3DX_PI / 4;

	D3DXMatrixPerspectiveFovLH(&ProjectionMatrix, FOV, ScreenAspect, 1.0f, 330.0f);
	g_pDevice->SetTransform(D3DTS_PROJECTION, &ProjectionMatrix);


	D3DXMATRIX ViewMatrix;
	D3DXMatrixLookAtLH(&ViewMatrix, &D3DXVECTOR3(0.0f, 70.0f, -100.0f), &D3DXVECTOR3(0.0f, 45.0f,-30.0f), &D3DXVECTOR3(0.0f,1.0f,0.0f));
	g_pDevice->SetTransform(D3DTS_VIEW, &ViewMatrix);

	D3DXMATRIX WorldMatrix;
	D3DXMatrixIdentity(&WorldMatrix);
	g_pDevice->SetTransform(D3DTS_WORLD, &WorldMatrix);


	// =====================================================================================
	// Set up the terrain engine
	// =====================================================================================

	g_Terrain.Initialise(g_pDevice, "island.bmp");

	g_SkyBox.Initialise(g_pDevice);
	
	// =====================================================================================
	// Font for FPS
	// =====================================================================================

	g_FPSFont.SetColourKey(g_pDevice, D3DCOLOR_XRGB(0,0,0));
	g_FPSFont.SetText("32BITS.CO.UK");
	g_FPSFont.SetCharsPerLine(10);
	g_FPSFont.SetLetterWidth(32);
	g_FPSFont.SetLetterHeight(32);
	g_FPSFont.SetTranslation(D3DXVECTOR2(290.0f, 550.0f));
	rslt=g_FPSFont.Load(g_pDevice, "font1.bmp");
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to load raster font."); }



	g_32BitsFont.SetColourKey(g_pDevice, D3DCOLOR_XRGB(0,0,0));
	g_32BitsFont.SetText("32BITS.CO.UK");
	g_32BitsFont.SetCharsPerLine(10);
	g_32BitsFont.SetLetterWidth(32);
	g_32BitsFont.SetLetterHeight(32);
	g_32BitsFont.SetTranslation(D3DXVECTOR2(5.0f, 15.0f));
	rslt=g_32BitsFont.Load(g_pDevice, "font1.bmp");
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to load raster font."); }

	rslt=g_TimerGlobal.Initialise();
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to initialise timer."); }


	return S_OK;
}

HRESULT GameLoop()
{
	return Render();
}

HRESULT GameShutDown()
{
	if(g_pBackSurface)
		g_pBackSurface->Release();
	if(g_pDevice)
		g_pDevice->Release();
	if(g_pD3D)
		g_pD3D->Release();
	return S_OK;
}



// =====================================================================================
// Main render function to perform D3D drawing
// =====================================================================================

HRESULT Render()
{
	HRESULT rslt=NULL;
	

	// ====================================================================================
	// - Do all the usual checks to make sure we have the right pointers, etc...
	// ====================================================================================

	// Make sure we have a valid D3D Device
	if(!g_pDevice) { return E_FAIL;	}

	// Return if the device is not ready
	rslt=ValidateDevice(g_pDevice, g_pBackSurface, g_D3DSettings);
	if(FAILED(rslt)) { return rslt;	}

	// Clear the back buffer
	g_pDevice->Clear(0,0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,55), 1.0f, 0);

	// Get a pointer to the back buffer (remember, page flipping has taken place)
	rslt=g_pDevice->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &g_pBackSurface);
	if(FAILED(rslt)) { return E_FAIL; }



	rslt=g_pDevice->BeginScene();
	if(FAILED(rslt)) { return E_FAIL; }

	// ====================================================================================
	// - Do our drawing operations
	// ====================================================================================
	
	D3DXMATRIX WorldMatrix;
	D3DXMatrixRotationY(&WorldMatrix, (timeGetTime()/1500.0f));
	g_pDevice->SetTransform(D3DTS_WORLD, &WorldMatrix);

	//g_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
	
	g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	g_SkyBox.Render(g_pDevice, g_pBackSurface);

	g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	g_Terrain.Render(g_pDevice, g_pBackSurface);
	


	char frate[15];
	wsprintf(frate, "FRAMERATE: %d", g_TimerGlobal.GetFrameRate());
	g_FPSFont.SetText(frate);
	g_FPSFont.Render(g_pDevice, g_pBackSurface);
	g_32BitsFont.Render(g_pDevice, g_pBackSurface);
	

	// ====================================================================================
	// - Clean up and present the back buffer to be page flipped
	// ====================================================================================

	g_pDevice->EndScene();
	g_pBackSurface->Release();

	// Present the back buffer to the display adapter to be drawn
	g_pDevice->Present(NULL, NULL, NULL, NULL);

	g_TimerGlobal.Frame();

	return S_OK;
}