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

#include "cl_replaymanager.h"
#include "replay/ienginereplay.h"
#include "replay/iclientreplay.h"
#include "replay/ireplaymoviemanager.h"
#include "replay/ireplayfactory.h"
#include "replay/replayutils.h"
#include "replay/ireplaymovierenderer.h"
#include "replay/shared_defs.h"
#include "baserecordingsession.h"
#include "cl_screenshotmanager.h"
#include "cl_recordingsession.h"
#include "cl_recordingsessionblock.h"
#include "replaysystem.h"
#include "cl_replaymoviemanager.h"
#include "replay_dbg.h"
#include "inetchannel.h"
#include "cl_replaycontext.h"
#include <time.h>
#include "vprof.h"

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

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

extern IEngineClientReplay *g_pEngineClient;
extern ConVar replay_postdeathrecordtime;

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

#define REPLAY_INDEX_VERSION	0

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

CReplayManager::CReplayManager()
:	m_pPendingReplay( NULL ),
	m_pReplayLastLife( NULL ),
	m_pReplayThisLife( NULL ),
	m_flPlayerSpawnCreateReplayFailTime( 0.0f )
{
}

CReplayManager::~CReplayManager()
{
}

bool CReplayManager::Init( CreateInterfaceFn fnCreateFactory )
{
	// Get out if the user is running an unsupported mod or platform
	if ( !g_pEngine->IsSupportedModAndPlatform() )
		return false;

	// Clear anything already loaded (since we reuse the same instance)
	Clear();

	// Register replay factory
	m_pReplayFactory = GetReplayFactory( fnCreateFactory );		Assert( m_pReplayFactory );

	// Load all replays from disk
	if ( !BaseClass::Init() )
	{
		Warning( "Failed to load replay history!\n" );
	}

	// Session manager init'd by this point - go through and link up replays to sessions
	CL_GetRecordingSessionManager()->OnReplaysLoaded();

	return true;
}

void CReplayManager::Shutdown()
{
	// Get out if the user is running an unsupported mod or platform
	if ( !g_pEngine->IsSupportedModAndPlatform() )
		return;

	// Make sure we aren't waiting to write
	BaseClass::Shutdown();	// Saves
}

IReplayFactory *CReplayManager::GetReplayFactory( CreateInterfaceFn fnCreateFactory )
{
	return (IReplayFactory *)fnCreateFactory( INTERFACE_VERSION_REPLAY_FACTORY, NULL );
}

void CReplayManager::OnSessionStart()
{
	// The pending replay doesn't exist yet at this point as far as I've seen, since the "replay_sessioninfo"
	// event comes down a frame or more after the "replay_recording" replicated cvar is set to 1, which is
	// what triggers AttemptToSetupNewReplay().
	if ( !m_pPendingReplay )
	{
		AttemptToSetupNewReplay();
	}

	if ( m_pPendingReplay )
	{
		// Link up the pending replay to the recording session in progress
		if ( m_pPendingReplay->m_hSession == REPLAY_HANDLE_INVALID )
		{
			ReplayHandle_t hSessionInProgress = CL_GetRecordingSessionManager()->GetRecordingSessionInProgress()->GetHandle();
			m_pPendingReplay->m_hSession = hSessionInProgress;
		}

		// Make sure the spawn tick has the proper server start tick subtracted out
		if ( m_pPendingReplay->m_nSpawnTick < 0 )
		{
			const int nServerStartTick = CL_GetRecordingSessionManager()->m_ServerRecordingState.m_nStartTick;	Assert( nServerStartTick > 0 );
			m_pPendingReplay->m_nSpawnTick = MAX( 0, -m_pPendingReplay->m_nSpawnTick - nServerStartTick );
		}
	}
}

void CReplayManager::OnSessionEnd()
{
	// Complete the pending replay, if there is one
	CompletePendingReplay();
}

const char *CReplayManager::GetRelativeIndexPath() const
{
	return Replay_va( "%s%c", SUBDIR_REPLAYS, CORRECT_PATH_SEPARATOR );
}

CReplay *CReplayManager::Create()
{
	return m_pReplayFactory->Create();
}

IReplayContext *CReplayManager::GetReplayContext() const
{
	return g_pClientReplayContextInternal;
}

bool CReplayManager::ShouldLoadObj( const CReplay *pReplay ) const
{
	return pReplay && pReplay->m_bComplete;
}

void CReplayManager::OnObjLoaded( CReplay *pReplay )
{
	if ( !pReplay )
		return;

	pReplay->m_bSavedDuringThisSession = false;
}

int CReplayManager::GetVersion() const
{
	return REPLAY_INDEX_VERSION;
}

void CReplayManager::ClearPendingReplay()
{
	m_pPendingReplay = NULL;
}

void CReplayManager::SanityCheckReplay( CReplay *pReplay )
{
	if ( !pReplay )
		return;

	// DEBUG: Make sure this replay does not already exist in the list
	FOR_EACH_VEC( Replays(), i )
	{
		if ( Replays()[ i ]->GetHandle() == pReplay->GetHandle() )
		{
			IF_REPLAY_DBG( Warning( "Replay %i already found in history!\n", pReplay->GetHandle() ) );
		}
	}

	if ( pReplay->m_nDeathTick < pReplay->m_nSpawnTick )
	{
		IF_REPLAY_DBG( Warning( "Spawn tick (%i) is greater than death tick (%i)!\n", pReplay->m_nSpawnTick, pReplay->m_nDeathTick ) );
	}
}

void CReplayManager::SaveDanglingReplay()
{
	if ( !m_pReplayThisLife )
		return;

	if ( m_pReplayThisLife->m_bRequestedByUser )
	{
		CompletePendingReplay();
		FlagReplayForFlush( m_pReplayThisLife, false );
	}
}

void CReplayManager::FreeLifeIfNotSaved( CReplay *&pReplay )
{
	if ( pReplay )
	{
		if ( !pReplay->m_bSaved && !IsDirty( pReplay ) )
		{
			CleanupReplay( pReplay );		
		}
		else
		{
			// If it's been saved, don't free the memory, just clear the pointer
			pReplay = NULL;
		}
	}
}

void CReplayManager::CleanupReplay( CReplay *&pReplay )
{
	if ( !pReplay )
		return;

	// Get rid of a replay that was never committed:
	// Remove screenshots taken
	CL_GetScreenshotManager()->DeleteScreenshotsForReplay( pReplay );

	// Free
	delete pReplay;
	pReplay = NULL;
}

void CReplayManager::OnReplayRecordingCvarChanged()
{
	DBG( "OnReplayRecordingCvarChanged()\n" );

	// If set to 0, get out - we don't care
	extern ConVar replay_recording;
	if ( !replay_recording.GetBool() )
	{
		DBG( "   replay_recording is false...aborting\n" );
		return;
	}

	// If OnPlayerSpawn() hasn't failed to create the scratch replay, get out
	if ( m_flPlayerSpawnCreateReplayFailTime == 0.0f )
	{
		DBG( "   m_flPlayerSpawnCreateReplayFailTime == 0.0f...aborting.\n" );
		return;
	}

	DBG( "   Calling AttemptToSetupNewReplay()\n" );

	// Try to create & setup again
	AttemptToSetupNewReplay();

	// Reset
	m_flPlayerSpawnCreateReplayFailTime = 0.0f;
}

void CReplayManager::OnClientSideDisconnect()
{
	SaveDanglingReplay();
	ClearPendingReplay();

	FreeLifeIfNotSaved( m_pReplayLastLife );
	FreeLifeIfNotSaved( m_pReplayThisLife );

	m_flPlayerSpawnCreateReplayFailTime = 0.0f;
}

void CReplayManager::CommitPendingReplayAndBeginDownload()
{
	// Update the last session block we should download
	CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( m_pReplayThisLife->m_hSession ) );
	const int iPostDeathBlockIndex = pSession->UpdateLastBlockToDownload();

	// Update the # of blocks required to reconstruct the replay
	m_pReplayThisLife->m_iMaxSessionBlockRequired = iPostDeathBlockIndex;

	Commit( m_pReplayThisLife );
}

void CReplayManager::CompletePendingReplay()
{
	// Get out if no pending replay
	if ( !m_pPendingReplay )
		return;

	// Get session associated w/ the replay
	CBaseRecordingSession *pSession = CL_GetRecordingSessionManager()->FindSession( m_pPendingReplay->m_hSession );
	
	// Sometimes the session isn't valid here, like when we're first joining a server
	if ( !pSession )
		return;

	Assert( pSession->m_nServerStartRecordTick >= 0 );

	// Cache death tick
	m_pPendingReplay->m_nDeathTick = g_pEngineClient->GetLastServerTickTime() - pSession->m_nServerStartRecordTick;

	SanityCheckReplay( m_pPendingReplay );

	// Calc replay length
	m_pPendingReplay->m_flLength = g_pEngine->TicksToTime(
		m_pPendingReplay->m_nDeathTick -
		m_pPendingReplay->m_nSpawnTick +
		g_pEngine->TimeToTicks( replay_postdeathrecordtime.GetFloat() )
	);

	// Cache player slot so we can start playback of replays from recorder player's perspective
	m_pPendingReplay->m_nPlayerSlot = g_pEngineClient->GetPlayerSlot() + 1;

	// Setup status
	m_pPendingReplay->m_nStatus = CReplay::REPLAYSTATUS_DOWNLOADPHASE;

	// The replay is now "complete," ie has all the data needed
	m_pPendingReplay->m_bComplete = true;

	// Let derived classes do whatever it wants
	m_pPendingReplay->OnComplete();

	// If the replay was requested by the user already, update the # of blocks we should download & commit the replay
	if ( m_pPendingReplay->m_bRequestedByUser )
	{
#ifdef DBGFLAG_ASSERT
		Assert( m_pReplayThisLife->m_bComplete );
#endif
		CommitPendingReplayAndBeginDownload();
	}

	// Before we copy the pointer to "this life," end recording so the replay can do any cleanup (eg listening for game events)
	m_pPendingReplay->OnEndRecording();

	// Cache off scratch replay to "this life"
	m_pReplayThisLife = m_pPendingReplay;

	ClearPendingReplay();
}

bool CReplayManager::Commit( CReplay *pNewReplay )
{
	if ( !g_pClientReplayContextInternal->IsInitialized() || !pNewReplay )
		return false;

	SanityCheckReplay( pNewReplay );

	// NOTE: Marks index as dirty, as well as pNewReplay
	Add( pNewReplay );

	// Save now
	Save();

	return true;
}

//
// IReplayManager implementation
//
CReplay	*CReplayManager::GetReplay( ReplayHandle_t hReplay )
{
	if ( m_pReplayThisLife && m_pReplayThisLife->GetHandle() == hReplay )
		return m_pReplayThisLife;

	return Find( hReplay );
}

void CReplayManager::DeleteReplay( ReplayHandle_t hReplay, bool bNotifyUI )
{
	CReplay *pReplay = GetReplay( hReplay );	Assert( pReplay );

	// The session manager will delete the .dem, the session .dmx and remove the session
	// item itself if this is the last replay associated with it.
	CL_GetRecordingSessionManager()->OnReplayDeleted( pReplay );

	// Notify the replay browser if necessary
	if ( bNotifyUI )
	{
		extern IClientReplay *g_pClient;
		g_pClient->OnDeleteReplay( hReplay );
	}

	// Remove it
	Remove( pReplay );

	// If the replay deleted was just saved and we haven't respawned yet,
	// we need to clear out some stuff so GetReplay() doesn't crash.
	if ( m_pReplayThisLife == pReplay )
	{
		m_pReplayThisLife = NULL;
		m_pPendingReplay = NULL;
	}

	if ( m_pReplayLastLife == pReplay )
	{
		m_pReplayLastLife = NULL;
	}
}

void CReplayManager::FlagReplayForFlush( CReplay *pReplay, bool bForceImmediate )
{
	FlagForFlush( pReplay, bForceImmediate );
}

int CReplayManager::GetUnrenderedReplayCount()
{
	int nCount = 0;
	FOR_EACH_VEC( m_vecObjs, i )
	{
		CReplay *pCurReplay = m_vecObjs[ i ];
		if ( !pCurReplay->m_bRendered &&
			  pCurReplay->m_nStatus == CReplay::REPLAYSTATUS_READYTOCONVERT )
		{
			++nCount;
		}
	}
	return nCount;
}

void CReplayManager::InitReplay( CReplay *pReplay )
{
	// Setup record time right now
	pReplay->m_RecordTime.InitDateAndTimeToNow();

	// Store start time
	pReplay->m_flStartTime = g_pEngine->GetHostTime();

	// Get map name (w/o the path)
	V_FileBase( g_pEngineClient->GetLevelName(), m_pPendingReplay->m_szMapName, sizeof( m_pPendingReplay->m_szMapName ) );

	// Give the replay a default name
	pReplay->AutoNameTitleIfEmpty();
}

CReplay *CReplayManager::CreatePendingReplay()
{
	Assert( m_pPendingReplay == NULL );
	m_pPendingReplay = CreateAndGenerateHandle();

	// If we've already begun recording, link to the session now, otherwise link once
	// we start recording.
	CBaseRecordingSession *pSessionInProgress = CL_GetRecordingSessionInProgress();
	if ( pSessionInProgress )
	{
		m_pPendingReplay->m_hSession = pSessionInProgress->GetHandle();
	}

	InitReplay( m_pPendingReplay );

	// Setup replay handle for screenshots
	CL_GetScreenshotManager()->SetScreenshotReplay( m_pPendingReplay->GetHandle() );

	return m_pPendingReplay;
}

void CReplayManager::AttemptToSetupNewReplay()
{
	DBG( "AttemptToSetupNewReplay()\n" );

	if ( !g_pReplay->IsRecording() || g_pEngineClient->IsPlayingReplayDemo() )
	{
		DBG( "   Aborting...not recording, or playing back replay.\n" );
		m_flPlayerSpawnCreateReplayFailTime = g_pEngine->GetHostTime();
		return;
	}

	// Create the replay if necessary - we only do setup if we're creating
	// a new replay, because on a full update this function may be called
	// even though we're not actually spawning.
	if ( !m_pPendingReplay )
	{
		DBG( "   Creating new replay...\n" );

		// If there is a "last life" replay that was not saved already, delete it
		FreeLifeIfNotSaved( m_pReplayLastLife );

		// Cache last life
		m_pReplayLastLife = m_pReplayThisLife;

		// Create the scratch replay (sets m_pPendingReplay and returns it)
		CReplay *pPendingReplay = CreatePendingReplay();

		SanityCheckReplay( pPendingReplay );

		// "This life" is the scratch replay
		m_pReplayThisLife = pPendingReplay;

		// Setup spawn tick
		const int nServerStartTick = CL_GetRecordingSessionManager()->m_ServerRecordingState.m_nStartTick;
		pPendingReplay->m_nSpawnTick = g_pEngineClient->GetLastServerTickTime() - nServerStartTick;
		if ( nServerStartTick == 0 )
		{
			// Didn't receive the replay_sessioninfo event yet - make spawn tick negative so when the
			// event IS received, we can detect that the server start tick still needs to be subtracted.
			pPendingReplay->m_nSpawnTick *= -1;
		}

		// Setup post-death record time
		extern ConVar replay_postdeathrecordtime;
		pPendingReplay->m_nPostDeathRecordTime = replay_postdeathrecordtime.GetFloat();

		// Let the replay know we're recording
		pPendingReplay->OnBeginRecording();
	}
	else
	{
		DBG( "   NOT creating new replay.\n" );
	}

	// Served its purpose
	m_flPlayerSpawnCreateReplayFailTime = 0.0f;
}

void CReplayManager::Think()
{
	VPROF_BUDGET( "CReplayManager::Think", VPROF_BUDGETGROUP_REPLAY );

	BaseClass::Think();

	DebugThink();

	// Only update pending replay, since it's recording
	// NOTE: we use Sys_FloatTime() here, since the client sets the next update time with engine->Time(),
	// which also uses Sys_FloatTime().
	if ( m_pPendingReplay && m_pPendingReplay->m_flNextUpdateTime <= Sys_FloatTime() )
	{
		m_pPendingReplay->Update();		// Allow the replay's Update() function to set the next update time
	}
}

void CReplayManager::DebugThink()
{
	// Debugging
	if ( replay_debug.GetBool() )
	{
		const char *pReplayNames[] = { "Scratch", "This life", "Last life" };
		CReplay *pReplays[] = { m_pPendingReplay, m_pReplayThisLife, m_pReplayLastLife };
		for ( int i = 0; i < 3; ++i )
		{
			CReplay *pCurReplay = pReplays[ i ];
			if ( !pCurReplay )
			{
				g_pEngineClient->Con_NPrintf( i, "%s: NULL", pReplayNames[ i ] );
				continue;
			}

			g_pEngineClient->Con_NPrintf( i, "%s:  handle=%i  [%i, %i]  C? %s R? %s  MaxBlock: %i", pReplayNames[ i ],
				pCurReplay->GetHandle(), pCurReplay->m_nSpawnTick,
				pCurReplay->m_nDeathTick, pCurReplay->m_bComplete ? "YES" : "NO",
				pCurReplay->m_bRequestedByUser ? "YES" : "NO",
				pCurReplay->m_iMaxSessionBlockRequired
			);
		
			// Screenshot handle
			int nCurLine = 5;
			g_pEngineClient->Con_NPrintf( nCurLine, "Screenshot replay: handle=%i", CL_GetScreenshotManager()->GetScreenshotReplay() );
			nCurLine += 2;

			// Saved replay handles
			g_pEngineClient->Con_NPrintf( nCurLine++, "REPLAYS:" );
			FOR_EACH_REPLAY( j )
			{
				CReplay *pReplay = GET_REPLAY_AT( j );
				g_pEngineClient->Con_NPrintf( nCurLine++, "%i: handle=%i  ticks=[%i %i]", i, pReplay->GetHandle(),
					pReplay->m_nSpawnTick, pReplay->m_nDeathTick );
			}

			// Current tick:
			g_pEngineClient->Con_NPrintf( ++nCurLine, "MAIN tick: %f", g_pEngineClient->GetLastServerTickTime() );
			g_pEngineClient->Con_NPrintf( ++nCurLine, "   server tick: %f", g_pEngineClient->GetLastServerTickTime() );
			nCurLine += 2;
		}
	}
}

float CReplayManager::GetNextThinkTime() const
{
	return g_pEngine->GetHostTime() + 0.1f;
}

CReplay *CReplayManager::GetPlayingReplay()
{
	return g_pReplayDemoPlayer->GetCurrentReplay();
}

CReplay *CReplayManager::GetReplayForCurrentLife()
{
	return m_pReplayThisLife;
}

void CReplayManager::GetReplays( CUtlLinkedList< CReplay *, int > &lstReplays )
{
	lstReplays.RemoveAll();
	FOR_EACH_REPLAY( i )
	{
		lstReplays.AddToTail( GET_REPLAY_AT( i ) );
	}
}

void CReplayManager::GetReplaysAsQueryableItems( CUtlLinkedList< IQueryableReplayItem *, int > &lstReplays )
{
	lstReplays.RemoveAll();
	FOR_EACH_REPLAY( i )
	{
		lstReplays.AddToHead( dynamic_cast< IQueryableReplayItem * >( GET_REPLAY_AT( i ) ) );
	}

	if ( m_pPendingReplay &&
	    !m_pPendingReplay->m_bComplete &&
		 m_pPendingReplay->m_bRequestedByUser )
	{
		Assert( lstReplays.Find( m_pPendingReplay ) == lstReplays.InvalidIndex() );
		lstReplays.AddToHead( m_pPendingReplay );
	}
}

int CReplayManager::GetNumReplaysDependentOnSession( ReplayHandle_t hSession )
{
	int nResult = 0;
	FOR_EACH_REPLAY( i )
	{
		CReplay *pCurReplay = GET_REPLAY_AT( i );
		if ( pCurReplay->m_hSession == hSession )
		{
			++nResult;
		}
	}
	return nResult;
}

const char *CReplayManager::GetReplaysDir() const
{
	return GetIndexPath();
}
	
float CReplayManager::GetDownloadProgress( const CReplay *pReplay )
{
	// Give each downloadable session block equal weight since we won't know the size of blocks that
	// have not been created/written yet on the server.

	// Go through all blocks in the replay and figure out how many bytes have been downloaded
	float flSum = 0.0f;

	CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pReplay->m_hSession ) );		Assert( pSession );
	if ( !pSession )
		return 0.0f;

	const CBaseRecordingSession::BlockContainer_t &vecBlocks = pSession->GetBlocks();
	FOR_EACH_VEC( vecBlocks, i )
	{
		CClientRecordingSessionBlock *pCurBlock = CL_CastBlock( vecBlocks[ i ] );
		if ( !pReplay->IsSignificantBlock( pCurBlock->m_iReconstruction ) )
			continue;

		// Calculate progress for this block
		Assert( pCurBlock->m_uFileSize > 0 );
		const float flSubProgress = pCurBlock->m_uFileSize == 0 ? 0.0f : clamp( (float)pCurBlock->m_uBytesDownloaded / pCurBlock->m_uFileSize, 0.0f, 1.0f );

		flSum += flSubProgress;
	}

	// Account for blocks that haven't been created yet
	// NOTE: This will cause a bug in download progress if the round ends and cuts the number of
	// expected blocks down - but that situation is probably less likely to occur than the situation
	// where a client is expecting more blocks that *will* be created.  To avoid pops in the latter
	// situation, we account for those blocks here.
	const int nTotalSubBlocks = pReplay->m_iMaxSessionBlockRequired + 1;

	// Calculate mean
	Assert( nTotalSubBlocks > 0 );
	return nTotalSubBlocks == 0 ? 0.0f : ( flSum / (float)nTotalSubBlocks );
}

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