#include "../../Rune Engine/Rune/Rune.h"
#include "../../Rune Engine/Rune/Rune Engine D3D.h"

#include "AppFramework.h"

// ************************************************************************************************************************************************************

class CSimpleSprite
{
public:
	CRuEntity*							m_entity;
	CRuFusion_Mime*						m_mime;
	CRuFusion_Actor*					m_actor;

public:
										CSimpleSprite();
										~CSimpleSprite();

	void								Update(REAL elapsedTime);
	void								Submit(CRuRendererNode_RenderContext *renderContext);
};

// ************************************************************************************************************************************************************

CSimpleSprite::CSimpleSprite()
:	m_entity(NULL),
	m_mime(NULL),
	m_actor(NULL)
{
}

CSimpleSprite::~CSimpleSprite()
{
	ruSAFE_RELEASE(m_entity);
	ruSAFE_RELEASE(m_mime);
	ruSAFE_RELEASE(m_actor);
}

void CSimpleSprite::Update(REAL elapsedTime)
{
	m_entity->Update(elapsedTime);
	m_mime->Update(elapsedTime);
	m_actor->Update(elapsedTime);
}

void CSimpleSprite::Submit(CRuRendererNode_RenderContext *renderContext)
{
	renderContext->Submit(m_entity);
}

// ************************************************************************************************************************************************************

class RuneDevPlatform : public CAppFramework
{
protected:
	// Global settings
	CRuString							m_rootDir;
	INT32								m_resourceArenaSize;
	REAL								m_cameraNearPlane;
	REAL								m_cameraFarPlane;
	REAL								m_cameraFOV;
	REAL								m_cameraBackdropNearPlane;
	REAL								m_cameraBackdropFarPlane;

	// Runtime
	UINT32								m_xScreen;
	UINT32								m_yScreen;
	BOOL								m_isWindowed;
	DWORD								m_windowStyle;

	SIZE								m_windowedResolution;
	SIZE								m_fullscreenResolution;

	RECT								m_clientRect;
	RECT								m_windowRect;

	POINT								m_mousePosition;
	BOOL								m_cameraRotateOn;

	// Core engine objects
	IRuSwapChain*						m_swapChain;

	// Timing tools
	CRuPerformanceTimer*				m_timer;
	CRuProfiler*						m_profiler;

	DWORD								m_lastUpdateTime;
	DWORD								m_lastRenderTime;

	INT32								m_probe_FrameTime;
	INT32								m_probe_UpdateTime;
	INT32								m_probe_C0;
	INT32								m_probe_C1;
	INT32								m_probe_C2;
	INT32								m_probe_C3;

	INT32								m_probe_3D_Flush;

	BOOL								m_disableRender;
	BOOL								m_showProbeValues;

	REAL								m_moveSpeed;

	// System fonts
	CRuFontType*						m_sysFontType;

	// Stuff?
	CRuCamera*							m_camera;
	CRuEntity_Widget_Grid*				m_gridWidget;
	CRuEntity_Environment*				m_worldEnvironment;
	CRuOptimalTerrain*					m_terrain;

	CRuFusion_Mime_Template*			m_mimeTemplate;

	CRuVector3							m_lastPlayerPos;
	CRuEntity*							m_playerEntity;
	CRuFusion_Mime*						m_playerMime;
	CRuFusion_Actor*					m_playerActor;

	CRuArrayList<CSimpleSprite *>		m_sprites;

	REAL								m_timeSinceLastWorldEnvironmentUpdate;

public:
										RuneDevPlatform();
	virtual								~RuneDevPlatform();

	void								SetRootDirectory(const char *rootDir);

	virtual BOOL						OnInitialized();

	virtual BOOL						Update();
	virtual BOOL						ProcessWindowMessage(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam, LRESULT &resultOut);

protected:
	void								UpdateWorldEnvironment(REAL elapsedTime);

	BOOL								SetScreenSize(UINT32 width, UINT32 height, BOOL isWindowed);

	void								PlantPaperdoll(const char *paperdollResName, const CRuVector3 &position, BOOL anchorEntity);
};

// ************************************************************************************************************************************************************

RuneDevPlatform::RuneDevPlatform()
:	m_resourceArenaSize(8388608 * 5),
	m_cameraNearPlane(1.0f),
	m_cameraFarPlane(2500.0f),
	m_cameraFOV(0.9599f),
	m_cameraBackdropNearPlane(960.0f),
	m_cameraBackdropFarPlane(4800.0f),
	m_xScreen(0),
	m_yScreen(0),
	m_isWindowed(TRUE),
	m_windowStyle(0),
	m_cameraRotateOn(FALSE),
	m_swapChain(NULL),
	m_profiler(NULL),
	m_disableRender(FALSE),
	m_showProbeValues(TRUE),
	m_moveSpeed(50.0f),
	m_mimeTemplate(NULL),
	m_playerEntity(NULL),
	m_playerMime(NULL),
	m_playerActor(NULL),
	m_timeSinceLastWorldEnvironmentUpdate(10.0f)
{
}

RuneDevPlatform::~RuneDevPlatform()
{
	// Shutdown Rune Engine
	RuShutdown();
}

void RuneDevPlatform::SetRootDirectory(const char *rootDir)
{
	m_rootDir = rootDir;
}

BOOL RuneDevPlatform::OnInitialized()
{
	// Initialize Rune Engine Core
	RuInitialize_Core();

	// Initialize Direct3D driver
	if((m_swapChain = RuInitialize_D3D(m_hFrameWnd, m_hFrameWnd, m_resourceArenaSize)) == NULL)
	{
		assert(FALSE);
		return FALSE;
	}	

	// Initialize scene system
	BOOL sceneInitializeOK = RuInitialize_Scene(m_swapChain);

	// Create timer
	m_timer = ruNEW CRuPerformanceTimer();
	m_timer->Initialize(128);
	m_timer->ElapsedTime(FALSE);

	m_lastUpdateTime = m_timer->GetTime();
	m_lastRenderTime = m_timer->GetTime();

	// Create profiler
	m_profiler = ruNEW CRuProfiler();
	m_profiler->SetHistogramSize(128);

	m_probe_FrameTime = g_ruProfiler->CreateProbe("Frame Time", "default");
	m_probe_UpdateTime = g_ruProfiler->CreateProbe("Update Time", "default");
	m_probe_C0 = g_ruProfiler->CreateProbe("C0", "default");
	m_probe_C1 = g_ruProfiler->CreateProbe("C1", "default");
	m_probe_C2 = g_ruProfiler->CreateProbe("C2", "default");
	m_probe_C3 = g_ruProfiler->CreateProbe("C3", "default");

	m_probe_3D_Flush = g_ruProfiler->CreateProbe("3D Flush", "Scene");

	// Initialize font char class
	CRuFontChar::Initialize(NULL);

	// Create system fonts
	m_sysFontType = CRuFontType::GetFontType("Arial", 15, ruFONTWEIGHT_BOLD, ruFONTOUTLINE_NORMAL);

	// Setup paths
	g_ruResourceManager->SetRootDirectory(m_rootDir);
	g_ruResourceManager->LoadPackageFile("fdb\\other.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\cache.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\interface.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\model.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\motion.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\music.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\sound.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\texture0.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\texture1.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\texture2.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\texture3.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\texture4.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\texture5.fdb");
	g_ruResourceManager->LoadPackageFile("fdb\\texture6.fdb");

/*
	for(INT32 i = 0; i < 100000; ++i)
	{
		g_ruResourceManager->ClearEntityDictionary();

		CRuEntity *blargh = g_ruResourceManager->LoadEntity("model\fx\environment\wailing_mountains\_ploygon\nor_dunegeon_door.ros");
		ruSAFE_RELEASE(blargh);
	}


	return TRUE;
*/
	// Initialize default camera
	m_camera = ruNEW CRuCamera();
//	m_camera->EnableYSmoothing(TRUE);
	m_camera->SetCameraPosition(CRuVector3(0.0f, 20.0f, -30.0f));
	m_camera->SetTargetPosition(CRuVector3(0.0f, 0.0f, 0.0f));
	m_camera->SetCameraUpVector(CRuVector3(0.0f, 1.0f, 0.0f));

	// Initialize a grid widget
	m_gridWidget = ruNEW CRuEntity_Widget_Grid();
	m_gridWidget->SetGridProperties(CRuVector3(1.0f, 0.0f, 0.0f), CRuVector3(0.0f, 1.0f, 0.0f), CRuVector3(0.0f, 0.0f, 0.0f), 100.0f, 10.0f);

	// Initialize the world environment
	m_worldEnvironment = ruNEW CRuEntity_Environment();
	m_worldEnvironment->Initialize(32, 10, 1.570796f, 300.0f, 1.0f, 2.0f);

	// Load a map
	const char *mapFile = "wdb\\world\\world.wdb";
	char mapPath[2048];
	sprintf(mapPath, "%s\\%s", g_ruResourceManager->GetRootDirectory(), mapFile);

	CRuDataStore_Disk *mapFileDataStore = ruNEW CRuDataStore_Disk();
	if(mapFileDataStore->Open(mapPath, TRUE))
	{
		CRuOptimalTerrain_DataSource_DS *terrainDS = ruNEW CRuOptimalTerrain_DataSource_DS();

		if(terrainDS->Open(mapFileDataStore))
		{
			m_terrain = ruNEW CRuOptimalTerrain();
			m_terrain->Initialize();
			m_terrain->AttachDataSource(terrainDS);
			m_terrain->GetObjectContainer()->EnableDeferredLoading(TRUE);
			m_terrain->GetDoodadContainer()->EnableDeferredLoading(TRUE);
		}

		ruSAFE_RELEASE(terrainDS);
	}

	ruSAFE_RELEASE(mapFileDataStore);

	// Construct mime templates
	m_mimeTemplate = ruNEW CRuFusion_Mime_Template();

	// Load player entity
	m_playerEntity = g_ruResourceManager->LoadEntity("model\\character\\pc\\human\\female\\act_hm_female.ros");

	RuPaperdoll_SetComponent(m_playerEntity, "head", "type01");
	RuPaperdoll_SetComponent(m_playerEntity, "torso", "body000-001");
	RuPaperdoll_SetComponent(m_playerEntity, "leg", "cloth020-003");
	RuPaperdoll_SetComponent(m_playerEntity, "hand", "cloth020-002");
	RuPaperdoll_SetComponent(m_playerEntity, "foot", "cloth020-002");
	RuPaperdoll_SetComponent(m_playerEntity, "shoulder", "cloth030-001");
	RuPaperdoll_SetComponent(m_playerEntity, "back", "leather040-001");
	RuPaperdoll_SetComponent(m_playerEntity, "hair", "type01");

	RuPaperdoll_Build(m_playerEntity);

	// Construct player mime
	m_playerMime = ruNEW CRuFusion_Mime();
	m_playerMime->SetMimeTemplate(m_mimeTemplate);
	m_playerMime->SetDefaultForwardSpeed(50.0f);
	m_playerMime->SetDefaultBackwardSpeed(50.0f);
	m_playerMime->SetTarget(m_playerEntity);

	// Construct player actor
	m_playerActor = ruNEW CRuFusion_Actor();
	m_playerActor->SetCollisionObject(m_terrain);
	m_playerActor->SetTargetMime(m_playerMime);

	m_playerEntity->SetTranslation(CRuVector3(0.0f, 20000.0f, 0.0f));
	m_camera->SetTargetPosition(m_playerEntity->GetTranslation(ruFRAMETYPE_WORLD) + CRuVector3(0.0f, 19.0f, 0.0f));
	m_camera->SetCameraPosition(m_camera->GetTargetPosition() + m_playerActor->GetTargetForwardVector() * -50.0f);
	m_camera->SetCameraUpVector(CRuVector3(0.0f, 1.0f, 0.0f));

	m_lastPlayerPos = m_playerEntity->GetTranslation(ruFRAMETYPE_WORLD);

	return TRUE;
}

BOOL RuneDevPlatform::Update()
{
	static LONG lastMallocCount = g_ruMallocCount;

	REAL updateElapsedTime = (m_timer->GetTime() - m_lastUpdateTime) * 0.001f;
	REAL renderElapsedTime = (m_timer->GetTime() - m_lastRenderTime) * 0.001f;

	if(updateElapsedTime >= 0.016f)
	{
		// Update last update time
		m_lastUpdateTime = m_timer->GetTime();

		// Reset profiler samples
		RUPROFILE_SAMPLE_CLEAR()

		// Set frame time
		g_ruProfiler->SetProbeValue(m_probe_C3, g_ruCounter_3);

		RUPROFILE_SAMPLE_BEGIN(m_probe_UpdateTime, 0);
/*
		REAL arf, res;
		DWORD t0, et;

		t0 = m_timer->GetTime();

		arf = 50.0f;
		for(INT32 i = 0; i < 1000000; ++i)
		{
			res = ssesqrt(arf);
		}

		et = m_timer->GetTime() - t0;
		g_ruProfiler->SetProbeValue(m_probe_C0, et);

		t0 = m_timer->GetTime();

		arf = 50.0f;
		for(INT32 i = 0; i < 1000000; ++i)
		{
			res = RuSqrt(arf);
		}

		et = m_timer->GetTime() - t0;
		g_ruProfiler->SetProbeValue(m_probe_C1, et);

		g_ruProfiler->SetProbeValue(m_probe_C2, g_ruCounter_0);

		g_ruCounter_0 = 0;
*/
/*
		DWORD t0, et;

		CRuHashMap<CRuGUID, PTRVALUE> *testHashMap;
		testHashMap = ruNEW CRuHashMap<CRuGUID, PTRVALUE>(2048);

		CRuArrayList<CRuGUID> guidList(512);
		for(INT32 i = 0; i < 512; ++i)
		{
			testHashMap->Add(guidList[i], guidList[i].HashCode(), i);
		}

		PTRVALUE blargh;

		t0 = m_timer->GetTime();

		for(INT32 i = 0; i < 1000000; ++i)
		{
			testHashMap->Get(guidList[i % 512], guidList[i % 512].HashCode(), blargh);
			++blargh;
		}

		et = m_timer->GetTime() - t0;
		g_ruProfiler->SetProbeValue(m_probe_C0, et);

		delete testHashMap;
*/


//		m_camera->Cycle(20.0f * updateElapsedTime, CRuVector3(0.0f, 1.0f, 0.0f));


		m_playerActor->Update(updateElapsedTime);

		CRuVector3 moveImpulse(0.0f, 0.0f, 0.0f);

		if(GetAsyncKeyState('W'))
		{
			moveImpulse.m_z += 1.0f;
		}

		if(GetAsyncKeyState('A'))
		{
			moveImpulse.m_x -= 1.0f;
		}

		if(GetAsyncKeyState('S'))
		{
			moveImpulse.m_z -= 1.0f;
		}

		if(GetAsyncKeyState('D'))
		{
			moveImpulse.m_x += 1.0f;
		}

		if(moveImpulse.Magnitude() > ruEPSILON)
		{
			moveImpulse.Normalize();
			moveImpulse = moveImpulse * m_moveSpeed;
		}

		m_playerActor->Move(moveImpulse);

		// Sync camera with player
		CRuVector3 curPlayerPos = m_playerEntity->GetTranslation(ruFRAMETYPE_WORLD);
		CRuVector3 offsetVector = curPlayerPos - m_lastPlayerPos;
		m_camera->Move(offsetVector, FALSE);

		m_lastPlayerPos = curPlayerPos;

		// Update camera
		m_camera->Update(updateElapsedTime);

		// Update world environment
		UpdateWorldEnvironment(updateElapsedTime);

		// Update grid widget
		m_gridWidget->Update(updateElapsedTime);

		// Update terrain
		m_terrain->SetCamera(m_camera);
		m_terrain->Update(updateElapsedTime);

		// Update player objects
		m_playerEntity->Update(updateElapsedTime);
		m_playerMime->Update(updateElapsedTime);

		// Update sprites
		for(INT32 i = 0; i < m_sprites.Count(); ++i)
		{
			m_sprites[i]->Update(updateElapsedTime);
		}

		// Update global function schedule
		g_ruFunctionSchedule->Update();

		RUPROFILE_SAMPLE_END(m_probe_UpdateTime, 0);
	}

//	if(renderElapsedTime >= min(0.1f, m_timer->GetAverageTimeSample()))
//	if(renderElapsedTime >= 0.008f)
	{
		m_timer->ElapsedTime();

		// Update last render time
		m_lastRenderTime = m_timer->GetTime();

		g_ruProfiler->SetProbeValue(m_probe_FrameTime, updateElapsedTime * 1000.0f);

		// If swap chain was invalidated, reset the device
		if(m_swapChain->IsInvalidated())
		{
			if(g_ruEngine->ResetDevice() == FALSE)
			{
			}
		}

		RUPROFILE_SAMPLE_BEGIN(m_probe_C3, 0);

		if(!m_swapChain->IsInvalidated() && g_ruEngine->BeginScene())
		{
			RUPROFILE_SAMPLE_END(m_probe_C3, 0);

			// Begin rendering
			g_ruRenderer->Begin();

			// Push camera into default render context
			g_ruDefaultRenderContext->Camera_Push(m_camera);
			g_ruUIRenderer->Camera_Push(m_camera);

			// Submit world environment
			g_ruDefaultRenderContext->Submit(m_worldEnvironment);

			if(!m_disableRender)
			{
				// Submit grid widget
//				g_ruDefaultRenderContext->Submit(m_gridWidget);

				// Submit terrain
				g_ruDefaultRenderContext->Submit(m_terrain);

				// Submit player
				g_ruDefaultRenderContext->Submit(m_playerEntity);

				// Submit sprites
				for(INT32 i = 0; i < m_sprites.Count(); ++i)
				{
					m_sprites[i]->Submit(g_ruDefaultRenderContext);
				}
			}

			// Output profiler prefix data
			RUCOLOR textColor = RUCOLOR_ARGB(255, 160, 160, 240);
			WCHAR *tmp = NULL;
			char text[MAX_PATH];

			sprintf(text, "Malloc Count %d Frame Time %.2f Flush: %.2f Move Speed: %f", g_ruMallocCount - lastMallocCount, g_ruProfiler->GetProbeMeanValue(m_probe_FrameTime), g_ruProfiler->GetProbeMeanValue(m_probe_3D_Flush), m_moveSpeed);
			tmp = TransToWChar(text);
			CRuFontChar::DrawText(m_sysFontType, 10, 10, 0xFFFFFF00, tmp);
			delete[] tmp;

			lastMallocCount = g_ruMallocCount;

			if(m_showProbeValues)
			{
				sprintf(text, "Profiling Level %d", g_ruProfilingLevel);

				tmp = TransToWChar(text);
				CRuFontChar::DrawText(m_sysFontType, 0, 0, textColor, tmp );
				delete[] tmp;

				char *probeGroups[] = { "default", "Engine", "Scene", "Terrain", "World", "Misc", NULL };
				int probeGroupOrigins[][2] = { { 0, 32 }, { 160, 32 }, { 320, 32 }, { 480, 32 }, { 640, 32 }, { 800, 32 } };

				for(INT32 i = 0; probeGroups[i] != NULL; ++i)
				{
					// Output probe group prefix
					tmp = TransToWChar(probeGroups[i]);
					CRuFontChar::DrawText(m_sysFontType, probeGroupOrigins[i][0], probeGroupOrigins[i][1], textColor, tmp );
					delete[] tmp;

					for(INT32 j = 0, oX = probeGroupOrigins[i][0], oY = probeGroupOrigins[i][1] + 32, numProbes = g_ruProfiler->GetNumProbes(); j < numProbes; ++j)
					{
						if(stricmp(g_ruProfiler->GetProbeGroup(j), probeGroups[i]) == 0)
						{
							sprintf(text, "[%s %.2f]", g_ruProfiler->GetProbeName(j), g_ruProfiler->GetProbeMeanValue(j));

							tmp = TransToWChar(text);
							CRuFontChar::DrawText(m_sysFontType, oX - 1, oY - 1, 0xFF000000, tmp );
							CRuFontChar::DrawText(m_sysFontType, oX, oY, textColor, tmp );
							delete[] tmp;

							oY += 12;
						}
					}
				}
			}

			// End rendering
			g_ruRenderer->End();

			// Invoke end scene on engine
			g_ruEngine->EndScene();

			DWORD t0 = m_timer->GetTime();

			// Present the swap chain
			m_swapChain->Present();

			DWORD et = m_timer->GetTime() - t0;

			g_ruProfiler->SetProbeValue(m_probe_C0, et);

		}
	}

	return TRUE;
}

BOOL RuneDevPlatform::ProcessWindowMessage(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam, LRESULT &resultOut)
{
	switch(iMsg)
	{
		case WM_SIZE:
			switch(wParam)
			{
				case SIZE_RESTORED:		
					{
						RECT rect;
						GetClientRect(hWnd, &rect);

						UINT32 height = (UINT32) (rect.bottom - rect.top);
						UINT32 width = (UINT32) (rect.right - rect.left);

						SetScreenSize(width, height, m_isWindowed);
					}
					break;

				case SIZE_MAXIMIZED:
					{
						RECT rect;
						GetClientRect(hWnd, &rect);

						UINT32 height = (UINT32) (rect.bottom - rect.top);
						UINT32 width = (UINT32) (rect.right - rect.left);

						SetScreenSize(width, height, m_isWindowed);
					}
					break;
			}
			break;

		case WM_KEYDOWN:
			{
				// Process key code
				switch(wParam)
				{
					case 'W':
						break;
				}

				// Store result value
				resultOut = 0;
			}
			return TRUE;

		case WM_KEYUP:
			{
				// Process key code
				switch(wParam)
				{
					case VK_OEM_MINUS:
						{
							--g_ruProfilingLevel;
						}
						break;

					case VK_OEM_PLUS:
						{
							++g_ruProfilingLevel;
						}
						break;

					case 'M':
						{
							if(GetAsyncKeyState(VK_CONTROL) & 0x70000000)
							{
								// Resource manager metrics
								g_ruResourceManager->OutputResourceManifest();

								// Engine meterics
								INT32 deviceBufferPoolSize = g_ruEngine->Metric_GetDeviceBufferPoolFootprint();
								char outputStr[200];
								sprintf(outputStr, "Device Buffer Pool Footprint: %d\n", deviceBufferPoolSize);
								OutputDebugString(outputStr);

								CRuKeyHash<PTRVALUE> enumHash(4096);
								INT32 terrainFootprint = m_terrain->GetMemoryFootprint(&enumHash);
								sprintf(outputStr, "Terrain Footprint: %d\n", terrainFootprint);
								OutputDebugString(outputStr);
							}
						}
						break;

					case 'P':
						{
							if(GetAsyncKeyState(VK_CONTROL) & 0x70000000)
							{
								PlantPaperdoll("model\\character\\pc\\human\\male\\act_hm_male.ros", m_playerEntity->GetTranslation(ruFRAMETYPE_WORLD), TRUE);
							}
						}
						break;

					case ' ':
						{
							m_playerActor->Jump(45.0f);
						}
						break;

					case 'T':
						if(GetAsyncKeyState(VK_CONTROL) & 0x70000000)
						{
							g_ruInstanceManager->ClearCache();
						}
						break;

					case 'C':
						if(GetAsyncKeyState(VK_CONTROL) & 0x70000000)
						{
							for(INT32 i = 0; i < m_sprites.Count(); ++i)
							{
								ruSAFE_DELETE(m_sprites[i]);
							}
							m_sprites.Clear();
						}
						break;

					case VK_OEM_4:		// [
						{
							m_moveSpeed -= 10.0f;
						}
						break;

					case VK_OEM_6:		// ]
						{
							m_moveSpeed += 10.0f;
						}
						break;

					case VK_F5:
						{
//							m_disableRender = !m_disableRender;
							g_ruDefaultRenderContext->m_disableRender = !g_ruDefaultRenderContext->m_disableRender;
						}
						break;

					case VK_F12:
						{
							m_showProbeValues = !m_showProbeValues;
						}
						break;
				}

				// Store result value
				resultOut = 0;
			}
			return TRUE;

		case WM_RBUTTONDOWN:
			{
				// Enable camera rotation
				m_cameraRotateOn = TRUE;

				// Capture the mouse
				SetCapture(m_hFrameWnd);

				// Hide the mouse cursor
				ShowCursor(FALSE);

				// Store mouse position at time of capture
				m_mousePosition.x = LOWORD(lParam);
				m_mousePosition.y = HIWORD(lParam);
			}
			return TRUE;

		case WM_RBUTTONUP:
			{
				// Disable camera rotation
				m_cameraRotateOn = FALSE;

				// Release the mouse
				ReleaseCapture();

				// Show the mouse cursor
				ShowCursor(TRUE);
			}
			return TRUE;

		case WM_MOUSEMOVE:
			{
				if(m_cameraRotateOn)
				{
					// Calculate mouse delta
					INT32 xDelta = LOWORD(lParam) - m_mousePosition.x;
					INT32 yDelta = HIWORD(lParam) - m_mousePosition.y;

					POINT mousePosScr = m_mousePosition;
					ClientToScreen(m_hFrameWnd, &mousePosScr);

					// Reset mouse position
					SetCursorPos(mousePosScr.x, mousePosScr.y);

					// Build Y-axis rotation for the player actor's forward vector
					CRuQuaternion rotQuat;
					rotQuat.FromAngleAxis(CRuVector3(0.0f, 1.0f, 0.0f), -xDelta * 0.002f);

					// Rotate the player actor's forward vector
					CRuVector3 playerForward = m_playerActor->GetTargetForwardVector();
					rotQuat.TransformVector(playerForward);
					m_playerActor->SetForwardVector(playerForward);

					// Cycle camera
					m_camera->Cycle(-xDelta * 0.002f, CRuVector3(0.0f, 1.0f, 0.0f));
					m_camera->Cycle(-yDelta * 0.002f, m_camera->GetCameraRightVector());
				}
			}
			return TRUE;

		case WM_MOUSEWHEEL:
			{
				REAL rotDist = static_cast<INT16>(HIWORD(wParam)) * WHEEL_DELTA * 0.0001f;

				CRuVector3 camVec = m_camera->GetTargetPosition() - m_camera->GetCameraPosition();
				CRuVector3 moveDist = m_camera->GetCameraLookAtVector() * rotDist;
				CRuVector3 newCamPos = m_camera->GetCameraPosition() + moveDist;
				CRuVector3 newCamVec = m_camera->GetTargetPosition() - newCamPos;

				if(DotProduct(camVec, newCamVec) > 0.0f)
				{
					m_camera->SetCameraPosition(newCamPos);
				}
			}
			return TRUE;
	}

	return FALSE;
}

void RuneDevPlatform::UpdateWorldEnvironment(REAL elapsedTime)
{
	if(m_worldEnvironment)
	{
		m_timeSinceLastWorldEnvironmentUpdate += elapsedTime;

		if(m_timeSinceLastWorldEnvironmentUpdate > 1.0f)
		{
			m_timeSinceLastWorldEnvironmentUpdate = 0.0f;

			// Select proper zone descriptor to use
			if(m_terrain)
			{
				CRuWorld_ZoneDescriptor *zoneDescriptor = m_terrain->GetZoneDescriptor(m_camera->GetCameraPosition());

				if(zoneDescriptor)
				{
					CRuWorld_ZoneSettings *zoneSettings = zoneDescriptor->GetZoneSettings(0, 0);

					if(zoneSettings == NULL)
					{
						CRuWorld_ZoneDescriptor *parentZoneDescriptor = m_terrain->GetZoneDescriptorByID(zoneDescriptor->GetParentZoneID());

						while(parentZoneDescriptor)
						{
							zoneSettings = parentZoneDescriptor->GetZoneSettings(0, 0);

							if(zoneSettings)
								break;

							parentZoneDescriptor = m_terrain->GetZoneDescriptorByID(parentZoneDescriptor->GetParentZoneID());
						}
					}

					// Select zone descriptor with time and weather settings
					m_worldEnvironment->SelectZoneDescriptor(zoneDescriptor, zoneSettings);
				}
			}
		}

		// Update world environment
		m_worldEnvironment->SetCamera(m_camera);
		m_worldEnvironment->Update(elapsedTime);
	}
}

BOOL RuneDevPlatform::SetScreenSize(UINT32 width, UINT32 height, BOOL isWindowed)
{
	if(m_xScreen != width || m_yScreen != height || m_isWindowed != isWindowed)
	{
		// Store new window parameters
		m_isWindowed = isWindowed;
		m_xScreen = width;
		m_yScreen = height;

		if(m_isWindowed)
		{
			m_windowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
		}
		else
		{
			m_windowStyle = WS_POPUP | WS_VISIBLE;
		}

		// Adjust window style
		SetWindowLong(m_hFrameWnd, GWL_STYLE, m_windowStyle);

		// Adjust window rect based on the current window style
		RECT rc;
		SetRect(&rc, 0, 0, width, height);
		AdjustWindowRect(&rc, m_windowStyle, FALSE);

		if(isWindowed)
		{
			SetWindowPos(m_hFrameWnd, HWND_NOTOPMOST, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW | SWP_NOMOVE);

			// Store windowed resolution
			m_windowedResolution.cx = width;
			m_windowedResolution.cy = height;
		}
		else
		{
			POINT origin = { 0, 0 };
			ScreenToClient(m_hFrameWnd, &origin);
			SetWindowPos(m_hFrameWnd, HWND_TOPMOST, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW);

			// Store fullscreen resolution
			m_fullscreenResolution.cx = width;
			m_fullscreenResolution.cy = height;
		}

		// Update window and client rects
		GetWindowRect(m_hFrameWnd, &m_windowRect);
		GetClientRect(m_hFrameWnd, &m_clientRect);

		// Set resolution
		if (!RuSetResolution(m_swapChain, width, height, !isWindowed))
			return FALSE;

		// Set camera FOV and near/far planes
		if(m_camera)
		{
			m_camera->SetPerspectiveProjectionFOV(m_cameraNearPlane, m_cameraFarPlane, m_cameraFOV, (float) width / height);
			m_camera->SetBackdropDistance(m_cameraBackdropNearPlane, m_cameraBackdropFarPlane);
		}

		return TRUE;
	}

	return FALSE;
}

void RuneDevPlatform::PlantPaperdoll(const char *paperdollResName, const CRuVector3 &position, BOOL anchorEntity)
{
	CSimpleSprite *sprite = ruNEW CSimpleSprite();

	// Load player entity
	sprite->m_entity = g_ruResourceManager->LoadEntity(paperdollResName);

	// Set paperdoll components
	RuPaperdoll_SetComponent(sprite->m_entity, "head", "type01");
	RuPaperdoll_SetComponent(sprite->m_entity, "torso", "body000-001");
	RuPaperdoll_SetComponent(sprite->m_entity, "leg", "cloth020-003");
	RuPaperdoll_SetComponent(sprite->m_entity, "hand", "cloth020-002");
	RuPaperdoll_SetComponent(sprite->m_entity, "foot", "cloth020-002");
	RuPaperdoll_SetComponent(sprite->m_entity, "shoulder", "cloth030-001");
	RuPaperdoll_SetComponent(sprite->m_entity, "back", "leather040-001");
	RuPaperdoll_SetComponent(sprite->m_entity, "hair", "type01");

	// Build
	RuPaperdoll_Build(sprite->m_entity);

	// Construct player mime
	sprite->m_mime = ruNEW CRuFusion_Mime();
	sprite->m_mime->SetMimeTemplate(m_mimeTemplate);
	sprite->m_mime->SetDefaultForwardSpeed(50.0f);
	sprite->m_mime->SetDefaultBackwardSpeed(50.0f);
	sprite->m_mime->SetTarget(sprite->m_entity);

	// Construct player actor
	sprite->m_actor = ruNEW CRuFusion_Actor();
	sprite->m_actor->SetCollisionObject(m_terrain);
	sprite->m_actor->SetTargetMime(sprite->m_mime);

	// Set sprite initial position
	sprite->m_entity->SetTranslation(position);

	m_sprites.Add(sprite);
}

// ************************************************************************************************************************************************************

TCHAR MiniDumpFilenameW[64] = TEXT("");
char  MiniDumpFilenameA[64] = "";		// Don't use alloca before writing out minidump.

INT32 CreateMiniDump(LPEXCEPTION_POINTERS exceptionInfo)
{
/*
	// Try to create file for minidump
	HANDLE fileHandle	= TCHAR_CALL_OS( 
		CreateFileW( MiniDumpFilenameW, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ), 
		CreateFileA( MiniDumpFilenameA, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ) 
		);

	// Write a minidump
	if(fileHandle)
	{
		MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo;

		dumpExceptionInfo.ThreadId			= GetCurrentThreadId();
		dumpExceptionInfo.ExceptionPointers	= exceptionInfo;
		dumpExceptionInfo.ClientPointers	= true;

		MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), FileHandle, MiniDumpNormal, &dumpExceptionInfo, NULL, NULL);
		CloseHandle( FileHandle );
	}

	// Walk the stack
	DWORD64 backTrace[64];
	appInitStackWalking();
	const INT result = appWalkStack( ExceptionInfo, BackTrace, ARRAY_COUNT( BackTrace ) );
	appDumpStack( BackTrace, ARRAY_COUNT( BackTrace ), CatSymbolToErrorHist );

	return result;
*/
	return 0;
}

// ************************************************************************************************************************************************************

static int GuardedMain(const TCHAR *cmdLine, HINSTANCE hInstance, HINSTANCE hPrevInstance, int iCmdShow)
{
	RuneDevPlatform appFramework;
	appFramework.SetRootDirectory("d:\\resource\\");
//	appFramework.SetRootDirectory(".\\");
	appFramework.Initialize(hInstance, 1024, 768);
	return appFramework.Run();
}

// ************************************************************************************************************************************************************

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	INT32 errorLevel = 0;
	const TCHAR *cmdLine = GetCommandLine();

#ifdef _DEBUG
	if(true)
#else
	if(IsDebuggerPresent())
#endif
	{
		// Don't use exception handling when a debugger is attached to exactly trap the crash.
		errorLevel = GuardedMain(cmdLine, hInstance, hPrevInstance, iCmdShow);
	}
	else
	{
		// Use structured exception handling to trap any crashes, walk the the stack and display a crash dialog box.
		__try
		{
			errorLevel = GuardedMain(cmdLine, hInstance, hPrevInstance, iCmdShow);
		}
		__except(CreateMiniDump(GetExceptionInformation()))
		{
			// Crashed, do something here
			errorLevel = 1;
		}
	}

	// Final shut down
	return errorLevel;
}

// ************************************************************************************************************************************************************
