///////////////////////////////////////////////////////////////////////////////
//
// File: Asura_ClientEntity_CameraVolume.cpp
//
// Created by Martin G.Harris (4/9/2006)
// 
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
// Activatable volume that has a set of project specific camera properties associated with it.
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Entity/Triggers/Asura_ClientEntity_CameraVolume.h"

#ifdef ASURA_USE_CAMERA_VOLUME

// Includes
#include "Chunksystem/Asura_Chunk_Macros.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_OrientedVolume.h"
#include "Entity/Asura_Entity.h"
#include "Entity/Asura_Entity_Classification.h"

#include "Network/Asura_Message.h"

#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif //ASURA_TOOLS

/////////////////
// Static data //
/////////////////
Asura_Hashed_List<Asura_ClientEntity_CameraVolume> Asura_ClientEntity_CameraVolume::s_xList;

// Constants
const u_int uSizeOfOrientatedVolume = sizeof(float)*(6+4);

/////////////////////////////
// Asura_CameraMultiVolume //
/////////////////////////////

const int Asura_CameraMultiVolume::s_iCurrentChunkVersion(2);

Asura_CameraMultiVolume::Asura_CameraMultiVolume()
: m_iNumOBB(0)
, m_pxVolumes(0)
, m_iPriority(iASURA_CAMERA_VOLUME_DEFAULT_PRIORITY)
{}

Asura_CameraMultiVolume::~Asura_CameraMultiVolume()
{
	delete[] m_pxVolumes;
}


void Asura_CameraMultiVolume::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	// Read version number.
	int iChunkVersion(0);
	xStream >> iChunkVersion;

	if( (iChunkVersion<0) || ( iChunkVersion>s_iCurrentChunkVersion) )
	{
		Asura_Assert( false,"Asura_CameraMultiVolume: Unrecognised chunk version" );
		return;
	}

	// Delete any existing volumes.
	delete [] m_pxVolumes;
	m_pxVolumes = NULL;

	// Version 0 has no data.
	m_iNumOBB = 0;
	
	if( iChunkVersion > 0 )
	{	
		// All versions greater than zero have volume data next.
		int iNumOBB(0);
		xStream >> iNumOBB;

		if( iNumOBB>0 )
		{
			m_iNumOBB = iNumOBB;
		
			m_pxVolumes = new Asura_OrientedVolume[m_iNumOBB];
		
			for( int i(0); i<m_iNumOBB; ++i )
			{
				xStream >> m_pxVolumes[i].m_xBox;
				xStream >> m_pxVolumes[i].m_xOrient;
			}
		}
	}

	// All versions greater than 1 have a priority next.
	if( iChunkVersion > 1 )
	{
		xStream >> m_iPriority;
	}

}

void Asura_CameraMultiVolume::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	// Write version
	xStream << s_iCurrentChunkVersion;

	// Write number of volumes
	xStream << m_iNumOBB;

	// Write array of volumes
	for( int i(0); i<m_iNumOBB; ++i )
	{
		xStream << m_pxVolumes[i].m_xBox;
		xStream << m_pxVolumes[i].m_xOrient;
	}

	// Write priority
	xStream << m_iPriority;
}

// Assignment
Asura_CameraMultiVolume& Asura_CameraMultiVolume::operator=(const Asura_CameraMultiVolume& xOther)
{
	delete[] m_pxVolumes;
	m_pxVolumes = 0;

	m_iNumOBB = xOther.m_iNumOBB;

	if (m_iNumOBB)
	{
		m_pxVolumes = new Asura_OrientedVolume[m_iNumOBB];

		for (int i=0; i<m_iNumOBB; ++i)
		{
			m_pxVolumes[i] = xOther.m_pxVolumes[i];
		}
	}

	m_iPriority = xOther.m_iPriority;

	return *this;
}

// Displace all the boxes by the specified offset.
void Asura_CameraMultiVolume::Displace(const Asura_Vector_3& xMove)
{
	if (m_pxVolumes)
	{
		for (int i=0; i<m_iNumOBB; ++i)
		{
			m_pxVolumes[i].m_xBox.Displace(xMove);
		}
	}
}

// Chunk buffer functions
int Asura_CameraMultiVolume::GetChunkBufferSize() const
{
	// Version, m_iNumOBB, array of volumes of size m_iNumOBB, priority.
	return sizeof(int) + sizeof(int) + uSizeOfOrientatedVolume*m_iNumOBB + sizeof(int);
}

void Asura_CameraMultiVolume::FillInChunkBuffer(char*& pcChunkBuffer)
{
	// Write version
	ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, s_iCurrentChunkVersion, int);

	// Write number of volumes
	ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, m_iNumOBB, int);

	// Write array of volumes
	for (int i=0; i<m_iNumOBB; ++i)
	{
		ASURA_CHUNK_ARRAY_FILLANDVERIFY(pcChunkBuffer, m_pxVolumes[i].m_xBox, Asura_Bounding_Box,float, 6);
		ASURA_CHUNK_ARRAY_FILLANDVERIFY(pcChunkBuffer, m_pxVolumes[i].m_xOrient, Asura_Quat, float, 4);
	}

	// Write priority
	ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, m_iPriority, int);
}

void Asura_CameraMultiVolume::InitialiseFromChunkBuffer(const char*& pcChunkBuffer)
{
	// Delete any existing volumes.
	delete[] m_pxVolumes;
	m_pxVolumes = 0;

	// Read version number.
	int iVersion;
	ASURA_CHUNK_EXTRACT( pcChunkBuffer, iVersion, int);

	if (iVersion<0 || iVersion>s_iCurrentChunkVersion)
	{
		Asura_Assert(0, "Asura_CameraMultiVolume: Unrecognised chunk version");
		return;
	}

	if (iVersion == 0)
	{
		// Version 0 has no data.
		m_iNumOBB = 0;
		return;
	}

	// All versions greater than zero have volume data next.
	ASURA_CHUNK_EXTRACT(pcChunkBuffer, m_iNumOBB, int);
	if (m_iNumOBB<0)
	{
		Asura_Assert(0, "Asura_CameraMultiVolume: invalid number of volumes");
		m_iNumOBB = 0;
	}

	if (m_iNumOBB)
	{
		m_pxVolumes = new Asura_OrientedVolume[m_iNumOBB];
		for (int i=0; i<m_iNumOBB; ++i)
		{
			ASURA_CHUNK_EXTRACT( pcChunkBuffer, m_pxVolumes[i].m_xBox, Asura_Bounding_Box );
			ASURA_CHUNK_EXTRACT( pcChunkBuffer, m_pxVolumes[i].m_xOrient, Asura_Quat );
		}
	}
	
	// All versions greater than 1 have a priority next.
	if (iVersion>1)
	{
		ASURA_CHUNK_EXTRACT( pcChunkBuffer, m_iPriority, int );
	}
}

void Asura_CameraMultiVolume::SkipChunkStream( Asura_Chunk_Stream& xStream )
{
	Asura_CameraMultiVolume xVolume;

	xVolume.ReadFromChunkStream( xStream );
}

// Allow server entities to skip the relevant number of bytes.
void Asura_CameraMultiVolume::SkipChunk(const char*& pcChunkBuffer)
{
	int iVersion;
	ASURA_CHUNK_EXTRACT( pcChunkBuffer, iVersion, int);

	if (iVersion<0 || iVersion>s_iCurrentChunkVersion)
	{
		Asura_Assert(0, "Asura_CameraMultiVolume: Unrecognised chunk version");
		return;
	}

	if (iVersion == 0)
	{
		// No more data.
		return;
	}

	// Versions greater than zero.	
	int iNumOBB;
	ASURA_CHUNK_EXTRACT( pcChunkBuffer, iNumOBB, int );
	if (iNumOBB<0)
	{
		Asura_Assert(0, "Asura_CameraMultiVolume: invalid number of volumes");
		iNumOBB = 0;
	}
	pcChunkBuffer += iNumOBB*uSizeOfOrientatedVolume; // Skip volume data

	// All versions greater than 1 have a priority next.
	if (iVersion>1)
	{
		pcChunkBuffer += sizeof(int);
	}
}

// Fallback code for when the client entity is missing (chunk data version = 0).
int Asura_CameraMultiVolume::GetChunkBufferSize_V0()
{
	return sizeof(int);
}

void Asura_CameraMultiVolume::FillInChunkBuffer_V0(char*& pcChunkBuffer)
{
	// Write a zero version number to mark that there is no volume data.
	int iVersion = 0;
	ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, iVersion, int);
}


// Tools functions
#ifdef ASURA_TOOLS
// Add a new volume with default values.
void Asura_CameraMultiVolume::AddVolume()
{
	// Allocate a new array of volumes, and copy the old ones across.
	const int iOldNum = GetNumVolumes();
	Asura_OrientedVolume* pxVolumes = new Asura_OrientedVolume[iOldNum+1];

	for (int i=0; i<iOldNum; ++i)
	{
		pxVolumes[i] = m_pxVolumes[i];
	}

	// Initialise the new volume.
	pxVolumes[iOldNum].m_xBox.Reset();
	pxVolumes[iOldNum].m_xBox.Set(Asura_Maths::ZeroVector3);
	pxVolumes[iOldNum].m_xOrient = Asura_Maths::IdentityQuat;

	// Replace the old array.
	delete[] m_pxVolumes;
	m_pxVolumes = pxVolumes;
	m_iNumOBB++;
}

// Delete the specified volume.
void Asura_CameraMultiVolume::DeleteVolume(const int iIndex)
{
	if (iIndex>=0 && iIndex<m_iNumOBB)
	{
		if (m_pxVolumes)
		{
			// Shuffle down any volumes after the one being deleted
			for (int i=iIndex; i < GetNumVolumes()-1; ++i)
			{
				m_pxVolumes[i] = m_pxVolumes[i+1];
			}
			--m_iNumOBB;
		}
	}
	else
	{
		Asura_Assert(0, "Asura_CameraMultiVolume: Invalid volume index");
	}
}
#endif //ASURA_TOOLS

//////////////
// Contains //
//////////////
// Does the multi-volume contain the specified point?
bool Asura_CameraMultiVolume::Contains(const Asura_Vector_3& xPosition) const
{
	// MGH 19/9/2006: NOTE: If there are no volume BBs defined then this is taken
	// to be a global volume that covers the whole world.
	if (m_iNumOBB == 0) return true;

	Asura_Assert(m_pxVolumes, "Null volume pointer");

	for (int i=0; i<m_iNumOBB; ++i)
	{
		if (m_pxVolumes[i].Contains(xPosition))
		{
			return true;
		}
	}

	// Not contained.
	return false;
}

/////////////////////////////////////
// ApplyChangesForTemplateInstance //
/////////////////////////////////////
#ifdef ASURA_TOOLS
void Asura_CameraMultiVolume::ApplyChangesForTemplateInstance(Tools_EntityTemplateInstance* pxInstance)
{
	if (m_iNumOBB == 0) return;
	Asura_Assert(m_pxVolumes, "Null volume pointer");
	if (!m_pxVolumes) return;

	for (int i=0; i<m_iNumOBB; ++i)
	{
		m_pxVolumes[i] = pxInstance->TransformOrientedVolume(m_pxVolumes[i]);
	}
}
#endif //ASURA_TOOLS

/////////////////////////////////////
// Asura_ClientEntity_CameraVolume //
/////////////////////////////////////
const int Asura_ClientEntity_CameraVolume::s_iCurrentChunkVersion(0);

Asura_ClientEntity_CameraVolume::Asura_ClientEntity_CameraVolume(Asura_Guid uGuid)
: PARENT(uGuid)
, m_uOwnerGuid(ASURA_GUID_UNREGISTERED)
{
	Classification = AsuraEntityClass_CameraVolume;	
	s_xList.AddToList(this);
}

Asura_ClientEntity_CameraVolume::Asura_ClientEntity_CameraVolume(Asura_Guid uGuid,const char*& pcChunkBuffer)
: PARENT(uGuid, pcChunkBuffer)
, m_uOwnerGuid(ASURA_GUID_UNREGISTERED)
{
	Classification = AsuraEntityClass_CameraVolume;
	s_xList.AddToList(this);

	// Read the next part of the chunk
	m_xVolume.InitialiseFromChunkBuffer(pcChunkBuffer);
}

Asura_ClientEntity_CameraVolume::~Asura_ClientEntity_CameraVolume()
{
	s_xList.RemoveFromList(this);
}

void Asura_ClientEntity_CameraVolume::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	int iChunkVersion(0);

	xStream >> iChunkVersion;

	Asura_Assertf( iChunkVersion <= s_iCurrentChunkVersion,("Chunk version '%u' not yet supported",iChunkVersion) );

	PARENT::ReadFromChunkStream( xStream );

	m_xVolume.ReadFromChunkStream(xStream);
}

///////////////////
// HandleMessage //
///////////////////
bool Asura_ClientEntity_CameraVolume::HandleMessage(const Asura_Message* pxMessage)
{
	if (!pxMessage) return false;

	if ( IsDestroyed() )
	{
		// MGH 5/9/2006: This shouldn't happen as we aren't allowing camera volumes to be
		// destroyed except when we are unloading a zone/level. However I'll leave it here in
		// case someone changes the behaviour later.
		return true; // ignore all further messages?
	}

	switch (pxMessage->GetMsgID())
	{
		default:
			Asura_Assert(0, "Unhandled message for client CameraVolume!");
			break;

		case ASURA_MSG_ACTIVATE:
		{
			if (!IsActive())
			{
				OnActivation();
				SetActive(true);
			}
			return true;
		}
		case ASURA_MSG_DEACTIVATE:
		{
			if (IsActive())
			{
				OnDeactivation();
				SetActive(false);
			}
			return true;
		}
	}

	return false;
}

////////////
// Update //
////////////
void Asura_ClientEntity_CameraVolume::Update()
{
	// MGH 5/9/2006: Camera volumes never need to be updated.
	// Don't add them to the updateable list as it is a waste of CPU time.
	Asura_Assert(0, "CameraVolume shouldn't be in update list");
}

/////////////////
// CheckVolume //
/////////////////
// If the entity is active, the position is within the multi-volume and has higher priority,
// then update the priority and return true.
bool Asura_ClientEntity_CameraVolume::CheckVolume(const Asura_Vector_3& xPosition, int& iPriority, Asura_Guid uGuidRequesting) const
{
	if (!IsActive()) return false; // Not active.
	if (m_xVolume.GetPriority() <= iPriority) return false; // Priority not higher.

	if (m_uOwnerGuid != ASURA_GUID_UNREGISTERED && uGuidRequesting != m_uOwnerGuid) return false; // This character shouldn't be affected by this camera

	if (m_xVolume.Contains(xPosition))
	{
		// Position is within the multi-volume.
		iPriority = m_xVolume.GetPriority();
		return true;
	}

	// Position not within volume.
	return false;
}

////////////////
// FindVolume //
////////////////
// Find the most relevant camera volume given the position and previous camera volume.
// Note that this might return null if no volumes are found.
// We always test the previous volume first to make it a bit sticky (aka hysteresis) when two
// volumes with the same priority overlap.

// MGH 19/9/2006: NOTE: we could modify this to improve the efficiency, however we will
// typically only be calling this function once per frame, so that it is probably not necessary.
// If there are large numbers of camera volumes in the level and this function shows up in
// the profiler then we may need to rethink eg precalculate AABBs or use a tree.

const Asura_ClientEntity_CameraVolume* 
Asura_ClientEntity_CameraVolume::FindVolume(const Asura_Vector_3& xPosition,
											const Asura_Guid uPreviousVolume,
											const Asura_Guid uGuidRequesting)
{
	// Keep track of the best volume found so far.
	int iBestPriority = -1;
	const Asura_ClientEntity_CameraVolume* pxBestVolume = 0;

	// Check the previous volume first.
	if (uPreviousVolume != ASURA_GUID_UNREGISTERED)
	{
		const Asura_ClientEntity_CameraVolume* pxVolume = FindEntity(uPreviousVolume);
		if (pxVolume && pxVolume->CheckVolume(xPosition, iBestPriority, uGuidRequesting))
		{
			pxBestVolume = pxVolume;
		}
	}

	// Check all the other volumes.
	for (Asura_Hashed_List_ConstIt<Asura_ClientEntity_CameraVolume> xIt(GetList());
		 !xIt.Done(); xIt.Next())
	{
		const Asura_ClientEntity_CameraVolume* pxVolume = xIt.GetCurrent();
		if (pxVolume->GetGuid() == uPreviousVolume) continue; // Already done this one.

		if (pxVolume->CheckVolume(xPosition, iBestPriority, uGuidRequesting))
		{
			pxBestVolume = pxVolume;
		}
	}

	// Return the most relevant volume found, or null.
	return pxBestVolume;
}

/////////////////////////////////////
// ApplyChangesForTemplateInstance //
/////////////////////////////////////
#ifdef ASURA_TOOLS
void Asura_ClientEntity_CameraVolume::ApplyChangesForTemplateInstance(Tools_EntityTemplateInstance* pxInstance)
{
	PARENT::ApplyChangesForTemplateInstance(pxInstance);

	m_xVolume.ApplyChangesForTemplateInstance(pxInstance);
}
#endif //ASURA_TOOLS

#endif //#ifdef ASURA_USE_CAMERA_VOLUME
