#include "../../Scene/Terrain/RuEntity_Environment.h"

#include "../../Helper/RuHelper_Math.h"
#include "../../Helper/RuHelper_Entity.h"
#include "../../Scene/Base/RuSceneBase.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuEntity_Environment, CRuEntity_Container_Impl)
ruHEAP_DEFINE_SUBCLASS(CRuEntity_Environment, "CRuEntity_Environment", "CRuEntity_Container_Impl")
ruCLASSGUID_DEFINE(CRuEntity_Environment, 0x9DA68307, 0xBE0244d4, 0x9321679F, 0xC5FF96FD)

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

CRuEntity_Environment::CRuEntity_Environment()
{
	m_iteration = 1;
	m_lastApplyIteration = 0;

	m_activeZoneDesc = NULL;
	m_queuedZoneDesc = NULL;
	m_zoneDescriptorDefaultTransitionDuration = 3.0f;
	m_zoneDescriptorTransitionT = 0.0f;
	m_zoneDescriptorTransitionDuration = 0.0f;

	m_zoneDescriptor0 = NULL;
	m_zoneDescriptor1 = NULL;

	m_lightTree = NULL;
	m_envLight0 = NULL;
	m_envLight1 = NULL;
	m_envLight2 = NULL;
	m_envLight3 = NULL;
	m_envLight4 = NULL;

	m_skyDome = NULL;
	m_sky_Gradient = NULL;
	m_sky_Horizon = NULL;
	m_sky_Overhead = NULL;

	m_customSkyBox0 = NULL;
	m_customSkyBox1 = NULL;
}

CRuEntity_Environment::~CRuEntity_Environment()
{
	ruSAFE_RELEASE(m_lightTree);
	ruSAFE_RELEASE(m_envLight0);
	ruSAFE_RELEASE(m_envLight1);
	ruSAFE_RELEASE(m_envLight2);
	ruSAFE_RELEASE(m_envLight3);
	ruSAFE_RELEASE(m_envLight4);

	ruSAFE_RELEASE(m_skyDome);
	ruSAFE_RELEASE(m_sky_Gradient);
	ruSAFE_RELEASE(m_sky_Horizon);
	ruSAFE_RELEASE(m_sky_Overhead);

	ruSAFE_RELEASE(m_customSkyBox0);
	ruSAFE_RELEASE(m_customSkyBox1);
}

BOOL CRuEntity_Environment::SerializeFrom(IRuStream *stream)
{
	return FALSE;
}

BOOL CRuEntity_Environment::SerializeTo(IRuStream *stream)
{
	return FALSE;
}

BOOL CRuEntity_Environment::ResetStates()
{
	if(m_skyDome)
	{
		m_skyDome->ResetStates();
	}

	return TRUE;
}

BOOL CRuEntity_Environment::Update(float elapsedTime)
{
	if(m_zoneDescriptor0)
	{
		CRuColor4 color_Primary;
		CRuColor4 color_Secondary;
		CRuColor4 color_Ambient;
		CRuColor4 color_Specular;
		CRuColor4 color_Fog;

		REAL fog_Start;
		REAL fog_End;

		REAL theta_Standard;
		REAL phi_Standard;

		REAL theta_Specular;
		REAL phi_Specular;

		CRuColor4 color_Horizon;
		CRuColor4 color_Overhead;

		// Interpolate lighting values
		if(m_zoneDescriptorTransitionDuration > 0.0f && m_zoneDescriptor1)
		{
			REAL t1 = m_zoneDescriptorTransitionT / m_zoneDescriptorTransitionDuration;
			REAL t0 = 1.0f - t1;

			color_Primary = CRuColor4(m_zoneDescriptor0->m_color_Primary) * t0 + CRuColor4(m_zoneDescriptor1->m_color_Primary) * t1;
			color_Secondary = CRuColor4(m_zoneDescriptor0->m_color_Secondary) * t0 + CRuColor4(m_zoneDescriptor1->m_color_Secondary) * t1;
			color_Ambient = CRuColor4(m_zoneDescriptor0->m_color_Ambient) * t0 + CRuColor4(m_zoneDescriptor1->m_color_Ambient) * t1;
			color_Specular = CRuColor4(m_zoneDescriptor0->m_color_Specular) * t0 + CRuColor4(m_zoneDescriptor1->m_color_Specular) * t1;
			color_Fog = CRuColor4(m_zoneDescriptor0->m_color_Fog) * t0 + CRuColor4(m_zoneDescriptor1->m_color_Fog) * t1;

			fog_Start = m_zoneDescriptor0->m_fog_Start * t0 + m_zoneDescriptor1->m_fog_Start * t1;
			fog_End = m_zoneDescriptor0->m_fog_End * t0 + m_zoneDescriptor1->m_fog_End * t1;

			theta_Standard = m_zoneDescriptor0->m_theta_Standard * t0 + m_zoneDescriptor1->m_theta_Standard * t1;
			phi_Standard = m_zoneDescriptor0->m_phi_Standard * t0 + m_zoneDescriptor1->m_phi_Standard * t1;

			theta_Specular = m_zoneDescriptor0->m_theta_Specular * t0 + m_zoneDescriptor1->m_theta_Specular * t1;
			phi_Specular = m_zoneDescriptor0->m_phi_Specular * t0 + m_zoneDescriptor1->m_phi_Specular * t1;

			color_Horizon = CRuColor4(m_zoneDescriptor0->m_cloud_HorizonColor) * t0 + CRuColor4(m_zoneDescriptor1->m_cloud_HorizonColor) * t1;
			color_Overhead = CRuColor4(m_zoneDescriptor0->m_cloud_OverheadColor) * t0 + CRuColor4(m_zoneDescriptor1->m_cloud_OverheadColor) * t1;

			if(m_customSkyBox0)
			{
				m_customSkyBox0->GetParameterBlock()->SetParameter(ruPARAMTYPE_GLOBAL_ALPHA, RuCastFloatToDWORD(t0));
			}

			if(m_customSkyBox1)
			{
				m_customSkyBox1->GetParameterBlock()->SetParameter(ruPARAMTYPE_GLOBAL_ALPHA, RuCastFloatToDWORD(t1));
			}

			// Update transition time and terminate transition if criteria are met
			m_zoneDescriptorTransitionT += elapsedTime;
			if(m_zoneDescriptorTransitionT >= m_zoneDescriptorTransitionDuration)
			{
				m_zoneDescriptorTransitionT = 0.0f;
				m_zoneDescriptorTransitionDuration = 0.0f;

				m_zoneDescriptor0 = m_zoneDescriptor1;
				m_zoneDescriptor1 = NULL;

				m_customSkyBox0->DetachFromParent();
				ruSAFE_RELEASE(m_customSkyBox0);
				m_customSkyBox0 = m_customSkyBox1;
				m_customSkyBox1 = NULL;
			}

			++m_iteration;
		}
		else
		{
			color_Primary = CRuColor4(m_zoneDescriptor0->m_color_Primary);
			color_Secondary = CRuColor4(m_zoneDescriptor0->m_color_Secondary);
			color_Ambient = CRuColor4(m_zoneDescriptor0->m_color_Ambient);
			color_Specular = CRuColor4(m_zoneDescriptor0->m_color_Specular);
			color_Fog = CRuColor4(m_zoneDescriptor0->m_color_Fog);

			fog_Start = m_zoneDescriptor0->m_fog_Start;
			fog_End = m_zoneDescriptor0->m_fog_End;

			theta_Standard = m_zoneDescriptor0->m_theta_Standard;
			phi_Standard = m_zoneDescriptor0->m_phi_Standard;

			theta_Specular = m_zoneDescriptor0->m_theta_Specular;
			phi_Specular = m_zoneDescriptor0->m_phi_Specular;

			color_Horizon = CRuColor4(m_zoneDescriptor0->m_cloud_HorizonColor);
			color_Overhead = CRuColor4(m_zoneDescriptor0->m_cloud_OverheadColor);

			// If there is a queued zone transition, apply it now
			if(m_queuedZoneDesc)
			{
				// Apply transition
				TransitionZoneDescriptor(m_queuedZoneDesc, m_zoneDescriptorDefaultTransitionDuration);

				// Reset queued zone ID
				m_activeZoneDesc = m_queuedZoneDesc;
				m_queuedZoneDesc = NULL;
			}
		}

		// Apply lighting values into system
		if(m_iteration != m_lastApplyIteration)
		{
			// Set cloud color
			m_skyDome->SetHorizonCloudColor(color_Horizon);
			m_skyDome->SetOverheadCloudColor(color_Overhead);

			// Set light direction
			m_envLight0->SetDirection(RuMathHelper_GenerateLightingVector(theta_Standard, phi_Standard));

			// Set light colors
			m_envLight0->SetDiffuse(color_Primary);
			m_envLight1->SetDiffuse(color_Secondary);
			m_envLight2->SetDiffuse(color_Secondary);
			m_envLight3->SetDiffuse(color_Secondary);
			m_envLight4->SetDiffuse(color_Secondary);

			// Update lights
			m_envLight0->Update(0.0f);
			m_envLight1->Update(0.0f);
			m_envLight2->Update(0.0f);
			m_envLight3->Update(0.0f);
			m_envLight4->Update(0.0f);

			// Update lighting tree
			m_lightTree->Update(0.0f);

			// Set fog parameters into the scene parameter block
			g_ruSceneRenderer->GetShaderParameterBlock()->SetParameter(ruPARAMTYPE_FOG_START, RuCastFloatToDWORD(fog_Start));
			g_ruSceneRenderer->GetShaderParameterBlock()->SetParameter(ruPARAMTYPE_FOG_END, RuCastFloatToDWORD(fog_End));
			g_ruSceneRenderer->GetShaderParameterBlock()->SetParameter(ruPARAMTYPE_FOG_COLOR, color_Fog.GetARGB());

			// Set specular color
			g_ruSceneRenderer->SetSpecularColor(color_Specular.GetARGB());

			// Set specular direction
			g_ruSceneRenderer->SetSpecularDirection(RuMathHelper_GenerateLightingVector(theta_Specular, phi_Specular) * -1.0f);

			// Set lighting prop
			g_ruSceneRenderer->SetLightingProp(m_lightTree);

			// Update iteration
			m_lastApplyIteration = m_iteration;
		}
	}

	REAL multiplier0 = 1.0f;
	REAL multiplier1 = 1.0f;

	if(m_zoneDescriptor0)
	{
		multiplier0 = m_zoneDescriptor0->m_frameRateMultiplier;
	}

	if(m_zoneDescriptor1)
	{
		multiplier1 = m_zoneDescriptor1->m_frameRateMultiplier;
	}

	if(m_skyDome)
	{
		m_skyDome->Update(elapsedTime);
	}

	if(m_customSkyBox0)
	{
		m_customSkyBox0->Update(elapsedTime * multiplier0);
	}

	if(m_customSkyBox1)
	{
		m_customSkyBox1->Update(elapsedTime * multiplier0);
	}

	// Chain update call to parent class
	return CRuEntity_Container_Impl::Update(0.0f);
}

BOOL CRuEntity_Environment::Initialize(INT32 horzSegments, INT32 vertSegments, float vertSweep, float radius, float vertScale, float horzCompression, INT32 cloudPlaneSegments)
{
	// Create lighting set
	m_lightTree = ruNEW CRuAABBTree();

	m_envLight0 = ruNEW CRuEntity_Light();
	m_envLight1 = ruNEW CRuEntity_Light();
	m_envLight2 = ruNEW CRuEntity_Light();
	m_envLight3 = ruNEW CRuEntity_Light();
	m_envLight4 = ruNEW CRuEntity_Light();

	CRuVector3 direction0(0.5f, -1.0f, 0.5f);
	CRuVector3 direction1(0.0f, 0.0f, 1.0f);
	CRuVector3 direction2(0.0f, 0.0f, -1.0f);
	CRuVector3 direction3(1.0f, 0.0f, 0.0f);
	CRuVector3 direction4(-1.0f, 0.0f, 0.0f);

	direction0.Normalize();
	direction1.Normalize();
	direction2.Normalize();
	direction3.Normalize();
	direction4.Normalize();

	m_envLight0->SetLightType(ruLIGHTTYPE_DIRECTIONAL);
	m_envLight0->SetDirection(direction0);
	m_envLight0->SetDiffuse(CRuColor4(1.0f, 0.8f, 0.8f, 0.8f));
	m_envLight0->Update(0.0f);

	m_envLight1->SetLightType(ruLIGHTTYPE_DIRECTIONAL);
	m_envLight1->SetDirection(direction1);
	m_envLight1->SetDiffuse(CRuColor4(1.0f, 0.2f, 0.2f, 0.2f));
	m_envLight1->Update(0.0f);

	m_envLight2->SetLightType(ruLIGHTTYPE_DIRECTIONAL);
	m_envLight2->SetDirection(direction2);
	m_envLight2->SetDiffuse(CRuColor4(1.0f, 0.2f, 0.2f, 0.2f));
	m_envLight2->Update(0.0f);

	m_envLight3->SetLightType(ruLIGHTTYPE_DIRECTIONAL);
	m_envLight3->SetDirection(direction3);
	m_envLight3->SetDiffuse(CRuColor4(1.0f, 0.2f, 0.2f, 0.2f));
	m_envLight3->Update(0.0f);

	m_envLight4->SetLightType(ruLIGHTTYPE_DIRECTIONAL);
	m_envLight4->SetDirection(direction4);
	m_envLight4->SetDiffuse(CRuColor4(1.0f, 0.2f, 0.2f, 0.2f));
	m_envLight4->Update(0.0f);

	m_lightTree->AddEntity(m_envLight0);
	m_lightTree->AddEntity(m_envLight1);
	m_lightTree->AddEntity(m_envLight2);
	m_lightTree->AddEntity(m_envLight3);
	m_lightTree->AddEntity(m_envLight4);

	// Release old sky dome
	ruSAFE_RELEASE(m_skyDome);

	// Create new sky dome
	m_skyDome = ruNEW CRuSkyDome();
	m_skyDome->Initialize(horzSegments, vertSegments, vertSweep, radius, vertScale, horzCompression, cloudPlaneSegments);

	this->AddChild(m_skyDome);

	return TRUE;
}

void CRuEntity_Environment::SetCamera(CRuCamera *viewCamera)
{
	if(m_skyDome)
	{
		m_skyDome->SetCamera(viewCamera);
	}

	if(m_customSkyBox0)
	{
 		m_customSkyBox0->SetTranslation(viewCamera->GetCameraPosition());
	}

	if(m_customSkyBox1)
	{
		m_customSkyBox1->SetTranslation(viewCamera->GetCameraPosition());
	}
}

BOOL CRuEntity_Environment::SetZoneDescriptor(const CRuGaia_ZoneDescriptor *zoneDescriptor)
{
	// Increment iteration
	++m_iteration;

	// Store parameters
	m_zoneDescriptorTransitionT = 0.0f;
	m_zoneDescriptorTransitionDuration = 0.0f;

	m_zoneDescriptor0 = zoneDescriptor;
	m_zoneDescriptor1 = NULL;

	if(m_zoneDescriptor0)
	{
		// Set cloud settings
		m_skyDome->SetHorizonCloudColor(CRuColor4(m_zoneDescriptor0->m_cloud_HorizonColor));
		m_skyDome->SetHorizonCloudCount(m_zoneDescriptor0->m_cloud_HorizonCount);

		m_skyDome->SetOverheadCloudColor(CRuColor4(m_zoneDescriptor0->m_cloud_OverheadColor));
		m_skyDome->SetOverheadCloudCount(m_zoneDescriptor0->m_cloud_OverheadCount);

		// Load the specified sky gradient texture
		ruSAFE_RELEASE(m_sky_Gradient);
		if(m_zoneDescriptor0->m_skyGradientTextureName)
		{
			m_sky_Gradient = g_ruResourceManager->LoadTexture(m_zoneDescriptor0->m_skyGradientTextureName);
		}

		// Load the specified horizon cloud texture
		ruSAFE_RELEASE(m_sky_Horizon);
		if(m_zoneDescriptor0->m_horizonCloudTextureName)
		{
			m_sky_Horizon = g_ruResourceManager->LoadTexture(m_zoneDescriptor0->m_horizonCloudTextureName);
		}

		// Load the specified overhead cloud texture
		ruSAFE_RELEASE(m_sky_Overhead);
		if(m_zoneDescriptor0->m_overheadCloudTextureName)
		{
			m_sky_Overhead = g_ruResourceManager->LoadTexture(m_zoneDescriptor0->m_overheadCloudTextureName);
		}

		// Set the textures into sky dome
		if(m_skyDome)
		{
			if(m_sky_Gradient)
			{
				m_skyDome->SetSkyDomeGradientTexture(m_sky_Gradient);
			}

			if(m_sky_Horizon)
			{
				m_skyDome->SetHorizonCloudTexture(m_sky_Horizon);
			}

			if(m_sky_Overhead)
			{
				m_skyDome->SetOverheadCloudTexture(m_sky_Overhead);
			}
		}

		// Load the custom sky box if it is available
		if(m_zoneDescriptor0->m_customSkyBoxName)
		{
			if(m_customSkyBox0 == NULL || strcmp(m_customSkyBox0->GetResourceName(), m_zoneDescriptor0->m_customSkyBoxName) != 0)
			{
				// Load sky box
				ruSAFE_RELEASE(m_customSkyBox0);
				m_customSkyBox0 = g_ruResourceManager->LoadEntity(m_zoneDescriptor0->m_customSkyBoxName);

				// Begin motion on the sky box
				if(m_customSkyBox0)
					RuACT_PlayMotionEx(m_customSkyBox0, 0xFFFFFFFF, "idle");
			}
		}

		// Reset custom sky box 1
		ruSAFE_RELEASE(m_customSkyBox1);

		// Remove all entities attached to the container
		this->RemoveAllChildren();

		// Insert custom sky box if it is available
		if(m_customSkyBox0)
		{
			this->AddChild(m_customSkyBox0);
		}

		// Insert default sky box if there is no custom sky box loaded
		if(m_customSkyBox0 == NULL && m_customSkyBox1 == NULL)
		{
			this->AddChild(m_skyDome);
		}
	}

	return TRUE;
}

BOOL CRuEntity_Environment::TransitionZoneDescriptor(const CRuGaia_ZoneDescriptor *zoneDescriptor, float transitionDuration)
{
	// If there is no existing zone descriptor, set this one into the environment directly
	if(m_zoneDescriptor0 == NULL)
	{
		return SetZoneDescriptor(zoneDescriptor);
	}

	// Ignore call if the same transition is already taking place
	if(m_zoneDescriptor1 == zoneDescriptor)
	{
		return TRUE;
	}

	// Increment iteration
	++m_iteration;

	// Store parameters
	m_zoneDescriptorTransitionT = 0.0f;
	m_zoneDescriptorTransitionDuration = transitionDuration;

	m_zoneDescriptor1 = zoneDescriptor;

	if(m_zoneDescriptor1)
	{
		// Set cloud counts
		m_skyDome->SetHorizonCloudCount(m_zoneDescriptor1->m_cloud_HorizonCount);
		m_skyDome->SetOverheadCloudCount(m_zoneDescriptor1->m_cloud_OverheadCount);

		// Load the specified sky gradient texture
		ruSAFE_RELEASE(m_sky_Gradient);
		if(m_zoneDescriptor1->m_skyGradientTextureName)
		{
			m_sky_Gradient = g_ruResourceManager->LoadTexture(m_zoneDescriptor1->m_skyGradientTextureName);
		}

		// Load the specified horizon cloud texture
		ruSAFE_RELEASE(m_sky_Horizon);
		if(m_zoneDescriptor1->m_horizonCloudTextureName)
		{
			m_sky_Horizon = g_ruResourceManager->LoadTexture(m_zoneDescriptor1->m_horizonCloudTextureName);
		}

		// Load the specified overhead cloud texture
		ruSAFE_RELEASE(m_sky_Overhead);
		if(m_zoneDescriptor1->m_overheadCloudTextureName)
		{
			m_sky_Overhead = g_ruResourceManager->LoadTexture(m_zoneDescriptor1->m_overheadCloudTextureName);
		}

		// Set the textures into sky dome
		if(m_skyDome)
		{
			if(m_sky_Gradient)
			{
				m_skyDome->TransitionSkyDomeGradientTexture(m_sky_Gradient, transitionDuration);
			}

			if(m_sky_Horizon)
			{
				m_skyDome->TransitionHorizonCloudTexture(m_sky_Horizon, transitionDuration);
			}

			if(m_sky_Overhead)
			{
				m_skyDome->TransitionOverheadCloudTexture(m_sky_Overhead, transitionDuration);
			}
		}

		// Load the custom sky box if it is available
		if(m_zoneDescriptor0->m_customSkyBoxName)
		{
			if(m_customSkyBox0 == NULL || strcmp(m_customSkyBox0->GetResourceName(), m_zoneDescriptor0->m_customSkyBoxName) != 0)
			{
				// Load sky box
				ruSAFE_RELEASE(m_customSkyBox0);
				m_customSkyBox0 = g_ruResourceManager->LoadEntity(m_zoneDescriptor0->m_customSkyBoxName);

				// Begin motion on the sky box
				if(m_customSkyBox0)
					RuACT_PlayMotionEx(m_customSkyBox0, 0xFFFFFFFF, "idle");
			}
		}

		// Load the custom sky box if it is available
		if(m_zoneDescriptor1->m_customSkyBoxName)
		{
			if(m_customSkyBox1 == NULL || strcmp(m_customSkyBox1->GetResourceName(), m_zoneDescriptor1->m_customSkyBoxName) != 0)
			{
				// Load sky box
				ruSAFE_RELEASE(m_customSkyBox1);
				m_customSkyBox1 = g_ruResourceManager->LoadEntity(m_zoneDescriptor1->m_customSkyBoxName);

				// Begin motion on the sky box
				if(m_customSkyBox1)
					RuACT_PlayMotionEx(m_customSkyBox1, 0xFFFFFFFF, "idle");
			}
		}

		// Remove all entities attached to the container
		this->RemoveAllChildren();

		// Insert custom sky box if it is available
		if(m_customSkyBox0)
		{
			this->AddChild(m_customSkyBox0);
		}
		if(m_customSkyBox1)
		{
			this->AddChild(m_customSkyBox1);
		}

		// Insert default sky box if there is no custom sky box loaded
		if(m_customSkyBox0 == NULL && m_customSkyBox1 == NULL)
		{
			this->AddChild(m_skyDome);
		}
	}

	return TRUE;
}

void CRuEntity_Environment::SelectZoneDescriptor(CRuGaia_Base *gaiaObj, IRuObject *colObj, const CRuVector3 &pos, INT32 timeGroup, INT32 weatherGroup)
{
	CRuCollisionQuery_User_Flag_Get_Segment segQuery;
	segQuery.m_colSegment = CRuSegment3(pos, pos + CRuVector3(0.0f, -500.0f, 0.0f));

	// If a collision object is specified, use it instead of the gaia object
	if(colObj)
	{
		if(colObj->GetType().IsSubClassOf(CRuEntity::Type))
		{
			static_cast<CRuEntity *>(colObj)->QueryCollision(&segQuery);
		}
	}
	else
	{
		gaiaObj->QueryCollision(&segQuery);
	}

	if(segQuery.m_positiveHit)
	{
		// Extract zone ID
		INT32 zoneID = RuGaia_ZoneFlags_GetID(segQuery.m_flag);

		// Select zone descriptor
		SelectZoneDescriptor(gaiaObj, zoneID, timeGroup, weatherGroup);
	}
}

void CRuEntity_Environment::SelectZoneDescriptor(CRuGaia_Base *gaiaObj, INT32 zoneID, INT32 timeGroup, INT32 weatherGroup)
{
	// Set active time group and retrieve zone descriptor
	const CRuGaia_ZoneDescriptor *zoneDesc = gaiaObj->GetZoneDescriptor(zoneID, timeGroup, weatherGroup);

	if(zoneDesc && zoneDesc != m_activeZoneDesc)
	{
		if(m_zoneDescriptorTransitionDuration > 0.0f || m_skyDome->IsInTransition())
		{
			// Queue the zone for transition
			m_queuedZoneDesc = zoneDesc;
		}
		else
		{
			// Set new active zone ID
			m_activeZoneDesc = zoneDesc;

			// Start transition
			TransitionZoneDescriptor(zoneDesc, m_zoneDescriptorDefaultTransitionDuration);
		}
	}
}

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

#pragma managed
