/**
*
* \file Asura_ServerEntity_Glass.cpp
*
* Created by Andrew Weinkove - 18/07/2002
*
* Copyright (C) 2002 Rebellion, All Rights Reserved.
*
* \brief
*
* MGH: NOTE: The glass object inherits from Asura_ServerEntity_PhysicsObject.
* This means that it has a shape instance (client) and dynamics block (server)
* (with position and orientation) that aren't currently used or contain duplicated data.
*
* *TODO* This needs fixing, ideally the glass should be rewritten so that the glass
* object is a mesh and this gets automatically converted to shards when it breaks.
* Eg big quads get subdivided into many shards and small triangles generate single shards.
* This would fix most of the client/server/chunkbuffer problems. As you'd only need
* to pass a hash ID from the server to the client. This would also allow curved glass
* and would make glass placement in tools operate just like any other shape.
*
**/
//////////////
// Includes //
//////////////

#include "Core/Asura.h"

#include "Entity/Glass/Asura_ServerEntity_Glass.h"

#include "Debugging/Asura_Assert.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
#include "Entity/Glass/Asura_Entity_Glass_Messages.h"
#include "Entity/Asura_Entity_Classification.h"
#include "ChunkSystem/Asura_Chunk_Macros.h"

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


//////////////////////////////////////////////////////////////////////////
/// static vars
//////////////////////////////////////////////////////////////////////////

Asura_Hashed_List<Asura_ServerEntity_Glass> Asura_ServerEntity_Glass::s_xList;

//////////////////////////////
// Asura_ServerEntity_Glass //
//////////////////////////////
Asura_ServerEntity_Glass::Asura_ServerEntity_Glass()
: Asura_Entity_Server(ASURA_GUID_UNREGISTERED) // ignored
, m_xCollisionPane(1.0f, 1.0f)
, m_uColour(ASURA_ENTITY_GLASS_DEFAULT_COLOUR)
{
	Classification = AsuraEntityClass_Glass;
	SetDestructible( true );

	m_afU[0] = m_afU[2] = m_afV[0] = m_afV[1] = 0.0f;
	m_afU[1] = m_afU[3] = m_afV[2] = m_afV[3] = 1.0f;

	m_xTextureName.MakeBlank();
	m_uGlassFlags = 0;
	m_bUseEnvironmentMap = true;

	m_pxDynamicObject = Asura_Physics_Object::Create();

	// (MH - 08.09.2006) force all glass entities to be static.
	SetupPhysicsObject( ASURA_PHYSICS_OBJECT_STATIC );

	s_xList.AddToList(this);

	m_bHasClientBeenUpdated = false;
}

Asura_ServerEntity_Glass::~Asura_ServerEntity_Glass()
{
	// MGH 14/3/03: We are just about to delete our collision object. So we better release
	// our dynamics block otherwise one of the parent class destructors may try to access
	// the deleted collision object.
	if (m_pxDynamicObject)
	{
		Asura_Physics_Object::Destroy( m_pxDynamicObject );
		m_pxDynamicObject = NULL;
	}

	s_xList.RemoveFromList(this);
}

//////////////////////
// ChunkBuffer code //
//////////////////////
int Asura_ServerEntity_Glass::GetChunkBufferSize()
{
	Asura_Assert(!m_xTextureName.IsNull(), "");
	return sizeof(ChunkData) + sizeof(unsigned) + (m_xTextureName.GetSizeInInts() * 4); // flags + Texture name.
}


void Asura_ServerEntity_Glass::FillInChunkBuffer(char*& pcChunkBuffer)
{
	/*
	This entity type can be used in template files , so any changes to FillInChunkBuffer may
	require ApplyChangesForTemplateInstance() to be updated as well.
	Please see the comment at the top of Tools/EntityTemplate/Tools_EntityTemplate_System.h before 
	making any changes.
	RAR 29/04/2004
	*/

	Asura_Assert(!m_xTextureName.IsNull(), "");

	ChunkData xData;
	xData.m_iVersion = ChunkData::CURRENT_VERSION;
	ASURA_CHUNK_VERIFY(xData.m_iVersion, int);
	xData.m_xPane    = m_xPane;
	ASURA_CHUNK_ARRAY_VERIFY(xData.m_xPane, float, 14);
	xData.m_uColour  = m_uColour;
	ASURA_CHUNK_VERIFY(xData.m_uColour, unsigned int);
	for (int i=0; i<4; i++)
	{
		xData.m_afU[i] = m_afU[i];
		xData.m_afV[i] = m_afV[i];
	}
	ASURA_CHUNK_ARRAY_VERIFY(xData.m_afU, float, 4);
	ASURA_CHUNK_ARRAY_VERIFY(xData.m_afV, float, 4);
	  
	ASURA_CHUNK_FILL(pcChunkBuffer, xData, ChunkData);
	ASURA_CHUNK_BITFIELD_FILLANDVERIFY(pcChunkBuffer, m_uGlassFlags, unsigned);
	ASURA_CHUNK_FILL_STRING(pcChunkBuffer, m_xTextureName);
}


void Asura_ServerEntity_Glass::InitialiseFromChunkBuffer(const char*& pcChunkBuffer)
{
	ChunkData xData;
	ASURA_CHUNK_EXTRACT(pcChunkBuffer, xData, ChunkData);
	
	m_xPane   = xData.m_xPane;
	m_xCollisionPane.SetSize(m_xPane.Dimensions.x, m_xPane.Dimensions.y);

 	m_uColour = xData.m_uColour;
	for (int i=0; i<4; i++)
	{
		m_afU[i] = xData.m_afU[i];
		m_afV[i] = xData.m_afV[i];
	}
	if (xData.m_iVersion < 4)
	{
		m_uGlassFlags = 0;
	}
	else
	{
		ASURA_CHUNK_EXTRACT( pcChunkBuffer, m_uGlassFlags, u_int );	
	}

	ASURA_CHUNK_EXTRACT_STRING(pcChunkBuffer, m_xTextureName);
}

////////////
// Update //
////////////
void Asura_ServerEntity_Glass::Update()
{
	// MGH 14/3/03: May as well do this every update, as it isn't expensive and prevents
	// the collision pane getting out of sync with the rendered version.
	bool bResized = m_xCollisionPane.SetSize(m_xPane.Dimensions.x, m_xPane.Dimensions.y);
	if (m_pxDynamicObject)
	{
		m_pxDynamicObject->SetPosition(m_xPane.CornerPosition);

		if (bResized)
		{
			// This will force the AABB to be recalculated from scratch.
			m_pxDynamicObject->SetOrientation(m_xPane.Orientation);
		}
	}

	// don't pass to parent
}

//////////////
// MakeDead //
//////////////
void Asura_ServerEntity_Glass::MakeDead()
{
	// Ok. We ended up dead somehow, break the glass, but just drop it down
	// as we don't know the right direction.
	SetHealth(0.0f); // Make sure we are dead.
	SmashGlass(0.05f, true);
}


void Asura_ServerEntity_Glass::MakeClientUpdateMessage()
{
	if(m_bHasClientBeenUpdated)
	{
		return;
	}

	m_bHasClientBeenUpdated = true;

	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_GLASS_CLIENTUPDATE);

	if(pxMessage)
	{
		pxMessage->From = Guid;
		pxMessage->To = ASURA_GUID_MY_CLIENT_ENTITIES;
		
		Asura_Network::Send(pxMessage);
	}
}

bool Asura_ServerEntity_Glass::HandleMessage(const Asura_Message* pxMessage)
{
	Asura_Assert(pxMessage, "");
	switch(pxMessage->GetMsgID())
	{
		case ASURA_MSG_GLASS_SMASH:
		{
			const Asura_Message_Glass_Smash* pxData = reinterpret_cast<const Asura_Message_Glass_Smash*>(pxMessage->GetData());
			SmashGlass(pxData->m_fPower, pxData->m_bSmashInPositiveZDir);
			return true;
		}
		default:
			return false;
	}

}


// Copy position and orientation from dynamics block to pane.
// Call this whenever you change position/orientation in tools.
void Asura_ServerEntity_Glass::CopyOrientationToPane()
{
	m_xPane.Orientation = GetOrientation();
}


void Asura_ServerEntity_Glass::SetCornerPosition(const Asura_Vector_3& xCorner)
{
	m_xPane.CornerPosition = xCorner;
	SetPosition(xCorner);
}

////////////////////////////////////////////////////////////////////////////////////////
// ApplyDecal : apply a decal at the given world space position.
//
// xHitPosition : world space hit position of ray.
//
// NOTE: the hit position is assumed to lie on the glass surface, if it doesn't the
//       2d coords are clamped if necessary.
////////////////////////////////////////////////////////////////////////////////////////
void Asura_ServerEntity_Glass::ApplyDecal(const Asura_Vector_3& xHitPosition)
{
	Asura_Vector_2 xGlassCoord;
	MapFromWorldToObject(xHitPosition, xGlassCoord);

	ApplyDecal(xGlassCoord);
}

////////////////////////////////////////////////////////////////////////////////////////
// SmashGlass : smash glass at the given world space position.
//
// xHitPosition : world space hit position of ray.
// xDirection   : world space direction of ray.
// fPower       : power of the hit (from 0.0 to 1.0)
//
// NOTE: the hit position is assumed to lie on the glass surface, if it doesn't the
//       2d coords are clamped if necessary.
////////////////////////////////////////////////////////////////////////////////////////
void Asura_ServerEntity_Glass::SmashGlass(const Asura_Vector_3& xHitPosition, float fPower, const Asura_Vector_3& xDirection)
{
	Asura_Vector_2 xGlassCoord;
	MapFromWorldToObject(xHitPosition, xGlassCoord);

	SmashGlass(xGlassCoord, fPower, xDirection);
}

/////////////////////////////////////////////////////////////////////////////////////////
// MapFromWorldToObject:
// Function to map from 3d world space to 2d object space coords, by simple projection,
// with rescaling to range 0.0->1.0 and clamping if necessary.
/////////////////////////////////////////////////////////////////////////////////////////
void Asura_ServerEntity_Glass::MapFromWorldToObject(const Asura_Vector_3& xWorldPosition, Asura_Vector_2& xGlassCoord) const
{
	Asura_Vector_3 xAlpha = xWorldPosition;
	xAlpha -= m_xPane.CornerPosition;

	Asura_Matrix_3x3 xMat = m_xPane.Orientation;
	xMat.Transpose();

	xAlpha = xAlpha * xMat;
	
	float fX = (m_xPane.Dimensions.x) ? (xAlpha.x / m_xPane.Dimensions.x) : 0.5f;
	if (fX>1.0f) fX = 1.0f;
	if (fX<0.0f) fX = 0.0f;
	xGlassCoord.x = fX;

	float fY = (m_xPane.Dimensions.y) ? (xAlpha.y / m_xPane.Dimensions.y) : 0.5f;
	if (fY>1.0f) fY = 1.0f;
	if (fY<0.0f) fY = 0.0f;
	xGlassCoord.y = fY;
}


//////////////////////////////////////////////////////
// Functions to send message from server to clients //
//////////////////////////////////////////////////////
void Asura_ServerEntity_Glass::ApplyDecal(const Asura_Vector_2& xDecalCoordinates)
{
	if (IsDestroyed()) return;

	// Server does nothing except tell the client.
	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_GLASS_APPLYDECAL, sizeof(Asura_Message_Glass_ApplyDecal));
	if (pxMessage)
	{
		Asura_Message_Glass_ApplyDecal* pxData = static_cast<Asura_Message_Glass_ApplyDecal*>(pxMessage->GetData());
		pxData->m_xDecalCoordinates = xDecalCoordinates;
		Send(pxMessage, ASURA_GUID_MY_CLIENT_ENTITIES);
	}
}

void Asura_ServerEntity_Glass::SmashGlass(const Asura_Vector_2& xHitCoordinates, float fPower, const Asura_Vector_3& xDirection)
{
	if (IsDestroyed()) return;

	// Server does nothing except tell the client and then destroy itself.
	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_GLASS_SMASHWITHPOSITION, sizeof(Asura_Message_Glass_SmashWithPosition));
	if (pxMessage)
	{
		Asura_Message_Glass_SmashWithPosition* pxData = static_cast<Asura_Message_Glass_SmashWithPosition*>(pxMessage->GetData());

        pxData->m_xHitCoordinates = xHitCoordinates;
		pxData->m_fPower = fPower;
		pxData->m_xDirection = xDirection;

		Send(pxMessage, ASURA_GUID_MY_CLIENT_ENTITIES);
	}
	SetAsDestroyed();
}

void Asura_ServerEntity_Glass::SmashGlass(float fPower, bool bSmashInPositiveZDir)
{
	if (IsDestroyed()) return;

	// Server does nothing except tell the client and then destroy itself.
	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_GLASS_SMASH, sizeof(Asura_Message_Glass_Smash));
	if (pxMessage)
	{
		Asura_Message_Glass_Smash* pxData = static_cast<Asura_Message_Glass_Smash*>(pxMessage->GetData());

        pxData->m_bSmashInPositiveZDir = bSmashInPositiveZDir;
		pxData->m_fPower = fPower;

		Send(pxMessage, ASURA_GUID_MY_CLIENT_ENTITIES);
	}
	SetAsDestroyed();
}

#ifdef ASURA_TOOLS
void Asura_ServerEntity_Glass::ApplyChangesForTemplateInstance(Tools_EntityTemplateInstance* pxInstance)
{

	m_xPane.Orientation = pxInstance->TransformMatrix(m_xPane.Orientation);
	m_xPane.CornerPosition = pxInstance->TransformPosition(m_xPane.CornerPosition);

}
#endif // ASURA_TOOLS

