//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "filesystem.h"
#include "sentence.h"
#include "hud_closecaption.h"
#include "engine/ivmodelinfo.h"
#include "engine/ivdebugoverlay.h"
#include "bone_setup.h"
#include "soundinfo.h"
#include "tools/bonelist.h"
#include "KeyValues.h"
#include "tier0/vprof.h"
#include "toolframework/itoolframework.h"
#include "choreoevent.h"
#include "choreoscene.h"
#include "choreoactor.h"
#include "toolframework_client.h"

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

bool UseHWMorphVCDs();

ConVar g_CV_PhonemeDelay("phonemedelay", "0", 0, "Phoneme delay to account for sound system latency." );
ConVar g_CV_PhonemeFilter("phonemefilter", "0.08", 0, "Time duration of box filter to pass over phonemes." );
ConVar g_CV_FlexRules("flex_rules", "1", 0, "Allow flex animation rules to run." );
ConVar g_CV_BlinkDuration("blink_duration", "0.2", 0, "How many seconds an eye blink will last." );
ConVar g_CV_FlexSmooth("flex_smooth", "1", 0, "Applies smoothing/decay curve to flex animation controller changes." );

#if defined( CBaseFlex )
#undef CBaseFlex
#endif

IMPLEMENT_CLIENTCLASS_DT(C_BaseFlex, DT_BaseFlex, CBaseFlex)
	RecvPropArray3( RECVINFO_ARRAY(m_flexWeight), RecvPropFloat(RECVINFO(m_flexWeight[0]))),
	RecvPropInt(RECVINFO(m_blinktoggle)),
	RecvPropVector(RECVINFO(m_viewtarget)),

#ifdef HL2_CLIENT_DLL
	RecvPropFloat( RECVINFO(m_vecViewOffset[0]) ),
	RecvPropFloat( RECVINFO(m_vecViewOffset[1]) ),
	RecvPropFloat( RECVINFO(m_vecViewOffset[2]) ),

	RecvPropVector(RECVINFO(m_vecLean)),
	RecvPropVector(RECVINFO(m_vecShift)),
#endif

END_RECV_TABLE()

BEGIN_PREDICTION_DATA( C_BaseFlex )

/*
	// DEFINE_FIELD( C_BaseFlex, m_viewtarget, FIELD_VECTOR ),
	// DEFINE_ARRAY( C_BaseFlex, m_flexWeight, FIELD_FLOAT, 64 ),
	// DEFINE_FIELD( C_BaseFlex, m_blinktoggle, FIELD_INTEGER ),
	// DEFINE_FIELD( C_BaseFlex, m_blinktime, FIELD_FLOAT ),
	// DEFINE_FIELD( C_BaseFlex, m_prevviewtarget, FIELD_VECTOR ),
	// DEFINE_ARRAY( C_BaseFlex, m_prevflexWeight, FIELD_FLOAT, 64 ),
	// DEFINE_FIELD( C_BaseFlex, m_prevblinktoggle, FIELD_INTEGER ),
	// DEFINE_FIELD( C_BaseFlex, m_iBlink, FIELD_INTEGER ),
	// DEFINE_FIELD( C_BaseFlex, m_iEyeUpdown, FIELD_INTEGER ),
	// DEFINE_FIELD( C_BaseFlex, m_iEyeRightleft, FIELD_INTEGER ),
	// DEFINE_FIELD( C_BaseFlex, m_FileList, CUtlVector < CFlexSceneFile * > ),
*/

END_PREDICTION_DATA()

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool GetHWMExpressionFileName( const char *pFilename, char *pHWMFilename )
{
	// Are we even using hardware morph?
	if ( !UseHWMorphVCDs() )
		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, "player/hwm" ) != NULL ) || ( V_strstr( pFilename, "player\\hwm" ) != NULL ) )
	{
		V_strcpy( pHWMFilename, pFilename );
		return true;
	}

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

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

	char *pszToken = strtok( szExpression, "/\\" );
	while ( pszToken != NULL )
	{
		V_strcat( szExpressionHWM, pszToken, sizeof( szExpressionHWM ) );
		if ( !V_stricmp( pszToken, "player" ) )
		{
			V_strcat( szExpressionHWM, "\\hwm", sizeof( szExpressionHWM ) );
		}

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

	V_strcpy( pHWMFilename, szExpressionHWM );
	return true;
}

C_BaseFlex::C_BaseFlex() : 
	m_iv_viewtarget( "C_BaseFlex::m_iv_viewtarget" ), 
	m_iv_flexWeight("C_BaseFlex:m_iv_flexWeight" ),
#ifdef HL2_CLIENT_DLL
	m_iv_vecLean("C_BaseFlex:m_iv_vecLean" ),
	m_iv_vecShift("C_BaseFlex:m_iv_vecShift" ),
#endif
	m_LocalToGlobal( 0, 0, FlexSettingLessFunc )
{
#ifdef _DEBUG
	((Vector&)m_viewtarget).Init();
#endif

	AddVar( &m_viewtarget, &m_iv_viewtarget, LATCH_ANIMATION_VAR | INTERPOLATE_LINEAR_ONLY );
	AddVar( m_flexWeight, &m_iv_flexWeight, LATCH_ANIMATION_VAR );

	// Fill in phoneme class lookup
	SetupMappings( "phonemes" );

	m_flFlexDelayedWeight = NULL;
	m_cFlexDelayedWeight = 0;

	/// Make sure size is correct
	Assert( PHONEME_CLASS_STRONG + 1 == NUM_PHONEME_CLASSES );

#ifdef HL2_CLIENT_DLL
	// Get general lean vector
	AddVar( &m_vecLean, &m_iv_vecLean, LATCH_ANIMATION_VAR );
	AddVar( &m_vecShift, &m_iv_vecShift, LATCH_ANIMATION_VAR );
#endif
}

C_BaseFlex::~C_BaseFlex()
{
	delete[] m_flFlexDelayedWeight;
	m_SceneEvents.RemoveAll();
	m_LocalToGlobal.RemoveAll();
}


void C_BaseFlex::Spawn()
{
	BaseClass::Spawn();

	InitPhonemeMappings();
}

// TF Player overrides all of these with class specific files
void C_BaseFlex::InitPhonemeMappings()
{
	SetupMappings( "phonemes" );
}

void C_BaseFlex::SetupMappings( char const *pchFileRoot )
{
	// Fill in phoneme class lookup
	memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) );

	Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ];
	Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot );
	normal->required = true;

	Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ];
	Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot );
	Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ];
	Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot );
}

//-----------------------------------------------------------------------------
// Purpose: initialize fast lookups when model changes
//-----------------------------------------------------------------------------

CStudioHdr *C_BaseFlex::OnNewModel()
{
	CStudioHdr *hdr = BaseClass::OnNewModel();
	
	// init to invalid setting
	m_iBlink = -1;
	m_iEyeUpdown = LocalFlexController_t(-1);
	m_iEyeRightleft = LocalFlexController_t(-1);
	m_bSearchedForEyeFlexes = false;
	m_iMouthAttachment = 0;

	delete[] m_flFlexDelayedWeight;
	m_flFlexDelayedWeight = NULL;
	m_cFlexDelayedWeight = 0;

	if (hdr)
	{
		if (hdr->numflexdesc())
		{
			m_cFlexDelayedWeight = hdr->numflexdesc();
			m_flFlexDelayedWeight = new float[ m_cFlexDelayedWeight ];
			memset( m_flFlexDelayedWeight, 0, sizeof( float ) * m_cFlexDelayedWeight );
		}

		m_iv_flexWeight.SetMaxCount( hdr->numflexcontrollers() );

		m_iMouthAttachment = LookupAttachment( "mouth" );

		LinkToGlobalFlexControllers( hdr );
	}

	return hdr;
}


void C_BaseFlex::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask )
{
	BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask );

#ifdef HL2_CLIENT_DLL
	// shift pelvis, rotate body
	if (hdr->GetNumIKChains() != 0 && (m_vecShift.x != 0.0 || m_vecShift.y != 0.0))
	{
		//CIKContext auto_ik;
		//auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, boneMask );
		//auto_ik.AddAllLocks( pos, q );

		matrix3x4_t rootxform;
		AngleMatrix( GetRenderAngles(), GetRenderOrigin(), rootxform );

		Vector localShift;
		VectorIRotate( m_vecShift, rootxform, localShift );
		Vector localLean;
		VectorIRotate( m_vecLean, rootxform, localLean );

		Vector p0 = pos[0];
		float length = VectorNormalize( p0 );

		// shift the root bone, but keep the height off the origin the same
		Vector shiftPos = pos[0] + localShift;
		VectorNormalize( shiftPos );
		Vector leanPos = pos[0] + localLean;
		VectorNormalize( leanPos );
		pos[0] = shiftPos * length;

		// rotate the root bone based on how much it was "leaned"
		Vector p1;
		CrossProduct( p0, leanPos, p1 );
		float sinAngle = VectorNormalize( p1 );
		float cosAngle = DotProduct( p0, leanPos );
		float angle = atan2( sinAngle, cosAngle ) * 180 / M_PI;
		Quaternion q1;
		angle = clamp( angle, -45, 45 );
		AxisAngleQuaternion( p1, angle, q1 );
		QuaternionMult( q1, q[0], q[0] );
		QuaternionNormalize( q[0] );

		// DevMsgRT( "   (%.2f) %.2f %.2f %.2f\n", angle, p1.x, p1.y, p1.z );
		// auto_ik.SolveAllLocks( pos, q );
	}
#endif
}



//-----------------------------------------------------------------------------
// Purpose: place "voice" sounds on mouth
//-----------------------------------------------------------------------------

bool C_BaseFlex::GetSoundSpatialization( SpatializationInfo_t& info )
{
	bool bret = BaseClass::GetSoundSpatialization( info );
	// Default things it's audible, put it at a better spot?
	if ( bret )
	{
		if ((info.info.nChannel == CHAN_VOICE || info.info.nChannel == CHAN_VOICE2) && m_iMouthAttachment > 0)
		{
			Vector origin;
			QAngle angles;
			
			C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false );

			if (GetAttachment( m_iMouthAttachment, origin, angles ))
			{
				if (info.pOrigin)
				{
					*info.pOrigin = origin;
				}

				if (info.pAngles)
				{
					*info.pAngles = angles;
				}
			}
		}
	}

	return bret;
}


//-----------------------------------------------------------------------------
// Purpose: run the interpreted FAC's expressions, converting global flex_controller 
//			values into FAC weights
//-----------------------------------------------------------------------------
void C_BaseFlex::RunFlexRules( CStudioHdr *hdr, float *dest )
{
	if ( !g_CV_FlexRules.GetInt() )
		return;

	if ( !hdr )
		return;

/*
	// 0 means run them all
	int nFlexRulesToRun = 0;

	const char *pszExpression = flex_expression.GetString();
	if ( pszExpression )
	{
		nFlexRulesToRun = atoi(pszExpression); // 0 will be returned if not a numeric string
	}
//*/

	hdr->RunFlexRules( g_flexweight, dest );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CFlexSceneFileManager::Init()
{
	// Trakcer 16692:  Preload these at startup to avoid hitch first time we try to load them during actual gameplay
	FindSceneFile( NULL, "phonemes", true );
	FindSceneFile( NULL, "phonemes_weak", true );
	FindSceneFile(NULL,  "phonemes_strong", true );

#if defined( HL2_CLIENT_DLL )
	FindSceneFile( NULL, "random", true );
	FindSceneFile( NULL, "randomAlert", true );
#endif

#if defined( TF_CLIENT_DLL )
	// HACK TO ALL TF TO HAVE PER CLASS OVERRIDES
	char const *pTFClasses[] = 
	{
		"scout",
		"sniper",
		"soldier",
		"demo",
		"medic",
		"heavy",
		"pyro",
		"spy",
		"engineer",
	};

	char fn[ MAX_PATH ];
	for ( int i = 0; i < ARRAYSIZE( pTFClasses ); ++i )
	{
		Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes", pTFClasses[i] );
		FindSceneFile( NULL, fn, true );
		Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_weak", pTFClasses[i] );
		FindSceneFile( NULL, fn, true );
		Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_strong", pTFClasses[i] );
		FindSceneFile( NULL, fn, true );

		if ( !IsX360() )
		{
			Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes", pTFClasses[i] );
			FindSceneFile( NULL, fn, true );
			Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_weak", pTFClasses[i] );
			FindSceneFile( NULL, fn, true );
			Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_strong", pTFClasses[i] );
			FindSceneFile( NULL, fn, true );
		}

		Q_snprintf( fn, sizeof( fn ), "player/%s/emotion/emotion", pTFClasses[i] );
		FindSceneFile( NULL, fn, true );
		if ( !IsX360() )
		{
			Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/emotion/emotion", pTFClasses[i] );
			FindSceneFile( NULL, fn, true );
		}
	}
#endif

	return true;
}

//-----------------------------------------------------------------------------
// Tracker 14992:  We used to load 18K of .vfes for every C_BaseFlex who lipsynced, but now we only load those files once globally.
// Note, we could wipe these between levels, but they don't ever load more than the weak/normal/strong phoneme classes that I can tell
//  so I'll just leave them loaded forever for now
//-----------------------------------------------------------------------------
void CFlexSceneFileManager::Shutdown()
{
	DeleteSceneFiles();
}

//-----------------------------------------------------------------------------
// Purpose: Sets up translations
// Input  : *instance - 
//			*pSettinghdr - 
// Output : 	void
//-----------------------------------------------------------------------------
void CFlexSceneFileManager::EnsureTranslations( IHasLocalToGlobalFlexSettings *instance, const flexsettinghdr_t *pSettinghdr )
{
	// The only time instance is NULL is in Init() above, where we're just loading the .vfe files off of the hard disk.
	if ( instance )
	{
		instance->EnsureTranslations( pSettinghdr );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void *CFlexSceneFileManager::FindSceneFile( IHasLocalToGlobalFlexSettings *instance, const char *filename, bool allowBlockingIO )
{
	char szFilename[MAX_PATH];
	Assert( V_strlen( filename ) < MAX_PATH );
	V_strcpy( szFilename, filename );
	
#if defined( TF_CLIENT_DLL )	
	char szHWMFilename[MAX_PATH];
	if ( GetHWMExpressionFileName( szFilename, szHWMFilename ) )
	{
		V_strcpy( szFilename, szHWMFilename );
	}
#endif

	Q_FixSlashes( szFilename );

	// See if it's already loaded
	int i;
	for ( i = 0; i < m_FileList.Count(); i++ )
	{
		CFlexSceneFile *file = m_FileList[ i ];
		if ( file && !Q_stricmp( file->filename, szFilename ) )
		{
			// Make sure translations (local to global flex controller) are set up for this instance
			EnsureTranslations( instance, ( const flexsettinghdr_t * )file->buffer );
			return file->buffer;
		}
	}
	
	if ( !allowBlockingIO )
	{
		return NULL;
	}

	// Load file into memory
	void *buffer = NULL;
	int len = filesystem->ReadFileEx( VarArgs( "expressions/%s.vfe", szFilename ), "GAME", &buffer );

	if ( !len )
		return NULL;

	// Create scene entry
	CFlexSceneFile *pfile = new CFlexSceneFile;
	// Remember filename
	Q_strncpy( pfile->filename, szFilename, sizeof( pfile->filename ) );
	// Remember data pointer
	pfile->buffer = buffer;
	// Add to list
	m_FileList.AddToTail( pfile );

	// Swap the entire file
	if ( IsX360() )
	{
		CByteswap swap;
		swap.ActivateByteSwapping( true );
		byte *pData = (byte*)buffer;
		flexsettinghdr_t *pHdr = (flexsettinghdr_t*)pData;
		swap.SwapFieldsToTargetEndian( pHdr );

		// Flex Settings
		flexsetting_t *pFlexSetting = (flexsetting_t*)((byte*)pHdr + pHdr->flexsettingindex);
		for ( int i = 0; i < pHdr->numflexsettings; ++i, ++pFlexSetting )
		{
			swap.SwapFieldsToTargetEndian( pFlexSetting );
			
			flexweight_t *pWeight = (flexweight_t*)(((byte*)pFlexSetting) + pFlexSetting->settingindex );
			for ( int j = 0; j < pFlexSetting->numsettings; ++j, ++pWeight )
			{
				swap.SwapFieldsToTargetEndian( pWeight );
			}
		}

		// indexes
		pData = (byte*)pHdr + pHdr->indexindex;
		swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numindexes );

		// keymappings
		pData  = (byte*)pHdr + pHdr->keymappingindex;
		swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys );

		// keyname indices
		pData = (byte*)pHdr + pHdr->keynameindex;
		swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys );
	}

	// Fill in translation table
	EnsureTranslations( instance, ( const flexsettinghdr_t * )pfile->buffer );

	// Return data
	return pfile->buffer;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFlexSceneFileManager::DeleteSceneFiles()
{
	while ( m_FileList.Count() > 0 )
	{
		CFlexSceneFile *file = m_FileList[ 0 ];
		m_FileList.Remove( 0 );
		free( file->buffer );
		delete file;
	}
}

CFlexSceneFileManager g_FlexSceneFileManager;

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *filename - 
//-----------------------------------------------------------------------------
void *C_BaseFlex::FindSceneFile( const char *filename )
{
	return g_FlexSceneFileManager.FindSceneFile( this, filename, false );
}

//-----------------------------------------------------------------------------
// Purpose: make sure the eyes are within 30 degrees of forward
//-----------------------------------------------------------------------------
Vector C_BaseFlex::SetViewTarget( CStudioHdr *pStudioHdr )
{
  	if ( !pStudioHdr )
  		return Vector( 0, 0, 0);

	// aim the eyes
	Vector tmp = m_viewtarget;

	if ( !m_bSearchedForEyeFlexes )
	{
		m_bSearchedForEyeFlexes = true;

		m_iEyeUpdown = FindFlexController( "eyes_updown" );
		m_iEyeRightleft = FindFlexController( "eyes_rightleft" );

		if ( m_iEyeUpdown != -1 )
		{
			pStudioHdr->pFlexcontroller( m_iEyeUpdown )->localToGlobal = AddGlobalFlexController( "eyes_updown" );
		}
		if ( m_iEyeRightleft != -1 )
		{
			pStudioHdr->pFlexcontroller( m_iEyeRightleft )->localToGlobal = AddGlobalFlexController( "eyes_rightleft" );
		}
	}

	if (m_iEyeAttachment > 0)
	{
		matrix3x4_t attToWorld;
		if (!GetAttachment( m_iEyeAttachment, attToWorld ))
		{
			return Vector( 0, 0, 0);
		}

		Vector local;
		VectorITransform( tmp, attToWorld, local );
		
		// FIXME: clamp distance to something based on eyeball distance
		if (local.x < 6)
		{
			local.x = 6;
		}
		float flDist = local.Length();
		VectorNormalize( local );

		// calculate animated eye deflection
		Vector eyeDeflect;
		QAngle eyeAng( 0, 0, 0 );
		if ( m_iEyeUpdown != -1 )
		{
			mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeUpdown );
			eyeAng.x = g_flexweight[ pflex->localToGlobal ];
		}
		
		if ( m_iEyeRightleft != -1 )
		{
			mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeRightleft );
			eyeAng.y = g_flexweight[ pflex->localToGlobal ];
		}

		// debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%5.3f %5.3f", eyeAng.x, eyeAng.y );

		AngleVectors( eyeAng, &eyeDeflect );
		eyeDeflect.x = 0;

		// reduce deflection the more the eye is off center
		// FIXME: this angles make no damn sense
		eyeDeflect = eyeDeflect * (local.x * local.x);
		local = local + eyeDeflect;
		VectorNormalize( local );

		// check to see if the eye is aiming outside the max eye deflection
		float flMaxEyeDeflection = pStudioHdr->MaxEyeDeflection();
		if ( local.x < flMaxEyeDeflection )
		{
			// if so, clamp it to 30 degrees offset
			// debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 1, 0, "%5.3f %5.3f %5.3f", local.x, local.y, local.z );
			local.x = 0;
			float d = local.LengthSqr();
			if ( d > 0.0f )
			{
				d = sqrtf( ( 1.0f - flMaxEyeDeflection * flMaxEyeDeflection ) / ( local.y*local.y + local.z*local.z ) );
				local.x = flMaxEyeDeflection;
				local.y = local.y * d;
				local.z = local.z * d;
			}
			else
			{
				local.x = 1.0;
			}
		}
		local = local * flDist;
		VectorTransform( local, attToWorld, tmp );
	}

	modelrender->SetViewTarget( GetModelPtr(), GetBody(), tmp );

	/*
	debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%.2f %.2f %.2f  : %.2f %.2f %.2f", 
		m_viewtarget.x, m_viewtarget.y, m_viewtarget.z, 
		m_prevviewtarget.x, m_prevviewtarget.y, m_prevviewtarget.z );
	*/

	return tmp;
}

#define STRONG_CROSSFADE_START		0.60f
#define WEAK_CROSSFADE_START		0.40f

//-----------------------------------------------------------------------------
// Purpose: 
// Here's the formula
// 0.5 is neutral 100 % of the default setting
// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END
// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START
//  so we don't get huge numbers
// Input  : *classes - 
//			emphasis_intensity - 
//-----------------------------------------------------------------------------
void C_BaseFlex::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity )
{
	// See which blends are available for the current phoneme
	bool has_weak	= classes[ PHONEME_CLASS_WEAK ].valid;
	bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid;

	// Better have phonemes in general
	Assert( classes[ PHONEME_CLASS_NORMAL ].valid );

	if ( emphasis_intensity > STRONG_CROSSFADE_START )
	{
		if ( has_strong )
		{
			// Blend in some of strong
			float dist_remaining = 1.0f - emphasis_intensity;
			float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START );

			classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START;
			classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac; 
		}
		else
		{
			emphasis_intensity = MIN( emphasis_intensity, STRONG_CROSSFADE_START );
			classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
		}
	}
	else if ( emphasis_intensity < WEAK_CROSSFADE_START )
	{
		if ( has_weak )
		{
			// Blend in some weak
			float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity;
			float frac = dist_remaining / ( WEAK_CROSSFADE_START );

			classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START;
			classes[ PHONEME_CLASS_WEAK ].amount = frac; 
		}
		else
		{
			emphasis_intensity = MAX( emphasis_intensity, WEAK_CROSSFADE_START );
			classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
		}
	}
	else
	{
		// Assume 0.5 (neutral) becomes a scaling of 1.0f
		classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *classes - 
//			phoneme - 
//			scale - 
//			newexpression - 
//-----------------------------------------------------------------------------
void C_BaseFlex::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression )
{
	int type;

	// Setup weights for any emphasis blends
	bool skip = SetupEmphasisBlend( classes, phoneme );
	// Uh-oh, missing or unknown phoneme???
	if ( skip )
	{
		return;
	}
		
	// Compute blend weights
	ComputeBlendedSetting( classes, emphasis_intensity );

	for ( type = 0; type < NUM_PHONEME_CLASSES; type++ )
	{
		Emphasized_Phoneme *info = &classes[ type ];
		if ( !info->valid || info->amount == 0.0f )
			continue;

		const flexsettinghdr_t *actual_flexsetting_header = info->base;
		const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme );
		if (!pSetting)
		{
			continue;
		}

		flexweight_t *pWeights = NULL;

		int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights );
		if ( pWeights )
		{
			for ( int i = 0; i < truecount; i++)
			{
				// Translate to global controller number
				int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key );
				// Add scaled weighting in
				g_flexweight[j] += info->amount * scale * pWeights->weight;
				// Go to next setting
				pWeights++;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: A lot of the one time setup and also resets amount to 0.0f default
//  for strong/weak/normal tracks
// Returning true == skip this phoneme
// Input  : *classes - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_BaseFlex::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme )
{
	int i;

	bool skip = false;

	for ( i = 0; i < NUM_PHONEME_CLASSES; i++ )
	{
		Emphasized_Phoneme *info = &classes[ i ];

		// Assume it's bogus
		info->valid = false;
		info->amount = 0.0f;

		// One time setup
		if ( !info->basechecked )
		{
			info->basechecked = true;
			info->base = (flexsettinghdr_t *)FindSceneFile( info->classname );
		}
		info->exp = NULL;
		if ( info->base )
		{
			Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') );
			info->exp = info->base->pIndexedSetting( phoneme );
		}

		if ( info->required && ( !info->base || !info->exp ) )
		{
			skip = true;
			break;
		}

		if ( info->exp )
		{
			info->valid = true;
		}
	}
	
	return skip;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *classes - 
//			*sentence - 
//			t - 
//			dt - 
//			juststarted - 
//-----------------------------------------------------------------------------
ConVar g_CV_PhonemeSnap("phonemesnap", "2", 0, "Lod at level at which visemes stops always considering two phonemes, regardless of duration." );
void C_BaseFlex::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted )
{
	CStudioHdr *hdr = GetModelPtr();
	if ( !hdr )
	{
		return;
	}

	int pcount = sentence->GetRuntimePhonemeCount();
	for ( int k = 0; k < pcount; k++ )
	{
		const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k );

		if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime())
		{
			bool bCrossfade = true;
			if ((hdr->flags() & STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE) == 0)
			{
				if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD0)
				{
					bCrossfade = (g_CV_PhonemeSnap.GetInt() > 0);
				}
				else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD1)
				{
					bCrossfade = (g_CV_PhonemeSnap.GetInt() > 1);
				}
				else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD2)
				{
					bCrossfade = (g_CV_PhonemeSnap.GetInt() > 2);
				}
				else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD3)
				{
					bCrossfade = (g_CV_PhonemeSnap.GetInt() > 3);
				}
				else
				{
					bCrossfade = false;
				}
			}

			if (bCrossfade)
			{
				if (k < pcount-1)
				{
					const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 );
					// if I have a neighbor
					if ( next )
					{
						//  and they're touching
						if (next->GetStartTime() == phoneme->GetEndTime() )
						{
							// no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme
							dt = MAX( dt, MIN( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
						}
						else
						{
							// dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme
							dt = MAX( dt, MIN( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
						}
					}
					else
					{
						// last phoneme in list, increase the blend length to the length of the current phoneme
						dt = MAX( dt, phoneme->GetEndTime() - phoneme->GetStartTime() );
					}
				}
			}
		}

		float t1 = ( phoneme->GetStartTime() - t) / dt;
		float t2 = ( phoneme->GetEndTime() - t) / dt;

		if (t1 < 1.0 && t2 > 0)
		{
			float scale;

			// clamp
			if (t2 > 1)
				t2 = 1;
			if (t1 < 0)
				t1 = 0;

			// FIXME: simple box filter.  Should use something fancier
			scale = (t2 - t1);

			AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *classes - 
//-----------------------------------------------------------------------------
void C_BaseFlex::ProcessVisemes( Emphasized_Phoneme *classes )
{
	// Any sounds being played?
	if ( !MouthInfo().IsActive() )
		return;

	// Multiple phoneme tracks can overlap, look across all such tracks.
	for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ )
	{
		CVoiceData *vd = MouthInfo().GetVoiceSource( source );
		if ( !vd || vd->ShouldIgnorePhonemes() )
			continue;

		CSentence *sentence = engine->GetSentence( vd->GetSource() );
		if ( !sentence )
			continue;

		float	sentence_length = engine->GetSentenceLength( vd->GetSource() );
		float	timesincestart = vd->GetElapsedTime();

		// This sound should be done...why hasn't it been removed yet???
		if ( timesincestart >= ( sentence_length + 2.0f ) )
			continue;

		// Adjust actual time
		float t = timesincestart - g_CV_PhonemeDelay.GetFloat();

		// Get box filter duration
		float dt = g_CV_PhonemeFilter.GetFloat();

		// Streaming sounds get an additional delay...
		/*
		// Tracker 20534:  Probably not needed any more with the async sound stuff that
		//  we now have (we don't have a disk i/o hitch on startup which might have been
		//  messing up the startup timing a bit )
		bool streaming = engine->IsStreaming( vd->m_pAudioSource );
		if ( streaming )
		{
			t -= g_CV_PhonemeDelayStreaming.GetFloat();
		}
		*/

		// Assume sound has been playing for a while...
		bool juststarted = false;

		// Get intensity setting for this time (from spline)
		float emphasis_intensity = sentence->GetIntensity( t, sentence_length );

		// Blend and add visemes together
		AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted );
	}
}

//-----------------------------------------------------------------------------
// Purpose: fill keyvalues message with flex state
// Input  :
//-----------------------------------------------------------------------------
void C_BaseFlex::GetToolRecordingState( KeyValues *msg )
{
	if ( !ToolsEnabled() )
		return;

	VPROF_BUDGET( "C_BaseFlex::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS );

	BaseClass::GetToolRecordingState( msg );

	CStudioHdr *hdr = GetModelPtr();
	if ( !hdr )
		return;

	memset( g_flexweight, 0, sizeof( g_flexweight ) );

	if ( hdr->numflexcontrollers() == 0 )
		return;

	LocalFlexController_t i;

	ProcessSceneEvents( true );

	// FIXME: shouldn't this happen at runtime?
	// initialize the models local to global flex controller mappings
	if (hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1)
	{
		for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
		{
			int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() );
			hdr->pFlexcontroller( i )->localToGlobal = j;
		}
	}

	// blend weights from server
	for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
	{
		mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );

		g_flexweight[pflex->localToGlobal] = m_flexWeight[i];
		// rescale
		g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min;
	}

	ProcessSceneEvents( false );

	// check for blinking
	if (m_blinktoggle != m_prevblinktoggle)
	{
		m_prevblinktoggle = m_blinktoggle;
		m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat();
	}

	if (m_iBlink == -1)
		m_iBlink = AddGlobalFlexController( "blink" );
	g_flexweight[m_iBlink] = 0;

	// FIXME: this needs a better algorithm
	// blink the eyes
	float t = (m_blinktime - gpGlobals->curtime) * M_PI * 0.5 * (1.0/g_CV_BlinkDuration.GetFloat());
	if (t > 0)
	{
		// do eyeblink falloff curve
		t = cos(t);
		if (t > 0)
		{
			g_flexweight[m_iBlink] = sqrtf( t ) * 2;
			if (g_flexweight[m_iBlink] > 1)
				g_flexweight[m_iBlink] = 2.0 - g_flexweight[m_iBlink];
		}
	}

	// Drive the mouth from .wav file playback...
	ProcessVisemes( m_PhonemeClasses );

	// Necessary???
	SetViewTarget( hdr );

	Vector viewtarget = m_viewtarget; // Use the unfiltered value

	// HACK HACK: Unmap eyes right/left amounts
	if (m_iEyeUpdown != -1 && m_iEyeRightleft != -1)
	{
		mstudioflexcontroller_t *flexupdown = hdr->pFlexcontroller( m_iEyeUpdown );
		mstudioflexcontroller_t *flexrightleft = hdr->pFlexcontroller( m_iEyeRightleft );

		if ( flexupdown->localToGlobal != -1 && flexrightleft->localToGlobal != -1 )
		{
			float updown = g_flexweight[ flexupdown->localToGlobal ];
			float rightleft = g_flexweight[ flexrightleft->localToGlobal ];

			if ( flexupdown->min != flexupdown->max )
			{
				updown = RemapVal( updown, flexupdown->min, flexupdown->max, 0.0f, 1.0f );
			}
			if ( flexrightleft->min != flexrightleft->max )
			{
				rightleft = RemapVal( rightleft, flexrightleft->min, flexrightleft->max, 0.0f, 1.0f );
			}
	
			g_flexweight[ flexupdown->localToGlobal ] = updown;
			g_flexweight[ flexrightleft->localToGlobal ] = rightleft;
		}
	}

	// Convert back to normalized weights
	for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
	{
		mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );

		// rescale
		if ( pflex->max != pflex->min )
		{
			g_flexweight[pflex->localToGlobal] = ( g_flexweight[pflex->localToGlobal] - pflex->min ) / ( pflex->max - pflex->min );
		}
	}

	static BaseFlexRecordingState_t state;
	state.m_nFlexCount = MAXSTUDIOFLEXCTRL;
	state.m_pDestWeight = g_flexweight;
	state.m_vecViewTarget = viewtarget;
	msg->SetPtr( "baseflex", &state );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseFlex::OnThreadedDrawSetup()
{
	if (m_iEyeAttachment < 0)
		return;

	CStudioHdr *hdr = GetModelPtr();
	if ( !hdr )
	{
		return;
	}
	CalcAttachments();
}


//-----------------------------------------------------------------------------
// Should we use delayed flex weights?
//-----------------------------------------------------------------------------
bool C_BaseFlex::UsesFlexDelayedWeights()
{
	return ( m_flFlexDelayedWeight && g_CV_FlexSmooth.GetBool() );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseFlex::LinkToGlobalFlexControllers( CStudioHdr *hdr )
{
	if ( hdr && hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1 )
	{
		for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
		{
			int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() );
			hdr->pFlexcontroller( i )->localToGlobal = j;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Rendering callback to allow the client to set up all the model specific flex weights
//-----------------------------------------------------------------------------
void C_BaseFlex::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
{
	// hack in an initialization
	LinkToGlobalFlexControllers( GetModelPtr() );
#ifndef MAPBASE
	m_iBlink = AddGlobalFlexController( "UH" );
#endif

	if ( SetupGlobalWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ) )
	{
		SetupLocalWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Use the local bone positions to set flex control weights
//          via boneflexdrivers specified in the model
//-----------------------------------------------------------------------------
void C_BaseFlex::BuildTransformations( CStudioHdr *pStudioHdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
{
	const int nBoneFlexDriverCount = pStudioHdr->BoneFlexDriverCount();

	for ( int i = 0; i < nBoneFlexDriverCount; ++i )
	{
		const mstudioboneflexdriver_t *pBoneFlexDriver = pStudioHdr->BoneFlexDriver( i );
		const Vector &position = pos[ pBoneFlexDriver->m_nBoneIndex ];

		const int nControllerCount = pBoneFlexDriver->m_nControlCount;
		for ( int j = 0; j < nControllerCount; ++j )
		{
			const mstudioboneflexdrivercontrol_t *pController = pBoneFlexDriver->pBoneFlexDriverControl( j );
			Assert( pController->m_nFlexControllerIndex >= 0 && pController->m_nFlexControllerIndex < pStudioHdr->numflexcontrollers() );
			Assert( pController->m_nBoneComponent >= 0 && pController->m_nBoneComponent <= 2 );
			SetFlexWeight( static_cast< LocalFlexController_t >( pController->m_nFlexControllerIndex ), RemapValClamped( position[pController->m_nBoneComponent], pController->m_flMin, pController->m_flMax, 0.0f, 1.0f ) );
		}
	}

	BaseClass::BuildTransformations( pStudioHdr, pos, q, cameraTransform, boneMask, boneComputed );
}


//-----------------------------------------------------------------------------
// Purpose: process the entities networked state, vcd playback, wav file visemes, and blinks into a global shared flex controller array
//-----------------------------------------------------------------------------
bool C_BaseFlex::SetupGlobalWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
{
	CStudioHdr *hdr = GetModelPtr();
	if ( !hdr )
		return false;

	memset( g_flexweight, 0, sizeof(g_flexweight) );

	// FIXME: this should assert then, it's too complex a class for the model
	if ( hdr->numflexcontrollers() == 0 )
	{
		int nSizeInBytes = nFlexWeightCount * sizeof( float );
		memset( pFlexWeights, 0, nSizeInBytes );
		if ( pFlexDelayedWeights )
		{
			memset( pFlexDelayedWeights, 0, nSizeInBytes );
		}
		return false;
	}

	LocalFlexController_t i;

	ProcessSceneEvents( true );

	Assert( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 );

	// get the networked flexweights and convert them from 0..1 to real dynamic range
	for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
	{
		mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );

		g_flexweight[pflex->localToGlobal] = m_flexWeight[i];
		// rescale
		g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min;
	}

	ProcessSceneEvents( false );

	// check for blinking
	if (m_blinktoggle != m_prevblinktoggle)
	{
		m_prevblinktoggle = m_blinktoggle;
		m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat();
	}

	if (m_iBlink == -1)
	{
		m_iBlink = AddGlobalFlexController( "blink" );
	}

	// FIXME: this needs a better algorithm
	// blink the eyes
	float flBlinkDuration = g_CV_BlinkDuration.GetFloat();
	float flOOBlinkDuration = ( flBlinkDuration > 0 ) ? 1.0f / flBlinkDuration : 0.0f;
	float t = ( m_blinktime - gpGlobals->curtime ) * M_PI * 0.5 * flOOBlinkDuration;
	if (t > 0)
	{
		// do eyeblink falloff curve
		t = cos(t);
		if (t > 0.0f && t < 1.0f)
		{
			t = sqrtf( t ) * 2.0f;
			if (t > 1.0f)
				t = 2.0f - t;
			t = clamp( t, 0.0f, 1.0f );
			// add it to whatever the blink track is doing
			g_flexweight[m_iBlink] = clamp( g_flexweight[m_iBlink] + t, 0.0f, 1.0f );
		}
	}

	// Drive the mouth from .wav file playback...
	ProcessVisemes( m_PhonemeClasses );

	return true;
}

void C_BaseFlex::RunFlexDelay( int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights, float &flFlexDelayTime )
{
	// process the delayed version of the flexweights
	if ( flFlexDelayTime > 0.0f && flFlexDelayTime < gpGlobals->curtime )
	{
		float d = clamp( gpGlobals->curtime - flFlexDelayTime, 0.0, gpGlobals->frametime );
		d = ExponentialDecay( 0.8, 0.033, d );

		for ( int i = 0; i < nFlexWeightCount; i++)
		{
			pFlexDelayedWeights[i] = pFlexDelayedWeights[i] * d + pFlexWeights[i] * (1.0f - d);
		}
	}
	flFlexDelayTime = gpGlobals->curtime;
}


//-----------------------------------------------------------------------------
// Purpose: convert the global flex controllers into model specific flex weights
//-----------------------------------------------------------------------------
void C_BaseFlex::SetupLocalWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
{
	CStudioHdr *hdr = GetModelPtr();
	if ( !hdr )
		return;

	// BUGBUG: We have a bug with SetCustomModel that causes a disagreement between the studio header here and the one used in l_studio.cpp CModelRender::DrawModelExecute
	// So when we hit that case, let's not do any work because otherwise we'd crash since the array sizes (m_flFlexDelayedWeight vs pFlexWeights) don't match.
	// Note that this check is duplicated in CEconEntity::SetupWeights.
	AssertMsg( nFlexWeightCount == m_cFlexDelayedWeight, "Disagreement between the number of flex weights. Do the studio headers match?" );
	if ( nFlexWeightCount != m_cFlexDelayedWeight )
	{
		return;
	}

	// convert the flex controllers into actual flex values
	RunFlexRules( hdr, pFlexWeights );

	// aim the eyes
	SetViewTarget( hdr );

	AssertOnce( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 );

	if ( pFlexDelayedWeights )
	{
		RunFlexDelay( nFlexWeightCount, pFlexWeights, m_flFlexDelayedWeight, m_flFlexDelayTime );
		memcpy( pFlexDelayedWeights, m_flFlexDelayedWeight, sizeof( float ) * nFlexWeightCount );
	}

	/*
	LocalFlexController_t i;

	for (i = 0; i < hdr->numflexdesc; i++)
	{
		debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), i-hdr->numflexcontrollers, 0, "%2d:%s : %3.2f", i, hdr->pFlexdesc( i )->pszFACS(), pFlexWeights[i] );
	}
	*/

	/*
	for (i = 0; i < g_numflexcontrollers; i++)
	{
		int j = hdr->pFlexcontroller( i )->link;
		debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), -i, 0, "%s %3.2f", g_flexcontroller[i], g_flexweight[j] );
	}
	*/
}


//-----------------------------------------------------------------------------
// Purpose: Unified set of flex controller entries that all systems can talk to
//-----------------------------------------------------------------------------

int C_BaseFlex::g_numflexcontrollers;
char * C_BaseFlex::g_flexcontroller[MAXSTUDIOFLEXCTRL*4];
float C_BaseFlex::g_flexweight[MAXSTUDIOFLEXDESC];

int C_BaseFlex::AddGlobalFlexController( const char *szName )
{
	int i;
	for (i = 0; i < g_numflexcontrollers; i++)
	{
		if (Q_stricmp( g_flexcontroller[i], szName ) == 0)
		{
			return i;
		}
	}

	if ( g_numflexcontrollers < MAXSTUDIOFLEXCTRL * 4 )
	{
		g_flexcontroller[g_numflexcontrollers++] = strdup( szName );
	}
	else
	{
		// FIXME: missing runtime error condition
	}
	return i;
}

char const *C_BaseFlex::GetGlobalFlexControllerName( int idx )
{
	if ( idx < 0 || idx >= g_numflexcontrollers )
	{
		return "";
	}

	return g_flexcontroller[ idx ];
}

const flexsetting_t *C_BaseFlex::FindNamedSetting( const flexsettinghdr_t *pSettinghdr, const char *expr )
{
	int i;
	const flexsetting_t *pSetting = NULL;

	for ( i = 0; i < pSettinghdr->numflexsettings; i++ )
	{
		pSetting = pSettinghdr->pSetting( i );
		if ( !pSetting )
			continue;

		const char *name = pSetting->pszName();

		if ( !stricmp( name, expr ) )
			break;
	}

	if ( i>=pSettinghdr->numflexsettings )
	{
		return NULL;
	}

	return pSetting;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseFlex::StartChoreoScene( CChoreoScene *scene )
{
	if ( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() )
	{
		return;
	}

	m_ActiveChoreoScenes.AddToTail( scene );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseFlex::RemoveChoreoScene( CChoreoScene *scene )
{
	// Assert( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() );

	m_ActiveChoreoScenes.FindAndRemove( scene );
}

//-----------------------------------------------------------------------------
// Purpose: Remove all active SceneEvents
//-----------------------------------------------------------------------------
void C_BaseFlex::ClearSceneEvents( CChoreoScene *scene, bool canceled )
{
	if ( !scene )
	{
		m_SceneEvents.RemoveAll();
		return;
	}

	for ( int i = m_SceneEvents.Count() - 1; i >= 0; i-- )
	{
		CSceneEventInfo *info = &m_SceneEvents[ i ];

		Assert( info );
		Assert( info->m_pScene );
		Assert( info->m_pEvent );

		if ( info->m_pScene != scene )
			continue;

		if ( !ClearSceneEvent( info, false, canceled ))
		{
			// unknown expression to clear!!
			Assert( 0 );
		}

		// Free this slot
		info->m_pEvent		= NULL;
		info->m_pScene		= NULL;
		info->m_bStarted	= false;

		m_SceneEvents.Remove( i );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Stop specifics of expression
//-----------------------------------------------------------------------------

bool C_BaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled )
{
	Assert( info );
	Assert( info->m_pScene );
	Assert( info->m_pEvent );
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Add string indexed scene/expression/duration to list of active SceneEvents
// Input  : scenefile - 
//			expression - 
//			duration - 
//-----------------------------------------------------------------------------
void C_BaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget, bool bClientSide, C_SceneEntity* pSceneEntity)
{
	if ( !scene || !event )
	{
		Msg( "C_BaseFlex::AddSceneEvent:  scene or event was NULL!!!\n" );
		return;
	}

	CChoreoActor *actor = event->GetActor();
	if ( !actor )
	{
		Msg( "C_BaseFlex::AddSceneEvent:  event->GetActor() was NULL!!!\n" );
		return;
	}


	CSceneEventInfo info;

	memset( (void *)&info, 0, sizeof( info ) );

	info.m_pEvent		= event;
	info.m_pScene		= scene;
	info.m_hTarget		= pTarget;
	info.m_bStarted		= false;
	info.m_bClientSide	= bClientSide;
	info.m_hSceneEntity = pSceneEntity;

	if (StartSceneEvent( &info, scene, event, actor, pTarget ))
	{
		m_SceneEvents.AddToTail( info );
	}
	else
	{
		scene->SceneMsg( "C_BaseFlex::AddSceneEvent:  event failed\n" );
		// Assert( 0 ); // expression failed to start
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
{
	switch ( event->GetType() )
	{
	default:
		break;

	case CChoreoEvent::FLEXANIMATION:
		info->InitWeight( this );
		return true;

	case CChoreoEvent::EXPRESSION:
		return true;
		
	case CChoreoEvent::SEQUENCE: 
		if ( info->m_bClientSide )
		{
			return RequestStartSequenceSceneEvent( info, scene, event, actor, pTarget );
		}
		break;

	case CChoreoEvent::SPEAK:
		if ( info->m_bClientSide )
		{
			return true;
		}
		break;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BaseFlex::RequestStartSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
{
	info->m_nSequence = LookupSequence( event->GetParameters() );

	// make sure sequence exists
	if ( info->m_nSequence < 0 )
		return false;

	info->m_pActor = actor;
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Remove expression
// Input  : scenefile - 
//			expression - 
//-----------------------------------------------------------------------------
void C_BaseFlex::RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill )
{
	Assert( event );

	for ( int i = 0 ; i < m_SceneEvents.Count(); i++ )
	{
		CSceneEventInfo *info = &m_SceneEvents[ i ];

		Assert( info );
		Assert( info->m_pEvent );

		if ( info->m_pScene != scene )
			continue;

		if ( info->m_pEvent != event)
			continue;

		if (ClearSceneEvent( info, fastKill, false ))
		{
			// Free this slot
			info->m_pEvent		= NULL;
			info->m_pScene		= NULL;
			info->m_bStarted	= false;

			m_SceneEvents.Remove( i );
			return;
		}
	}

	// many events refuse to start due to bogus parameters
}

//-----------------------------------------------------------------------------
// Purpose: Checks to see if the event should be considered "completed"
//-----------------------------------------------------------------------------
bool C_BaseFlex::CheckSceneEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
{
	for ( int i = 0 ; i < m_SceneEvents.Count(); i++ )
	{
		CSceneEventInfo *info = &m_SceneEvents[ i ];

		Assert( info );
		Assert( info->m_pEvent );

		if ( info->m_pScene != scene )
			continue;

		if ( info->m_pEvent != event)
			continue;
		
		return CheckSceneEventCompletion( info, currenttime, scene, event );
	}
	return true;
}



bool C_BaseFlex::CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event )
{
	return true;
}

void C_BaseFlex::SetFlexWeight( LocalFlexController_t index, float value )
{
	if (index >= 0 && index < GetNumFlexControllers())
	{
		CStudioHdr *pstudiohdr = GetModelPtr( );
		if (! pstudiohdr)
			return;

		mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index );

		if (pflexcontroller->max != pflexcontroller->min)
		{
			value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min);
			value = clamp( value, 0.0f, 1.0f );
		}

		m_flexWeight[ index ] = value;
	}
}

float C_BaseFlex::GetFlexWeight( LocalFlexController_t index )
{
	if (index >= 0 && index < GetNumFlexControllers())
	{
		CStudioHdr *pstudiohdr = GetModelPtr( );
		if (! pstudiohdr)
			return 0;

		mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index );

		if (pflexcontroller->max != pflexcontroller->min)
		{
			return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min;
		}
				
		return m_flexWeight[index];
	}
	return 0.0;
}

LocalFlexController_t C_BaseFlex::FindFlexController( const char *szName )
{
	for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
	{
		if (stricmp( GetFlexControllerName( i ), szName ) == 0)
		{
			return i;
		}
	}

	// AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) );
	return LocalFlexController_t(-1);
}

//-----------------------------------------------------------------------------
// Purpose: Default implementation
//-----------------------------------------------------------------------------
void C_BaseFlex::ProcessSceneEvents( bool bFlexEvents )
{
	CStudioHdr *hdr = GetModelPtr();
	if ( !hdr )
	{
		return;
	}

	// slowly decay to netural expression

	if ( bFlexEvents )
	{
		for ( LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
		{
			SetFlexWeight( i, GetFlexWeight( i ) * 0.95 );
		}
	}

	// Iterate SceneEvents and look for active slots
	for ( int i = 0; i < m_SceneEvents.Count(); i++ )
	{
		CSceneEventInfo *info = &m_SceneEvents[ i ];
		Assert( info );

		// FIXME:  Need a safe handle to m_pEvent in case of memory deletion?
		CChoreoEvent *event = info->m_pEvent;
		Assert( event );

		CChoreoScene *scene = info->m_pScene;
		Assert( scene );

		if ( ProcessSceneEvent( bFlexEvents, info, scene, event ) )
		{
			info->m_bStarted = true;
		}
	}
}

//-----------------------------------------------------------------------------
// Various methods to process facial SceneEvents: 
//-----------------------------------------------------------------------------
bool C_BaseFlex::ProcessFlexAnimationSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
{
	Assert( event->HasEndTime() );
	if ( event->HasEndTime() )
	{
		AddFlexAnimation( info );
	}
	return true;
}

bool C_BaseFlex::ProcessFlexSettingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
{
	// Flexanimations have to have an end time!!!
	if ( !event->HasEndTime() )
		return true;

	VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" );

	// Look up the actual strings
	const char *scenefile	= event->GetParameters();
	const char *name		= event->GetParameters2();
	
	// Have to find both strings
	if ( scenefile && name )
	{
		// Find the scene file
		const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
		if ( pExpHdr )
		{
			float scenetime = scene->GetTime();
			
			float scale = event->GetIntensity( scenetime );
			
			// Add the named expression
			AddFlexSetting( name, scale, pExpHdr, !info->m_bStarted );
		}
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Each CBaseFlex maintains a UtlRBTree of mappings, one for each loaded flex scene file it uses.  This is used to
//  sort the entries in the RBTree
// Input  : lhs - 
//			rhs - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool FlexSettingLessFunc( const FS_LocalToGlobal_t& lhs, const FS_LocalToGlobal_t& rhs )
{
	return lhs.m_Key < rhs.m_Key;
}

//-----------------------------------------------------------------------------
// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but 
//  we just do this in memory with an array of integers (could be shorts, I suppose)
// Input  : *pSettinghdr - 
//-----------------------------------------------------------------------------
void C_BaseFlex::EnsureTranslations( const flexsettinghdr_t *pSettinghdr )
{
	Assert( pSettinghdr );

	FS_LocalToGlobal_t entry( pSettinghdr );

	unsigned short idx = m_LocalToGlobal.Find( entry );
	if ( idx != m_LocalToGlobal.InvalidIndex() )
		return;

	entry.SetCount( pSettinghdr->numkeys );

	for ( int i = 0; i < pSettinghdr->numkeys; ++i )
	{
		entry.m_Mapping[ i ] = AddGlobalFlexController( pSettinghdr->pLocalName( i ) );
	}

	m_LocalToGlobal.Insert( entry );
}

//-----------------------------------------------------------------------------
// Purpose: Look up instance specific mapping
// Input  : *pSettinghdr - 
//			key - 
// Output : int
//-----------------------------------------------------------------------------
int C_BaseFlex::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key )
{
	FS_LocalToGlobal_t entry( pSettinghdr );

	int idx = m_LocalToGlobal.Find( entry );
	if ( idx == m_LocalToGlobal.InvalidIndex() )
	{
		// This should never happen!!!
		Assert( 0 );
		Warning( "Unable to find mapping for flexcontroller %i, settings %p on %i/%s\n", key, pSettinghdr, entindex(), GetClassname() );
		EnsureTranslations( pSettinghdr );
		idx = m_LocalToGlobal.Find( entry );
		if ( idx == m_LocalToGlobal.InvalidIndex() )
		{
			Error( "CBaseFlex::FlexControllerLocalToGlobal failed!\n" );
		}
	}

	FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ];
	// Validate lookup
	Assert( result.m_nCount != 0 && key < result.m_nCount );
	int index = result.m_Mapping[ key ];
	return index;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *expr - 
//			scale - 
//			*pSettinghdr - 
//			newexpression - 
//-----------------------------------------------------------------------------
void C_BaseFlex::AddFlexSetting( const char *expr, float scale, 
	const flexsettinghdr_t *pSettinghdr, bool newexpression )
{
	int i;
	const flexsetting_t *pSetting = NULL;

	// Find the named setting in the base
	for ( i = 0; i < pSettinghdr->numflexsettings; i++ )
	{
		pSetting = pSettinghdr->pSetting( i );
		if ( !pSetting )
			continue;

		const char *name = pSetting->pszName();

		if ( !V_stricmp( name, expr ) )
			break;
	}

	if ( i>=pSettinghdr->numflexsettings )
	{
		return;
	}

	flexweight_t *pWeights = NULL;
	int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights );
	if ( !pWeights )
		return;

	for (i = 0; i < truecount; i++, pWeights++)
	{
		// Translate to local flex controller
		// this is translating from the settings's local index to the models local index
		int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key );

		// blend scaled weighting in to total (post networking g_flexweight!!!!)
		float s = clamp( scale * pWeights->influence, 0.0f, 1.0f );
		g_flexweight[index] = g_flexweight[index] * (1.0f - s) + pWeights->weight * s;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BaseFlex::ProcessSceneEvent( bool bFlexEvents, CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
{
	switch ( event->GetType() )
	{
	default:
		break;
	case CChoreoEvent::FLEXANIMATION:
		if ( bFlexEvents )
		{
			return ProcessFlexAnimationSceneEvent( info, scene, event );
		}
		return true;

	case CChoreoEvent::EXPRESSION:
		if ( !bFlexEvents )
		{
            return ProcessFlexSettingSceneEvent( info, scene, event );
		}
		return true;

	case CChoreoEvent::SEQUENCE:
		if ( info->m_bClientSide )
		{
			if ( !bFlexEvents )
			{
				return ProcessSequenceSceneEvent( info, scene, event );
			}
			return true;
		}
		break;

	case CChoreoEvent::SPEAK:
		if ( info->m_bClientSide )
		{
			return true;
		}
		break;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*parameters - 
//-----------------------------------------------------------------------------
bool C_BaseFlex::ProcessSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
{
	if ( !info  || !event || !scene )
		return false;

	SetSequence( info->m_nSequence );
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *event - 
//-----------------------------------------------------------------------------
void C_BaseFlex::AddFlexAnimation( CSceneEventInfo *info )
{	
	if ( !info )
		return;

	CChoreoEvent *event = info->m_pEvent;
	if ( !event )
		return;

	CChoreoScene *scene = info->m_pScene;
	if ( !scene )
		return;

	if ( !event->GetTrackLookupSet() )
	{
		// Create lookup data
		for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
		{
			CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
			if ( !track )
				continue;

			if ( track->IsComboType() )
			{
				char name[ 512 ];
				Q_strncpy( name, "right_" ,sizeof(name));
				Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );

				track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 );

				Q_strncpy( name, "left_" ,sizeof(name));
				Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );

				track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 );
			}
			else
			{
				track->SetFlexControllerIndex( MAX( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 );
			}
		}

		event->SetTrackLookupSet( true );
	}

	if ( !scene_clientflex.GetBool() )
		return;

	float scenetime = scene->GetTime();

	float weight = event->GetIntensity( scenetime );

	// decay if this is a background scene and there's other flex animations playing
	weight = weight * info->UpdateWeight( this );

	// Compute intensity for each track in animation and apply
	// Iterate animation tracks
	for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
	{
		CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
		if ( !track )
			continue;

		// Disabled
		if ( !track->IsTrackActive() )
			continue;

		// Map track flex controller to global name
		if ( track->IsComboType() )
		{
			for ( int side = 0; side < 2; side++ )
			{
				LocalFlexController_t controller = track->GetRawFlexControllerIndex( side );

				// Get spline intensity for controller
				float flIntensity = track->GetIntensity( scenetime, side );
				if ( controller >= LocalFlexController_t(0) )
				{
					float orig = GetFlexWeight( controller );
					float value = orig * (1 - weight) + flIntensity * weight;
					SetFlexWeight( controller, value );
				}
			}
		}
		else
		{
			LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 );

			// Get spline intensity for controller
			float flIntensity = track->GetIntensity( scenetime, 0 );
			if ( controller >= LocalFlexController_t(0) )
			{
				float orig = GetFlexWeight( controller );
				float value = orig * (1 - weight) + flIntensity * weight;
				SetFlexWeight( controller, value );
			}
		}
	}

	info->m_bStarted = true;
}

void CSceneEventInfo::InitWeight( C_BaseFlex *pActor )
{
	m_flWeight = 1.0;
}

//-----------------------------------------------------------------------------
// Purpose: update weight for background events.  Only call once per think
//-----------------------------------------------------------------------------

float CSceneEventInfo::UpdateWeight( C_BaseFlex *pActor )
{
	m_flWeight = MIN( m_flWeight + 0.1, 1.0 );
	return m_flWeight;
}

BEGIN_BYTESWAP_DATADESC( flexsettinghdr_t )
	DEFINE_FIELD( id, FIELD_INTEGER ),
	DEFINE_FIELD( version, FIELD_INTEGER ),
	DEFINE_ARRAY( name, FIELD_CHARACTER, 64 ),
	DEFINE_FIELD( length, FIELD_INTEGER ),
	DEFINE_FIELD( numflexsettings, FIELD_INTEGER ),
	DEFINE_FIELD( flexsettingindex, FIELD_INTEGER ),
	DEFINE_FIELD( nameindex, FIELD_INTEGER ),
	DEFINE_FIELD( numindexes, FIELD_INTEGER ),
	DEFINE_FIELD( indexindex, FIELD_INTEGER ),
	DEFINE_FIELD( numkeys, FIELD_INTEGER ),
	DEFINE_FIELD( keynameindex, FIELD_INTEGER ),
	DEFINE_FIELD( keymappingindex, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()

BEGIN_BYTESWAP_DATADESC( flexsetting_t )
	DEFINE_FIELD( nameindex, FIELD_INTEGER ),
	DEFINE_FIELD( obsolete1, FIELD_INTEGER ),
	DEFINE_FIELD( numsettings, FIELD_INTEGER ),
	DEFINE_FIELD( index, FIELD_INTEGER ),
	DEFINE_FIELD( obsolete2, FIELD_INTEGER ),
	DEFINE_FIELD( settingindex, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()

BEGIN_BYTESWAP_DATADESC( flexweight_t )
	DEFINE_FIELD( key, FIELD_INTEGER ),
	DEFINE_FIELD( weight, FIELD_FLOAT ),
	DEFINE_FIELD( influence, FIELD_FLOAT ),
END_BYTESWAP_DATADESC()

