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

#include "cbase.h"

#if defined( REPLAY_ENABLED )

#include "genericclassbased_replay.h"
#include "clientmode_shared.h"
#include "replay/ireplaymoviemanager.h"
#include "replay/ireplayfactory.h"
#include "replay/ireplayscreenshotmanager.h"
#include "replay/screenshot.h"
#include "replay/gamedefs.h"
#include <time.h>

//----------------------------------------------------------------------------------------

extern IReplayScreenshotManager *g_pReplayScreenshotManager;

//----------------------------------------------------------------------------------------

CGenericClassBasedReplay::CGenericClassBasedReplay()
:	m_nPlayerTeam( 0 )
{
	m_szKillerName[ 0 ] = 0;

	m_nPlayerClass = REPLAY_CLASS_UNDEFINED;
	m_nKillerClass = REPLAY_CLASS_UNDEFINED;
}

CGenericClassBasedReplay::~CGenericClassBasedReplay()
{
	OnEndRecording();

	m_vecKills.PurgeAndDeleteElements();
	m_vecDominations.PurgeAndDeleteElements();
	m_vecAssisterDominations.PurgeAndDeleteElements();
	m_vecRevenges.PurgeAndDeleteElements();
	m_vecAssisterRevenges.PurgeAndDeleteElements();
}

void CGenericClassBasedReplay::OnBeginRecording()
{
	BaseClass::OnBeginRecording();

	Assert( gameeventmanager );
	ListenForGameEvent( "player_death" );
}

void CGenericClassBasedReplay::OnEndRecording()
{
	if ( gameeventmanager )
	{
		gameeventmanager->RemoveListener( this );
	}

	BaseClass::OnEndRecording();
}

void CGenericClassBasedReplay::OnComplete()
{
	BaseClass::OnComplete();
}

bool CGenericClassBasedReplay::ShouldAllowDelete() const
{
	return g_pClientReplayContext->GetMovieManager()->GetNumMoviesDependentOnReplay( this ) == 0;
}

void CGenericClassBasedReplay::OnDelete()
{
	BaseClass::OnDelete();
}

void CGenericClassBasedReplay::FireGameEvent( IGameEvent *pEvent )
{
}

void CGenericClassBasedReplay::Update()
{
	BaseClass::Update();

	// Record any new stats
	RecordUpdatedStats();

	// Setup next update
	m_flNextUpdateTime = engine->Time() + .1f;
}

float CGenericClassBasedReplay::GetKillScreenshotDelay()
{
	ConVarRef replay_screenshotkilldelay( "replay_screenshotkilldelay" );
	return replay_screenshotkilldelay.IsValid() ? replay_screenshotkilldelay.GetFloat() : 0.5f;
}

void CGenericClassBasedReplay::RecordUpdatedStats()
{
	// Get current stats
	static RoundStats_t s_curStats;
	if ( !GetCurrentStats( s_curStats ) )
		return;

	// Go through each stat and see if it's changed
	for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
	{
		const int nCurStat = s_curStats.Get( i );
		const int nRefStat = m_refStats.Get( i );

		if ( nCurStat != nRefStat )
		{
			// Calculate new stat based on reference
			const int nLifeStat = nCurStat - nRefStat;

			if ( nLifeStat != m_lifeStats.Get( i ) )
			{
				ConVarRef replay_debug( "replay_debug" );
				if ( replay_debug.IsValid() && replay_debug.GetBool() )
				{
					Msg( "REPLAY: Player stat \"%s\" changed from %i to %i.\n", GetStatString( i ), nRefStat, nCurStat );
				}

				// Set the new stat
				m_lifeStats.Set( i, nLifeStat );
			}
		}
	}
}

bool CGenericClassBasedReplay::Read( KeyValues *pIn )
{
	if ( !BaseClass::Read( pIn ) )
		return false;

	// Read player class
	m_nPlayerClass = pIn->GetInt( "player_class" );		Assert( IsValidClass( m_nPlayerClass ) );
	
	// Read player team
	m_nPlayerTeam = pIn->GetInt( "player_team" );		Assert( IsValidTeam( m_nPlayerTeam ) );

	// Read killer info
	m_nKillerClass = pIn->GetInt( "killer_class" );
	V_strcpy( m_szKillerName, pIn->GetString( "killer_name" ) );

	// Make sure vector is clear
	Assert( GetKillCount() == 0 );

	// Read all kill data and add the kills vector
	KeyValues *pKills = pIn->FindKey( "kills" );
	if ( pKills )
	{
		FOR_EACH_TRUE_SUBKEY( pKills, pKill )
		{
			// Create the kill data
			AddKill(
				pKill->GetString( "victim_name" ),
				pKill->GetInt( "victim_class" )
			);
		}
	}

	AddKillStats( m_vecDominations        , pIn, "dominations", REPLAY_GAMESTATS_DOMINATIONS );
	AddKillStats( m_vecAssisterDominations, pIn, "assister_dominations", REPLAY_GAMESTATS_UNDEFINED );
	AddKillStats( m_vecRevenges           , pIn, "revenges", REPLAY_GAMESTATS_REVENGE );
	AddKillStats( m_vecAssisterRevenges   , pIn, "assister_revenges", REPLAY_GAMESTATS_UNDEFINED );

	// Read stats by index
	KeyValues *pStats = pIn->FindKey( "stats" );
	if ( pStats )
	{
		for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
		{
			char szStatKey[ 16 ];
			V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
			m_lifeStats.Set( i, pStats->GetInt( szStatKey ) );
		}
	}

	return true;
}

void CGenericClassBasedReplay::AddKillStats( CUtlVector< GenericStatInfo_t * > &vecKillStats, KeyValues *pIn, const char *pSubKeyName, int iStatIndex )
{
	Assert( vecKillStats.Count() == 0 );
	KeyValues *pSubKey = pIn->FindKey( pSubKeyName );
	if ( pSubKey )
	{
		FOR_EACH_TRUE_SUBKEY( pSubKey, pCurKillStat )
		{
			GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
			pNewKillStat->m_nVictimFriendId = pCurKillStat->GetInt( "victim_friend_id" );
			pNewKillStat->m_nAssisterFriendId = pCurKillStat->GetInt( "assister_friend_id" );
			vecKillStats.AddToTail( pNewKillStat );
		}
	}

	// Duplicate the data in the life stats
	if ( iStatIndex > m_nStatUndefined )
	{
		m_lifeStats.Set( iStatIndex, vecKillStats.Count() );
	}
}

void CGenericClassBasedReplay::Write( KeyValues *pOut )
{
	BaseClass::Write( pOut );

	// Write player class
	pOut->SetInt( "player_class", m_nPlayerClass );

	// Write player team
	pOut->SetInt( "player_team", m_nPlayerTeam );

	// Write killer info
	pOut->SetInt( "killer_class", m_nKillerClass );
	pOut->SetString( "killer_name", m_szKillerName );

	// Write kills
	KeyValues *pKills = new KeyValues( "kills" );
	pOut->AddSubKey( pKills );

	for ( int i = 0; i < GetKillCount(); ++i )
	{
		KillData_t *pCurKill = m_vecKills[ i ];

		KeyValues *pKillOut = new KeyValues( "kill" );
		pKills->AddSubKey( pKillOut );

		// Write kill data
		pKillOut->SetString( "victim_name", pCurKill->m_szPlayerName );
		pKillOut->SetInt( "victim_class", pCurKill->m_nPlayerClass );
	}

	WriteKillStatVector( m_vecDominations        , "dominations"         , "domination"         , pOut, 1 );
	WriteKillStatVector( m_vecAssisterDominations, "assister_dominations", "assister_domination", pOut, 2 );
	WriteKillStatVector( m_vecRevenges           , "revenges"            , "revenge"            , pOut, 1 );
	WriteKillStatVector( m_vecAssisterRevenges   , "assister_revenges"   , "assister_revenge"   , pOut, 2 );

	// Write non-zero stats by index
	KeyValues *pStats = new KeyValues( "stats" );
	pOut->AddSubKey( pStats );

	for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
	{
		const int nCurStat = m_lifeStats.Get( i );
		if ( nCurStat )
		{
			char szStatKey[ 16 ];
			V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
			pStats->SetInt( szStatKey, nCurStat );
		}
	}
}

void CGenericClassBasedReplay::WriteKillStatVector( CUtlVector< CGenericClassBasedReplay::GenericStatInfo_t * > const &vec, const char *pSubKeyName,
													const char *pElementKeyName, KeyValues *pRootKey, int nNumMembersToWrite ) const
{
	Assert( nNumMembersToWrite >= 1 );

	// Write dominations
	KeyValues *pSubKey = new KeyValues( pSubKeyName );
	pRootKey->AddSubKey( pSubKey );

	for ( int i = 0; i < vec.Count(); ++i )
	{
		GenericStatInfo_t *pSrcData = vec[ i ];

		KeyValues *pCurSubKey = new KeyValues( pElementKeyName );
		pSubKey->AddSubKey( pCurSubKey );

		// Always write
		pCurSubKey->SetInt( "victim_friend_id", pSrcData->m_nVictimFriendId );

		if ( nNumMembersToWrite > 1 )
		{
			pCurSubKey->SetInt( "assister_friend_id", pSrcData->m_nAssisterFriendId );
		}
	}
}

void CGenericClassBasedReplay::AddKill( const char *pPlayerName, int nPlayerClass )
{
	KillData_t *pNewKillData = new KillData_t;

	V_strcpy( pNewKillData->m_szPlayerName , pPlayerName );
	pNewKillData->m_nPlayerClass = nPlayerClass;

	ConVarRef replay_debug( "replay_debug" );
	if ( replay_debug.IsValid() && replay_debug.GetBool() )
	{
		DevMsg( "\n\nRecorded kill: name=%s, class=%s (this=%i)\n\n", pPlayerName, GetPlayerClass( nPlayerClass ), (int)this );
	}

	m_vecKills.AddToTail( pNewKillData );
}

const char *CGenericClassBasedReplay::GetPlayerClass() const
{
	return GetPlayerClass( m_nPlayerClass );
}

void CGenericClassBasedReplay::AddDomination( int nVictimID )
{
	AddKillStatFromUserIds( m_vecDominations, nVictimID );
}

void CGenericClassBasedReplay::AddAssisterDomination( int nVictimID, int nAssiterID )
{
	AddKillStatFromUserIds( m_vecAssisterDominations, nVictimID, nAssiterID );
}

void CGenericClassBasedReplay::AddRevenge( int nVictimID )
{
	AddKillStatFromUserIds( m_vecRevenges, nVictimID );
}

void CGenericClassBasedReplay::AddAssisterRevenge( int nVictimID, int nAssiterID )
{
	AddKillStatFromUserIds( m_vecAssisterRevenges, nVictimID, nAssiterID );
}

void CGenericClassBasedReplay::AddKillStatFromUserIds( CUtlVector< GenericStatInfo_t * > &vec, int nVictimId, int nAssisterId/*=0*/ )
{
	uint32 nVictimFriendId;
	if ( !GetFriendIdFromUserId( engine->GetPlayerForUserID( nVictimId ), nVictimFriendId ) )
		return;

	uint32 nAssisterFriendId = 0;
	if ( nAssisterId && !GetFriendIdFromUserId( engine->GetPlayerForUserID( nAssisterId ), nAssisterFriendId ) )
		return;

	AddKillStatFromFriendIds( vec, nVictimFriendId, nAssisterFriendId );
}

void CGenericClassBasedReplay::AddKillStatFromFriendIds( CUtlVector< GenericStatInfo_t * > &vec, uint32 nVictimFriendId, uint32 nAssisterFriendId/*=0*/ )
{
	GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
	pNewKillStat->m_nVictimFriendId = nVictimFriendId;
	pNewKillStat->m_nAssisterFriendId = nAssisterFriendId;
	vec.AddToTail( pNewKillStat );
}

bool CGenericClassBasedReplay::GetFriendIdFromUserId( int nPlayerIndex, uint32 &nFriendIdOut ) const
{
	player_info_t pi;
	if ( !steamapicontext->SteamFriends() ||
		 !steamapicontext->SteamUtils() ||
		 !engine->GetPlayerInfo( nPlayerIndex, &pi ) )
	{
		AssertMsg( 0, "REPLAY: Failed to add domination" );
		nFriendIdOut = 0;
		return false;
	}

	nFriendIdOut = pi.friendsID;

	return true;
}

const char *CGenericClassBasedReplay::GetMaterialFriendlyPlayerClass() const
{
	return GetPlayerClass();
}

const char *CGenericClassBasedReplay::GetKillerName() const
{
	Assert( WasKilled() );
	return m_szKillerName;
}

const char *CGenericClassBasedReplay::GetKillerClass() const
{
	Assert( WasKilled() );
	return GetPlayerClass( m_nKillerClass );
}

void CGenericClassBasedReplay::DumpGameSpecificData() const
{
	DevMsg( "  class: %s\n", GetPlayerClass() );
	
	// Print kills
	DevMsg( "  %i kills:\n", GetKillCount() );
	for ( int i = 0; i < GetKillCount(); ++i )
	{
		KillData_t *pCurKill = m_vecKills[ i ];
		Msg( "    kill %i: name=%s class=%s\n", i, pCurKill->m_szPlayerName, GetPlayerClass( pCurKill->m_nPlayerClass ) );
	}

	if ( !WasKilled() )
	{
		Msg( "  No killer.\n" );
		return;
	}

	// Print killer info
	Msg( "  killer: name=%s class=%s\n", m_szKillerName, GetPlayerClass( m_nKillerClass ) );
}

void CGenericClassBasedReplay::SetPlayerClass( int nPlayerClass )
{
	//Assert( IsValidClass( nPlayerClass ) );
	m_nPlayerClass = nPlayerClass;

	// Setup reference stats if this is a valid class
	if ( IsValidClass( nPlayerClass ) )
	{
		GetCurrentStats( m_refStats );
	}
}

void CGenericClassBasedReplay::SetPlayerTeam( int nPlayerTeam )
{
	m_nPlayerTeam = nPlayerTeam;
}

void CGenericClassBasedReplay::RecordPlayerDeath( const char *pKillerName, int nKillerClass )
{
	V_strcpy( m_szKillerName, pKillerName );
	m_nKillerClass = nKillerClass;
}


//----------------------------------------------------------------------------------------

#endif