//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "networkstringtable_clientdll.h"
#include "dt_utlvector_recv.h"
#include "choreoevent.h"
#include "choreoactor.h"
#include "choreochannel.h"
#include "filesystem.h"
#include "ichoreoeventcallback.h"
#include "scenefilecache/ISceneFileCache.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "tier2/tier2.h"
#include "hud_closecaption.h"
#include "tier0/icommandline.h"

#include "c_sceneentity.h"

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

//-----------------------------------------------------------------------------
// Purpose: Decodes animtime and notes when it changes
// Input  : *pStruct - ( C_BaseEntity * ) used to flag animtime is changine
//			*pVarData - 
//			*pIn - 
//			objectID - 
//-----------------------------------------------------------------------------
void RecvProxy_ForcedClientTime( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	C_SceneEntity *pScene = reinterpret_cast< C_SceneEntity * >( pStruct );
	*(float *)pOut = pData->m_Value.m_Float;
	pScene->OnResetClientTime();
}

#if defined( CSceneEntity )
#undef CSceneEntity
#endif

IMPLEMENT_CLIENTCLASS_DT(C_SceneEntity, DT_SceneEntity, CSceneEntity)
	RecvPropInt(RECVINFO(m_nSceneStringIndex)),
	RecvPropBool(RECVINFO(m_bIsPlayingBack)),
	RecvPropBool(RECVINFO(m_bPaused)),
	RecvPropBool(RECVINFO(m_bMultiplayer)),
	RecvPropFloat(RECVINFO(m_flForceClientTime), 0, RecvProxy_ForcedClientTime ),
	RecvPropUtlVector( 
		RECVINFO_UTLVECTOR( m_hActorList ), 
		MAX_ACTORS_IN_SCENE,
		RecvPropEHandle(NULL, 0, 0)),
END_RECV_TABLE()

C_SceneEntity::C_SceneEntity( void )
{
	m_pScene = NULL;
	m_bMultiplayer = false;

	m_hOwner = NULL;
	m_bClientOnly = false;
}

C_SceneEntity::~C_SceneEntity( void )
{
	UnloadScene();
}

void C_SceneEntity::OnResetClientTime()
{
	// In TF2 we ignore this as the scene is played entirely client-side.
#ifndef TF_CLIENT_DLL
	m_flCurrentTime = m_flForceClientTime;
#endif
}

char const *C_SceneEntity::GetSceneFileName()
{
	return g_pStringTableClientSideChoreoScenes->GetString( m_nSceneStringIndex );
}

ConVar mp_usehwmvcds( "mp_usehwmvcds", "0", NULL, "Enable the use of the hw morph vcd(s). (-1 = never, 1 = always, 0 = based upon GPU)" ); // -1 = never, 0 = if hasfastvertextextures, 1 = always
bool UseHWMorphVCDs()
{
// 	if ( mp_usehwmvcds.GetInt() == 0 )
// 		return g_pMaterialSystemHardwareConfig->HasFastVertexTextures();
// 	return mp_usehwmvcds.GetInt() > 0;
	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_SceneEntity::GetHWMorphSceneFileName( const char *pFilename, char *pHWMFilename )
{
	// Are we even using hardware morph?
	if ( !UseHWMorphVCDs() )
		return false;

	// Multi-player only!
	if ( !m_bMultiplayer )
		return false;

	// Do we have a valid filename?
	if ( !( pFilename && pFilename[0] ) )
		return false;

	// Check to see if we already have an player/hwm/* filename.
	if ( ( V_strstr( pFilename, "/high" ) != NULL ) || ( V_strstr( pFilename, "\\high" ) != NULL ) )
	{
		V_strcpy( pHWMFilename, pFilename );
		return true;
	}

	// Find the hardware morph scene name and pass that along as well.
	char szScene[MAX_PATH];
	V_strcpy_safe( szScene, pFilename );

	char szSceneHWM[MAX_PATH];
	szSceneHWM[0] = '\0';

	char *pszToken = strtok( szScene, "/\\" );
	while ( pszToken != NULL )
	{
		if ( !V_stricmp( pszToken, "low" ) )
		{
			V_strcat( szSceneHWM, "high", sizeof( szSceneHWM ) );
		}
		else
		{
			V_strcat( szSceneHWM, pszToken, sizeof( szSceneHWM ) );
		}

		pszToken = strtok( NULL, "/\\" );
		if ( pszToken != NULL )
		{
			V_strcat( szSceneHWM, "\\", sizeof( szSceneHWM ) );
		}
	}

	V_strcpy( pHWMFilename, szSceneHWM );
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_SceneEntity::ResetActorFlexesForScene()
{
	int nActorCount = m_pScene->GetNumActors();
	for( int iActor = 0; iActor < nActorCount; ++iActor )
	{
		CChoreoActor *pChoreoActor = m_pScene->GetActor( iActor );
		if ( !pChoreoActor )
			continue;

		C_BaseFlex *pFlexActor = FindNamedActor( pChoreoActor );
		if ( !pFlexActor )
			continue;

		CStudioHdr *pStudioHdr = pFlexActor->GetModelPtr();
		if ( !pStudioHdr )
			continue;

		if ( pStudioHdr->numflexdesc() == 0 )
			continue;

		// Reset the flex weights to their starting position.
		LocalFlexController_t iController;
		for ( iController = LocalFlexController_t(0); iController < pStudioHdr->numflexcontrollers(); ++iController )
		{
			pFlexActor->SetFlexWeight( iController, 0.0f );
		}

		// Reset the prediction interpolation values.
		pFlexActor->m_iv_flexWeight.Reset();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_SceneEntity::StopClientOnlyScene()
{
	if ( m_pScene )
	{
		m_pScene->ResetSimulation();

		if ( m_hOwner.Get() )
		{
			m_hOwner->RemoveChoreoScene( m_pScene );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_SceneEntity::SetupClientOnlyScene( const char *pszFilename, C_BaseFlex *pOwner /* = NULL */, bool bMultiplayer /* = false */ )
{
	m_bIsPlayingBack = true;
	m_bMultiplayer = bMultiplayer;
	m_hOwner = pOwner;
	m_bClientOnly = true;

	char szFilename[128];
	Assert( V_strlen( pszFilename ) < 128 );
	V_strcpy_safe( szFilename, pszFilename );

	char szSceneHWM[128];
	if ( GetHWMorphSceneFileName( szFilename, szSceneHWM ) )
	{
		V_strcpy_safe( szFilename, szSceneHWM );
	}

	Assert(  szFilename[ 0 ] );
	if ( szFilename[ 0 ] )
	{
		LoadSceneFromFile( szFilename );

		if ( !HushAsserts() )
		{
			Assert( m_pScene );
		}

		// Should handle gestures and sequences client side.
		if ( m_bMultiplayer )
		{
			if ( m_pScene )
			{
				int types[6];
				types[0] = CChoreoEvent::FLEXANIMATION;
				types[1] = CChoreoEvent::EXPRESSION;
				types[2] = CChoreoEvent::GESTURE;
				types[3] = CChoreoEvent::SEQUENCE;
				types[4] = CChoreoEvent::SPEAK;
				types[5] = CChoreoEvent::LOOP;
				m_pScene->RemoveEventsExceptTypes( types, 6 );
			}

			PrefetchAnimBlocks( m_pScene );
		}
		else
		{
			if ( m_pScene )
			{
				int types[ 2 ];
				types[ 0 ] =  CChoreoEvent::FLEXANIMATION;
				types[ 1 ] =  CChoreoEvent::EXPRESSION;
				m_pScene->RemoveEventsExceptTypes( types, 2 );
			}
		}

		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}

	if ( m_hOwner.Get() )
	{
		if ( !HushAsserts() )
		{
			Assert( m_pScene );
		}

		if ( m_pScene )
		{
			ClearSceneEvents( m_pScene, false );

			if ( m_bIsPlayingBack )
			{
				m_pScene->ResetSimulation();
				m_hOwner->StartChoreoScene( m_pScene );
			}
			else
			{
				m_pScene->ResetSimulation();
				m_hOwner->RemoveChoreoScene( m_pScene );
			}

			// Reset the flex weights when we start a new scene.  This is normally done on the player model, but since
			// we don't have a player here yet - we need to do this!
			ResetActorFlexesForScene();
		}
	}
	else
	{
		for( int i = 0; i < m_hActorList.Count() ; ++i )
		{
			C_BaseFlex *actor = m_hActorList[ i ].Get();
			if ( !actor )
				continue;

			Assert( m_pScene );

			if ( m_pScene )
			{
				ClearSceneEvents( m_pScene, false );

				if ( m_bIsPlayingBack )
				{
					m_pScene->ResetSimulation();
					actor->StartChoreoScene( m_pScene );
				}
				else
				{
					m_pScene->ResetSimulation();
					actor->RemoveChoreoScene( m_pScene );
				}
			}
		}
	}
}

void C_SceneEntity::PostDataUpdate( DataUpdateType_t updateType )
{
	BaseClass::PostDataUpdate( updateType );

	char const *str = GetSceneFileName();
	char szFilename[MAX_PATH];
	if ( str )
	{
		Assert( V_strlen( str ) < MAX_PATH );
		V_strcpy_safe( szFilename, str );
	}
	else
	{
		szFilename[0] = '\0';
	}

	char szSceneHWM[MAX_PATH];
	if ( GetHWMorphSceneFileName( szFilename, szSceneHWM ) )
	{
		V_strcpy_safe( szFilename, szSceneHWM );
	}

	if ( updateType == DATA_UPDATE_CREATED )
	{
		Assert( szFilename[ 0 ] );
		if ( szFilename[ 0 ] )
		{
			LoadSceneFromFile( szFilename );

			// Kill everything except flex events
			Assert( m_pScene );

			// Should handle gestures and sequences clientside.
			if ( m_bMultiplayer )
			{
				if ( m_pScene )
				{
					int types[6];
					types[0] = CChoreoEvent::FLEXANIMATION;
					types[1] = CChoreoEvent::EXPRESSION;
					types[2] = CChoreoEvent::GESTURE;
					types[3] = CChoreoEvent::SEQUENCE;				
					types[4] = CChoreoEvent::SPEAK;
					types[5] = CChoreoEvent::LOOP;
					m_pScene->RemoveEventsExceptTypes( types, 6 );
				}

				PrefetchAnimBlocks( m_pScene );
			}
			else
			{
				if ( m_pScene )
				{
					int types[ 2 ];
					types[ 0 ] =  CChoreoEvent::FLEXANIMATION;
					types[ 1 ] =  CChoreoEvent::EXPRESSION;
					m_pScene->RemoveEventsExceptTypes( types, 2 );
				}
			}

			SetNextClientThink( CLIENT_THINK_ALWAYS );
		}

		m_bWasPlaying = !m_bIsPlayingBack; // force it to be "changed"
	}

	// Playback state changed...
	if ( m_bWasPlaying != m_bIsPlayingBack )
	{
		for(int i = 0; i < m_hActorList.Count() ; ++i )
		{
			C_BaseFlex *actor = m_hActorList[ i ].Get();
			if ( !actor )
				continue;

			Assert( m_pScene );

			if ( m_pScene )
			{
				ClearSceneEvents( m_pScene, false );

				if ( m_bIsPlayingBack )
				{
					m_pScene->ResetSimulation();
					actor->StartChoreoScene( m_pScene );
				}
				else
				{
					m_pScene->ResetSimulation();
					actor->RemoveChoreoScene( m_pScene );
				}
			}
		}
	}
}

void C_SceneEntity::PreDataUpdate( DataUpdateType_t updateType )
{
	BaseClass::PreDataUpdate( updateType );

	m_bWasPlaying = m_bIsPlayingBack;
}

//-----------------------------------------------------------------------------
// Purpose: Called every frame that an event is active (Start/EndEvent as also
//  called)
// Input  : *event - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
void C_SceneEntity::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
{
	// For now we only need to process events if we go back in time.
	if ( currenttime < event->m_flPrevTime )
	{
		//if ( !V_strstr( scene->GetFilename(), "idleloop" ) )
		//{
		//	Msg( "ProcessEvent( %6.4f, %32s %6.4f )    %6.4f\n", currenttime, event->GetName(), event->m_flPrevTime, m_flCurrentTime );
		//}

		C_BaseFlex *pActor = NULL;
		CChoreoActor *actor = event->GetActor();
		if ( actor )
		{
			pActor = FindNamedActor( actor );
			if ( NULL == pActor )
			{
				// TODO: QueueProcessEvent
				// This can occur if we haven't been networked an actor yet... we need to queue it so that we can 
				//  fire off the process event as soon as we have the actor resident on the client.
				return;
			}
		}

		switch ( event->GetType() )
		{
		case CChoreoEvent::GESTURE:
			{
				// Verify data.
				Assert( m_bMultiplayer );
				Assert( scene != NULL );
				Assert( event != NULL );

				if ( pActor )
				{
					DispatchProcessGesture( scene, pActor, event );
				}
			}
			break;
		case CChoreoEvent::SEQUENCE:
			{
				// Verify data.
				Assert( m_bMultiplayer );
				Assert( scene != NULL );
				Assert( event != NULL );

				if ( pActor )
				{
					DispatchProcessSequence( scene, pActor, event );
				}
			}
			break;
		}
	}

	event->m_flPrevTime = currenttime;
}

//-----------------------------------------------------------------------------
// Purpose: Called for events that are part of a pause condition
// Input  : *event - 
// Output : Returns true on event completed, false on non-completion.
//-----------------------------------------------------------------------------
bool C_SceneEntity::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
{
	return true;
}

C_BaseFlex *C_SceneEntity::FindNamedActor( CChoreoActor *pChoreoActor )
{
	if ( !m_pScene )
		return NULL;

	if ( m_hOwner.Get() != NULL )
	{
		return m_hOwner.Get();
	}

	int idx = m_pScene->FindActorIndex( pChoreoActor );
	if ( idx < 0 || idx >= m_hActorList.Count() )
		return NULL;

	return m_hActorList[ idx ].Get();
}

//-----------------------------------------------------------------------------
// Purpose: All events are leading edge triggered
// Input  : currenttime - 
//			*event - 
//-----------------------------------------------------------------------------
void C_SceneEntity::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
{
	Assert( event );

	if ( !Q_stricmp( event->GetName(), "NULL" ) )
 	{
 		Scene_Printf( "%s : %8.2f:  ignored %s\n", GetSceneFileName(), currenttime, event->GetDescription() );
 		return;
 	}
 

	C_BaseFlex *pActor = NULL;
	CChoreoActor *actor = event->GetActor();
	if ( actor )
	{
		pActor = FindNamedActor( actor );
		if ( NULL == pActor )
		{
			// This can occur if we haven't been networked an actor yet... we need to queue it so that we can 
			//  fire off the start event as soon as we have the actor resident on the client.
			QueueStartEvent( currenttime, scene, event );
			return;
		}
	}

	Scene_Printf( "%s : %8.2f:  start %s\n", GetSceneFileName(), currenttime, event->GetDescription() );

	switch ( event->GetType() )
	{
	case CChoreoEvent::FLEXANIMATION:
		{
			if ( pActor )
			{
				DispatchStartFlexAnimation( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::EXPRESSION:
		{
			if ( pActor )
			{
				DispatchStartExpression( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::GESTURE:
		{
			// Verify data.
			Assert( m_bMultiplayer );
			Assert( scene != NULL );
			Assert( event != NULL );

			if ( pActor )
			{
				DispatchStartGesture( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::SEQUENCE:
		{
			// Verify data.
			Assert( m_bMultiplayer );
			Assert( scene != NULL );
			Assert( event != NULL );

			if ( pActor )
			{
				DispatchStartSequence( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::LOOP:
		{
			// Verify data.
			Assert( m_bMultiplayer );
			Assert( scene != NULL );
			Assert( event != NULL );

			DispatchProcessLoop( scene, event );
		}
	case CChoreoEvent::SPEAK:
		{
			if ( IsClientOnly() && pActor )
			{
				// FIXME: dB hack.  soundlevel needs to be moved into inside of wav?
				soundlevel_t iSoundlevel = SNDLVL_TALKING;
				if ( event->GetParameters2() )
				{
					iSoundlevel = (soundlevel_t)atoi( event->GetParameters2() );
					if ( iSoundlevel == SNDLVL_NONE )
					{
						iSoundlevel = SNDLVL_TALKING;
					}
				}

				DispatchStartSpeak( scene, pActor, event, iSoundlevel );
			}
		}
		break;
	default:
		break;
	}

	event->m_flPrevTime = currenttime;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *scene - 
//			*event - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchProcessLoop( CChoreoScene *scene, CChoreoEvent *event )
{
	Assert( event->GetType() == CChoreoEvent::LOOP );

	float backtime = (float)atof( event->GetParameters() );

	bool process = true;
	int counter = event->GetLoopCount();
	if ( counter != -1 )
	{
		int remaining = event->GetNumLoopsRemaining();
		if ( remaining <= 0 )
		{
			process = false;
		}
		else
		{
			event->SetNumLoopsRemaining( --remaining );
		}
	}

	if ( !process )
		return;

	scene->LoopToTime( backtime );
	SetCurrentTime( backtime, true );
}

//-----------------------------------------------------------------------------
// Purpose: Playback sound file that contains phonemes
// Input  : *actor - 
//			*parameters - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchStartSpeak( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event, soundlevel_t iSoundlevel )
{
	// Emit sound
	if ( IsClientOnly() && actor )
	{
		CSingleUserRecipientFilter filter( C_BasePlayer::GetLocalPlayer() );

		float time_in_past = m_flCurrentTime - event->GetStartTime() ;
		float soundtime = gpGlobals->curtime - time_in_past;

		EmitSound_t es;
		es.m_nChannel = CHAN_VOICE;
		es.m_flVolume = 1;
		es.m_SoundLevel = iSoundlevel;
		es.m_flSoundTime = soundtime;

		// No CC since we do it manually
		// FIXME:  This will  change
		es.m_bEmitCloseCaption = false;
		es.m_pSoundName = event->GetParameters();

		EmitSound( filter, actor->entindex(), es );
		actor->AddSceneEvent( scene, event, NULL, IsClientOnly() );

		// Close captioning only on master token no matter what...
		if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER )
		{
			char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ];
			bool validtoken = event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) );
			if ( validtoken )
			{
				CRC32_t tokenCRC;
				CRC32_Init( &tokenCRC );

				char lowercase[ 256 ];
				Q_strncpy( lowercase, tok, sizeof( lowercase ) );
				Q_strlower( lowercase );

				CRC32_ProcessBuffer( &tokenCRC, lowercase, Q_strlen( lowercase ) );
				CRC32_Final( &tokenCRC );

				float endtime = event->GetLastSlaveEndTime();
				float durationShort = event->GetDuration();
				float durationLong = endtime - event->GetStartTime();
				float duration = MAX( durationShort, durationLong );

				CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption );
				if ( hudCloseCaption )
				{
					hudCloseCaption->ProcessCaption( lowercase, duration );
				}
			}

		}
	}
}

void C_SceneEntity::DispatchEndSpeak( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	if ( IsClientOnly() )
	{
		actor->RemoveSceneEvent( scene, event, false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : currenttime - 
//			*event - 
//-----------------------------------------------------------------------------
void C_SceneEntity::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
{
	Assert( event );

	if ( !Q_stricmp( event->GetName(), "NULL" ) )
 	{
 		return;
 	}

	C_BaseFlex *pActor = NULL;
	CChoreoActor *actor = event->GetActor();
	if ( actor )
	{
		pActor = FindNamedActor( actor );
	}

	Scene_Printf( "%s : %8.2f:  finish %s\n", GetSceneFileName(), currenttime, event->GetDescription() );

	switch ( event->GetType() )
	{
	case CChoreoEvent::FLEXANIMATION:
		{
			if ( pActor )
			{
				DispatchEndFlexAnimation( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::EXPRESSION:
		{
			if ( pActor )
			{
				DispatchEndExpression( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::GESTURE:
		{
			if ( pActor )
			{
				DispatchEndGesture( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::SEQUENCE:
		{
			if ( pActor )
			{
				DispatchEndSequence( scene, pActor, event );
			}
		}
		break;
	case CChoreoEvent::SPEAK:
		{
			if ( IsClientOnly() && pActor )
			{
				DispatchEndSpeak( scene, pActor, event );
			}
		}
		break;
	default:
		break;
	}
}

bool CChoreoStringPool::GetString( short stringId, char *buff, int buffSize )
{
	// fetch from compiled pool
	const char *pString = scenefilecache->GetSceneString( stringId );
	if ( !pString )
	{
		V_strncpy( buff, "", buffSize );
		return false;
	}
	V_strncpy( buff, pString, buffSize );
	return true;
} 	

CChoreoStringPool g_ChoreoStringPool;

CChoreoScene *C_SceneEntity::LoadScene( const char *filename )
{
	char loadfile[ 512 ];
	Q_strncpy( loadfile, filename, sizeof( loadfile ) );
	Q_SetExtension( loadfile, ".vcd", sizeof( loadfile ) );
	Q_FixSlashes( loadfile );

	char *pBuffer = NULL;
	size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile );
	if ( bufsize <= 0 )
		return NULL;

	pBuffer = new char[ bufsize ];
	if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) )
	{
		delete[] pBuffer;
		return NULL;
	}

	CChoreoScene *pScene;
	if ( IsBufferBinaryVCD( pBuffer, bufsize ) )
	{
		pScene = new CChoreoScene( this );
		CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY );
		if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) )
		{
			Warning( "Unable to restore binary scene '%s'\n", loadfile );
			delete pScene;
			pScene = NULL;
		}
		else
		{
			pScene->SetPrintFunc( Scene_Printf );
			pScene->SetEventCallbackInterface( this );
		}
	}
	else
	{
		g_TokenProcessor.SetBuffer( pBuffer );
		pScene = ChoreoLoadScene( loadfile, this, &g_TokenProcessor, Scene_Printf );
	}

	delete[] pBuffer;
	return pScene;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *filename - 
//-----------------------------------------------------------------------------
void C_SceneEntity::LoadSceneFromFile( const char *filename )
{
	UnloadScene();
	m_pScene = LoadScene( filename );
}

void C_SceneEntity::ClearSceneEvents( CChoreoScene *scene, bool canceled )
{
	if ( !m_pScene )
		return;

	Scene_Printf( "%s : %8.2f:  clearing events\n", GetSceneFileName(), m_flCurrentTime );

	int i;
	for ( i = 0 ; i < m_pScene->GetNumActors(); i++ )
	{
		C_BaseFlex *pActor = FindNamedActor( m_pScene->GetActor( i ) );
		if ( !pActor )
			continue;

		// Clear any existing expressions
		pActor->ClearSceneEvents( scene, canceled );
	}

	WipeQueuedEvents();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_SceneEntity::UnloadScene( void )
{
	WipeQueuedEvents();

	if ( m_pScene )
	{
		ClearSceneEvents( m_pScene, false );
		for ( int i = 0 ; i < m_pScene->GetNumActors(); i++ )
		{
			C_BaseFlex *pTestActor = FindNamedActor( m_pScene->GetActor( i ) );

			if ( !pTestActor )
				continue;
		
			pTestActor->RemoveChoreoScene( m_pScene );
		}
	}
	delete m_pScene;
	m_pScene = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*event - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchStartFlexAnimation( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	actor->AddSceneEvent( scene, event, NULL, IsClientOnly() );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*event - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchEndFlexAnimation( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	actor->RemoveSceneEvent( scene, event, false );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*event - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchStartExpression( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	actor->AddSceneEvent( scene, event, NULL, IsClientOnly() );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*event - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchEndExpression( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	actor->RemoveSceneEvent( scene, event, false );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*parameters - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchStartGesture( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	// Ingore null gestures
	if ( !Q_stricmp( event->GetName(), "NULL" ) )
		return;

	actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); 
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*parameters - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchProcessGesture( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	// Ingore null gestures
	if ( !Q_stricmp( event->GetName(), "NULL" ) )
		return;

	actor->RemoveSceneEvent( scene, event, false );
	actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); 
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*parameters - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchEndGesture( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event )
{
	// Ingore null gestures
	if ( !Q_stricmp( event->GetName(), "NULL" ) )
		return;

	actor->RemoveSceneEvent( scene, event, false );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchStartSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event )
{
	actor->AddSceneEvent( scene, event, NULL, IsClientOnly() );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchProcessSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event )
{
	actor->RemoveSceneEvent( scene, event, false );
	actor->AddSceneEvent( scene, event, NULL, IsClientOnly() );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//-----------------------------------------------------------------------------
void C_SceneEntity::DispatchEndSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event )
{
	actor->RemoveSceneEvent( scene, event, false );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_SceneEntity::DoThink( float frametime )
{
	if ( !m_pScene )
		return;

	if ( !m_bIsPlayingBack )
	{
		WipeQueuedEvents();
		return;
	}

	CheckQueuedEvents();

	if ( m_bPaused )
	{
		return;
	}

	// Msg( "CL:  %d, %f for %s\n", gpGlobals->tickcount, m_flCurrentTime, m_pScene->GetFilename() );

	// Tell scene to go
	m_pScene->Think( m_flCurrentTime );
	// Drive simulation time for scene
	m_flCurrentTime += gpGlobals->frametime;
}

void C_SceneEntity::ClientThink()
{
	DoThink( gpGlobals->frametime );
}

void C_SceneEntity::CheckQueuedEvents()
{
// Check for duplicates
	CUtlVector< QueuedEvents_t > events;
	events = m_QueuedEvents;
	m_QueuedEvents.RemoveAll();

	int c = events.Count();
	for ( int i = 0; i < c; ++i )
	{
		const QueuedEvents_t& check = events[ i ];
		
		// Retry starting this event
		StartEvent( check.starttime, check.scene, check.event );
	}
}

void C_SceneEntity::WipeQueuedEvents()
{
	m_QueuedEvents.Purge();
}

void C_SceneEntity::QueueStartEvent( float starttime, CChoreoScene *scene, CChoreoEvent *event )
{
	// Check for duplicates
	int c = m_QueuedEvents.Count();
	for ( int i = 0; i < c; ++i )
	{
		const QueuedEvents_t& check = m_QueuedEvents[ i ];
		if ( check.scene == scene && 
			 check.event == event )
			return;
	}

	QueuedEvents_t qe;
	qe.scene = scene;
	qe.event = event;
	qe.starttime = starttime;
	m_QueuedEvents.AddToTail( qe );
}

//-----------------------------------------------------------------------------
// Purpose: Resets time such that the client version of the .vcd is also updated, if appropriate
// Input  : t - 
//			forceClientSync - unused for now, we may want to reenable this at some point
//-----------------------------------------------------------------------------
void C_SceneEntity::SetCurrentTime( float t, bool forceClientSync )
{
	m_flCurrentTime = t;
	m_flForceClientTime = t;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_SceneEntity::PrefetchAnimBlocks( CChoreoScene *pScene )
{
	if ( !HushAsserts() )
	{
		Assert( pScene && m_bMultiplayer );
	}
	if ( !pScene || !m_bMultiplayer )
		return;

	// Build a fast lookup, too
	CUtlMap<CChoreoActor*,CBaseFlex*> actorMap( 0, 0, DefLessFunc( CChoreoActor* ) );

	int nSpew = 0;
	int nResident = 0;
	int nChecked = 0;

	// Iterate events and precache necessary resources
	for ( int i = 0; i < pScene->GetNumEvents(); i++ )
	{
		CChoreoEvent *pEvent = pScene->GetEvent( i );
		if ( !pEvent )
			continue;

		// load any necessary data
		switch ( pEvent->GetType() )
		{
		default:
			break;
		case CChoreoEvent::SEQUENCE:
		case CChoreoEvent::GESTURE:
			{
				CChoreoActor *pActor = pEvent->GetActor();
				if ( pActor )
				{
					CBaseFlex *pFlex = NULL;
					int idx = actorMap.Find( pActor );
					if ( idx == actorMap.InvalidIndex() )
					{
						pFlex = FindNamedActor( pActor );
						idx = actorMap.Insert( pActor, pFlex );
					}
					else
					{
						pFlex = actorMap[ idx ];
					}

					if ( pFlex )
					{
						int iSequence = pFlex->LookupSequence( pEvent->GetParameters() );
						if ( iSequence >= 0 )
						{
							CStudioHdr *pStudioHdr = pFlex->GetModelPtr();
							if ( pStudioHdr )
							{
								// Now look up the animblock
								mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence );
								for ( int iGroup = 0 ; iGroup < seqdesc.groupsize[ 0 ] ; ++iGroup )
								{
									for ( int j = 0; j < seqdesc.groupsize[ 1 ]; ++j )
									{
										int iAnimation = seqdesc.anim( iGroup, j );
										int iBaseAnimation = pStudioHdr->iRelativeAnim( iSequence, iAnimation );
										mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( iBaseAnimation );

										++nChecked;

										if ( nSpew != 0 )
										{
											Msg( "%s checking block %d\n", pStudioHdr->pszName(), animdesc.animblock );
										}

										// Async load the animation
										int iFrame = 0;
										const mstudioanim_t *panim = animdesc.pAnim( &iFrame );
										if ( panim )
										{
											++nResident;
											if ( nSpew > 1 )
											{
												Msg( "%s:%s[%i:%i] was resident\n", pStudioHdr->pszName(), animdesc.pszName(), iGroup, j );
											}
										}
										else
										{
											if ( nSpew != 0 )
											{
												Msg( "%s:%s[%i:%i] async load\n", pStudioHdr->pszName(), animdesc.pszName(), iGroup, j );
											}
										}
									}
								}
							}
						}
					}
				}
				break;
			}
		}
	}

	if ( !nSpew || nChecked <= 0 )
		return;

	Msg( "%d of %d animations resident\n", nResident, nChecked );
}