//////////////////////////////////////////////////////////////////////////////
//
// Asura_EffectVolume_Camera.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// Created by Emil Popovic - 19/12/2008
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

// Core
#include "Core/Asura.h"

// This file's header.
#include "SpecialFX/EffectVolume/Asura_EffectVolume_Camera.h"

// Engine independent.
#include "Entity/Triggers/EffectVolume/Asura_ClientEntity_EffectVolume.h"
#include "Entity/Triggers/EffectVolume/Asura_ServerEntity_EffectVolume.h"
#include "Core/Asura_LinkList.h"

// Tools
#ifdef ASURA_TOOLS
#include "Tools_System.h"
#include "Entity/EffectVolume/Tools_Entity_EffectVolume.h"
#endif



void Asura_EffectVolume_Camera::Update( const Asura_Vector_3& xPosition, u_int uCameraIndex )
{
	// First, decide if we are in radius of the Effect. If we are in radius	
	// create camera entity states. (or find them, if they already exist)
	CheckForNewEntitiesInCameraRadius( xPosition, uCameraIndex );

	// Handle Entity State. From there create EffectVolume_Fog, EffectVolume_FSFX, etc... states.
	UpdateCurrentEntities( xPosition, uCameraIndex );

	// Blend the states together and apply them for the game.
	UpdateAllEffectStates();
}


void Asura_EffectVolume_Camera::CheckForNewEntitiesInCameraRadius( const Asura_Vector_3& xPosition, u_int uCameraIndex )
{
#ifdef ASURA_TOOLS
	// We have to look into server side objects, because client side objects doesn't exist in tools
	if(Tools_System::IsActive())
	{
		Asura_Hashed_List_It<Asura_ServerEntity_EffectVolume> xIter( Asura_ServerEntity_EffectVolume::GetList() );

		for( ; !xIter.Done() ; xIter.Next())
		{
			Asura_ServerEntity_EffectVolume* pxCurrent = xIter.GetCurrent();

			bool bIsEntityActive = pxCurrent->IsEntityActive();
			bool bIsPositionWithinVolume = (pxCurrent->GetData().IsPositionWithinVolume( xPosition ) || 
											pxCurrent->GetData().IsPositionWithinTransitionVolume( xPosition ));
			bool bCanPreviewThisObjectInTools = Tools_Entity_EffectVolume::CanPreviewThisObjectInTools(pxCurrent);

			if( bIsEntityActive && bIsPositionWithinVolume && bCanPreviewThisObjectInTools )			// If this entity is active, the camera is in its volume and the data object itself doesn't exist, then we can assume that we just entered new volume. So it is time create data object and add it to handle list. Also to set its flag to ENTERING VOLUME.
			{
				Asura_EffectVolume_Camera_EntityData* pxEntityData = CreateOrGetExistingEntityData( pxCurrent->GetGuid() );

				pxEntityData->SetPriority( pxCurrent->GetData().GetPriority() );
			}
		}

	}
	else
#endif // ASURA_TOOLS
		// Since we are not in tools, we must look into client side objects. 
	{
		Asura_Hashed_List_It<Asura_ClientEntity_EffectVolume> xIter( Asura_ClientEntity_EffectVolume::GetList() );

		for( ; !xIter.Done() ; xIter.Next())
		{
			Asura_ClientEntity_EffectVolume* pxCurrent = xIter.GetCurrent();

			bool bIsEntityActive = pxCurrent->IsEntityActive();
			bool bIsPositionWithinVolume = (pxCurrent->GetData().IsPositionWithinVolume( xPosition ) || 
											pxCurrent->GetData().IsPositionWithinTransitionVolume( xPosition ));

			if( bIsEntityActive && bIsPositionWithinVolume )		// If this entity is active, the camera is in its volume and the data object itself doesn't exist, then we can assume that we just entered new volume. So it is time create data object and add it to handle list. Also to set its flag to ENTERING VOLUME.
			{
				Asura_EffectVolume_Camera_EntityData* pxEntityData = CreateOrGetExistingEntityData( pxCurrent->GetGuid() );
				
				pxEntityData->SetPriority( pxCurrent->GetData().GetPriority() );
			}
		}
	}
}


// If the data object doesn't exist, then we can assume that we just entered new volume. So it is time create 
// data object and add it to handle list. Also to set its flag to ENTERING VOLUME.
Asura_EffectVolume_Camera_EntityData* Asura_EffectVolume_Camera::CreateOrGetExistingEntityData(const Asura_Hash_ID uHash)
{ 
	// Make sure that the object is not already in the list. Because if it is, we don't have to add it.
	Asura_EffectVolume_Camera_EntityData* pxEntityData = FindEntityData( uHash );

	// Object doesn't exist in the current list of objects.
	if( pxEntityData==NULL )
	{
		// Create new data object.
		pxEntityData = m_xEntityDataContainer.AddNode(uHash);

		// And make sure we set its data correctly on creation.
		if( pxEntityData )
		{
			pxEntityData->SetGuid( uHash );
		}
	}

	return pxEntityData;
}


void Asura_EffectVolume_Camera::UpdateCurrentEntities( const Asura_Vector_3& xPosition, u_int uCameraIndex )
{
	// Local list for ordering objects by priority.
	typedef Asura_LinkList<Asura_EffectVolume_Camera_EntityData*> PriorityList;
	typedef Asura_ListElt<Asura_EffectVolume_Camera_EntityData*> PriorityListItem;
	PriorityList xPriorityDataContainer;


	// First order all EntityData to top priority list. So we will update low priority objects first and then we will go to higher priority objects.
	// So, make new list that will be ordered depending on Priority.
	for( EntityDataIterator xIt(m_xEntityDataContainer); !xIt.Done() ; xIt.Next())
	{
		Asura_EffectVolume_Camera_EntityData* pxEntityData = xIt.GetCurrent();
		if( pxEntityData )
		{
			// Calculate current strength depending on the fact how long we are inside effect time volume or where we are inside transition volume.
			// Also it will update flag KeepAlive.
			pxEntityData->Update( xPosition );

			xPriorityDataContainer.Insert( pxEntityData, Asura_EffectVolume_Camera_EntityData::CompareEntityDataPriority );
 		}
	}



	// First traverse entities from high priority to low priority. And update Strength according to this:
	// Imagine we have one global world effect-volume with priority 1. Lets call it EV1. Then you have second one inside the first one with priority 6. Lets call it EV6.
	// So you are inside EV1 with strength 1.0. Then you suddenly enter EV6 with strength 0.8.	That means both EV1 and EV6 will be rendered.  EV6 with strength 0.8 and EV1 with strength 0.2.
	// But if EV6 will have strength 1.0, this will reduce EV1 strength to 0.0, thus EV1 will be discarded from rendering.
	float fReduceStrength = 0.0f;
	for(PriorityListItem* pxItem = xPriorityDataContainer.Back(); (pxItem != NULL); pxItem = pxItem->Prev())
	{
		Asura_EffectVolume_Camera_EntityData* pxEntityData = pxItem->Data();

		if( pxEntityData )
		{
			float fStrengthBeforeReduction = pxEntityData->GetCurrentStrength();

			pxEntityData->ReduceCurrentStrength( fReduceStrength );

			fReduceStrength += fStrengthBeforeReduction;
			fReduceStrength = Asura_Maths::ClampToRange( fReduceStrength, 0.0f, 1.0f );
		}
	}

	// Now blend result. Do it from low priority to high priority effects. We can expect that low priority effect will have Strength 0.0
	// because higher level priority effects are overriding strength of lower priority effects.
	for(PriorityListItem* pxItem = xPriorityDataContainer.Front(); pxItem; pxItem = pxItem->Next() )
	{
		Asura_EffectVolume_Camera_EntityData* pxEntityData = pxItem->Data();

		if( pxEntityData )
		{
			BlendEffectVolume( pxEntityData->GetGuid(), pxEntityData->GetCurrentStrength() );
			//pxEntityData->Blend( *this );

			if( pxEntityData->IsKeepingAlive()==false )
			{
				m_xEntityDataContainer.DeleteNodeByHash( pxEntityData->GetGuid() );
			}
		}
	}
}

void Asura_EffectVolume_Camera::BlendEffectVolume( Asura_Guid uEffectVolumeGuid, float fEffectVolumeStrength )
{
#ifdef ASURA_TOOLS
	Asura_ServerEntity_EffectVolume* pxEntity = Asura_ServerEntity_EffectVolume::FindEntity( uEffectVolumeGuid );
#else
	Asura_ClientEntity_EffectVolume* pxEntity = Asura_ClientEntity_EffectVolume::FindEntity( uEffectVolumeGuid );
#endif

	// Now Apply the fEffectVolumeStrength to camera.
	if( pxEntity )
	{
		Asura_EffectVolume_EntityParameters* xData = &pxEntity->GetData();

		u_int uNumOfEffects = xData->GetNumOfEffects();

		for( u_int u=0; u<uNumOfEffects; u++ )
		{
			Asura_EffectVolume_Effect* pxEffect = xData->GetEffect(u);

			Asura_EffectVolume_EffectState* pxEffectState = pxEffect->CreateEffectState();

			BlendNewEffectStateToCurrentOnes( pxEffectState, fEffectVolumeStrength );
		}
	}
}


void Asura_EffectVolume_Camera::Initialise()
{
}


void Asura_EffectVolume_Camera::Reset()
{
	m_xEntityDataContainer.Reset();
	m_xEffectStateList.ClearList();
}


void Asura_EffectVolume_Camera::Shutdown()
{
	Reset();
}


void Asura_EffectVolume_Camera::BlendNewEffectStateToCurrentOnes( Asura_EffectVolume_EffectState* pxNewEffectState, float fStrength )
{
	for( EffectStateIterator xIt(&m_xEffectStateList); !xIt.Done() ; xIt.Next() )
	{
		Asura_EffectVolume_EffectState* pxEffectState = xIt.GetCurrent();

		// Make sure that we are blending only objects with the same type!
		if( pxEffectState->GetEffectTypeHash()==pxNewEffectState->GetEffectTypeHash() )
		{
			// If they are, blend them together.
			bool bBlendSucceeded = pxEffectState->Blend( *pxNewEffectState, fStrength );

			if( bBlendSucceeded )
			{
				// We don't need this state anymore. Time to destroy it.
				delete pxNewEffectState;
				return;
			}
		}
	}

	// Blend failed. We need to add this effect to list...
	AddNewEffectStateToList( pxNewEffectState, fStrength );
}


void Asura_EffectVolume_Camera::AddNewEffectStateToList( Asura_EffectVolume_EffectState* pxNewEffectState, float fStrength )
{
	// Before we actually do the adding, we have to execute specific functions which are different per state type.

	switch( pxNewEffectState->GetEffectTypeHash() )
	{
#if defined(ASURA_USE_FSFX)
	case uEFFECTVOLUME_EFFECTSTATE_FSFX:
		{
			// First, we need to blend object strength with effect strength. Usually this is done in successful blend, but in this case, all blends failed, 
			// so we have to fix the strength manually. For now the issue is only for FSFX because only FSFX has internal strength.
			Asura_EffectVolume_EffectState_FSFX* xCurrentFSFX = static_cast< Asura_EffectVolume_EffectState_FSFX* >( pxNewEffectState );

			xCurrentFSFX->BlendStrengthOnly( fStrength );

			break;
		}
#endif	//defined(ASURA_USE_FSFX)

	case uEFFECTVOLUME_EFFECTSTATE_FOG:
		{
			// We have to do one very important check!
			// We need to check if this is very first fog we are going to add to the list. Because if it is, we have to blend it together with Weather
			// fog that already exists in background.

			bool bSomFogAlreadyExistsInTheList = false;
			for( EffectStateIterator xIt(&m_xEffectStateList); !xIt.Done() ; xIt.Next() )
			{
				Asura_EffectVolume_EffectState* pxEffectState = xIt.GetCurrent();

				if( pxEffectState->GetEffectTypeHash()==uEFFECTVOLUME_EFFECTSTATE_FOG )
				{
					bSomFogAlreadyExistsInTheList = true;
					break;
				}
			}

			// There is no other fog. We have to read the weather fog first and blend our fog on top of it.
			if( bSomFogAlreadyExistsInTheList==false )
			{
				Asura_EffectVolume_EffectState_Fog* pxFog = new Asura_EffectVolume_EffectState_Fog();
				pxFog->InitialiseToCurrentExistingFog();								// We need to take current fog and create first state from it.
	
				pxFog->Blend( *pxNewEffectState, fStrength );
				m_xEffectStateList.AddToList( pxFog );

				delete pxNewEffectState;
				return;
			}
		}
	}


	// Just add this effect so we can process it later.
	m_xEffectStateList.AddToList( pxNewEffectState );
}


void Asura_EffectVolume_Camera::UpdateAllEffectStates()
{
	EffectStateIterator xIter(&m_xEffectStateList);

	while (!xIter.Done())
	{
		Asura_EffectVolume_EffectState* pxEffectState = xIter.GetCurrent();

		pxEffectState->ApplyToCamera(0);

		// Immediately after use, destroy it as we don't need it anymore.
		xIter.DeleteCurrent();
		delete pxEffectState;
	}
}
