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

#include "replay/replay.h"
#include "replay/iclientreplaycontext.h"
#include "replay/ireplaymanager.h"
#include "replay/replayutils.h"
#include "replay/screenshot.h"
#include "replay/shared_defs.h"
#include "replay/ireplayscreenshotmanager.h"
#include "replay/ireplayperformancemanager.h"
#include "replay/performance.h"
#include "KeyValues.h"
#include "filesystem.h"
#include "vgui/ILocalize.h"

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

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

extern IClientReplayContext *g_pClientReplayContext;
extern vgui::ILocalize *g_pVGuiLocalize;

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

CReplay::CReplay()
:	m_pDownloadEventHandler( NULL ),
	m_pUserData( NULL ),
	m_bComplete( false ),
	m_bRequestedByUser( false ),
	m_bSaved( false ),
	m_bRendered( false ),
	m_bDirty( false ),
	m_bSavedDuringThisSession( true ),
	m_flLength( 0 ),
	m_nPlayerSlot( -1 ),
	m_nSpawnTick( -1 ),
	m_nDeathTick( -1 ),
	m_iMaxSessionBlockRequired( 0 ),
	m_nStatus( REPLAYSTATUS_INVALID ),
	m_hSession( REPLAY_HANDLE_INVALID ),
	m_pFileURL( NULL ),
	m_nPostDeathRecordTime( 0 ),
	m_flStartTime( 0.0f ),
	m_flNextUpdateTime( 0.0f )
{
	m_wszTitle[0] = L'\0';
	m_szMapName[0] = 0;
}

bool CReplay::IsDownloaded() const
{
	return m_nStatus == REPLAYSTATUS_READYTOCONVERT;
}

const CReplayPerformance *CReplay::GetPerformance( int i ) const
{
	return const_cast< CReplay * >( this )->GetPerformance( i );
}

CReplayPerformance *CReplay::GetPerformance( int i )
{
	if ( i < 0 || i >= m_vecPerformances.Count() )
		return NULL;

	return m_vecPerformances[ i ];
}

bool CReplay::FindPerformance( CReplayPerformance *pPerformance, int &iResult )
{
	const int it = m_vecPerformances.Find( pPerformance );
	if ( it == m_vecPerformances.InvalidIndex() )
	{
		iResult = -1;
		return false;
	}

	iResult = it;
	return true;
}

CReplayPerformance *CReplay::GetPerformanceWithTitle( const wchar_t *pTitle )
{
	FOR_EACH_VEC( m_vecPerformances, i )
	{
		CReplayPerformance *pCurPerformance = m_vecPerformances[ i ];
		if ( !V_wcscmp( pTitle, pCurPerformance->m_wszTitle ) )
		{
			return pCurPerformance;
		}
	}
	return NULL;
}

CReplayPerformance *CReplay::AddNewPerformance( bool bGenTitle/*=true*/, bool bGenFilename/*=true*/ )
{
	// Create a performance
	IReplayPerformanceManager *pPerformanceManager = g_pClientReplayContext->GetPerformanceManager();
	CReplayPerformance *pPerformance = pPerformanceManager->CreatePerformance( this );

	if ( bGenTitle )
	{
		// Give the performance a name
		pPerformance->AutoNameIfHasNoTitle( m_szMapName );
	}

	if ( bGenFilename )
	{
		// Generate a filename for the new performance
		pPerformance->SetFilename( pPerformanceManager->GeneratePerformanceFilename( this ) );
	}

	// Cache
	m_vecPerformances.AddToTail( pPerformance );
	
	return pPerformance;
}

void CReplay::AddPerformance( KeyValues *pIn )
{
	// Create a performance
	IReplayPerformanceManager *pPerformanceManager = g_pClientReplayContext->GetPerformanceManager();
	CReplayPerformance *pPerformance = pPerformanceManager->CreatePerformance( this );

	// Read
	pPerformance->Read( pIn );

	// Cache
	m_vecPerformances.AddToTail( pPerformance );
}

void CReplay::AddPerformance( CReplayPerformance *pPerformance )
{
	Assert( pPerformance );
	m_vecPerformances.AddToTail( pPerformance );
}

const CReplayTime &CReplay::GetItemDate() const
{
	return m_RecordTime;
}

bool CReplay::IsItemRendered() const
{
	return m_bRendered;
}

CReplay *CReplay::GetItemReplay()
{
	return this;
}

ReplayHandle_t	CReplay::GetItemReplayHandle() const
{
	return GetHandle();
}

QueryableReplayItemHandle_t	CReplay::GetItemHandle() const
{
	return GetHandle();
}

const wchar_t *CReplay::GetItemTitle() const
{
	return m_wszTitle;
}

void CReplay::SetItemTitle( const wchar_t *pTitle )
{
	V_wcsncpy( m_wszTitle, pTitle, sizeof( m_wszTitle ) );
}

float CReplay::GetItemLength() const
{
	return m_flLength;
}

void *CReplay::GetUserData()
{
	return m_pUserData;
}

void CReplay::SetUserData( void* pUserData )
{
	m_pUserData = pUserData;
}

bool CReplay::IsItemAMovie() const
{
	return false;
}

void CReplay::AddScreenshot( int nWidth, int nHeight, const char *pBaseFilename )
{
	m_vecScreenshots.AddToTail( new CReplayScreenshot( nWidth, nHeight, pBaseFilename ) );
}

void CReplay::AutoNameTitleIfEmpty()
{
	// Autoname it
	if ( !m_wszTitle[0] )
	{
		Replay_GetAutoName( m_wszTitle, sizeof( m_wszTitle ), m_szMapName );
	}
}

const char *CReplay::GetSubKeyTitle() const
{
	return Replay_va( "replay_%i", GetHandle() );
}

const char *CReplay::GetPath() const
{
	return Replay_va( "%s%s%c", g_pClientReplayContext->GetBaseDir(), SUBDIR_REPLAYS, CORRECT_PATH_SEPARATOR );
}

void CReplay::OnDelete()
{
	BaseClass::OnDelete();

	// Delete reconstructed replay if one exists
	if ( HasReconstructedReplay() )
	{
		g_pFullFileSystem->RemoveFile( m_strReconstructedFilename.Get() );
	}

	// Delete screenshots
	g_pClientReplayContext->GetScreenshotManager()->DeleteScreenshotsForReplay( this );

	// TODO: Delete performance(s)
}

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

	m_hSession = (ReplayHandle_t)pIn->GetInt( "session", REPLAY_HANDLE_INVALID );
	V_strcpy_safe( m_szMapName, pIn->GetString( "map", "" ) );
	m_nSpawnTick = pIn->GetInt( "spawn_tick", -1 );
	m_nDeathTick = pIn->GetInt( "death_tick", -1 );
	m_nStatus = static_cast< CReplay::ReplayStatus_t >( pIn->GetInt( "status", (int)CReplay::REPLAYSTATUS_INVALID ) );
	m_bComplete = pIn->GetInt( "complete" ) != 0;
	m_flLength = pIn->GetFloat( "length" );
	m_nPostDeathRecordTime = pIn->GetInt( "postdeathrecordtime" );
	m_bRendered = pIn->GetInt( "rendered" ) != 0;
	m_nPlayerSlot = pIn->GetInt( "player_slot", -1 );
	m_iMaxSessionBlockRequired = pIn->GetInt( "max_block", 0 );	Assert( m_iMaxSessionBlockRequired >= 0 );
	m_flStartTime = pIn->GetFloat( "start_time", -1.0f );		Assert( m_flStartTime >= 0.0f );
	V_wcsncpy( m_wszTitle, pIn->GetWString( "title" ), sizeof( m_wszTitle ) );

	// Read reconstructed filename and infer path
	const char *pReplaysDir = g_pClientReplayContext->GetReplayManager()->GetReplaysDir();
	const char *pReconFilename = pIn->GetString( "recon_filename" );
	if ( pReconFilename[0] != 0 )
	{
		m_strReconstructedFilename = Replay_va( "%s%s", pReplaysDir, pReconFilename );
	}

	// Read screenshots
	KeyValues *pScreenshots = pIn->FindKey( "screenshots" );
	if ( pScreenshots )
	{
		FOR_EACH_TRUE_SUBKEY( pScreenshots, pScreenshot )
		{
			int nWidth = pScreenshot->GetInt( "width" );
			int nHeight = pScreenshot->GetInt( "height" );
			const char *pBaseFilename = pScreenshot->GetString( "base_filename" );
			AddScreenshot( nWidth, nHeight, pBaseFilename );
		}
	}

	// Read performances
	KeyValues *pPerformances = pIn->FindKey( "edits" );
	if ( pPerformances )
	{
		FOR_EACH_TRUE_SUBKEY( pPerformances, pPerformance )
		{
			AddPerformance( pPerformance );
		}
	}

	// Record time
	KeyValues *pRecordTimeSubKey = pIn->FindKey( "record_time" );
	if ( pRecordTimeSubKey )
	{
		m_RecordTime.Read( pRecordTimeSubKey );
	}

	// Mark replay as saved, since it was just loaded from disk
	m_bSaved = true;

	return true;
}

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

	pOut->SetString( "map", m_szMapName );
	pOut->SetInt( "session", m_hSession );
	pOut->SetInt( "spawn_tick", m_nSpawnTick );
	pOut->SetInt( "death_tick", m_nDeathTick );
	pOut->SetInt( "status", static_cast< int >( m_nStatus ) );
	pOut->SetInt( "complete", static_cast< int >( m_bComplete ) );
	pOut->SetFloat( "length", m_flLength );
	pOut->SetInt( "postdeathrecordtime", m_nPostDeathRecordTime );
	pOut->SetInt( "rendered", m_bRendered );
	pOut->SetInt( "player_slot", m_nPlayerSlot );
	pOut->SetInt( "max_block", m_iMaxSessionBlockRequired );
	pOut->SetFloat( "start_time", m_flStartTime );
	pOut->SetWString( "title", m_wszTitle );

	// Store only filename for reconstructed .dem
	if ( !m_strReconstructedFilename.IsEmpty() )
	{
		const char *pReconFilename = V_UnqualifiedFileName( m_strReconstructedFilename.Get() );
		if ( pReconFilename[0] )
		{
			pOut->SetString( "recon_filename", pReconFilename );
		}
	}

	// Write screenshots
	KeyValues *pScreenshots = new KeyValues( "screenshots" );
	pOut->AddSubKey( pScreenshots );
	for ( int i = 0; i < m_vecScreenshots.Count(); ++i )
	{
		KeyValues *pScreenshotOut = new KeyValues( "screenshot" );
		CReplayScreenshot *pScreenshot = m_vecScreenshots[ i ];
		pScreenshotOut->SetInt( "width", pScreenshot->m_nWidth );
		pScreenshotOut->SetInt( "height", pScreenshot->m_nHeight );
		pScreenshotOut->SetString( "base_filename", pScreenshot->m_szBaseFilename );
		pScreenshots->AddSubKey( pScreenshotOut );
	}

	// Write performances
	KeyValues *pPerformances = new KeyValues( "edits" );
	pOut->AddSubKey( pPerformances );
	for ( int i = 0; i < m_vecPerformances.Count(); ++i )
	{
		KeyValues *pPerfOut = new KeyValues( "edit" );
		CReplayPerformance *pPerformance = m_vecPerformances[ i ];
		pPerformance->Write( pPerfOut );
		pPerformances->AddSubKey( pPerfOut );
	}

	KeyValues *pRecordTime = new KeyValues( "record_time" );
	pOut->AddSubKey( pRecordTime );
	m_RecordTime.Write( pRecordTime );

	// Mark as saved
	m_bSaved = true;
}

bool CReplay::HasReconstructedReplay() const
{
	return !m_strReconstructedFilename.IsEmpty() &&
		   g_pFullFileSystem->FileExists( m_strReconstructedFilename.Get() );
}

bool CReplay::IsSignificantBlock( int iBlockReconstruction ) const
{
	return iBlockReconstruction <= m_iMaxSessionBlockRequired;
}

void CReplay::OnComplete()
{
	AutoNameTitleIfEmpty();
}

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