//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "filesystem.h"
#include "viewangleanim.h"
#include "KeyValues.h"

#include "tier0/memdbgon.h"

extern ConVar cl_pitchdown;
extern ConVar cl_pitchup;


// ConCommands useful for creating view animations
CViewAngleAnimation *g_pTestAnimation = NULL;

// create a view animation object to be used for creating an animation. parameter is flags
CON_COMMAND( viewanim_create, "viewanim_create" )
{
	if ( g_pTestAnimation )
	{
		delete g_pTestAnimation;
		g_pTestAnimation = NULL;
	}

	int flags = 0;
	if ( args.ArgC() > 1 )
	{
		flags = atoi( args[1] );
	}

	g_pTestAnimation = CREATE_ENTITY( CViewAngleAnimation, "viewangleanim" );

	if ( g_pTestAnimation )
	{
		g_pTestAnimation->Spawn();
	}	
}

// run the test animation
void TestViewAnim( void )
{
	if ( g_pTestAnimation )
	{
		QAngle angles;
		engine->GetViewAngles( angles );

		g_pTestAnimation->RunAnimation( angles );
	}
	else
		Msg( "No view anim created\n" );
}
ConCommand viewanim_test( "viewanim_test", TestViewAnim, "test view animation" );

// set view angles to (0,0,0)
void ResetViewAngles( void )
{
	// create a blank anim
	QAngle angles = vec3_angle;
	engine->SetViewAngles( angles );
}
ConCommand viewanim_reset( "viewanim_reset", ResetViewAngles, "reset view angles!", FCVAR_CHEAT );

// add a key frame to the test animation. first parameter is the time taken to get to this keyframe
CON_COMMAND_F( viewanim_addkeyframe, "", FCVAR_CHEAT )
{
	if ( g_pTestAnimation )
	{
		QAngle vecTarget;
		engine->GetViewAngles( vecTarget );

		float flDelay = 0.2;
		if (args.ArgC() > 1)
		{
			flDelay = atof( args[1] );
		}

		int iFlags = 0;
		if (args.ArgC() > 1)
		{
			iFlags = atof( args[2] );
		}

		g_pTestAnimation->AddKeyFrame( new CViewAngleKeyFrame( vecTarget, flDelay, iFlags ) );
	}
	else
		Msg( "No view anim created, use viewanim_create" );
}


// save the current test anim, pass filename
CON_COMMAND( viewanim_save, "Save current animation to file" )
{
	if (args.ArgC() < 2)
		return;

	if ( g_pTestAnimation )
	{	
		g_pTestAnimation->SaveAsAnimFile( args[1] );
	}
	else
	{
		Msg( "No view anim created\n" );
	}
}

// load a view animation file into the test anim
CON_COMMAND( viewanim_load, "load animation from file" )
{
	if (args.ArgC() < 2)
		return;

	if ( g_pTestAnimation )
	{	
		g_pTestAnimation->LoadViewAnimFile( args[1] );
	}
	else
		Msg( "No view anim created\n" );
}

LINK_ENTITY_TO_CLASS( viewangleanim, CViewAngleAnimation );

CViewAngleAnimation::CViewAngleAnimation()
{
}

CViewAngleAnimation::~CViewAngleAnimation()
{
	DeleteKeyFrames();
}

void CViewAngleAnimation::Spawn( void )
{
	m_iFlags = 0;
	QAngle angles;
	engine->GetViewAngles( angles );	

	/*
	if ( m_iFlags & VIEWANIM_RELATIVE )
	{
		AddKeyFrame( new CViewAngleKeyFrame( vec3_angle, 0.0, 0 ) );

		// seed this so we can add keyframes and have them calc the delta properly
		m_vecBaseAngles = angles;
	}
	else
	{
		AddKeyFrame( new CViewAngleKeyFrame( angles, 0.0, 0 ) );
	}
	*/

	m_bFinished = true;	// don't run right away

	ClientEntityList().AddNonNetworkableEntity(	this );
	SetNextClientThink( CLIENT_THINK_ALWAYS );
}

void CViewAngleAnimation::DeleteKeyFrames()
{
	int i, c;

	c = m_KeyFrames.Count();
	for ( i = c - 1; i >= 0 ; --i )
	{
		delete m_KeyFrames[ i ];
	}
	m_KeyFrames.RemoveAll();
}

void CViewAngleAnimation::LoadViewAnimFile( const char *pKeyFrameFileName )
{
	DeleteKeyFrames();

	// load keyvalues from this file and stuff them in as keyframes
	KeyValues *pData = new KeyValues( pKeyFrameFileName );

	if ( false == pData->LoadFromFile( filesystem, pKeyFrameFileName, "GAME" ) )
	{
		Warning( "CViewAngleAnimation::LoadViewAnimFile failed to load script %s\n", pKeyFrameFileName );
		pData->deleteThis();
		return;
	}

	QAngle angles;
	float flTime;
	int iFlags;

	KeyValues *pKey = pData->GetFirstSubKey();

	while ( pKey )
	{
		// angles
		const char *pszAngles = pKey->GetString( "angles", "0 0 0" );
		sscanf( pszAngles, "%f %f %f", &angles[0], &angles[1], &angles[2] );

		// time
		flTime = pKey->GetFloat( "time", 0.001 );

		// flags
		iFlags = pKey->GetInt( "flags", 0 );

		AddKeyFrame( new CViewAngleKeyFrame( angles, flTime, iFlags ) );

		pKey = pKey->GetNextKey();
	}

	pData->deleteThis();
}

void CViewAngleAnimation::SaveAsAnimFile( const char *pKeyFrameFileName )
{
	// save all of our keyframes into the file
	KeyValues *pData = new KeyValues( pKeyFrameFileName );

	pData->SetInt( "flags", m_iFlags );

	KeyValues *pKey = new KeyValues( "keyframe" );
	int i;
	int c = m_KeyFrames.Count();
	char buf[64];
	for ( i=0;i<c;i++ )
	{
		pKey = pData->CreateNewKey();

		Q_snprintf( buf, sizeof(buf), "%f %f %f",
			m_KeyFrames[i]->m_vecAngles[0],
			m_KeyFrames[i]->m_vecAngles[1],
			m_KeyFrames[i]->m_vecAngles[2] );

		pKey->SetString( "angles", buf );
		pKey->SetFloat( "time", m_KeyFrames[i]->m_flTime );
		pKey->SetInt( "flags", m_KeyFrames[i]->m_iFlags );
	}

	pData->SaveToFile( filesystem, pKeyFrameFileName, NULL );
	pData->deleteThis();
}

void CViewAngleAnimation::AddKeyFrame( CViewAngleKeyFrame *pKeyFrame )
{
	pKeyFrame->m_vecAngles -= m_vecBaseAngles;
	m_KeyFrames.AddToTail( pKeyFrame );
}

bool CViewAngleAnimation::IsFinished( void )
{
	return m_bFinished;
}

void CViewAngleAnimation::RunAnimation( QAngle angles )
{
	if ( m_KeyFrames.Count() == 0 )
	{
		Warning( "CViewAngleAnimation::RunAnimation called on an empty view animation\n" );
		return;
	}

	m_flAnimStartTime = gpGlobals->curtime;
	m_bFinished = false;
	m_vecBaseAngles = angles;

	m_iFlags = m_KeyFrames[0]->m_iFlags;

	if ( !( m_iFlags & VIEWANIM_RELATIVE ) )
	{
		m_KeyFrames[0]->m_vecAngles = angles;
	}
}

void CViewAngleAnimation::ClientThink()
{
	if ( IsFinished() )
		return;

	float flCurrentTime = gpGlobals->curtime - m_flAnimStartTime;

	if ( flCurrentTime < 0 )
		flCurrentTime = 0.001;

	// find two nearest points
	int i, c;
	c = m_KeyFrames.Count();
	float flTime = 0;
	for ( i=0;i<c;i++ )
	{
		if ( flTime + m_KeyFrames[i]->m_flTime > flCurrentTime )
		{
			break;
		}

		flTime += m_KeyFrames[i]->m_flTime;
	}

	Assert( i > 0 );

	if ( i >= c )
	{
		if ( i > 0 )
		{
			// animation complete, set to end point
			SetAngles( m_KeyFrames[i-1]->m_vecAngles );
		}

		if ( m_pAnimCompleteCallback )
		{
			m_pAnimCompleteCallback();
		}

		m_bFinished = true;
		return;
	}

	if ( m_KeyFrames[i]->m_iFlags != m_iFlags )
	{
		if ( ( m_KeyFrames[i]->m_iFlags & VIEWANIM_RELATIVE ) && !( m_iFlags & VIEWANIM_RELATIVE ) )
		{
            // new relative position is current angles
			engine->GetViewAngles( m_vecBaseAngles );
		}

		// copy the rest over
		m_iFlags = m_KeyFrames[i]->m_iFlags;
	}

	// previous frame is m_KeyFrames[i-1];
	// next frame is m_KeyFrames[i];
	float flFraction = ( flCurrentTime - flTime ) / ( m_KeyFrames[i]->m_flTime );

	Vector v0, v1, v2, v3;

	if ( i-2 <= 0 )
	{
		QAngleToVector( m_KeyFrames[i-1]->m_vecAngles, v0 );
	}
	else
	{
		QAngleToVector( m_KeyFrames[i-2]->m_vecAngles, v0 );
	}

	QAngleToVector( m_KeyFrames[i-1]->m_vecAngles, v1 );
	QAngleToVector( m_KeyFrames[i]->m_vecAngles, v2 );

	if ( i+1 >= c )
	{
		QAngleToVector( m_KeyFrames[i]->m_vecAngles, v3 );
	}
	else
	{
		QAngleToVector( m_KeyFrames[i+1]->m_vecAngles, v3 );
	}

	Vector out;
	Catmull_Rom_Spline( v0,	v1, v2,	v3,	flFraction,	out );

	QAngle vecCalculatedAngles;
	QAngleToVector( out, vecCalculatedAngles );
	SetAngles( vecCalculatedAngles );
}

void CViewAngleAnimation::SetAngles( QAngle vecCalculatedAngles )
{
	if ( m_iFlags & VIEWANIM_RELATIVE )
		vecCalculatedAngles += m_vecBaseAngles;

	QAngle vecViewAngle;
	engine->GetViewAngles( vecViewAngle );

	if ( !(FBitSet( m_iFlags, VIEWANIM_IGNORE_X ) ) )
		vecViewAngle[PITCH] = vecCalculatedAngles[PITCH];

	if ( !(FBitSet( m_iFlags, VIEWANIM_IGNORE_Y ) ) )
		vecViewAngle[YAW] = vecCalculatedAngles[YAW];

	if ( !(FBitSet( m_iFlags, VIEWANIM_IGNORE_Z ) ) )
		vecViewAngle[ROLL] = vecCalculatedAngles[ROLL];

	// clamp pitch
	vecViewAngle[PITCH] = clamp( vecViewAngle[PITCH], -cl_pitchup.GetFloat(), cl_pitchdown.GetFloat() );

	engine->SetViewAngles( vecViewAngle );
}