///////////////////////////////////////////////////////////////////////////////
/// @file tut_01_init.cpp
///
/// @brief Tutorial 01 - engine initialization, spinning triangle, text
///
/// @author Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date July 2004
/// @author Assassin
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

/**********************
  Includes
**********************/
#include <cstdio>
#include <cmath>
#include <crtdbg.h>
#include <windows.h>
#include <ZMouse.h>

// automatic library usage done in Destiny3D.hpp
// via USING_DESTINY3D_DLL, defined in project settings

#include "Destiny3D.hpp"	// main Destiny3D include file, handles global destiny3d stuff

#include "deRender.hpp"		// IdeRender
#include "deDriver.hpp"		// IdeDriver
#include "deCamera.hpp"		// IdeCamera
#include "deProfile.hpp"	// deTimer
#include "deFont.hpp"		// IdeFont
#include "deMesh.hpp"		// IdeMesh
#include "deScene.hpp"		// IdeSceneGraph, IdeSceneObject
#include "deSurface.hpp"	// IdeSurface

#pragma message("Compiling under: " DESTINY3D_COMPILER)

#define DRIVER_NAME		("Direct3D9")	// the name of the driver we'll be loading
#define WINDOW_WIDTH	(800)			// width of the whole window
#define WINDOW_HEIGHT	(600)			// height of the whole window
#define VIEW_WIDTH		(800)			// width of a full-screen viewport
#define VIEW_HEIGHT		(600)			// height of a full-screen viewport
#define VIEW_FOV		(75)			// field-of-view of the camera
#define VIEW_FULLSCREEN	(false)			// whether to display in fullscreen (and not windowed)
#define VIEW_ADAPTER	(0)				// default to the first adapter (adapter number 0)
#define WINDOW_BORDER	(true)			// put a border & titlebar on the window. You can use ESC to quit.
#define FRAMESCOUNTED	(50)			// number of frames to average the framerate over, for smoother values

class TestApp
{
	HWND		m_WindowHandle;			// we have to render to a particular window
	IdeCamera *	m_Cam;					// we render using a camera viewpoint
	IdeDriver *	m_Driver;				// the driver does the basic rendering tasks
	IdeRender *	m_Render;				// the render interface takes care of high-level rendering
	IdeFont*	m_Font;					// we use a font to display text (framerate counter) graphically
	IdeDriver::deDisplay m_Display;		// some parameters for setting up the display device
	bool		m_LostFocus;			// this keeps track of whether this app window is active
	int			m_Wireframe;			// we can toggle render mode between filled and wireframe

	// this is our little test object - a triangle that we'll spin on-screen
	IdeSceneObject* m_Triangle;
	// we need to use a "scene room" for the triangle to be seen in the scenegraph
	IdeSceneRoom*	m_Room;

	// helper functions
	static HWND CreateNewWindow(HINSTANCE hInstance);
	static void MoveNewWindow(HWND hWnd, int x, int y);
	static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
public:
	TestApp()
	:	m_WindowHandle(0),
		m_Cam(NULL),
		m_Driver(NULL),
		m_Render(NULL),
		m_LostFocus(false),
		m_Wireframe(0),
		m_Triangle(NULL),
		m_Room(NULL)
	{
		memset(&m_Display, 0, sizeof(IdeDriver::deDisplay));
	}
	~TestApp() {}

	bool InitEngine();
	bool InitData();
	bool InitDisplay(HINSTANCE hInstance);
	bool StartDisplay();
	void ShutDown();

	int MainLoop();
};

// application entry point
int		WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hprev, PSTR cmdline, int ishow)
{
	TestApp theApp;
	int retval = 0;
	
	// get the engine set up & ready for use
	if (!theApp.InitEngine())
		goto end_main;
	// initialize our member data
	if (!theApp.InitData())
		goto end_main;
	// create a window and prepare the driver
	if (!theApp.InitDisplay(hInstance))
		goto end_main;
	// initialize the driver to a rendering state
	if (!theApp.StartDisplay())
		goto end_main;
	
	// this blocks until we want to quit
	retval = theApp.MainLoop();
	
end_main:
	// release resources and shut down the engine
	theApp.ShutDown();
	
	return retval;
}

// create a window for rendering on
HWND TestApp::CreateNewWindow(HINSTANCE hInstance)
{
	HWND		LocalHandle;
	WNDCLASSEX	wc;
	RECT		WindowRect;
	char		ClassName[] = "tut_01_init";
	long		X;
	long		Y;

	//create a class
	wc.cbSize = sizeof(wc);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
	wc.lpszMenuName = (const char*)NULL;
	wc.lpszClassName = ClassName;
	wc.hIcon = NULL; //LoadIcon(hInstance, IDI_APPLICATION);
	wc.hIconSm = NULL; //wc.hIcon;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	RegisterClassEx(&wc);

	//create the window and show it
	DWORD flags = WS_POPUP;
	if (WINDOW_BORDER)
		flags |= WS_CAPTION|WS_SYSMENU;
	LocalHandle = CreateWindowEx(0, ClassName, "Tutorial 01 - Initialization", flags, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);

	//figure out the rect size for the area we want
	WindowRect.left = 0;
	WindowRect.top = 0;
	WindowRect.right = WINDOW_WIDTH - 1;
	WindowRect.bottom = WINDOW_HEIGHT - 1;
	GetWindowRect(LocalHandle, &WindowRect);

	//	AdjustWindowRect(&WindowRect, WS_POPUP, FALSE);

	//grab the screen with
	X = GetSystemMetrics(SM_CXSCREEN);
	Y = GetSystemMetrics(SM_CYSCREEN);
	
	//now modify the rect so that the window is centered and set it
	WindowRect.left = (X - WindowRect.right) / 2;
	WindowRect.top = (Y - WindowRect.bottom) / 2;
	SetWindowPos(LocalHandle, NULL, WindowRect.left, WindowRect.top, WindowRect.right, WindowRect.bottom, SWP_NOCOPYBITS);

	//go show and update the window
	ShowWindow(LocalHandle, SW_SHOWNORMAL);
	UpdateWindow(LocalHandle);

	return LocalHandle;
}

// we can reposition the window if necessary, such as to center it on screen
void TestApp::MoveNewWindow(HWND hWnd, int x, int y)
{
	RECT windowrect;
	int width, height;
	GetWindowRect(hWnd, &windowrect);
	width = windowrect.right - windowrect.left;
	height = windowrect.bottom - windowrect.top;
	MoveWindow(hWnd, x, y, width, height, TRUE);
}

// after everything is initialized, this is all that runs
// all the main application logic goes through here
int TestApp::MainLoop()
{
	// these variables are all for doing framerate calculations
	double TotalTime = 0, FramesTime = 0;
	double FrameTime = 0;
	unsigned int TotalFrames = 0;
	double FrameTimes[FRAMESCOUNTED] = {0};
	
	// create a timer object, and get a starting time for the first frame
	deTimer timer;
	timer.GrabStartTime();
	// enter infinite loop for main program execution
	while (1)	
	{				
		// process windows messages until we get a quit message						
		MSG msg;
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))	// Check if there was a message
        { 
			if(msg.message == WM_QUIT)					// If the message wasnt to quit
				return msg.wParam;
            TranslateMessage(&msg);						// Find out what the message does
            DispatchMessage(&msg);						// Execute the message
        }
		else if (m_LostFocus)
		{
			// if we don't have any messages to process, and we're not the active application
			// then just wait until something happens - a message will wake us up when we go active
			WaitMessage();
		}
		else
		{
			// calculate the average framerate over the last X frames
			double FrameRate = (FramesTime == 0.0) ? 0 : FRAMESCOUNTED/FramesTime;

			// print out the framerate into a 2D object
			int SWidth, SHeight;
			Ide2DObject* object = m_Font->Print2D(m_Driver, NULL, 3, 0, 0.91, &SWidth, &SHeight, IdeFont::POSITION_DEFAULT, "fps: %0.2f", FrameRate);
			if (object)
			{
				// tell the object to destroy itself after being rendered
				object->MakeExpire();
				// add it to the 2D collection so it will get rendered
				m_Render->Get2DCollection(m_WindowHandle)->AddObject(object);
				// release our claim on it, it will destroy itself later
				object->Release();
			}

			// we want to do a gradual rotation on the triangle
			// we'll rotate at about 1 radian per second, to avoid throwing needless coefficients in
			deTransform tform;
			// get the current transform
			m_Triangle->GetWorldTransform(tform);
			// do a rotation around the Y (vertical) axis, scaled by elapsed time
			tform.FreeRotInPlace(0, FramesTime/FRAMESCOUNTED, 0);
			// give the object the modified transform
			m_Triangle->SetWorldTransform(tform);

			// prepare for rendering
			if (!m_Render->BeginFrame(m_WindowHandle, deTRUE))
			{
				MessageBox(m_WindowHandle, "BeginFrame() Failed", "Error", MB_OK);
				goto end_main;
			}

			// peform the rendering
			if (!m_Render->Render(m_Cam, FramesTime/FRAMESCOUNTED, deTRUE, DE_FALSE, DE_FALSE, DE_FALSE))
			{
				MessageBox(m_WindowHandle, "Render() Failed", "Error", MB_OK);
				goto end_main;
			}
			
			// render 2D objects, swap front & back buffers
			if (!m_Render->EndFrame(m_WindowHandle, FramesTime/FRAMESCOUNTED))
			{
				MessageBox(NULL, "EndFrame() Failed", "Error", MB_OK);
				goto end_main;
			}

			// calculate the frame timings
			FramesTime = 0;
			int i;
			for (i = 0; i < FRAMESCOUNTED-1; i++)
			{
				FramesTime += FrameTimes[i];
				FrameTimes[i] = FrameTimes[i+1];
			}
			timer.GrabStopTime();
			FrameTime = timer.DiffTime(1.0); // 1.0 value limits the maximum time we'll accept between frames
			timer.GrabStartTime();
			FramesTime += FrameTime;
			FrameTimes[FRAMESCOUNTED-1] = FrameTime;
			TotalTime += FrameTime;
			TotalFrames++;
        }
	}

end_main:
	return 0; // failure return, we would rather return from the WM_QUIT message
}

// initialize the engine for use
bool TestApp::InitEngine()
{
	Destiny3D_Init();
	return true;
}

// initialize the member data & get engine structures
bool TestApp::InitData()
{
	m_Render = IdeRender_GetPtr();
	
	// create the camera that will be used for rendering
	m_Cam = IdeCamera_CreateCamera();

	// set the camera position back a little from the origin
	// notice that negative Z is backwards, this is a left-handed (LH) coordinate system
	deTransform M; // initialized to identity
	M.Translation.X = 0;
	M.Translation.Y = 0;
	M.Translation.Z = -3;
	m_Cam->Position() = M;

	// set some camera data for perspective rendering
	// the field of view (FOV)
	m_Cam->SetFOV(VIEW_FOV);
	// the near and far clipping distances
	m_Cam->SetClip(0.1, 10000);

	// set up a font to render text with
	m_Font = IdeFont_CreateFont();
	// using a system font (Arial, it should be installed on all systems)
	m_Font->SetFont("Arial", 12, IdeFont::STYLE_BOLD);
	m_Font->SetScale(1.0f);
	m_Font->SetColor(0xff000000); // opaque black

	// set some rendering flags
	m_Render->SetRenderFlag(IdeRender::Render_Fill);
	m_Render->SetRenderFlag(IdeRender::Render_CullNone);
	m_Render->SetBGColor(0xffffff00);

	// retrieve the scenegraph pointer
	IdeSceneGraph * pSG = IdeSceneGraph_GetSceneGraphPtr();
	// we don't need any advanced culling for a simple triangle
	// it would just slow things down
	pSG->SetCullFlags(0);

	// now let's add a little triangle
	{
		// we use the IdeMesh class to handle simple static geometry
		IdeMesh* pMesh = IdeMesh_CreateMesh();
		// we're going to build a very simple piece of geometry,
		// so we can deal with the data directly
		// we just want one submesh, with one face (face = triangle)
		pMesh->SetNumSubMeshes(1, deFALSE);
		pMesh->SetNumFaces(0, 1);
		deVertex** positions;
		deTexCoord2** uvcoords;
		deARGB** colors;
		// we do a Lock to get the geometry buffers
		if (!pMesh->Lock(positions, uvcoords, colors))
		{
			pMesh->Release();
			return false;
		}
		// make a simple triangle, with a different color at each vertex
		positions[0][0] = deMake_Vertex(-1, -1, 0);
		positions[0][1] = deMake_Vertex( 1, -1, 0);
		positions[0][2] = deMake_Vertex( 0,  1, 0);
		uvcoords[0][0] = deMake_TexCoord(  0, 1);
		uvcoords[0][1] = deMake_TexCoord(  1, 1);
		uvcoords[0][2] = deMake_TexCoord(0.5, 0);
		colors[0][0] = 0xffff0000;
		colors[0][1] = 0xff00ff00;
		colors[0][2] = 0xff0000ff;
		// we Unlock to let the geometry get stored & used
		if (!pMesh->Unlock(positions, uvcoords, colors))
		{
			pMesh->Release();
			return false;
		}
		
		// we have to set up the material properties to use emissive color
		// because we won't be inserting any lights into the scene
		IdeRenderMaterial* pMaterial = IdeRenderState_CreateRenderMaterial();
		pMaterial->Lock();
		// we use emissive since it gives us color in the absence of external light
		pMaterial->SetEmissiveSource(IdeRenderMaterial::MATSOURCE_DIFFUSE);
		pMaterial->Unlock();
		// the rendering subsystems only keep track of a whole container for
		// surface attributes, an IdeSurface
		IdeSurface* pSurface = IdeSurface_CreateSurface();
		pSurface->SetRenderMaterial(pMaterial);
		DE3D_SAFE_RELEASE(pMaterial);
		pMesh->SetSurface(0, pSurface);
		DE3D_SAFE_RELEASE(pSurface);

		// create a scene object to instantiate the mesh in the scenegraph
		IdeSceneStaticMesh* pStaticMesh = IdeSceneObject_CreateSceneStaticMesh();
		if (!pStaticMesh)
			return false;
		// link it to the IdeMesh object that holds the triangle geometry
		pStaticMesh->SetMesh(pMesh);
		DE3D_SAFE_RELEASE(pMesh);
		// record this object so we can rotate it, and release it when we finish using it
		m_Triangle = pStaticMesh;
		pSG->AddSceneObject(m_Triangle);

		// we need to put the scene object inside a "scene room" for it to be found
		IdeSceneRoom* pRoom = IdeSceneRoom_CreateRoom();
		pRoom->AddSceneObject(m_Triangle);
		pSG->AddRoom(pRoom);
		m_Room = pRoom;
	}
	
	return true;
}

// create a window and prepare it for rendering
bool TestApp::InitDisplay(HINSTANCE hInstance)
{
	// create a window so we have something to render onto
	m_WindowHandle = CreateNewWindow(hInstance);
	if(!m_WindowHandle)
	{
		return false;
	}
	
	// set the user data for the window so that WndProc will work
	SetWindowLong(m_WindowHandle, GWL_USERDATA, (LONG)(this));
	
	// set the camera to fit inside our window "just right"
	// by conforming to the window's client area
	RECT ClientRect;
	GetClientRect(m_WindowHandle, &ClientRect);
	deRect CamRect;
	CamRect.Top = ClientRect.top;
	CamRect.Left = ClientRect.left;
	CamRect.Right = ClientRect.right;
	CamRect.Bottom = ClientRect.bottom;
	m_Cam->SetRect(CamRect);
	
	// attach a destiny3d driver to this window
	m_Driver = m_Render->AttachToWindow(m_WindowHandle, DRIVER_NAME);
	if (!m_Driver)
	{
		MessageBox(m_WindowHandle, "Failed to load driver - check your working directory", "ERROR!", MB_OK);
		return false;
	}
	// now we need to prepare the specific parameters to set up the rendering view
	memset(&m_Display, 0, sizeof(IdeDriver::deDisplay));
	m_Display.Adapter = VIEW_ADAPTER;
	m_Display.IgnoreVSync = deTRUE; // ignoring vertical sync lets us see very high framerates
	if (VIEW_FULLSCREEN)
	{
		// when in fullscreen mode, we can only use a resolution that the video card supports
		// funky monitor settings (like rotated into portrait mode) can cause issues with hardcoded resolutions
		int i=0;
		deBoolean RetVal;
		do
		{
			RetVal = m_Render->GetResolution(m_Driver, m_Display.Adapter, i, &m_Display.Width, &m_Display.Height, &m_Display.RefreshRate);
			i++;
		} while (RetVal && (m_Display.Width != VIEW_WIDTH || m_Display.Height != VIEW_HEIGHT || m_Display.RefreshRate < 60));
		if (RetVal)
			DEBUGRETURN(false, "failed to find a matching fullscreen video mode");
	}
	else
	{
		// windowed mode is pretty straightforward, we just want it to fill up the window
		m_Display.Width = CamRect.Right - CamRect.Left;
		m_Display.Height = CamRect.Bottom - CamRect.Top;
		m_Display.RefreshRate = 0;
	}
	// standard 32-bit pixel format
	m_Display.BackBpp = IdeDriver::BPP_A8R8G8B8;
	m_Display.FrontBpp = IdeDriver::BPP_X8R8G8B8;
	m_Display.ZDepth = 24;				// high resolution depth buffer
	m_Display.StencilDepth = 0;			// no stencil needed
	m_Display.FSAASamples = 0;			// no anti-aliasing
	m_Display.TripleBuffer = deFALSE;	// no triple-buffering (useful only with vsync enabled)
	m_Display.Windowed = !VIEW_FULLSCREEN;
	// now we use a helper function to validate that the adapter can handle this
	// if it can't, then we'll get the closest possible supported modes
	if (!m_Render->QueryFormat(m_Driver, &m_Display, deTRUE))
	{
		MessageBox(m_WindowHandle, "QueryFormat returned false, using recommended format", "Warning", MB_OK);
	}
	
	// now we're almost ready to jump into rendering, but we don't do it yet
	// in case some dialog needs to be displayed that requires the window handle
	return true;
}

// jump into rendering mode. this will go into fullscreen if the display parameters say to
bool TestApp::StartDisplay()
{
	// now we finally load up the driver into a state that can render things
	if (!m_Driver || !m_Render->LoadDriver(m_WindowHandle, &m_Display, DRIVER_NAME))
	{
		MessageBox(m_WindowHandle, "No driver available", "ERROR!", MB_OK);
		return false;
	}
	
	// make sure the camera is keeping track of the window we'll be using
	// the camera basically defines all the rendering parameters for a viewport,
	// including the window, rect, position, FOV, and others
	m_Cam->SetWindow(m_WindowHandle);
	
	return true;
}

void TestApp::ShutDown()
{
	// these activities could be done in a destructor, but it's more explicit here
	
	// release all our resources
	DE3D_SAFE_RELEASE(m_Triangle);
	DE3D_SAFE_RELEASE(m_Room);
	DE3D_SAFE_RELEASE(m_Cam);
	DE3D_SAFE_RELEASE(m_Font);

	// and shut down the engine, after we're completely done using it
	Destiny3D_ShutDown();
}

// this can be called at unexpected times, but will mostly be called from our main loop
// via DispatchMessage()
LRESULT CALLBACK TestApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// this pointer might be NULL during window creation
	TestApp* theApp = (TestApp*)GetWindowLong(hwnd, GWL_USERDATA);

    LONG			lRet = 1; 
    switch (message)
	{ 
	case WM_ACTIVATE: // sent when window from same app is activated or deactivated
		{
			int fActive = LOWORD(wParam);           // activation flag 
			BOOL fMinimized = (BOOL) HIWORD(wParam); // minimized flag 
			HWND hwndPrevious = (HWND) lParam;       // window handle 
			if (theApp)
				theApp->m_LostFocus = (fActive == WA_INACTIVE) ? true : false;
		}
	case WM_ACTIVATEAPP:	// sent when different application is activated or deactivated
		{
			BOOL fActive = (BOOL) wParam; // minimized flag 
			
			if (theApp)
				theApp->m_LostFocus = (fActive) ? false : true;
		}
	case WM_KEYDOWN:	// sent when a "non system key" is pressed
		switch(wParam) 
		{					
		case VK_ESCAPE: // we'll exit the application when we press the ESC key
			PostQuitMessage(0);		
			break;
		case VK_SPACE:	// toggle wireframe modes when the space bar is pushed
			theApp->m_Wireframe = (theApp->m_Wireframe+1)%3;
			switch (theApp->m_Wireframe)
			{
			default:
			case 0:
				theApp->m_Render->SetRenderFlag(IdeRender::Render_Fill);
				break;
			case 1:
				theApp->m_Render->SetRenderFlag(IdeRender::Render_Mesh);
				break;
			case 2:
				theApp->m_Render->SetRenderFlag(IdeRender::Render_DepthMesh);
				break;
			}
			break;
		case VK_F12:	// take a screenshot when the F12 key is pushed
			theApp->m_Render->Screenshot(theApp->m_WindowHandle, "!screen.bmp");
			break;
		}
		break;
    case WM_CLOSE:					// If the window is being closed
        PostQuitMessage(0);			// Send a QUIT Message to the window
        break; 
    default:	// if we don't have a custom action to perform, the system might want to do something
        lRet = DefWindowProc (hwnd, message, wParam, lParam); 
        break; 
    } 
 
    return lRet;										
}


