//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include "hlfaceposer.h"
#include "StudioModel.h"
#include "faceposer_models.h"
#include "filesystem.h"
#include "ifaceposerworkspace.h"
#include <mxtk/mx.h>
#include "mdlviewer.h"
#include "mxexpressiontray.h"
#include "ControlPanel.h"
#include "checksum_crc.h"
#include "ViewerSettings.h"
#include "matsyswin.h"
#include "KeyValues.h"
#include "utlbuffer.h" 
#include "expression.h"
#include "ProgressDialog.h"
#include "tier1/UtlString.h"
#include "tier1/FmtStr.h"
#include "tier1/KeyValues.h"


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

void SetupModelFlexcontrollerLinks( StudioModel *model );

IFaceposerModels::CFacePoserModel::CFacePoserModel( char const *modelfile, StudioModel *model )
{
	m_pModel = model;
	m_szActorName[ 0 ] = 0;
	m_szShortName[ 0 ] = 0;
	strcpy( m_szModelFileName, modelfile );
	Q_FixSlashes( m_szModelFileName );

	CStudioHdr *hdr = model->GetStudioHdr();
	if ( hdr )
	{	
		Q_StripExtension( hdr->pszName(), m_szShortName, sizeof( m_szShortName ) );
	}

	m_bVisibileIn3DView = false;
	m_bFirstBitmapLoad = true;

	LoadBitmaps();
}

IFaceposerModels::CFacePoserModel::~CFacePoserModel()
{
	FreeBitmaps();
}

void IFaceposerModels::CFacePoserModel::LoadBitmaps()
{
	CStudioHdr *hdr = m_pModel ? m_pModel->GetStudioHdr() : NULL;
	if ( hdr )
	{
		for ( int i = 0 ;i < hdr->GetNumSeq(); i++ )
		{
			mxbitmapdata_t *bm = new mxbitmapdata_t();

			AnimBitmap *entry = new AnimBitmap();
			entry->needsload = true;
			entry->bitmap = bm;

			// Need to load bitmap from disk image via crc, etc.
			//Assert( 0 );

			m_AnimationBitmaps.AddToTail( entry );
		}
	}
}

CRC32_t IFaceposerModels::CFacePoserModel::GetBitmapCRC( int sequence )
{
	CStudioHdr *hdr = m_pModel ? m_pModel->GetStudioHdr() : NULL;
	if ( !hdr )
		return (CRC32_t)-1;

	if ( sequence < 0 || sequence >= hdr->GetNumSeq() )
		return (CRC32_t)-1;

	mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( sequence );

	CRC32_t crc;
	CRC32_Init( &crc );

	// For sequences, we'll checsum a bit of data

	CRC32_ProcessBuffer( &crc, (void *)seqdesc.pszLabel(), Q_strlen( seqdesc.pszLabel() ) );
	CRC32_ProcessBuffer( &crc, (void *)seqdesc.pszActivityName(), Q_strlen( seqdesc.pszActivityName() ) );
	CRC32_ProcessBuffer( &crc, (void *)&seqdesc.flags, sizeof( seqdesc.flags ) );
	//CRC32_ProcessBuffer( &crc, (void *)&seqdesc.numevents, sizeof( seqdesc.numevents ) );
	CRC32_ProcessBuffer( &crc, (void *)&seqdesc.numblends, sizeof( seqdesc.numblends ) );
	CRC32_ProcessBuffer( &crc, (void *)seqdesc.groupsize, sizeof( seqdesc.groupsize ) );

	KeyValues *seqKeyValues = new KeyValues("");
	if ( seqKeyValues->LoadFromBuffer( m_pModel->GetFileName( ), m_pModel->GetKeyValueText( sequence ) ) )
	{
		// Yuck, but I need it in a contiguous block of memory... oh well...
		CUtlBuffer buf;
		seqKeyValues->RecursiveSaveToFile( buf, 0 );
		CRC32_ProcessBuffer( &crc, ( void * )buf.Base(), buf.TellPut() );
	}

	seqKeyValues->deleteThis();

	CRC32_Final( &crc );

	return crc;
}

const char *IFaceposerModels::CFacePoserModel::GetBitmapChecksum( int sequence )
{
	CRC32_t crc = GetBitmapCRC( sequence );

	// Create string name out of binary data
	static char filename[ 512 ];

	char hex[ 16 ];
	Q_binarytohex( (byte *)&crc, sizeof( crc ), hex, sizeof( hex ) );

	Q_snprintf( filename, sizeof( filename ), "%s", hex );
	return filename;
}

const char *IFaceposerModels::CFacePoserModel::GetBitmapFilename( int sequence )
{
	char *in, *out;
	static char filename[ 256 ];
	filename[ 0 ] = 0;

	char modelName[512], modelNameTemp[512];
	Q_strncpy( modelNameTemp, GetShortModelName(), sizeof( modelNameTemp ) );

	in = modelNameTemp;
	out = modelName;

	while ( *in )
	{
		if ( V_isalnum( *in ) ||
			*in == '_' || 
			*in == '\\' || 
			*in == '/' ||
			*in == '.' ||
			*in == ':' )
		{
			*out++ = *in;
		}
		in++;
	}
	*out = 0;


	Q_snprintf( filename, sizeof( filename ), "expressions/%s/animation/%s.bmp", modelName, GetBitmapChecksum( sequence ) );

	Q_FixSlashes( filename );
	strlwr( filename );

	CreatePath( filename );
	
	return filename;
}

void IFaceposerModels::CFacePoserModel::FreeBitmaps()
{
	while ( m_AnimationBitmaps.Count() > 0 )
	{
		AnimBitmap *bm = m_AnimationBitmaps[ 0 ];
		delete bm->bitmap;
		delete bm;
		m_AnimationBitmaps.Remove( 0 );
	}
}

void IFaceposerModels::CFacePoserModel::LoadBitmapForSequence( mxbitmapdata_t *bitmap, int sequence )
{
	// See if it exists
	char filename[ 512 ];
	Q_strncpy( filename, GetBitmapFilename( sequence ), sizeof( filename ) );

	if ( !LoadBitmapFromFile( filename, *bitmap ) )
	{
		CreateNewBitmap( filename, sequence, 256, false, NULL, bitmap );
	}
}

static float FindPoseCycle( StudioModel *model, int sequence )
{
	float cycle = 0.0f;
	if ( !model->GetStudioHdr() )
		return cycle;

	KeyValues *seqKeyValues = new KeyValues("");
	if ( seqKeyValues->LoadFromBuffer( model->GetFileName( ), model->GetKeyValueText( sequence ) ) )
	{
		// Do we have a build point section?
		KeyValues *pkvAllFaceposer = seqKeyValues->FindKey("faceposer");
		if ( pkvAllFaceposer )
		{
			int thumbnail_frame = pkvAllFaceposer->GetInt( "thumbnail_frame", 0 );
			if ( thumbnail_frame )
			{
				// Convert frame to cycle if we have valid data
				int maxFrame = model->GetNumFrames( sequence ) - 1;

				if ( maxFrame > 0 )
				{
					cycle = thumbnail_frame / (float)maxFrame;
				}
			}
		}
	}

	seqKeyValues->deleteThis();

	return cycle;
}


void EnableStickySnapshotMode( void )
{
	g_pMatSysWindow->EnableStickySnapshotMode( );
}

void DisableStickySnapshotMode( void )
{
	g_pMatSysWindow->DisableStickySnapshotMode( );
}


void IFaceposerModels::CreateNewBitmap( int modelindex, char const *pchBitmapFilename, int sequence, int nSnapShotSize, bool bZoomInOnFace, CExpression *pExpression, mxbitmapdata_t *bitmap )
{
	CFacePoserModel *m = m_Models[ modelindex ];
	if ( m )
	{
		m->CreateNewBitmap( pchBitmapFilename, sequence, nSnapShotSize, bZoomInOnFace, pExpression, bitmap );
	}
}

void IFaceposerModels::CFacePoserModel::CreateNewBitmap( char const *pchBitmapFilename, int sequence, int nSnapShotSize, bool bZoomInOnFace, CExpression *pExpression, mxbitmapdata_t *bitmap )
{
	MatSysWindow *pWnd = g_pMatSysWindow;
	if ( !pWnd ) 
		return;

	StudioModel *model = m_pModel;
	if ( !model )
		return;

	CStudioHdr *hdr = model->GetStudioHdr();
	if ( !hdr )
		return;
	if ( sequence < 0 || sequence >= hdr->GetNumSeq() )
		return;

	mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( sequence );

	Con_ColorPrintf( FILE_COLOR, "Creating bitmap %s for sequence '%s'\n", pchBitmapFilename, seqdesc.pszLabel() );

	model->ClearOverlaysSequences();
	int iLayer = model->GetNewAnimationLayer();
	model->SetOverlaySequence( iLayer, sequence, 1.0 );
	model->SetOverlayRate( iLayer, FindPoseCycle( model, sequence ), 0.0 );

	for (int i = 0; i < hdr->GetNumPoseParameters(); i++)
	{
		model->SetPoseParameter( i, 0.0 );
	}

	float flexValues[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ] = { 0 };

	if ( pExpression )
	{
		float *settings = pExpression->GetSettings();
		float *weights = pExpression->GetWeights();

		// Save existing settings from model
		for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); ++i )
		{
			int j = hdr->pFlexcontroller( i )->localToGlobal;
			if ( j == -1 )
				continue;
			flexValues[ i ] = model->GetFlexController( i );
			// Set Value from passed in settings
			model->SetFlexController( i, settings[ j ] * weights[ j ] );
		}
	}

	model->ClearLookTargets( );

	QAngle oldrot, oldLight;
	Vector oldtrans;
	
	VectorCopy( model->m_angles, oldrot );
	VectorCopy( model->m_origin, oldtrans );
	VectorCopy( g_viewerSettings.lightrot, oldLight );

	model->m_angles.Init();
	model->m_origin.Init();
	g_viewerSettings.lightrot.Init();

	g_viewerSettings.lightrot.y = -180;

	bool bSaveGround = g_viewerSettings.showGround;
	g_viewerSettings.showGround = false;

	if ( bZoomInOnFace )
	{
		Vector size;
		VectorSubtract( hdr->hull_max(), hdr->hull_min(), size );

		float eyeheight = hdr->hull_min().z + 0.9 * size.z;
		//	float width = ( size.x + size.y ) / 2.0f;

		model->m_origin.x = size.z * .6f;

		if ( hdr->GetNumAttachments() > 0 )
		{
			for (int i = 0; i < hdr->GetNumAttachments(); i++)
			{
				const mstudioattachment_t &attachment = hdr->pAttachment( i );
				int iBone = hdr->GetAttachmentBone( i );

				if ( Q_stricmp( attachment.pszName(), "eyes" ) )
					continue;

				mstudiobone_t *bone = hdr->pBone( iBone );
				if ( !bone )
					continue;

				matrix3x4_t boneToPose;
				MatrixInvert( bone->poseToBone, boneToPose );

				matrix3x4_t attachmentPoseToLocal;
				ConcatTransforms( boneToPose, attachment.local, attachmentPoseToLocal );

				Vector localSpaceEyePosition;
				VectorITransform( vec3_origin, attachmentPoseToLocal, localSpaceEyePosition );

				// Not sure why this must be negative?
				eyeheight = -localSpaceEyePosition.z + hdr->hull_min().z;
				break;
			}
		}

		KeyValues *seqKeyValues = new KeyValues("");
		if ( seqKeyValues->LoadFromBuffer( model->GetFileName( ), model->GetKeyValueText( sequence ) ) )
		{
			// Do we have a build point section?
			KeyValues *pkvAllFaceposer = seqKeyValues->FindKey("faceposer");
			if ( pkvAllFaceposer )
			{
				float flEyeheight = pkvAllFaceposer->GetFloat( "eye_height", -9999.0f );
				if ( flEyeheight != -9999.0f )
				{
					eyeheight = flEyeheight;
				}
			}
		}

		model->m_origin.z += eyeheight;
	}
	else
	{
		Vector mins, maxs;
		model->ExtractBbox(mins, maxs);
		Vector size;
		VectorSubtract( maxs, mins, size );

		float maxdim = size.x;
		if ( size.y > maxdim )
			maxdim = size.y;
		if ( size.z > maxdim )
			maxdim = size.z;

		float midpoint = mins.z + 0.5 * size.z;

		model->m_origin.x = 3 * maxdim;
		model->m_origin.z += midpoint;
	}

	g_pMatSysWindow->PushSnapshotMode( nSnapShotSize );

	// Snapshots are taken of the back buffer; 
	// we need to render to the back buffer but not move it to the front
	pWnd->SuppressBufferSwap( true );
	pWnd->redraw();
	pWnd->SuppressBufferSwap( false );

	// make it square, assumes w > h
	char fullpath[ 512 ];
	Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", GetGameDirectory(), pchBitmapFilename );
	pWnd->TakeSnapshotRect( fullpath, 0, 0, nSnapShotSize, nSnapShotSize );

	g_pMatSysWindow->PopSnapshotMode( );

	VectorCopy( oldrot, model->m_angles );
	VectorCopy( oldtrans, model->m_origin );
	VectorCopy( oldLight, g_viewerSettings.lightrot );

	g_viewerSettings.showGround = bSaveGround;

	if ( pExpression )
	{
		// Save existing settings from model
		for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); ++i )
		{
			int j = hdr->pFlexcontroller( i )->localToGlobal;
			if ( j == -1 )
				continue;

			model->SetFlexController( i, flexValues[ i ] );
		}
	}

	model->ClearOverlaysSequences();
	
	if ( bitmap->valid )
	{
		DeleteObject( bitmap->image );
		bitmap->image = 0;
		bitmap->valid = false;
	}

	LoadBitmapFromFile( pchBitmapFilename, *bitmap );
}

mxbitmapdata_t *IFaceposerModels::CFacePoserModel::GetBitmapForSequence( int sequence )
{
	static mxbitmapdata_t nullbitmap;
	if ( sequence < 0 || sequence >= m_AnimationBitmaps.Count() )
		return &nullbitmap;

	/*
	if ( m_bFirstBitmapLoad )
	{
		m_bFirstBitmapLoad = false;
		ReconcileAnimationBitmaps();
	}
	*/

	AnimBitmap *slot = m_AnimationBitmaps[ sequence ];
	if ( slot->needsload )
	{
		slot->needsload = false;
		LoadBitmapForSequence( slot->bitmap, sequence );
	}

	return m_AnimationBitmaps[ sequence ]->bitmap;
}

void IFaceposerModels::CFacePoserModel::BuildValidChecksums( CUtlRBTree< CRC32_t > &tree )
{
	StudioModel *model = m_pModel;
	if ( !model )
		return;

	CStudioHdr *hdr = model->GetStudioHdr();
	if ( !hdr )
		return;	

	for ( int i = 0; i < hdr->GetNumSeq(); i++ )
	{
		CRC32_t crc = GetBitmapCRC( i );
		tree.Insert( crc );
	}
}

void IFaceposerModels::CFacePoserModel::ReconcileAnimationBitmaps()
{
	// iterate files in directory and see if each checksum is valid and if not delete the .bmp
	char path[ 512 ];
	Q_snprintf( path, sizeof( path ), "expressions/%s/animation/*.bmp", GetShortModelName() );

	FileFindHandle_t hFindFile;

	char const *fn = filesystem->FindFirstEx( path, "MOD", &hFindFile );

	g_pProgressDialog->Start( CFmtStr( "%s - Reconcile Animation Thumbnails", GetShortModelName() ), "", true );

	CUtlVector< CUtlString > workList;

	if ( fn )
	{
		while ( fn )
		{
			// Don't do anything with directories
			if ( !filesystem->FindIsDirectory( hFindFile ) )
			{
				CUtlString s = fn;
				workList.AddToTail( s );
			}

			fn = filesystem->FindNext( hFindFile );
		}

		filesystem->FindClose( hFindFile );
	}

	CUtlRBTree< CRC32_t > tree( 0, 0, DefLessFunc( CRC32_t ) );
	BuildValidChecksums( tree );

	for ( int i = 0 ; i < workList.Count(); ++i )
	{
		char testname[ 256 ];
		Q_StripExtension( workList[ i ].String(), testname, sizeof( testname ) );

		g_pProgressDialog->UpdateText( "%s", testname );
		g_pProgressDialog->Update( (float)i / (float)workList.Count() );

		CRC32_t check;
		Q_hextobinary( testname, Q_strlen( testname ), (byte *)&check, sizeof( check ) );

		if ( tree.Find( check ) == tree.InvalidIndex() )
		{
			Q_snprintf( testname, sizeof( testname ), "expressions/%s/animation/%s", GetShortModelName(), fn );

			char fullpath[ 512 ];
			filesystem->RelativePathToFullPath( testname, "MOD", fullpath, sizeof( fullpath ) );
			// Delete it
			Con_ErrorPrintf( "Removing unused bitmap file %s\n", 
				fullpath );

			_unlink( fullpath );
		}

		if ( g_pProgressDialog->IsCancelled() )
		{
			Msg( "Cancelled\n" );
			break;
		}
	}

	g_pProgressDialog->Finish();
}

void IFaceposerModels::CFacePoserModel::RecreateAllAnimationBitmaps()
{
	StudioModel *model = m_pModel;
	if ( !model )
		return;

	CStudioHdr *hdr = model->GetStudioHdr();
	if ( !hdr )
		return;	

	g_pProgressDialog->Start( CFmtStr( "%s - Animation Thumbnails", GetShortModelName() ), "", true );

	for ( int i = 0; i < hdr->GetNumSeq(); ++i )
	{
		const mstudioseqdesc_t &seq = hdr->pSeqdesc( i );

		g_pProgressDialog->UpdateText( "%s", seq.pszLabel() );
		g_pProgressDialog->Update( (float)i / (float)hdr->GetNumSeq() );

		RecreateAnimationBitmap( i, false );

		if ( g_pProgressDialog->IsCancelled() )
		{
			Msg( "Cancelling\n" );
			break;
		}
	}

	g_pProgressDialog->Finish();

	ReconcileAnimationBitmaps();
}

void IFaceposerModels::CFacePoserModel::RecreateAnimationBitmap( int sequence, bool reconcile )
{
	if ( sequence < 0 || sequence >= m_AnimationBitmaps.Count() )
	{
		Assert( 0 );
		return;
	}
	AnimBitmap *slot = m_AnimationBitmaps[ sequence ];
	slot->needsload = true;
	if ( slot->bitmap->valid )
	{
		DeleteObject( slot->bitmap->image );
		slot->bitmap->image = 0;
		slot->bitmap->valid = false;
	}

	char filename[ 512 ];
	Q_snprintf( filename, sizeof( filename ), "%s", GetBitmapFilename( sequence ) );

	if ( filesystem->FileExists( filename ) )
	{
		char fullpath[ 512 ];
		filesystem->RelativePathToFullPath( filename, "MOD", fullpath, sizeof( fullpath ) );
		_unlink( fullpath );
	}

	// Force recreation
	GetBitmapForSequence( sequence );

	if ( reconcile )
	{
		ReconcileAnimationBitmaps( );
	}
}

void IFaceposerModels::CFacePoserModel::Release( void )
{
	m_pModel->FreeModel( true );
}

void IFaceposerModels::CFacePoserModel::Restore( void )
{
	StudioModel *save = g_pStudioModel;

	g_pStudioModel = m_pModel;

	if (m_pModel->LoadModel( m_pModel->GetFileName() ) )
	{
		m_pModel->PostLoadModel( m_pModel->GetFileName() );
		m_pModel->SetSequence( m_pModel->LookupSequence( "idle_subtle" ) );
	}
	
	g_pStudioModel = save;

	SetupModelFlexcontrollerLinks( m_pModel );
}


IFaceposerModels::IFaceposerModels()
{
	m_nLastRenderFrame = -1;
	m_nForceModelIndex = -1;
}

IFaceposerModels::~IFaceposerModels()
{
	while ( m_Models.Count() > 0 )
	{
		delete m_Models[ 0 ];
		m_Models.Remove( 0 );
	}
}

IFaceposerModels::CFacePoserModel *IFaceposerModels::GetEntry( int index )
{
	if ( index < 0 || index >= Count() )
		return NULL;

	CFacePoserModel *m = m_Models[ index ];
	if ( !m )
		return NULL;
	return m;
}

int IFaceposerModels::Count( void ) const
{
	return m_Models.Count();
}

char const *IFaceposerModels::GetModelName( int index )
{
	CFacePoserModel *entry = GetEntry( index );
	if ( !entry )
		return "";

	return entry->GetShortModelName();
}

char const *IFaceposerModels::GetModelFileName( int index )
{
	CFacePoserModel *entry = GetEntry( index );
	if ( !entry )
		return "";

	return entry->GetModelFileName();
}

void IFaceposerModels::ForceActiveModelIndex( int index )
{
	m_nForceModelIndex = index;
}

void IFaceposerModels::UnForceActiveModelIndex()
{
	m_nForceModelIndex = -1;
}

int IFaceposerModels::GetActiveModelIndex( void ) const
{
	if ( !g_MDLViewer )
		return 0;

	if ( m_nForceModelIndex != -1 )
		return m_nForceModelIndex;

	return g_MDLViewer->GetActiveModelTab();
}

char const *IFaceposerModels::GetActiveModelName( void )
{
	if ( !g_MDLViewer )
		return NULL;

	return GetModelName( GetActiveModelIndex() );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : StudioModel
//-----------------------------------------------------------------------------
StudioModel *IFaceposerModels::GetActiveStudioModel( void )
{
	StudioModel *mdl = GetStudioModel( GetActiveModelIndex() );
	if ( !mdl )
		return g_pStudioModel;
	return mdl;
}

int IFaceposerModels::FindModelByFilename( char const *filename )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		if ( !stricmp( m->GetModelFileName(), filename ) )
			return i;
	}

	return -1;
}

void SetupModelFlexcontrollerLinks( StudioModel *model );

int IFaceposerModels::LoadModel( char const *filename )
{
	MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );

	int idx = FindModelByFilename( filename );
	if ( idx == -1 && Count() < MAX_FP_MODELS )
	{
		StudioModel *model = new StudioModel();

		StudioModel *save = g_pStudioModel;
		g_pStudioModel = model;
		if ( !model->LoadModel( filename ) )
		{
			delete model;
			g_pStudioModel = save;
			return 0; // ?? ERROR
		}
		g_pStudioModel = save;

		model->SetSequence( model->LookupSequence( "idle_subtle" ) );
		int idx = model->GetSequence();
		model->SetSequence( idx );

		SetupModelFlexcontrollerLinks( model );

		if (!LoadViewerSettings( filename, model ))
		{
			InitViewerSettings( "faceposer" );
		}
		model->ClearOverlaysSequences();


		CFacePoserModel *newEntry = new CFacePoserModel( filename, model );
		
		idx = m_Models.AddToTail( newEntry );

		g_MDLViewer->InitModelTab();
		
		g_MDLViewer->SetActiveModelTab( idx );

		//g_pControlPanel->CenterOnFace();
	}
	return idx;
}

void IFaceposerModels::FreeModel( int index  )
{
	CFacePoserModel *entry = GetEntry( index );
	if ( !entry )
		return;

	StudioModel *m = entry->GetModel();

	SaveViewerSettings( m->GetFileName(), m );

	m->FreeModel( false );
	delete m;

	delete entry;
	m_Models.Remove( index );

	g_MDLViewer->InitModelTab();
}

void IFaceposerModels::CloseAllModels( void )
{
	int c = Count();
	for ( int i = c - 1; i >= 0; i-- )
	{
		FreeModel( i );
	}
}

StudioModel *IFaceposerModels::GetStudioModel( int index )
{
	CFacePoserModel *m = GetEntry( index );
	if ( !m )
		return NULL;

	if ( !m->GetModel() )
		return NULL;

	return m->GetModel();
}

CStudioHdr *IFaceposerModels::GetStudioHeader( int index )
{
	StudioModel *m = GetStudioModel( index );
	if ( !m )
		return NULL;

	CStudioHdr *hdr = m->GetStudioHdr();
	if ( !hdr )
		return NULL;
	return hdr;
}

int IFaceposerModels::GetModelIndexForActor( char const *actorname )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		if ( !stricmp( m->GetActorName(), actorname ) )
			return i;
	}

	return 0;
}

StudioModel *IFaceposerModels::GetModelForActor( char const *actorname )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		if ( !stricmp( m->GetActorName(), actorname ) )
			return m->GetModel();
	}

	return NULL;
}

char const *IFaceposerModels::GetActorNameForModel( int modelindex )
{
	CFacePoserModel *m = GetEntry( modelindex );
	if ( !m )
		return "";
	return m->GetActorName();
}

void IFaceposerModels::SetActorNameForModel( int modelindex, char const *actorname )
{
	CFacePoserModel *m = GetEntry( modelindex );
	if ( !m )
		return;

	m->SetActorName( actorname );
}

void IFaceposerModels::SaveModelList( void )
{
	workspacefiles->StartStoringFiles( IWorkspaceFiles::MODELDATA );
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		workspacefiles->StoreFile( IWorkspaceFiles::MODELDATA, m->GetModelFileName() );
	}
	workspacefiles->FinishStoringFiles( IWorkspaceFiles::MODELDATA );
}

void IFaceposerModels::LoadModelList( void )
{
	int files = workspacefiles->GetNumStoredFiles( IWorkspaceFiles::MODELDATA );
	for ( int i = 0; i < files; i++ )
	{
		char const *filename = workspacefiles->GetStoredFile( IWorkspaceFiles::MODELDATA, i );
		LoadModel( filename );
	}
}

void IFaceposerModels::ReleaseModels( void )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		m->Release();
	}
}

void IFaceposerModels::RestoreModels( void )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		m->Restore();
	}
}


/*
void IFaceposerModels::RefreshModels( void )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		m->Refresh();
	}
}
*/

int IFaceposerModels::CountVisibleModels( void )
{
	int num = 0;
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		if ( m->GetVisibleIn3DView() )
		{
			num++;
		}
	}

	return num;
}

void IFaceposerModels::ShowModelIn3DView( int modelindex, bool show )
{
	CFacePoserModel *m = GetEntry( modelindex );
	if ( !m )
		return;

	m->SetVisibleIn3DView( show );
}

bool IFaceposerModels::IsModelShownIn3DView( int modelindex )
{
	CFacePoserModel *m = GetEntry( modelindex );
	if ( !m )
		return false;

	return m->GetVisibleIn3DView();
}

int IFaceposerModels::GetIndexForStudioModel( StudioModel *model )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		if ( m->GetModel() == model )
			return i;
	}
	return -1;
}

void IFaceposerModels::CheckResetFlexes( void )
{
	int current_render_frame = g_MDLViewer->GetCurrentFrame();
	if ( current_render_frame == m_nLastRenderFrame )
		return;

	m_nLastRenderFrame = current_render_frame;

	// the phoneme editor just adds to the face, so reset the controllers 
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		StudioModel *model = m->GetModel();
		if ( !model )
			continue;

		CStudioHdr *hdr = model->GetStudioHdr();
		if ( !hdr )
			continue;

		for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++ )
		{
			model->SetFlexController( i, 0.0f );
		}
	}
}

void IFaceposerModels::ClearOverlaysSequences( void )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		StudioModel *model = m->GetModel();
		if ( !model )
			continue;

		model->ClearOverlaysSequences();
	}
}

mxbitmapdata_t *IFaceposerModels::GetBitmapForSequence( int modelindex, int sequence )
{
	static mxbitmapdata_t nullbitmap;
	CFacePoserModel *m = GetEntry( modelindex );
	if ( !m )
		return &nullbitmap;

	return m->GetBitmapForSequence( sequence );
}

void IFaceposerModels::RecreateAllAnimationBitmaps( int modelindex )
{
	CFacePoserModel *m = GetEntry( modelindex );
	if ( !m )
		return;

	m->RecreateAllAnimationBitmaps();

}

void IFaceposerModels::RecreateAnimationBitmap( int modelindex, int sequence )
{
	CFacePoserModel *m = GetEntry( modelindex );
	if ( !m )
		return;

	m->RecreateAnimationBitmap( sequence, true );
}

int IFaceposerModels::CountActiveSources()
{
	int count = 0;
	
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		StudioModel *model = m->GetModel();
		if ( !model )
			continue;

		count += model->m_mouth.GetNumVoiceSources();
	}

	return count;
}

void IFaceposerModels::ClearModelTargets( bool force /*=false*/ )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		StudioModel *mdl = m->GetModel();
		if ( !mdl )
			continue;

		mdl->ClearLookTargets();
	}
}

void IFaceposerModels::SetSolveHeadTurn( int solve )
{
	int c = Count();
	for ( int i = 0; i < c; i++ )
	{
		CFacePoserModel *m = GetEntry( i );
		if ( !m )
			continue;

		StudioModel *mdl = m->GetModel();
		if ( !mdl )
			continue;

		mdl->SetSolveHeadTurn( solve );
	}
}


static IFaceposerModels g_ModelManager;
IFaceposerModels *models = &g_ModelManager;