//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose: 
//
//=============================================================================

#include "cbase.h"
#include "particle_property.h"
#include "utlvector.h"

#ifdef CLIENT_DLL

#include "c_baseentity.h"
#include "c_baseanimating.h"
#include "recvproxy.h"
#include "particles_new.h"
#include "engine/ivdebugoverlay.h"

#else

#include "baseentity.h"
#include "baseanimating.h"
#include "sendproxy.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//-----------------------------------------------------------------------------
// Save/load
//-----------------------------------------------------------------------------
BEGIN_DATADESC_NO_BASE( CParticleProperty )
	//		DEFINE_FIELD( m_pOuter, FIELD_CLASSPTR ),
END_DATADESC()

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Prediction
//-----------------------------------------------------------------------------
BEGIN_PREDICTION_DATA_NO_BASE( CParticleProperty )
	//DEFINE_PRED_FIELD( m_vecMins, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
#endif

//-----------------------------------------------------------------------------
// Networking
//-----------------------------------------------------------------------------
BEGIN_NETWORK_TABLE_NOBASE( CParticleProperty, DT_ParticleProperty )
#ifdef CLIENT_DLL
//RecvPropVector( RECVINFO(m_vecMins), 0, RecvProxy_OBBMins ),
#else
//SendPropVector( SENDINFO(m_vecMins), 0, SPROP_NOSCALE),
#endif
END_NETWORK_TABLE()


//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CParticleProperty::CParticleProperty()
{
	Init( NULL );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CParticleProperty::~CParticleProperty()
{
	// We're being removed. Call StopEmission() on any particle system
	// that has an unlimited number of particles to emit.
	StopEmission( NULL, false, true );
}

//-----------------------------------------------------------------------------
// Initialization
//-----------------------------------------------------------------------------
void CParticleProperty::Init( CBaseEntity *pEntity )
{
	m_pOuter = pEntity;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CParticleProperty::GetParticleAttachment( C_BaseEntity *pEntity, const char *pszAttachmentName, const char *pszParticleName )
{
	Assert( pEntity && pEntity->GetBaseAnimating() );
	if ( !pEntity || !pEntity->GetBaseAnimating() )
		return -1;

	// Find the attachment point index
	int iAttachment = pEntity->GetBaseAnimating()->LookupAttachment( pszAttachmentName );
	if ( iAttachment == -1 )
	{
		Warning("Model '%s' doesn't have attachment '%s' to attach particle system '%s' to.\n", STRING(pEntity->GetBaseAnimating()->GetModelName()), pszAttachmentName, pszParticleName );
	}

	return iAttachment;
}

//-----------------------------------------------------------------------------
// Purpose: Create a new particle system and attach it to our owner
//-----------------------------------------------------------------------------
CNewParticleEffect *CParticleProperty::Create( const char *pszParticleName, ParticleAttachment_t iAttachType, const char *pszAttachmentName )
{
	int iAttachment = GetParticleAttachment( GetOuter(), pszAttachmentName, pszParticleName );
	if ( iAttachment == -1 )
		return NULL;

	// Create the system
	return Create( pszParticleName, iAttachType, iAttachment );
}
	  
//-----------------------------------------------------------------------------
// Purpose: Create a new particle system and attach it to our owner
//-----------------------------------------------------------------------------
static ConVar cl_particle_batch_mode( "cl_particle_batch_mode", "1" );
CNewParticleEffect *CParticleProperty::Create( const char *pszParticleName, ParticleAttachment_t iAttachType, int iAttachmentPoint, Vector vecOriginOffset )
{
	int nBatchMode = cl_particle_batch_mode.GetInt();
	CParticleSystemDefinition *pDef = g_pParticleSystemMgr->FindParticleSystem( pszParticleName );
	bool bRequestedBatch = ( nBatchMode == 2 ) || ( ( nBatchMode == 1 ) && pDef && pDef->ShouldBatch() ); 
	if ( ( iAttachType == PATTACH_CUSTOMORIGIN ) && bRequestedBatch )
	{
		int iIndex = FindEffect( pszParticleName );
		if ( iIndex >= 0 )
		{
			CNewParticleEffect *pEffect = m_ParticleEffects[iIndex].pParticleEffect.GetObject();
			pEffect->Restart();
			return pEffect;
		}
	}

	if ( !pDef )
	{
		AssertMsg( 0, "Attempting to create unknown particle system" );
		Warning( "Attempting to create unknown particle system '%s' \n", pszParticleName );
		return NULL;
	}

	int iIndex = m_ParticleEffects.AddToTail();
	ParticleEffectList_t *newEffect = &m_ParticleEffects[iIndex];
	newEffect->pParticleEffect = CNewParticleEffect::Create( m_pOuter, pDef );

	if ( !newEffect->pParticleEffect->IsValid() )
	{
		// Caused by trying to spawn an unregistered particle effect. Remove it.
		ParticleMgr()->RemoveEffect( newEffect->pParticleEffect.GetObject() );
		return NULL;
	}

	AddControlPoint( iIndex, 0, GetOuter(), iAttachType, iAttachmentPoint, vecOriginOffset );

	if ( m_pOuter )
	{
		m_pOuter->OnNewParticleEffect( pszParticleName, newEffect->pParticleEffect.GetObject() );
	}
	
	return newEffect->pParticleEffect.GetObject();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CParticleProperty::AddControlPoint( CNewParticleEffect *pEffect, int iPoint, C_BaseEntity *pEntity, ParticleAttachment_t iAttachType, const char *pszAttachmentName, Vector vecOriginOffset )
{
	int iAttachment = -1;
	if ( pszAttachmentName )
	{
		iAttachment = GetParticleAttachment( pEntity, pszAttachmentName, pEffect->GetEffectName() );
	}

	for ( int i = 0; i < m_ParticleEffects.Count(); i++ )
	{
		if ( m_ParticleEffects[i].pParticleEffect == pEffect )
		{
			AddControlPoint( i, iPoint, pEntity, iAttachType, iAttachment, vecOriginOffset );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CParticleProperty::AddControlPoint( int iEffectIndex, int iPoint, C_BaseEntity *pEntity, ParticleAttachment_t iAttachType, int iAttachmentPoint, Vector vecOriginOffset )
{
	Assert( iEffectIndex >= 0 && iEffectIndex < m_ParticleEffects.Count() );
	ParticleEffectList_t *pEffect = &m_ParticleEffects[iEffectIndex];
	Assert( pEffect->pControlPoints.Count() < MAX_PARTICLE_CONTROL_POINTS );

	int iIndex = pEffect->pControlPoints.AddToTail();
	ParticleControlPoint_t *pNewPoint = &pEffect->pControlPoints[iIndex];
	pNewPoint->iControlPoint = iPoint;
	pNewPoint->hEntity = pEntity;
	pNewPoint->iAttachType = iAttachType;
	pNewPoint->iAttachmentPoint = iAttachmentPoint;
	pNewPoint->vecOriginOffset = vecOriginOffset;

	UpdateParticleEffect( pEffect, true, iIndex );
}


//-----------------------------------------------------------------------------
// Used to replace a particle effect with a different one; attaches the control point updating to the new one
//-----------------------------------------------------------------------------
void CParticleProperty::ReplaceParticleEffect( CNewParticleEffect *pOldEffect, CNewParticleEffect *pNewEffect )
{
	int nCount = m_ParticleEffects.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		if ( pOldEffect != m_ParticleEffects[i].pParticleEffect.GetObject() )
			continue;

		m_ParticleEffects[i].pParticleEffect = pNewEffect;
		UpdateParticleEffect( &m_ParticleEffects[i], true );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Set the parent of a given control point to the index of some other
//			control point.
//-----------------------------------------------------------------------------
void CParticleProperty::SetControlPointParent( int iEffectIndex, int whichControlPoint, int parentIdx )
{

}

//-----------------------------------------------------------------------------
// Purpose: Stop effects from emitting more particles. If no effect is 
//			specified, all effects attached to this entity are stopped.
//-----------------------------------------------------------------------------
void CParticleProperty::StopEmission( CNewParticleEffect *pEffect, bool bWakeOnStop, bool bDestroyAsleepSystems )
{
	// If we return from dormancy and are then told to stop emitting,
	// we should have died while dormant. Remove ourselves immediately.
	bool bRemoveInstantly = (m_iDormancyChangedAtFrame == gpGlobals->framecount);

	if ( pEffect )
	{
		if ( FindEffect( pEffect ) != -1 )
		{
			pEffect->StopEmission( false, bRemoveInstantly, bWakeOnStop );
		}
	}
	else
	{
		// Stop all effects
		float flNow = g_pParticleSystemMgr->GetLastSimulationTime();
		int nCount = m_ParticleEffects.Count();
		for ( int i = nCount-1; i >= 0; i-- )
		{
			CNewParticleEffect *pTmp = m_ParticleEffects[i].pParticleEffect.GetObject();
			bool bRemoveSystem = bRemoveInstantly || ( bDestroyAsleepSystems && ( flNow >= pTmp->m_flNextSleepTime ) );
			if ( bRemoveSystem )
			{
				m_ParticleEffects.Remove( i );
				pTmp->SetOwner( NULL );
			}
			pTmp->StopEmission( false, bRemoveSystem, !bRemoveSystem && bWakeOnStop );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Remove effects immediately, including all current particles. If no
// effect is specified, all effects attached to this entity are removed.
//-----------------------------------------------------------------------------
void CParticleProperty::StopEmissionAndDestroyImmediately( CNewParticleEffect *pEffect )
{
	if ( pEffect )
	{
		int iIndex = FindEffect( pEffect );
		Assert( iIndex != -1 );
		if ( iIndex != -1 )
		{
			m_ParticleEffects.Remove( iIndex );

			// Clear the owner so it doesn't try to call back to us on deletion
			pEffect->SetOwner( NULL );
			pEffect->StopEmission( false, true );
		}
	}
	else
	{
		// Immediately destroy all effects
		int nCount = m_ParticleEffects.Count();
		for ( int i = nCount-1; i >= 0; i-- )
		{
			CNewParticleEffect *pTmp = m_ParticleEffects[i].pParticleEffect.GetObject();
			m_ParticleEffects.Remove( i );

			// Clear the owner so it doesn't try to call back to us on deletion
			pTmp->SetOwner( NULL );
			pTmp->StopEmission( false, true );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Stop all effects that have  a control point associated with the given
//          entity.
//-----------------------------------------------------------------------------
void CParticleProperty::StopParticlesInvolving( CBaseEntity *pEntity )
{
	Assert( pEntity );

	EHANDLE entHandle(pEntity);

	// If we return from dormancy and are then told to stop emitting,
	// we should have died while dormant. Remove ourselves immediately.
	bool bRemoveInstantly = (m_iDormancyChangedAtFrame == gpGlobals->framecount);
	
	int nCount = m_ParticleEffects.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		// for each effect...
		ParticleEffectList_t &part = m_ParticleEffects[i];
		// look through all the control points to see if any mention the given object
		int cpCount = part.pControlPoints.Count();
		for (int j = 0; j < cpCount ; ++j )
		{
			// if any control points respond to the given handle...
			if (part.pControlPoints[j].hEntity == entHandle)
			{
				part.pParticleEffect->StopEmission( false, bRemoveInstantly );
				break; // break out of the inner loop (to where it says BREAK TO HERE)
			}
		}
		// BREAK TO HERE
	}
}

//g_pParticleSystemMgr->FindParticleSystem( pParticleSystemName );

//-----------------------------------------------------------------------------
// Purpose: Stop all effects that were created using the given definition
//			name.
//-----------------------------------------------------------------------------
void CParticleProperty::StopParticlesNamed( const char *pszEffectName, bool bForceRemoveInstantly /* =false */ )
{
	CParticleSystemDefinition *pDef = g_pParticleSystemMgr->FindParticleSystem( pszEffectName );
	AssertMsg1(pDef, "Could not find particle definition %s", pszEffectName );
	if (!pDef)
		return;


	// If we return from dormancy and are then told to stop emitting,
	// we should have died while dormant. Remove ourselves immediately.
	bool bRemoveInstantly = (m_iDormancyChangedAtFrame == gpGlobals->framecount);
	// force remove particles instantly if caller specified
	bRemoveInstantly |= bForceRemoveInstantly;

	int nCount = m_ParticleEffects.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		// for each effect...
		CNewParticleEffect *pParticleEffect = m_ParticleEffects[i].pParticleEffect.GetObject();
		if (pParticleEffect->m_pDef() == pDef)
		{
			pParticleEffect->StopEmission( false, bRemoveInstantly );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CParticleProperty::OnParticleSystemUpdated( CNewParticleEffect *pEffect, float flTimeDelta )
{
	int iIndex = FindEffect( pEffect );
	Assert( iIndex != -1 );
	if ( iIndex == -1 )
		return;

	UpdateParticleEffect( &m_ParticleEffects[iIndex] );

	/*
	// Display the bounding box of the particle effect
	Vector vecMins, vecMaxs;
	pEffect->GetRenderBounds( vecMins, vecMaxs );
	debugoverlay->AddBoxOverlay( pEffect->GetRenderOrigin(), vecMins, vecMaxs, QAngle( 0, 0, 0 ), 0, 255, 255, 0, 0 );
	*/
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CParticleProperty::OnParticleSystemDeleted( CNewParticleEffect *pEffect )
{
	int iIndex = FindEffect( pEffect );
	if ( iIndex == -1 )
		return;

	m_ParticleEffects[iIndex].pParticleEffect.MarkDeleted();
	m_ParticleEffects.Remove( iIndex );
}

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: The entity we're attached to has change dormancy state on our client
//-----------------------------------------------------------------------------
void CParticleProperty::OwnerSetDormantTo( bool bDormant )
{
	m_iDormancyChangedAtFrame = gpGlobals->framecount;

	int nCount = m_ParticleEffects.Count();
	for ( int i = 0; i < nCount; i++ )
	{
		//m_ParticleEffects[i].pParticleEffect->SetShouldSimulate( !bDormant );
		m_ParticleEffects[i].pParticleEffect->SetDormant( bDormant );
	}
}
#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int	CParticleProperty::FindEffect( CNewParticleEffect *pEffect )
{
	for ( int i = 0; i < m_ParticleEffects.Count(); i++ )
	{
		if ( m_ParticleEffects[i].pParticleEffect == pEffect )
			return i;
	}

	return -1;
}

int CParticleProperty::FindEffect( const char *pEffectName )
{
	for ( int i = 0; i < m_ParticleEffects.Count(); i++ )
	{
		if ( !Q_stricmp( m_ParticleEffects[i].pParticleEffect->GetName(), pEffectName ) )
			return i;
	}

	return -1;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CParticleProperty::UpdateParticleEffect( ParticleEffectList_t *pEffect, bool bInitializing, int iOnlyThisControlPoint )
{
	if ( iOnlyThisControlPoint != -1 )
	{
		UpdateControlPoint( pEffect, iOnlyThisControlPoint, bInitializing );
		return;
	}

	// Loop through our control points and update them all
	for ( int i = 0; i < pEffect->pControlPoints.Count(); i++ )
	{
		UpdateControlPoint( pEffect, i, bInitializing );
	}
}

extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CParticleProperty::UpdateControlPoint( ParticleEffectList_t *pEffect, int iPoint, bool bInitializing )
{
	ParticleControlPoint_t *pPoint = &pEffect->pControlPoints[iPoint];

	if ( !pPoint->hEntity.Get() )
	{
		if ( pPoint->iAttachType == PATTACH_WORLDORIGIN && bInitializing )
		{
			pEffect->pParticleEffect->SetControlPointOrientation( pPoint->iControlPoint, Vector(1,0,0), Vector(0,1,0), Vector(0,0,1) );
			pEffect->pParticleEffect->SetControlPoint( pPoint->iControlPoint, pPoint->vecOriginOffset );
			pEffect->pParticleEffect->SetSortOrigin( pPoint->vecOriginOffset );
		}

		pEffect->pParticleEffect->SetControlPointEntity( pPoint->iControlPoint, NULL );
		return;
	}

	// Only update non-follow particles when we're initializing, 
	// unless we're parented to something, in which case we should always update
	if ( !bInitializing && !pPoint->hEntity->GetMoveParent() && (pPoint->iAttachType == PATTACH_ABSORIGIN || pPoint->iAttachType == PATTACH_POINT ) )
		return;

	if ( pPoint->iAttachType == PATTACH_CUSTOMORIGIN )
		return;

	Vector vecOrigin, vecForward, vecRight, vecUp;

	switch ( pPoint->iAttachType )
	{
	case PATTACH_POINT:
	case PATTACH_POINT_FOLLOW:
		{
			C_BaseAnimating *pAnimating = pPoint->hEntity->GetBaseAnimating();

			Assert( pAnimating );
			if ( pAnimating )
			{
				matrix3x4_t attachmentToWorld;

				pAnimating->GetAttachment( pPoint->iAttachmentPoint, attachmentToWorld );


				MatrixVectors( attachmentToWorld, &vecForward, &vecRight, &vecUp );
				MatrixPosition( attachmentToWorld, vecOrigin );

				if ( pEffect->pParticleEffect->m_pDef->IsViewModelEffect() )
				{
					FormatViewModelAttachment( vecOrigin, true );
				}

			}
		}
		break;

	case PATTACH_ABSORIGIN:
	case PATTACH_ABSORIGIN_FOLLOW:
	default:
		{
			vecOrigin = pPoint->hEntity->GetAbsOrigin() + pPoint->vecOriginOffset;
			pPoint->hEntity->GetVectors( &vecForward, &vecRight, &vecUp );
		}
		break;
	}
	pEffect->pParticleEffect->SetControlPointOrientation( pPoint->iControlPoint, vecForward, vecRight, vecUp );
	pEffect->pParticleEffect->SetControlPointEntity( pPoint->iControlPoint, pPoint->hEntity );
	pEffect->pParticleEffect->SetControlPoint( pPoint->iControlPoint, vecOrigin );
	pEffect->pParticleEffect->SetSortOrigin( vecOrigin );
}

//-----------------------------------------------------------------------------
// Purpose: Output all active effects
//-----------------------------------------------------------------------------
void CParticleProperty::DebugPrintEffects( void )
{
	int nCount = m_ParticleEffects.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		// for each effect...
		CNewParticleEffect *pParticleEffect = m_ParticleEffects[i].pParticleEffect.GetObject();

		if ( !pParticleEffect )
			continue;
	
		Msg( "(%d)  EffectName \"%s\"  Dormant? %s  Emission Stopped? %s \n",
			i,
			pParticleEffect->GetEffectName(),
			( pParticleEffect->m_bDormant ) ? "yes" : "no",
			( pParticleEffect->m_bEmissionStopped ) ? "yes" : "no" );
	}
}
