//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Helper methods + classes for file access
//
//===========================================================================//

#include "tier3/choreoutils.h"
#include "tier3/tier3.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "studio.h"
#include "../game/shared/choreoscene.h"
#include "../game/shared/choreoevent.h"
#include "tier1/KeyValues.h"
#include "bone_setup.h"
#include "soundchars.h"


//-----------------------------------------------------------------------------
// Find sequence by name 
//-----------------------------------------------------------------------------
static int LookupSequence( CStudioHdr *pStudioHdr, const char *pSequenceName )
{
	for ( int i = 0; i < pStudioHdr->GetNumSeq(); i++ )
	{
		if ( !Q_stricmp( pSequenceName, pStudioHdr->pSeqdesc( i ).pszLabel() ) )
			return i;
	}
	return -1;
}


//-----------------------------------------------------------------------------
// Returns sequence flags 
//-----------------------------------------------------------------------------
static int GetSequenceFlags( CStudioHdr *pStudioHdr, int nSequence )
{
	if ( !pStudioHdr || nSequence < 0 || nSequence >= pStudioHdr->GetNumSeq() )
		return 0;
	mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( nSequence );
	return seqdesc.flags;
}


//-----------------------------------------------------------------------------
// Does a sequence loop? 
//-----------------------------------------------------------------------------
static bool DoesSequenceLoop( CStudioHdr *pStudioHdr, int nSequence )
{
	int nFlags = GetSequenceFlags( pStudioHdr, nSequence );
	bool bLooping = ( nFlags & STUDIO_LOOPING ) ? true : false;
	return bLooping;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool AutoAddGestureKeys( CChoreoEvent *e, CStudioHdr *pStudioHdr, float *pPoseParameters, bool bCheckOnly )
{
	int iSequence = LookupSequence( pStudioHdr, e->GetParameters() );
	if ( iSequence < 0 )
		return false;

	KeyValues *pSeqKeyValues = new KeyValues( "" );
	if ( !pSeqKeyValues->LoadFromBuffer( pStudioHdr->pszName(), Studio_GetKeyValueText( pStudioHdr, iSequence ) ) )
	{
		pSeqKeyValues->deleteThis();
		return false;
	}

	// Do we have a build point section?
	KeyValues *pKVAllFaceposer = pSeqKeyValues->FindKey("faceposer");
	if ( !pKVAllFaceposer )
	{
		pSeqKeyValues->deleteThis();
		return false;
	}

	int nMaxFrame = Studio_MaxFrame( pStudioHdr, iSequence, pPoseParameters ) - 1;

	// Start grabbing the sounds and slotting them in
	KeyValues *pkvFaceposer;
	char szStartLoop[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "loop" };
	char szEndLoop[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "end" };
	char szEntry[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "apex" };
	char szExit[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "end" };

	for ( pkvFaceposer = pKVAllFaceposer->GetFirstSubKey(); pkvFaceposer; pkvFaceposer = pkvFaceposer->GetNextKey() )
	{
		if ( !Q_stricmp( pkvFaceposer->GetName(), "startloop" ) )
		{
			Q_strncpy( szStartLoop, pkvFaceposer->GetString(), sizeof(szStartLoop) );
			continue;
		}
		
		if ( !Q_stricmp( pkvFaceposer->GetName(), "endloop" ) )
		{
			Q_strncpy( szEndLoop, pkvFaceposer->GetString(), sizeof(szEndLoop) );
			continue;
		}
		
		if ( !Q_stricmp( pkvFaceposer->GetName(), "entrytag" ) )
		{
			Q_strncpy( szEntry, pkvFaceposer->GetString(), sizeof(szEntry) );
			continue;
		}
		
		if ( !Q_stricmp( pkvFaceposer->GetName(), "exittag" ) )
		{
			Q_strncpy( szExit, pkvFaceposer->GetString(), sizeof(szExit) );
			continue;
		}
		
		if ( !Q_stricmp( pkvFaceposer->GetName(), "tags" ) )
		{
			if ( nMaxFrame <= 0 )
				continue;

			KeyValues *pkvTags;
			for ( pkvTags = pkvFaceposer->GetFirstSubKey(); pkvTags; pkvTags = pkvTags->GetNextKey() )
			{
				float flPercentage = (float)pkvTags->GetInt() / nMaxFrame;

				CEventAbsoluteTag *ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, pkvTags->GetName() );
				if (ptag)
				{
					// reposition tag
					ptag->SetPercentage( flPercentage );
				}
				else
				{
					e->AddAbsoluteTag( CChoreoEvent::ORIGINAL, pkvTags->GetName(), flPercentage );
					e->AddAbsoluteTag( CChoreoEvent::PLAYBACK, pkvTags->GetName(), flPercentage );
				}
				// lock the original tags so they can't be edited
				ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, pkvTags->GetName() );
				Assert( ptag );
				ptag->SetLocked( true );
			}
			e->VerifyTagOrder();
			e->PreventTagOverlap();
			continue;
		}
	}

	// FIXME: lookup linear tags in sequence data
	{
		CEventAbsoluteTag *ptag;
		ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szStartLoop );
		if (ptag)
		{
			ptag->SetLinear( true );
		}
		ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szStartLoop );
		if (ptag)
		{
			ptag->SetLinear( true );
		}
		ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szEndLoop );
		if (ptag)
		{
			ptag->SetLinear( true );
		}
		ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szEndLoop );
		if (ptag)
		{
			ptag->SetLinear( true );
		}

		ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szEntry );
		if (ptag)
		{
			ptag->SetEntry( true );
		}
		ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szEntry );
		if (ptag)
		{
			ptag->SetEntry( true );
		}
		ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szExit );
		if (ptag)
		{
			ptag->SetExit( true );
		}
		ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szExit );
		if (ptag)
		{
			ptag->SetExit( true );
		}
	}

	pSeqKeyValues->deleteThis();
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool UpdateGestureLength( CChoreoEvent *e, CStudioHdr *pStudioHdr, float *pPoseParameters, bool bCheckOnly )
{
	Assert( e );
	if ( !e )
		return false;

	if ( e->GetType() != CChoreoEvent::GESTURE )
		return false;

	int iSequence = LookupSequence( pStudioHdr, e->GetParameters() );
	if ( iSequence < 0 )
		return false;

	bool bChanged = false;
	float flSeqDuration = Studio_Duration( pStudioHdr, iSequence, pPoseParameters );
	float flCurDuration;
	e->GetGestureSequenceDuration( flCurDuration );
	if ( flSeqDuration != 0.0f && flSeqDuration != flCurDuration )
	{
		bChanged = true;
		if ( !bCheckOnly )
		{
			e->SetGestureSequenceDuration( flSeqDuration );
		}
	}

	return bChanged;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool UpdateSequenceLength( CChoreoEvent *e, CStudioHdr *pStudioHdr, float *pPoseParameters, bool bCheckOnly, bool bVerbose )
{
	Assert( e );
	if ( !e )
		return false;

	if ( e->GetType() != CChoreoEvent::SEQUENCE )
	{
		if ( bVerbose )
		{
			ConMsg( "UpdateSequenceLength:  called on non-SEQUENCE event %s\n", e->GetName() );
		}
		return false;
	}

	int iSequence = LookupSequence( pStudioHdr, e->GetParameters() );
	if ( iSequence < 0 )
		return false;

	bool bChanged = false;
	bool bLooping = DoesSequenceLoop( pStudioHdr, iSequence );
	float flSeqDuration = Studio_Duration( pStudioHdr, iSequence, pPoseParameters );

	if ( bLooping )
	{
		if ( e->IsFixedLength() )
		{
			if ( bCheckOnly )
				return true;
	
			if ( bVerbose )
			{
				ConMsg( "UpdateSequenceLength:  %s is looping, removing fixed length flag\n", e->GetName() );
			}
			bChanged = true;
		}
		e->SetFixedLength( false );

		if ( !e->HasEndTime() )
		{
			if ( bCheckOnly )
				return true;

			if ( bVerbose )
			{
				ConMsg( "CheckSequenceLength:  %s is looping, setting default end time\n", e->GetName() );
			}
			e->SetEndTime( e->GetStartTime() + flSeqDuration );
			bChanged = true;
		}

		return bChanged;
	}

	if ( !e->IsFixedLength() )
	{
		if ( bCheckOnly )
			return true;

		if ( bVerbose )
		{
			ConMsg( "CheckSequenceLength:  %s is fixed length, removing looping flag\n", e->GetName() );
		}
		bChanged = true;
	}
	e->SetFixedLength( true );

	if ( e->HasEndTime() )
	{
		float dt = e->GetDuration();
		if ( fabs( dt - flSeqDuration ) > 0.01f )
		{
			if ( bCheckOnly )
				return true;
			if ( bVerbose )
			{
				ConMsg( "CheckSequenceLength:  %s has wrong duration, changing length from %f to %f seconds\n",
					e->GetName(), dt, flSeqDuration );
			}
			bChanged = true;
		}
	}
	else
	{
		if ( bCheckOnly )
			return true;
		if ( bVerbose )
		{
			ConMsg( "CheckSequenceLength:  %s has wrong duration, changing length to %f seconds\n",
				e->GetName(), flSeqDuration );
		}
		bChanged = true;
	}

	if ( !bCheckOnly )
	{
		e->SetEndTime( e->GetStartTime() + flSeqDuration );
	}

	return bChanged;
}


//-----------------------------------------------------------------------------
// Finds sound files associated with events
//-----------------------------------------------------------------------------
const char *GetSoundForEvent( CChoreoEvent *pEvent, CStudioHdr *pStudioHdr )
{
	const char *pSoundName = pEvent->GetParameters();
	if ( Q_stristr( pSoundName, ".wav" ) )
		return PSkipSoundChars( pSoundName );

	const char *pFileName = g_pSoundEmitterSystem->GetWavFileForSound( pSoundName, ( pStudioHdr && pStudioHdr->IsValid() ) ? pStudioHdr->pszName() : NULL );
	return PSkipSoundChars( pFileName );
}