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

#include "errorsystem.h"
#include "replay/ienginereplay.h"
#include "vgui/ILocalize.h"
#include "shared_replaycontext.h"

#if !defined( DEDICATED )

#include "cl_downloader.h"
#include "cl_sessionblockdownloader.h"
#include "cl_recordingsessionblock.h"
#include "replay/iclientreplay.h"

extern IClientReplay *g_pClient;

#endif	// !defined( DEDICATED )

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

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

extern IEngineReplay *g_pEngine;
extern vgui::ILocalize *g_pVGuiLocalize;

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

CErrorSystem::CErrorSystem( IErrorReporter *pErrorReporter )
:	m_pErrorReporter( pErrorReporter )
{
}

CErrorSystem::~CErrorSystem()
{
	Clear();
}

void CErrorSystem::Clear()
{
	FOR_EACH_LL( m_lstErrors, i )
	{
		wchar_t *pText = m_lstErrors[ i ];
		delete [] pText;
	}

	m_lstErrors.RemoveAll();
}

void CErrorSystem::AddError( const wchar_t *pError )
{
	if ( !pError || !pError[0] )
		return;

	// Cache a copied version of the string
	const int nLen = wcslen( pError ) + 1;
	wchar_t *pNewError = new wchar_t[ nLen ];
	const int nSize = nLen * sizeof( wchar_t );
	V_wcsncpy( pNewError, pError, nSize );

	m_lstErrors.AddToTail( pNewError );
}

void CErrorSystem::AddError( const char *pError )
{
	if ( !pError || !pError[0] )
		return;

	wchar_t wszError[1024];
	V_UTF8ToUnicode( pError, wszError, sizeof( wszError ) );

	AddError( wszError );
}

void CErrorSystem::AddErrorFromTokenName( const char *pToken )
{
	if ( g_pVGuiLocalize )
	{
		AddError( g_pVGuiLocalize->Find( pToken ) );
	}
	else
	{
		AddError( pToken );
	}
}

void CErrorSystem::AddFormattedErrorFromTokenName( const char *pFormatToken/*=NULL*/, KeyValues *pFormatArgs/*=NULL*/ )
{
	if ( !pFormatToken )
	{
		AssertMsg( 0, "Error token should always be valid." );
		return;
	}

	wchar_t wszErrorStr[1024];
	if ( g_pVGuiLocalize )
	{
		g_pVGuiLocalize->ConstructString( wszErrorStr, sizeof( wszErrorStr ), pFormatToken, pFormatArgs );
	}
	else
	{
		V_UTF8ToUnicode( pFormatToken, wszErrorStr, sizeof( wszErrorStr ) );
	}


	// Add the error
	AddError( wszErrorStr );

	// Delete args
	pFormatArgs->deleteThis();
}

#if !defined( DEDICATED )

int g_nGenericErrorCounter = 0;

void CErrorSystem::OGS_ReportSessionBlockDownloadError( const CHttpDownloader *pDownloader, const CClientRecordingSessionBlock *pBlock,
														int nLocalFileSize, int nMaxBlock, const bool *pSizesDiffer,
														const bool *pHashFail, uint8 *pLocalHash )
														
{
	// Create a download error and queue for upload
	KeyValues *pDownloadError = pDownloader->GetOgsRow( g_nGenericErrorCounter );
	g_pClient->UploadOgsData( pDownloadError, false );

	// Create block download error
	KeyValues *pBlockDownloadError = new KeyValues( "TF2ReplayBlockDownloadErrors" );
	pBlockDownloadError->SetInt( "ErrorCounter", g_nGenericErrorCounter );
	pBlockDownloadError->SetInt( "NumCurrentDownloads", CSessionBlockDownloader::sm_nNumCurrentDownloads );
	pBlockDownloadError->SetInt( "MaxBlock", nMaxBlock );
	pBlockDownloadError->SetInt( "RemoteStatus", (int)pBlock->m_nRemoteStatus );
	pBlockDownloadError->SetInt( "ReconstructionIndex", pBlock->m_iReconstruction );
	pBlockDownloadError->SetInt( "RemoteFileSize", (int)pBlock->m_uFileSize );
	pBlockDownloadError->SetInt( "LocalFileSize", nLocalFileSize );
	pBlockDownloadError->SetInt( "NumDownloadAttempts", pBlock->GetNumDownloadAttempts() );

	// Only include these if appropriate - otherwise, let them be NULL for the given row
	if ( pSizesDiffer )
	{
		pBlockDownloadError->SetInt( "SizesDiffer", (int)*pSizesDiffer );
	}

	if ( pHashFail )
	{
		pBlockDownloadError->SetInt( "HashFail", (int)*pHashFail );

		// Include hashes
		char szRemoteHash[64], szLocalHash[64];
		V_binarytohex( pBlock->m_aHash, sizeof( pBlock->m_aHash ), szRemoteHash, sizeof( szRemoteHash ) );
		V_binarytohex( pLocalHash, sizeof( pBlock->m_aHash ), szLocalHash, sizeof( szLocalHash ) );
		pBlockDownloadError->SetString( "RemoteHash", szRemoteHash );
		pBlockDownloadError->SetString( "LocalHash", szLocalHash );
	}

	// Upload block download error
	g_pClient->UploadOgsData( pBlockDownloadError, false );

	// Upload generic error and link to this specific block error.
	OGS_ReportGenericError( "Block download failed" );
}

void CErrorSystem::OGS_ReportSessioInfoDownloadError( const CHttpDownloader *pDownloader, const char *pErrorToken )
{
	// Create a download error and queue for upload
	KeyValues *pDownloadError = pDownloader->GetOgsRow( g_nGenericErrorCounter );
	g_pClient->UploadOgsData( pDownloadError, false );
	
	// Create session info download error
	KeyValues *pSessionInfoDownloadError = new KeyValues( "TF2ReplaySessionInfoDownloadErrors" );
	pSessionInfoDownloadError->SetInt( "ErrorCounter", g_nGenericErrorCounter++ );
	pSessionInfoDownloadError->SetString( "SessionInfoDownloadErrorID", pErrorToken );
	g_pClient->UploadOgsData( pSessionInfoDownloadError, false );

	// Upload generic error and link to this specific block error.
	OGS_ReportGenericError( "Session info download failed" );
}

// Note: we use the ErrorCounter as part of the key and so it must be unique. This means that all
// special error tables (ie., session info download errors) must call back into this base function
// to write out the base error and increment the counter.
void CErrorSystem::OGS_ReportGenericError( const char *pGenericErrorToken )
{
	KeyValues *pGenericError = new KeyValues( "TF2ReplayErrors" );
	pGenericError->SetInt( "ErrorCounter", g_nGenericErrorCounter++ );
	pGenericError->SetString( "ReplayErrorID", pGenericErrorToken );

	// Upload the generic error row now
	g_pClient->UploadOgsData( pGenericError, true );

	// Next error!
	++g_nGenericErrorCounter;
}

#endif	// !defined( DEDICATED )

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

void CErrorSystem::Think()
{
	CBaseThinker::Think();

	if ( m_lstErrors.Count() == 0 )
		return;

	const int nMaxLen = 4096;
	wchar_t wszErrorText[ nMaxLen ] = L"";
	FOR_EACH_LL( m_lstErrors, i )
	{
		const wchar_t *pError = m_lstErrors[ i ];
		if ( wcslen( wszErrorText ) + wcslen( pError ) + 1 >= nMaxLen )
			break;

		wcscat( wszErrorText, pError );
		wcscat( wszErrorText, L"\n" );
	}

	// Report now
	m_pErrorReporter->ReportErrorsToUser( wszErrorText );

	// Clear
	Clear();
}

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