//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "filesystem_engine.h"
#include "filesystem.h"
#include "dt_instrumentation_server.h"
#include "dt_send.h"
#include "utllinkedlist.h"
#include "vstring.h"
#include "dt.h"


#define DELTA_DISTANCE_BAND			200
#define NUM_DELTA_DISTANCE_BANDS	(8000/DELTA_DISTANCE_BAND)


// Data we track per SendTable on the server.
class CDTISendTable
{
public:
	// Which SendTable we're interested in.
	VString			m_NetTableName;

	// How many cycles we've spent in certain calls.
	CCycleCount		m_nCalcDeltaCycles;
	int				m_nCalcDeltaCalls;

	CCycleCount		m_nEncodeCycles;
	int				m_nEncodeCalls;

	CCycleCount		m_nShouldTransmitCycles;
	int				m_nShouldTransmitCalls;

	CCycleCount		m_nWriteDeltaPropsCycles;

	// Used to determine how much the class uses manual mode.
	int m_nChangeAutoDetects;
	int m_nNoChanges;

	// Set to false if no events were recorded for this class.
	bool HadAnyAction() const { return m_nCalcDeltaCalls || m_nEncodeCalls || m_nShouldTransmitCalls; }

	// This tracks how many times an entity was delta'd for each distance from a client.
	unsigned short	m_DistanceDeltaCounts[NUM_DELTA_DISTANCE_BANDS];
};


static CCycleCount g_TotalServerDTICycles;

static CUtlLinkedList<CDTISendTable*, unsigned short> g_DTISendTables;

bool g_bServerDTIEnabled = false;
static char const *g_pServerDTIFilename = 0;

static bool g_bFirstHookTimer = true;
static CCycleCount g_ServerDTITimer;



void ServerDTI_Init( char const *pFilename )
{
	g_pServerDTIFilename = pFilename;
	g_bServerDTIEnabled = true;
	g_TotalServerDTICycles.Init();
	g_bFirstHookTimer = true;
}


void ServerDTI_Term()
{
	if ( !g_pServerDTIFilename )
		return;

	ServerDTI_Flush();

	g_DTISendTables.PurgeAndDeleteElements();
	g_pServerDTIFilename = 0;
	g_bServerDTIEnabled = false;
}


void ServerDTI_Flush()
{
	if ( !g_pServerDTIFilename )
		return;

	CCycleCount curTime;
	curTime.Sample();
	
	CCycleCount runningTime;
	CCycleCount::Sub( curTime, g_ServerDTITimer, runningTime );

	// Write out a file that can be used by Excel.
	FileHandle_t fp = g_pFileSystem->Open( g_pServerDTIFilename, "wt", "LOGDIR" );
	
	if( fp != FILESYSTEM_INVALID_HANDLE )
	{
		// Write the header.
		g_pFileSystem->FPrintf( fp, 
			"DTName"
			
			"\tCalcDelta calls"
			"\tCalcDelta ms"
			
			"\tEncode calls"
			"\tEncode ms"
			
			"\tShouldTransmit calls"
			"\tShouldTransmit ms"

			"\tWriteDeltaProps ms"

			"\t%% manual mode"

			"\tTotal"
			"\tPercent"
			"\n"
			);

		// Calculate totals.
		CCycleCount totalCalcDelta, totalEncode, totalShouldTransmit, totalDeltaProps;
		totalCalcDelta.Init();
		totalEncode.Init();
		totalShouldTransmit.Init();
		
		FOR_EACH_LL( g_DTISendTables, i )
		{
			CDTISendTable *pTable = g_DTISendTables[i];
			
			CCycleCount::Add( pTable->m_nCalcDeltaCycles, totalCalcDelta, totalCalcDelta );
			CCycleCount::Add( pTable->m_nEncodeCycles, totalEncode, totalEncode );
			CCycleCount::Add( pTable->m_nShouldTransmitCycles, totalShouldTransmit, totalShouldTransmit );
			CCycleCount::Add( pTable->m_nWriteDeltaPropsCycles, totalDeltaProps, totalDeltaProps );
		}
	

		FOR_EACH_LL( g_DTISendTables, j )
		{
			CDTISendTable *pTable = g_DTISendTables[j];

			if ( !pTable->HadAnyAction() )
				continue;

			CCycleCount total;
			CCycleCount::Add( pTable->m_nEncodeCycles, pTable->m_nCalcDeltaCycles, total );
			CCycleCount::Add( pTable->m_nShouldTransmitCycles, total, total );

			g_pFileSystem->FPrintf( fp, 
				"%s"

				"\t%d"
				"\t%.3f"

				"\t%d"
				"\t%.3f"

				"\t%d"
				"\t%.3f"

				"\t%.3f"

				"\t%.2f"

				"\t%.3f"
				"\t%.3f"
				"\n",
				(char const*)pTable->m_NetTableName,
				
				pTable->m_nCalcDeltaCalls,
				pTable->m_nCalcDeltaCycles.GetMillisecondsF(),
				
				pTable->m_nEncodeCalls,
				pTable->m_nEncodeCycles.GetMillisecondsF(),
				
				pTable->m_nShouldTransmitCalls,
				pTable->m_nShouldTransmitCycles.GetMillisecondsF(),

				pTable->m_nWriteDeltaPropsCycles.GetMillisecondsF(),
				
				(float)pTable->m_nNoChanges * 100.0f / (pTable->m_nNoChanges + pTable->m_nChangeAutoDetects),

				total.GetMillisecondsF(),
				total.GetMillisecondsF() * 100 / runningTime.GetMillisecondsF()
				);
		}

		g_pFileSystem->FPrintf( fp, "\n\n" );

		g_pFileSystem->FPrintf( fp,
			"Total profile ms:"
			"\t%.3f\n",
			runningTime.GetMillisecondsF()
			);
		
		g_pFileSystem->FPrintf( fp, 
			"Total CalcDelta ms:"
			"\t%.3f"
			"\tPercent:"
			"\t%.3f\n",
			totalCalcDelta.GetMillisecondsF(),
			totalCalcDelta.GetMillisecondsF() * 100.0 / runningTime.GetMillisecondsF() 
			);

		g_pFileSystem->FPrintf( fp, 
			"Total Encode ms:"
			"\t%.3f"
			"\tPercent:"
			"\t%.3f\n",
			totalEncode.GetMillisecondsF(),
			totalEncode.GetMillisecondsF() * 100.0 / runningTime.GetMillisecondsF() 
			);

		g_pFileSystem->FPrintf( fp,
			"Total ShouldTransmit ms:"
			"\t%.3f"
			"\tPercent:"
			"\t%.3f\n",
			totalShouldTransmit.GetMillisecondsF(),
			totalShouldTransmit.GetMillisecondsF() * 100.0 / runningTime.GetMillisecondsF()
			);
		
		g_pFileSystem->FPrintf( fp,
			"Total WriteDeltaProps ms:"
			"\t%.3f"
			"\tPercent:"
			"\t%.3f\n",
			totalDeltaProps.GetMillisecondsF(),
			totalDeltaProps.GetMillisecondsF() * 100.0 / runningTime.GetMillisecondsF()
			);
		
		g_pFileSystem->Close( fp );

		Msg( "DTI: Wrote delta distances into %s.\n", g_pServerDTIFilename );
	}

	
	// Write the delta distances.
	const char *pDeltaDistancesFilename = "dti_delta_distances.txt";
	fp = g_pFileSystem->Open( pDeltaDistancesFilename, "wt", "LOGDIR" );
	if( fp != FILESYSTEM_INVALID_HANDLE )
	{
		// Write the column labels.
		g_pFileSystem->FPrintf( fp, "ClassName" );
		for ( int i=0; i < NUM_DELTA_DISTANCE_BANDS; i++ )
		{
			g_pFileSystem->FPrintf( fp, "\t<%d", (i+1) * DELTA_DISTANCE_BAND );
		}
		g_pFileSystem->FPrintf( fp, "\n" );
		
		// Now write the data.
		FOR_EACH_LL( g_DTISendTables, j )
		{
			CDTISendTable *pTable = g_DTISendTables[j];

			if ( !pTable->HadAnyAction() )
				continue;

			g_pFileSystem->FPrintf( fp, "%s", (char const*)pTable->m_NetTableName );
			for ( int i=0; i < NUM_DELTA_DISTANCE_BANDS; i++ )
			{
				g_pFileSystem->FPrintf( fp, "\t%d", pTable->m_DistanceDeltaCounts[i] );
			}
			g_pFileSystem->FPrintf( fp, "\n" );
		}		

		g_pFileSystem->Close( fp );
		
		Msg( "DTI: Wrote instrumentation data into %s.\n", pDeltaDistancesFilename );
	}
}


CDTISendTable* ServerDTI_HookTable( SendTable *pTable )
{
	if ( !g_bServerDTIEnabled )
		return NULL;

	CDTISendTable *pRet = new CDTISendTable;
	memset( pRet, 0, sizeof( *pRet ) );

	pRet->m_NetTableName.CopyString( pTable->m_pNetTableName );

	g_DTISendTables.AddToTail( pRet );
	return pRet;
}


void ServerDTI_AddEntityEncodeEvent( SendTable *pSendTable, float distToPlayer )
{
	CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;
	if ( !pPrecalc || !pPrecalc->m_pDTITable )
		return;

	CDTISendTable *pTable = pPrecalc->m_pDTITable;		
	if ( !pTable )
		return;

	int iDist = (int)( distToPlayer / DELTA_DISTANCE_BAND );
	iDist = clamp( iDist, 0, NUM_DELTA_DISTANCE_BANDS - 1 );
	pTable->m_DistanceDeltaCounts[iDist]++;
}


void _ServerDTI_HookTimer( const SendTable *pSendTable, ServerDTITimerType timerType, CCycleCount const &count )
{
	CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;
	if ( !pPrecalc || !pPrecalc->m_pDTITable )
		return;

	CDTISendTable *pTable = pPrecalc->m_pDTITable;		

	if ( g_bFirstHookTimer )
	{
		g_ServerDTITimer.Sample();
		g_bFirstHookTimer = false;
	}

	// Add to the total cycles.
	CCycleCount::Add( count, g_TotalServerDTICycles, g_TotalServerDTICycles );

	if ( timerType == SERVERDTI_CALCDELTA )
	{
		CCycleCount::Add( count, pTable->m_nCalcDeltaCycles, pTable->m_nCalcDeltaCycles );
		++pTable->m_nCalcDeltaCalls;
	}
	else if ( timerType == SERVERDTI_ENCODE )
	{
		CCycleCount::Add( count, pTable->m_nEncodeCycles, pTable->m_nEncodeCycles );
		++pTable->m_nEncodeCalls;
	}
	else if ( timerType == SERVERDTI_SHOULDTRANSMIT )
	{
		CCycleCount::Add( count, pTable->m_nShouldTransmitCycles, pTable->m_nShouldTransmitCycles );
		++pTable->m_nShouldTransmitCalls;
	}
	else if ( timerType == SERVERDTI_WRITE_DELTA_PROPS )
	{
		CCycleCount::Add( count, pTable->m_nWriteDeltaPropsCycles, pTable->m_nWriteDeltaPropsCycles );
	}
}


void _ServerDTI_RegisterNetworkStateChange( SendTable *pSendTable, EntityChange_t changeType )
{
	CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;
	if ( !pPrecalc || !pPrecalc->m_pDTITable )
		return;

	CDTISendTable *pTable = pPrecalc->m_pDTITable;		

	if ( changeType == ENTITY_CHANGE_AUTODETECT )
		++pTable->m_nChangeAutoDetects;
	else
		++pTable->m_nNoChanges;
}


