//////////////////////////////////////////////////////////////////////////////
//
// AVP_ServerEntity_InteractObject.cpp
//
// Copyright (c) 2007 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Entity/PhysicalObject/Interact/AVP_ServerEntity_InteractObject.h"

// Engine independent.
#include "Blueprint/Asura_Blueprint_System.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_OrientedVolume.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
//#include "Debugging/Asura_Assert.h"
// #include "Entity/Asura_Entity_Classification.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
// #include "Shapes/Asura_Shapes.h"
// #include "Shapes/Asura_Shape_System.h"
// #include "SpecialFX/Asura_Fragment_System.h"

#include "Core/AVP_Game_Core.h"
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/PhysicalObject/AVP_PhysicalObject_Messages.h"
#include "Entity/AVP_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_Actor_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/PhysicalObject/Interact/AVP_Messages_InteractObject.h"
#include "Network/AVP_Player.h"

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

static const u_int	g_uCurrentChunkVersion	= 2;

Asura_Hashed_List<AVP_ServerEntity_InteractObject> AVP_ServerEntity_InteractObject::s_xList;

AVP_ServerEntity_InteractObject::AVP_ServerEntity_InteractObject( Asura_Guid uGuid ) 
: Asura_Entity_Server( uGuid )
, m_pxActorVolume( NULL )
, m_pxOnTargetVolume( NULL )
, m_pxOffTargetVolume( NULL )
, m_usTurnOnMessageBlock( ASURA_STATICMESSAGE_EMPTYBLOCK )
, m_usTurnOffMessageBlock( ASURA_STATICMESSAGE_EMPTYBLOCK )
, m_uFlags( 0 )
{
	Classification = AVP_ENTITYCLASS_INTERACTOBJECT;
	m_bEnabled = true; // default this to true for tools. ( it will get set by the chunk processing )

	SetNeverUpdateClient();
	m_pxDynamicObject = Asura_Physics_Object::Create( ASURA_PHYSICS_OBJECT_STATIC );
	if ( m_pxDynamicObject ) { m_pxDynamicObject->SetAssociatedEntityGuid( GetGuid() ); }

	s_xList.AddToList(this);
}

AVP_ServerEntity_InteractObject::~AVP_ServerEntity_InteractObject()
{
	delete m_pxActorVolume;
	delete m_pxOnTargetVolume;
	delete m_pxOffTargetVolume;

    s_xList.RemoveFromList(this);
}

bool AVP_ServerEntity_InteractObject::HandleMessage(const Asura_Message* pxMsg)
{
	switch ( pxMsg->GetMsgID() )
	{
		case AVP_MSGID_INTERACTION_OBJECT_ENABLE:
		{
			m_bEnabled = true;
			Asura_Network::Forward( pxMsg, ASURA_GUID_MY_CLIENT_ENTITIES, GetGuid() );
			return true;
		}

		case AVP_MSGID_INTERACTION_OBJECT_DISABLE:
		{
			m_bEnabled = false;
			Asura_Network::Forward( pxMsg, ASURA_GUID_MY_CLIENT_ENTITIES, GetGuid() );
			return true;
		}

		case AVP_MSGID_TRIGGER_INTERACTION_ON_LOCAL_PLAYER:
		{
			Asura_Guid uPlayerGuid = AVP_Game_Core::GetPlayer() ? AVP_Game_Core::GetPlayer()->GetPuppetGuid() : ASURA_GUID_UNREGISTERED;
			SendInteractMessage( uPlayerGuid );
			return true;
		}

		case AVP_MSGID_REQUEST_INTERACTION:
		{
			// Actor is requesting interaction, do it
			const AVP_Message_Actor_RequestInteraction*	pxData = static_cast< const AVP_Message_Actor_RequestInteraction* >( pxMsg->GetData() );
			if ( pxData )
			{
				Asura_Assert( pxData->GetInteractionType() == AVP_INTERACTION_TYPE_INITIALISE || pxData->GetInteractionType() == AVP_INTERACTION_TYPE_DEFAULT, "Interaction objects only handle default interactions" );
			
				if ( pxData->GetInteractionType() == AVP_INTERACTION_TYPE_INITIALISE )
				{
					m_bOn = !pxData->IsOn();
				}
				else
				{
					if ( pxData->IsOn() )
					{
						OnTurnOff();
					}
					else
					{
						OnTurnOn();
					}
				}
			
				// Forward on to the client entities
				Asura_Network::Forward( pxMsg, ASURA_GUID_MY_CLIENT_ENTITIES, GetGuid() );
				return true;
			}
			else
			{
				Asura_Assert( false, "No data" );
				return false;
			}
		}

		case ASURA_MSG_TRIGGER: // handle the trigger/untrigger messages as well
		case ASURA_MSG_SWITCHON:
		{
			if ( !m_bOn )
			{
				SendInteractMessage( ASURA_GUID_UNREGISTERED );
			}
			return true;
		}

		case ASURA_MSG_UNTRIGGER: // handle the trigger/untrigger messages as well
		case ASURA_MSG_SWITCHOFF:
		{
			if ( m_bOn )
			{
				SendInteractMessage( ASURA_GUID_UNREGISTERED );
			}
			return true;
		}

		case AVP_MSGID_INTERACTION_OBJECT_INITIALISE_TO_ON:
		{
			if ( !m_bOn )
			{
				SendInteractMessage( ASURA_GUID_UNREGISTERED, true );
			}
			return true;
		}

		case AVP_MSGID_INTERACTION_OBJECT_INITIALISE_TO_OFF:
		{
			if ( m_bOn )
			{
				SendInteractMessage( ASURA_GUID_UNREGISTERED, true );
			}
			return true;
		}

		case ASURA_MSG_CLIENT_REQUESTCREATEMSG:
		{
			MakeCreateMessage(pxMsg->GetFromGuid());
			return true;
		}

		default:
		return PARENT::HandleMessage(pxMsg);
	}
}

void AVP_ServerEntity_InteractObject::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	u_int	uVersion;

	xStream >> uVersion;
	Asura_Assert( uVersion <= g_uCurrentChunkVersion, "Unsupported chunk version" );

//	PARENT_ASURA::ReadFromChunkStream(xStream);
	PARENT::ReadFromChunkStream(xStream);

	// Any data specific to this class would go here.
	if ( uVersion == 0 )
	{
		Asura_Assertf( false, ( "version 0 of Interaction Object is no longer supported, please delete and re-place entity %d", GetGuid() ) );
	}
	else if ( uVersion > 0 && uVersion <= g_uCurrentChunkVersion )
	{
		xStream >> m_usTurnOnMessageBlock;
		xStream >> m_usTurnOffMessageBlock;

		Asura_ReadFromStream_Bitfield_Wrapper< u_int > xBitfield( m_uFlags );
		xStream >> xBitfield;

		if ( uVersion < 2 )
		{
			m_bEnabled = true; // if we don't have the enabled flag in our chunk data we'd better default it to true
		}

		if ( m_bUseActorVolume )
		{
			m_pxActorVolume = new Asura_OrientedVolume();
			xStream >> m_pxActorVolume->m_xBox;
			xStream >> m_pxActorVolume->m_xOrient;
		}

		Asura_AVector3	xHalfExtents;
		Asura_AVector3	xCentre;
		Asura_Quat		xOrientation;
		Asura_AMatrix3x3 xOriMat;
		if ( m_bUseOnTargetVolume )
		{
			xStream >> xHalfExtents;
			xStream >> xCentre;
			xStream >> xOrientation;
			xOrientation.ToMatrix( xOriMat );
			m_pxOnTargetVolume = new Asura_OBB( xHalfExtents, xCentre, xOriMat );
		}

		if ( m_bUseOffTargetVolume )
		{
			xStream >> xHalfExtents;
			xStream >> xCentre;
			xStream >> xOrientation;
			xOrientation.ToMatrix( xOriMat );
			m_pxOffTargetVolume = new Asura_OBB( xHalfExtents, xCentre, xOriMat );
		}
	}
}

void AVP_ServerEntity_InteractObject::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
    xStream << g_uCurrentChunkVersion;

//	PARENT_ASURA::WriteToChunkStream( xStream );
	PARENT::WriteToChunkStream( xStream );

	xStream << m_usTurnOnMessageBlock;
	xStream << m_usTurnOffMessageBlock;

    // Any data specific to this class would go here.
	Asura_WriteToStream_Bitfield_Wrapper< u_int > xBitfield( m_uFlags );

	xBitfield.Add( m_bTriggerOnce, 1 );
	xBitfield.Add( m_bUseActorVolume, 1 );
	xBitfield.Add( m_bUseOnTargetVolume, 1 );
	xBitfield.Add( m_bUseOffTargetVolume, 1 );
	xBitfield.Add( m_bOn, 1 );
	xBitfield.Add( m_bUseByAlien, 1 );
	xBitfield.Add( m_bUseByHuman, 1 );
	xBitfield.Add( m_bUseByPredator, 1 );
	xBitfield.Add( m_bEnabled, 1 );

	xStream << xBitfield;

	if ( m_pxActorVolume )
	{
		xStream << m_pxActorVolume->m_xBox;
		xStream << m_pxActorVolume->m_xOrient;
	}

	Asura_Quat xOri;
	if ( m_pxOnTargetVolume )
	{
		xOri.FromMatrix( m_pxOnTargetVolume->GetOrientation() );
		xStream << m_pxOnTargetVolume->GetHalfExtents();
		xStream << m_pxOnTargetVolume->GetCentre();
		xStream << xOri;
	}

	if ( m_pxOffTargetVolume )
	{
		xOri.FromMatrix( m_pxOffTargetVolume->GetOrientation() );
		xStream << m_pxOffTargetVolume->GetHalfExtents();
		xStream << m_pxOffTargetVolume->GetCentre();
		xStream << xOri;
	}
}

void AVP_ServerEntity_InteractObject::MakeCreateMessage( Asura_Guid uRequestingClient, bool bSendGuaranteed /*= false*/ )
{
	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_CLIENTCREATE, sizeof( AVP_Message_InteractObject_ClientCreate ), GetClassification());
	if(pxMessage)
	{
		AVP_Message_InteractObject_ClientCreate* pxData = static_cast< AVP_Message_InteractObject_ClientCreate* >( pxMessage->GetData() );
		pxData->FillInteractionObject(this);

		pxMessage->SetToGuid( ASURA_GUID_MY_CLIENT_ENTITIES );
		pxMessage->SetFromGuid( GetGuid() );
		if (bSendGuaranteed)
		{
			pxMessage->UseGuaranteedDelivery();
		}
		Asura_Network::SendToSpecificMachine( pxMessage, uRequestingClient );
	}
}

void AVP_ServerEntity_InteractObject::InitialiseFromBlueprint( const Asura_Blueprint& xBlueprint )
{
	PARENT::InitialiseFromBlueprint( xBlueprint );

	Asura_Hash_ID uSkinHash = ASURA_HASH_ID_UNSET;
	Asura_Hash_ID uAnimHash = ASURA_HASH_ID_UNSET;

	Asura_Value xValue;
	if(xBlueprint.GetParameterValueByHashID(0x0035e57d, xValue)) // Asura_GetHashID( "Skin" )
	{
		uSkinHash = xValue.GetHashIDValue();
	}
	if(xBlueprint.GetParameterValueByHashID( m_bOn ? 0x6e416d75 : 0x59ec4099, xValue ) ) // Anim.Interaction.TurnOnf : Anim.Interaction.TurnOff
	{
		uAnimHash = xValue.GetHashIDValue();
		SetAnimFlags(0);
		SetSkinAndAnimID(uSkinHash, uAnimHash);
	}
#if defined(ASURA_TOOLS)
	Tools_PatchSkinAndAnimFileIDs();
#endif	// defined(ASURA_TOOLS)
}

#ifdef ASURA_TOOLS

void AVP_ServerEntity_InteractObject::CreateFromTools(Asura_Guid uGuid)
{
	new AVP_ServerEntity_InteractObject(uGuid);
}

void AVP_ServerEntity_InteractObject::ApplyChangesForTemplateInstance( Tools_EntityTemplateInstance* pxInstance )
{
    PARENT::ApplyChangesForTemplateInstance(pxInstance);

	if ( m_pxActorVolume )
	{
		*m_pxActorVolume = pxInstance->TransformOrientedVolume( *m_pxActorVolume );
	}

	if ( m_pxOnTargetVolume )
	{
		Asura_OrientedVolume xOV;
		const Asura_AVector3& xHalfExtents = m_pxOnTargetVolume->GetHalfExtents();
		const Asura_AVector3& xPoint = m_pxOnTargetVolume->GetCentre();
		xOV.m_xBox.Reset();
		xOV.m_xBox.Set( Asura_Vector_3( xPoint + xHalfExtents ) );
		xOV.m_xBox.Set( Asura_Vector_3( xPoint - xHalfExtents ) );
		xOV.m_xOrient.FromMatrix( m_pxOnTargetVolume->GetOrientation() );

		xOV = pxInstance->TransformOrientedVolume( xOV );

		m_pxOnTargetVolume->SetCentre( Asura_AVector3( xOV.m_xBox.CalculateCentre() ) );
		m_pxOnTargetVolume->SetHalfExtents( Asura_AVector3( xOV.m_xBox.CalculateExtents() * 0.5f ) );
		Asura_AMatrix3x3 xOri;
		xOV.m_xOrient.ToMatrix( xOri );
		m_pxOnTargetVolume->SetOrientation( xOri );
	}

	if ( m_pxOffTargetVolume )
	{
		Asura_OrientedVolume xOV;
		const Asura_AVector3& xHalfExtents = m_pxOffTargetVolume->GetHalfExtents();
		const Asura_AVector3& xPoint = m_pxOffTargetVolume->GetCentre();
		xOV.m_xBox.Reset();
		xOV.m_xBox.Set( Asura_Vector_3( xPoint + xHalfExtents ) );
		xOV.m_xBox.Set( Asura_Vector_3( xPoint - xHalfExtents ) );
		xOV.m_xOrient.FromMatrix( m_pxOffTargetVolume->GetOrientation() );

		xOV = pxInstance->TransformOrientedVolume( xOV );

		m_pxOffTargetVolume->SetCentre( Asura_AVector3( xOV.m_xBox.CalculateCentre() ) );
		m_pxOffTargetVolume->SetHalfExtents( Asura_AVector3( xOV.m_xBox.CalculateExtents() * 0.5f ) );
		Asura_AMatrix3x3 xOri;
		xOV.m_xOrient.ToMatrix( xOri );
		m_pxOffTargetVolume->SetOrientation( xOri );
	}

	m_usTurnOnMessageBlock = pxInstance->ConvertMessageBlock( m_usTurnOnMessageBlock );
	m_usTurnOffMessageBlock = pxInstance->ConvertMessageBlock( m_usTurnOffMessageBlock );
}

bool AVP_ServerEntity_InteractObject::CanBeIncludedInTemplate() const
{
    return (GetClassification() == AVP_ENTITYCLASS_INTERACTOBJECT);
}

#endif // #ifdef ASURA_TOOLS

#ifdef ASURA_TOOLS
void AVP_ServerEntity_InteractObject::GetCustomEventName(int iEventIndex, Asura_String& xEventName)
{
	switch(iEventIndex)
	{
		case MESSAGE_BLOCK_ON:	xEventName = "On turn on";		return;
		case MESSAGE_BLOCK_OFF:	xEventName = "On turn off";		return;
	}

	Asura_Assert(FALSE, "Invalid interact object event index");

	xEventName = "";
}

unsigned short* AVP_ServerEntity_InteractObject::GetCustomEvent(int iIndex)
{
	switch(iIndex)
	{
		case MESSAGE_BLOCK_ON:	return &m_usTurnOnMessageBlock;
		case MESSAGE_BLOCK_OFF:	return &m_usTurnOffMessageBlock;
	}

	Asura_Assert(FALSE, "Invalid interact object event index");

	return NULL;
}
#endif // ASURA_TOOLS

void AVP_ServerEntity_InteractObject::OnTurnOn()
{
	m_bOn = true;
	if ( m_usTurnOnMessageBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
	{
		Asura_StaticMessage_System::SendMessages( m_usTurnOnMessageBlock, GetGuid() );
	}
	if ( m_bTriggerOnce )
	{
		m_bEnabled = false;
		SendClientDisableMessage();
	}
}

void AVP_ServerEntity_InteractObject::OnTurnOff()
{
	m_bOn = false;
	if ( m_usTurnOffMessageBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
	{
		Asura_StaticMessage_System::SendMessages( m_usTurnOffMessageBlock, GetGuid() );
	}
	if ( m_bTriggerOnce )
	{
		m_bEnabled = false;
		SendClientDisableMessage();
	}
}

void AVP_ServerEntity_InteractObject::SendInteractMessage( const Asura_Guid uPlayerGuid, const bool bInitialise /*= false*/ )
{
	Asura_Message* pxNewMessage = Asura_Network::CreateMessage( AVP_MSGID_REQUEST_INTERACTION, sizeof( AVP_Message_Actor_RequestInteraction ) );
	if ( pxNewMessage != NULL )
	{
		AVP_Message_Actor_RequestInteraction* pxData = static_cast< AVP_Message_Actor_RequestInteraction* >( pxNewMessage->GetData() );

		// Fill-in the message...
		pxData->SetInteractionType( bInitialise ? AVP_INTERACTION_TYPE_INITIALISE : AVP_INTERACTION_TYPE_DEFAULT );
		pxData->SetActorGuid( uPlayerGuid );
		pxData->SetIsOn( m_bOn );
		// Send the request...
		pxNewMessage->SetToGuid( GetGuid() );
		pxNewMessage->SetFromGuid( uPlayerGuid );

		HandleMessage( pxNewMessage );
		Asura_Network::DestroyMessage( pxNewMessage );
	}
}

void AVP_ServerEntity_InteractObject::SendClientDisableMessage()
{
	Asura_Message*	pxMessage	= Asura_Network::CreateMessage( AVP_MSGID_INTERACTION_OBJECT_DISABLE, 0 );
	if ( pxMessage != NULL )
	{
		// Send the request...
		pxMessage->SetToGuid( ASURA_GUID_MY_CLIENT_ENTITIES );
		pxMessage->SetFromGuid( GetGuid() );
		Asura_Network::Send( pxMessage );
	}
}

// end of file
