///////////////////////////////////////////////////////////////////////////////
//
// File: Asura_ClientEntity_Container.h
//
// Created by Martin G.Harris (6/5/2008)
// 
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
// Client entity that manages a container instance.
//
///////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef __ASURA_CLIENTENTITY_CONTAINER_H__
#define __ASURA_CLIENTENTITY_CONTAINER_H__

#ifdef ASURA_USE_CONTAINER_SYSTEM

// Includes
#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Assert.h"
#include "Entity/Asura_Entity.h"
#include "Entity/Container/Asura_Container_Messages.h"
#include "Entity/Container/Asura_ClientContainerInstance.h"


// Forward declarations
class Asura_Message;
class Asura_LOS_InputData;
class Asura_LOS_OutputData;
class Asura_Physics_Object;
class Asura_PosAndOriInterpolator;

//////////////////////////////////
// Asura_Container_PhysicsBlock //
//////////////////////////////////
// Pointers to the relevant physics object and also to a pos/ori interpolator if one is needed.
// For non-server machines we will need an interpolator.
class Asura_Container_PhysicsBlock
{
public:
	Asura_Container_PhysicsBlock()
		: m_pxPhysics(0)
		, m_pxInterpolator(0)
		, m_pxPosOri(0)
		, m_uUniqueID(uASURA_CONTAINER_UNIQUE_ID_INVALID)
		, m_pxNext(0)
	{}

	// Deletes interpolator, pos/ori data, next element.
	~Asura_Container_PhysicsBlock();

	const Asura_Container_PhysicsBlock* GetNext() const { return m_pxNext; }
		  Asura_Container_PhysicsBlock* GetNext()		{ return m_pxNext; }

public:
	Asura_Physics_Object* m_pxPhysics; // Pointer to physics object, owned by mesh container.

	Asura_PosAndOriInterpolator* m_pxInterpolator;  // For non-servers only.
	Asura_Container_PosOrient* m_pxPosOri;			// For servers only.

	u_int m_uUniqueID;

protected:
	// Next element in linked list.
	Asura_Container_PhysicsBlock* m_pxNext;

	// Friends
	friend class Asura_Container_PhysicsBlock_Storage;
};

//////////////////////////////////////////
// Asura_Container_PhysicsBlock_Storage //
//////////////////////////////////////////
// Storage for the physics blocks.
class Asura_Container_PhysicsBlock_Storage
{
public:
	Asura_Container_PhysicsBlock_Storage()
		: m_iNumElements(0)
		, m_pxHead(0)
		, m_uOwnerEntityGuid(ASURA_GUID_UNREGISTERED)
	{
	}

	~Asura_Container_PhysicsBlock_Storage()
	{
		delete m_pxHead;
		m_pxHead = 0;
	}

	Asura_Guid GetOwner() const { return m_uOwnerEntityGuid; }
	void SetOwner(Asura_Guid uOwnerEntityGuid) { m_uOwnerEntityGuid = uOwnerEntityGuid; }

	int GetNumber() const { return m_iNumElements; }
	
	const Asura_Container_PhysicsBlock* GetHead() const { return m_pxHead; }
		  Asura_Container_PhysicsBlock* GetHead()		{ return m_pxHead; }

	// Find the physics block with the relevant unique ID or add a new one if needed.
	// Returns the physics block and optionally a boolean indicating whether the block was found (rather than added).
	Asura_Container_PhysicsBlock& FindOrAdd(const u_int uUniqueID, bool* pbFound = 0);

	void ResetPhysicsBlocks();

	// Copy data back to the physics objects.
	void CopyDataToDynamicPhysics();
	// Return true, if we initialised the pos/ori data in the physics object.
	bool CopyPosOriToDynamicPhysics(Asura_Container_PhysicsBlock* pxCurrent);

protected:
	void MovePhysics(Asura_Physics_Object* pxPhysics, const Asura_Vector_3& xPos, const Asura_Matrix_3x3& xOri);

private:
	int m_iNumElements;
	Asura_Container_PhysicsBlock* m_pxHead;
	Asura_Guid m_uOwnerEntityGuid;
};


/////////////////////////////
// Asura_Container_Storage //
/////////////////////////////
// Storage for extra serialisable data (e.g. health) that came from the server. This data needs
// to be preserved when we create/destroy individual Asura_ClientContainerInstances.
// We could store it in the Asura_ClientContainerInstance itself, but then we would need to
// keep the whole client instance tree around even for entities that were inactive.
// MGH 27/8/2008: TODO we need to think about this some more, but it'll do for now.
template <typename T>
class Asura_Container_Storage
{
public:
	Asura_Container_Storage()
		: m_uUniqueID(uASURA_CONTAINER_UNIQUE_ID_INVALID)
		, m_fServerTimestamp(0.0f)
		, m_xData()
	{}

	u_int m_uUniqueID;			// Sub-container ID.
	float m_fServerTimestamp;	// Time this data was last updated by the server.
	T m_xData;					// The data.
};

template <typename T>
class Asura_Container_StorageArray
{
public:
	Asura_Container_StorageArray() : m_pxData(0), m_uSize(0) {}

	~Asura_Container_StorageArray() { Clear(); }

	u_int GetSize() const { return m_uSize; }

	void Allocate(u_int uSize)
	{
		Clear();
		m_uSize = uSize;
		if (m_uSize > 0)
		{
			m_pxData = new Asura_Container_Storage<T>[uSize];
		}
	}

	// Add new data or replace existing entry.
	// Return true if data was added or updated.
	bool Add(u_int uUniqueID, const T& xData, float fServerTime)
	{
		if (uUniqueID == uASURA_CONTAINER_UNIQUE_ID_INVALID)
		{
			return false;
		}

		for (u_int u=0; u<m_uSize; ++u)
		{
			const u_int uOldID = m_pxData[u].m_uUniqueID;
			if (uOldID == uASURA_CONTAINER_UNIQUE_ID_INVALID ||
				uOldID == uUniqueID)
			{
				// Check that this isn't an out-of-date message.
				if (fServerTime >= m_pxData[u].m_fServerTimestamp)
				{
					m_pxData[u].m_uUniqueID = uUniqueID;
					m_pxData[u].m_fServerTimestamp = fServerTime;
					m_pxData[u].m_xData = xData;
					return true;
				}
				return false;
			}
		}

		// Not found and no more space left.
		Asura_Assert(0, "No more space left in container storage array.");
		return false;
	}

	// Copy all the stored data to the instance.
	void CopyToInstance(Asura_ClientContainerInstance* pxInstance)
	{
		if (!pxInstance) return;
		
		for (u_int u=0; u<m_uSize; ++u)
		{
			const u_int uID = m_pxData[u].m_uUniqueID;
			if (uID == uASURA_CONTAINER_UNIQUE_ID_INVALID)
			{
				return; // Finished
			}
		
			Asura_ClientContainerInstance* const pxFound = pxInstance->FindFromUniqueID(uID);
			if (pxFound)
			{
				pxFound->SetParam(m_pxData[u].m_xData);
			}
		}
	}

protected:
	void Clear()
	{
		m_uSize = 0;
		delete [] m_pxData;
		m_pxData = 0;
	}

protected:
	Asura_Container_Storage<T>* m_pxData;
	u_int m_uSize;
};

//////////////////////////////////
// Asura_ClientEntity_Container //
//////////////////////////////////
class Asura_ClientEntity_Container : public Asura_Entity_Renderable
{
	typedef Asura_Entity_Renderable PARENT;

public:
	Asura_ClientEntity_Container();
	virtual ~Asura_ClientEntity_Container();

	virtual bool HandleMessage(const Asura_Message* pxMessage);

	virtual void RenderShadow();

	virtual void TeleportTo( const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOrientation );

	// Returns the conservative axially-aligned WORLD SPACE box covering the entity.
	virtual void GetLineOfSightAABB( Asura_Bounding_Box& xAABB );

	virtual bool LOSTest(const Asura_LOS_InputData* pxInput, Asura_LOS_OutputData* pxOutput);
	
	// Return true if the state actually changed.
	bool SetInputState(const Asura_Container_InputState& xInputState, float fServerTime);
	const Asura_Container_InputState& GetInputState() const { return m_xInputState; }
	u_int GetNumInputs() const { return m_xInputState.GetNumInputs(); }

	// Add to the list of physics blocks.
	// Return true, if we initialised the pos/ori data in the physics object.
	bool AddDynamicPhysics(Asura_Physics_Object* pxPhysics, u_int uUniqueID);

	// Return first in the linked list of physics blocks.
	const Asura_Container_PhysicsBlock* GetFirstPhysicsBlock() const { return m_xPhysicsBlockStorage.GetHead(); }

	// Return the unique ID of the mesh container which has the given rigid body handle
	// (or return uASURA_CONTAINER_UNIQUE_ID_INVALID if not found).
	u_int FindUniqueIDFromRigidBodyHandle(Asura_Handle iHandle) const;

	// Find the container corresponding to a unique ID, assuming that it is enabled. Return 0, if not found.
	const Asura_ClientContainerInstance* FindFromUniqueID(u_int uUniqueID) const;

	const Asura_ClientContainerInstance* GetClientInstanceRoot() const { return m_pxInstance; }

	// Process dynamic physics pos/ori information from the server.
	void ProcessDynamicPhysicsData(u_int uUniqueID, const Asura_Vector_3& xPosition, const Asura_Quat& xOrient, float fServerTime);
	void ProcessPhysicsParam(u_int uNumPhysicsParam, const Asura_Container_PhysicsParam* pxPhysicsData, float fServerTime);
	void ProcessStateChange();

	// Process float parameters.
	void ProcessFloatParam(u_int uNumFloatParam, const Asura_Container_FloatParam* pxFloatParamData, float fServerTime);

	//Process the specified number of bytes of project-specific data.
	virtual void ProcessProjectCreateMessageData(u_int uNumBytes, const void* pData) {}

	// Called after the creation of the entity, and after a state change, activation, or deactivation.
	// Gives project code a chance to update any project-specific stuff.
	virtual void PostChangeInitialisation() {}

	// Find the blueprint in the root collection.
	// Set bIncludeDisabled to include both enabled and disabled blueprint containers.
	// If uBlueprintTypeHash != ASURA_HASH_ID_UNSET, then only return blueprints of the specified type.
	// Returns NULL if no relevant blueprint found.
	const Asura_Container_Blueprint* FindBlueprint(Asura_Hash_ID uBlueprintTypeHash, bool bIncludeDisabled) const;

protected:
	virtual void Update();
	virtual void Render();

	virtual	void OnEntityActivate();
	virtual	void OnEntityDeactivate();

	void CreateInstance();
	void DestroyInstance();

	void PreStateChange();
	void PostStateChange();

	void MovePhysics(Asura_Physics_Object* pxPhysics, const Asura_Vector_3& xPos, const Asura_Matrix_3x3& xOri);

	// Add to LOS list if not already in the list. Set flag.
	void AddToLOSList();
	// Remove from LOS list if present. Clear flag.
	void RemoveFromLOSList();

	// Set health from change health message.
	void SetHealth(u_int uUniqueID, float fHealth, float fServerTime);

	void CopyFloatParamToInstance();

private:
	// Make private. Use AddToLOSList/RemoveFromLOSList instead.
	using Asura_Entity_Renderable::AddToLineOfSightList;
	using Asura_Entity_Renderable::RemoveFromLineOfSightList;

protected:
	Asura_Hash_ID m_uContainerHash; // Hash for container.

	Asura_ClientContainerInstance* m_pxInstance;
	Asura_Bounding_Box* m_pxCachedLOSAABB;  // Cached AABB for LOS tests.

	// State
	Asura_Container_InputState m_xInputState; // Hash of input states.
	float m_fStateTimeStamp;	// Server time for most recent call to SetInputState due to a message.

	// Flags
	union 
	{
		u_int m_uFlags;
		struct
		{
			u_int	m_bAddedToLOSList				: 1,
					m_bShouldCallRender				: 1,
					m_bShouldCallRenderShadow		: 1,
					m_bShouldCallUpdate				: 1,
					m_bEnableLOSTest				: 1,
					m_bCanCacheAABBForLOS			: 1,
					m_bNeedToRecalculateAABBForLOS	: 1;
		};
	};

	// Storage for physics and interpolation.
	Asura_Container_PhysicsBlock_Storage m_xPhysicsBlockStorage;

	// Storage for float parameters (health is the only parameter at the moment).
	Asura_Container_StorageArray<float> m_xFloatParamStorage;

	// Static members
public:
	static void Create(const Asura_Message* pxMessage);
	static void SendRequestCreateMessage(Asura_Guid uGuid);

	static Asura_ClientEntity_Container* FindEntity( Asura_Guid uGuid ) { return s_xList.FindEntity( uGuid ); }
    static const Asura_Hashed_List<Asura_ClientEntity_Container>* GetList() { return &s_xList; }    

protected:
	static Asura_Hashed_List<Asura_ClientEntity_Container> s_xList;

	// Friends
	friend class Asura_Message_Container_Create;
};


#endif //#ifdef ASURA_USE_CONTAINER_SYSTEM

#endif //__ASURA_CLIENTENTITY_CONTAINER_H__

