/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2007.
-------------------------------------------------------------------------
$Id$
$DateTime$
Description:  network breakability: plazback of procedural breaks
-------------------------------------------------------------------------
History:
- 22/01/2007   10:34 : Created by Craig Tiller
*************************************************************************/

#include "StdAfx.h"
#include "ProceduralBreakingPlaybackListener.h"
#include "BreakReplicator.h"
#include "NetworkCVars.h"

CProceduralBreakingPlaybackListener::CProceduralBreakingPlaybackListener( _smart_ptr<CProceduralBreakingPlaybackStream> pStream, INetBreakagePlaybackPtr pBreakage ) 
: CProceduralBreakingBaseListener(pStream->m_pBreakType)
, m_pStream(pStream)
, m_pBreakage(pBreakage)
#if BREAK_HIERARCHICAL_TRACKING
, m_playbackFrame(0)
#endif
{
}

void CProceduralBreakingPlaybackListener::UpdateJointBreaks()
{
	DynArray<SJointBreakRec>& jbs = m_pStream->m_jointBreaks;

#if BREAK_HIERARCHICAL_TRACKING
	if (CNetworkCVars::Get().BreakageLog)
		CryLog("[brk] playback frame %d", m_playbackFrame);
#endif

	// replay breaks
	for (size_t i=0; i<jbs.size(); ++i)
	{
#if BREAK_HIERARCHICAL_TRACKING
		if (m_createdObjects.find(jbs[i].idxRef) == m_createdObjects.end())
			continue;
		if (m_playbackFrame != jbs[i].frame)
			continue;
#endif

		CObjectSelector obj = GetObjectSelector(jbs[i].idxRef);

#if BREAK_HIERARCHICAL_TRACKING
		if (CNetworkCVars::Get().BreakageLog)
			CryLog( "[brk] break joint %d on ref %d (%s) [reached joint frame %d]", jbs[i].id, jbs[i].idxRef, obj.GetDescription().c_str(), int(jbs[i].frame) );
#else
		if (CNetworkCVars::Get().BreakageLog)
			CryLog( "[brk] break joint %d on ref %d (%s)", jbs[i].id, jbs[i].idxRef, obj.GetDescription().c_str() );
#endif

		pe_params_structural_joint pj;
		pj.partidEpicenter = jbs[i].epicenter;
		if (jbs[i].id >= 0)
		{
			pj.id = jbs[i].id;
			pj.bBroken = true;
			pj.bReplaceExisting = true;
		}
		else
		{
			pj.idx = -1;
		}
		bool succeeded = false;
		if (IPhysicalEntity * pEnt = obj.Find())
		{
			if (!pEnt->SetParams(&pj))
				GameWarning("Failed to break joint-id %d on %s", pj.id, obj.GetDescription().c_str());
			else
				succeeded = true;
		}
		else
			GameWarning("Unable to find object %s to break joint-id %d on", obj.GetDescription().c_str(), pj.id);

		if (succeeded)
		{
			jbs.erase(i);
			--i;
		}
	}
}

void CProceduralBreakingPlaybackListener::Start()
{
	char message[256];
	sprintf(message, "begin playback %d", m_pStream->m_magicId);
	DumpSpawnRecs(m_pStream->m_spawnRecs, m_pStream->m_jointBreaks, message);
#if BREAK_HIERARCHICAL_TRACKING
	DynArray<SJointBreakRec>& jbs = m_pStream->m_jointBreaks;
	for (size_t i=0; i<jbs.size(); ++i)
		if (jbs[i].idxRef < 0)
			m_createdObjects.insert(jbs[i].idxRef);
#endif
	UpdateJointBreaks();
	CompleteStepFrom( m_pStream->m_spawnRecs.begin() );
}

int CProceduralBreakingPlaybackListener::GotBreakOp( ENetBreakOperation op, int idxRef, int partid, EntityId to )
{
	DynArray<SProceduralSpawnRec>& spawnRecs = m_pStream->m_spawnRecs;
	bool priorAreInvalidated = true;

	for (DynArray<SProceduralSpawnRec>::iterator it = spawnRecs.begin(); it != spawnRecs.end(); ++it)
	{
		bool priorWereInvalidated = priorAreInvalidated;
		priorAreInvalidated &= (it->op == eNBO_NUM_OPERATIONS);
		if (it->op != op)
			continue;
		if ((BIT(op) & OPS_REFERENCING_ENTS))
			if (it->idxRef != idxRef)
				continue;
		if ((BIT(op) & OPS_WITH_PARTIDS))
			if (it->partid != partid)
				continue;
#if BREAK_HIERARCHICAL_TRACKING
		if (!priorWereInvalidated)
		{
			if (op == eNBO_PostStep)
				break;
			else if (BIT(op) & OPS_JOINT_FRAME_COUNTERS)
				it->op = eNBO_FrameCounterFinished;
		}
		if (it->op == op) // still
		{
			it->op = eNBO_NUM_OPERATIONS; // invalidate
			if (BIT(op) & OPS_JOINT_FRAME_COUNTERS)
				IncrementFrame();
		}
#else
		it->op = eNBO_NUM_OPERATIONS;
#endif
		m_pBreakage->SpawnedEntity( it->idx, to );
		if (priorWereInvalidated)
			CompleteStepFrom(it+1);
		return it->idx;
	}
#if BREAK_HIERARCHICAL_TRACKING
	if (op != eNBO_PostStep)
#endif
		GameWarning("CProceduralBreakingPlaybackListener: mismatched spawn event %d:%d[part=%d]->%d", op, idxRef, partid, to);
	return -1;
}

#if BREAK_HIERARCHICAL_TRACKING
void CProceduralBreakingPlaybackListener::IncrementFrame()
{
	m_playbackFrame++;
	UpdateJointBreaks();
}
#endif

void CProceduralBreakingPlaybackListener::CompleteStepFrom( DynArray<SProceduralSpawnRec>::iterator it )
{
	DynArray<SProceduralSpawnRec>& spawnRecs = m_pStream->m_spawnRecs;
	while (it != spawnRecs.end())
	{
		switch (it->op)
		{
		case eNBO_AbsorbStep:
			GetBreakType()->AbsorbStep();
			it->op = eNBO_NUM_OPERATIONS;
			break;
#if BREAK_HIERARCHICAL_TRACKING
		case eNBO_FrameCounterFinished:
			it->op = eNBO_NUM_OPERATIONS;
			IncrementFrame();
			break;
#endif
		}
		if (it->op == eNBO_NUM_OPERATIONS)
			++it;
		else
			break;
	}
}

void CProceduralBreakingPlaybackListener::FinishBreakOp( ENetBreakOperation op, int idxRef, EntityId to, int virtId )
{
#if BREAK_HIERARCHICAL_TRACKING
	if (m_createdObjects.insert(virtId).second)
		UpdateJointBreaks();
#endif
}

void CProceduralBreakingPlaybackListener::Complete()
{
	char message[256];
	sprintf(message, "end playback %d", m_pStream->m_magicId);
	DumpSpawnRecs(m_pStream->m_spawnRecs, m_pStream->m_jointBreaks, message);
}

bool CProceduralBreakingPlaybackListener::AllowComplete()
{
	if (!m_pStream->m_jointBreaks.empty())
		return false;
	for (size_t i=0; i<m_pStream->m_spawnRecs.size(); i++)
		if (m_pStream->m_spawnRecs[i].op != eNBO_NUM_OPERATIONS)
			return false;
	return true;
}

CProceduralBreakingPlaybackStream::CProceduralBreakingPlaybackStream(IProceduralBreakTypePtr pBreakType) : m_pBreakType(pBreakType), m_magicId(-1)
{
}

bool CProceduralBreakingPlaybackStream::SetMagicId( int id )
{
	if (m_magicId >= 0 || id < 0)
		return false;
	m_magicId = id;
	return true;
}

bool CProceduralBreakingPlaybackStream::GotExplosiveObjectState( const SExplosiveObjectState* state )
{
	return m_pBreakType->GotExplosiveObjectState(state);
}

bool CProceduralBreakingPlaybackStream::GotJointBreakRec( const SJointBreakRec* rec )
{
	m_jointBreaks.push_back(*rec);
	return true;
}

bool CProceduralBreakingPlaybackStream::GotProceduralSpawnRec( const SProceduralSpawnRec* rec )
{
	assert( rec->op >= eNBO_Create && rec->op < eNBO_NUM_OPERATIONS );
	m_spawnRecs.push_back(*rec);
	return true;
}

IBreakReplicatorListenerPtr CProceduralBreakingPlaybackStream::Playback( CBreakReplicator * pReplicator, INetBreakagePlaybackPtr pBreak )
{
	bool hasJointBreaks = !m_jointBreaks.empty();
	_smart_ptr<CProceduralBreakingPlaybackListener> pListener = new CProceduralBreakingPlaybackListener(this, pBreak);
	pReplicator->AddListener( &*pListener, CNetworkCVars::Get().BreakTimeoutFrames );
	m_pBreakType->PreparePlayback();
	pListener->Start();
	m_pBreakType->BeginPlayback( hasJointBreaks );
	return &*pListener;
}

bool CProceduralBreakingPlaybackStream::AttemptAbsorb( const IProceduralBreakTypePtr& pBT )
{
	return m_pBreakType->AttemptAbsorb(pBT);
}
