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

#include "baserecordingsessionmanager.h"
#include "baserecordingsession.h"
#include "baserecordingsessionblock.h"
#include "replay/replayutils.h"
#include "replay/shared_defs.h"
#include "replaysystem.h"
#include "KeyValues.h"
#include "shared_replaycontext.h"
#include "filesystem.h"
#include "iserver.h"
#include "vprof.h"

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

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

inline const char *GetSessionsFullFilename()
{
	return Replay_va( "%s" SUBDIR_SESSIONS "%c", Replay_GetBaseDir(), CORRECT_PATH_SEPARATOR );
}

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

CBaseRecordingSessionManager::CBaseRecordingSessionManager( IReplayContext *pContext )
:	m_pContext( pContext ),
	m_pRecordingSession( NULL ),
	m_bLastSessionDitched( false )
{
}

CBaseRecordingSessionManager::~CBaseRecordingSessionManager()
{
}

bool CBaseRecordingSessionManager::Init()
{
	if ( !BaseClass::Init() )
		return false;

	// Go through each block handle and attempt find the block in the block manager
	typedef CGenericPersistentManager< CBaseRecordingSessionBlock > BaseBlockManager_t;
	BaseBlockManager_t *pBlockManager = dynamic_cast< BaseBlockManager_t * >( m_pContext->GetRecordingSessionBlockManager() );
	FOR_EACH_OBJ( pBlockManager, it )
	{
		CBaseRecordingSessionBlock *pCurBlock = pBlockManager->m_vecObjs[ it ];

		// Find the session for the current block
		CBaseRecordingSession *pSession = m_pContext->GetRecordingSessionManager()->FindSession( pCurBlock->m_hSession );
		if ( !pSession )
		{
			m_pContext->GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Load_CouldNotFindSession" );
			continue;
		}

		// Add the block
		pSession->AddBlock( pCurBlock, false );
	}

	return true;
}

CBaseRecordingSession *CBaseRecordingSessionManager::OnSessionStart( int nCurrentRecordingStartTick, const char *pSessionName )
{
	// Add a new session if one w/ the given name doesn't already exist.
	// This is necessary on the client, where a session may already exist if, for example,
	// the client reconnects to a server where they were already playing/saved replays.
	// On the server, NULL will always be passed in for pSessionName.
	CBaseRecordingSession *pNewSession = pSessionName ? FindSessionByName( pSessionName ) : NULL;
	if ( !pNewSession )
	{
		pNewSession = CreateAndGenerateHandle();
		Add( pNewSession );
	}

	// Initialize
	pNewSession->PopulateWithRecordingData( nCurrentRecordingStartTick );

	Save();

	// Update recording session
	m_pRecordingSession = pNewSession;

	return m_pRecordingSession;
}

void CBaseRecordingSessionManager::OnSessionEnd()
{
	if ( m_pRecordingSession )
	{
		// If we don't care about the given session, ditch it
		// NOTE: ShouldDitchSession() checks auto-delete flag!
		if ( m_pRecordingSession->ShouldDitchSession() )
		{
			m_bLastSessionDitched = true;

			DBG( "Marking session for ditch!\n" );

			MarkSessionForDelete( m_pRecordingSession->GetHandle() );
		}
		else
		{
			m_bLastSessionDitched = false;

			// Save
			FlagForFlush( m_pRecordingSession, false );

			// Unload from memory?
			if ( ShouldUnloadSessions() )
			{
				FlagForUnload( m_pRecordingSession );
			}
		}
	}
	m_pRecordingSession = NULL;
}

void CBaseRecordingSessionManager::DeleteSession( ReplayHandle_t hSession, bool bForce )
{
	CBaseRecordingSession *pSession = Find( hSession );
	if ( !pSession )
	{
		AssertMsg( 0, "Trying to delete a non-existent session - should never happen!" );
		return;
	}

	AssertMsg( !pSession->IsLocked(), "Shouldn't be free'ing a locked session!" );

	// If the given session is recording, flag for delete but don't actually remove now
	if ( pSession == m_pRecordingSession && !bForce )
	{
		pSession->m_bAutoDelete = true;
		return;
	}

	// Remove the session and save
	Remove( pSession );
	Save();
}

void CBaseRecordingSessionManager::MarkSessionForDelete( ReplayHandle_t hSession )
{
	m_lstSessionsToDelete.AddToTail( hSession );
}

const char *CBaseRecordingSessionManager::GetCurrentSessionName() const
{
	if ( !m_pRecordingSession )
	{
		AssertMsg( 0, "GetCurrentSessionName() called w/o a session context" );
		return NULL;
	}

	return m_pRecordingSession->m_strName.Get();
}

int CBaseRecordingSessionManager::GetCurrentSessionBlockIndex() const
{
	if ( !m_pRecordingSession )
	{
		AssertMsg( 0, "GetCurrentPartialIndex() called w/o a session context" );
		return -1;
	}

	// Need this MAX() here since GetNumBlocks() will return 0 until the first block is actually written.
	return MAX( 0, m_pRecordingSession->GetNumBlocks() - 1 );
}

void CBaseRecordingSessionManager::FlagSessionForFlush( CBaseRecordingSession *pSession, bool bForceImmediate )
{
	FlagForFlush( pSession, bForceImmediate );
}

int CBaseRecordingSessionManager::GetServerStartTickForSession( ReplayHandle_t hSession )
{
	CBaseRecordingSession *pSession = FindSession( hSession );
	if ( !pSession )
		return -1;
	
	return pSession->m_nServerStartRecordTick;
}

CBaseRecordingSession *CBaseRecordingSessionManager::FindSession( ReplayHandle_t hSession )
{
	return Find( hSession );
}

const CBaseRecordingSession	*CBaseRecordingSessionManager::FindSession( ReplayHandle_t hSession ) const
{
	return const_cast< CBaseRecordingSessionManager * >( this )->Find( hSession );
}

CBaseRecordingSession *CBaseRecordingSessionManager::FindSessionByName( const char *pSessionName )
{
	if ( !pSessionName || !pSessionName[0] )
		return NULL;

	FOR_EACH_OBJ( this, i )
	{
		CBaseRecordingSession *pCurSession = m_vecObjs[ i ];
		if ( !V_stricmp( pSessionName, pCurSession->m_strName.Get() ) )
			return pCurSession;
	}

	return NULL;
}

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

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

	DeleteSessionThink();

	BaseClass::Think();
}

void CBaseRecordingSessionManager::DeleteSessionThink()
{
	DoSessionCleanup();
}

void CBaseRecordingSessionManager::DoSessionCleanup()
{
	bool bDeletedASession = false;

	for ( int i = m_lstSessionsToDelete.Head(); i != m_lstSessionsToDelete.InvalidIndex(); )
	{
		ReplayHandle_t hSession = m_lstSessionsToDelete[ i ];

		const int itNext = m_lstSessionsToDelete.Next( i );

		if ( CanDeleteSession( hSession ) )
		{
			DBG( "Unloading session.\n" );

			DeleteSession( hSession, true );
			m_lstSessionsToDelete.Remove( i );

			bDeletedASession = true;
		}

		i = itNext;
	}

	// If we just deleted the last session, let the derived class do any post-work
	if ( !m_lstSessionsToDelete.Count() && bDeletedASession )
	{
		OnAllSessionsDeleted();
	}
}

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

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