//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "client_pch.h"
#include "ivideomode.h"

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

ConVar cl_entityreport( "cl_entityreport", "0", FCVAR_CHEAT, "For debugging, draw entity states to console" );
ConVar cl_entityreport_sorted( "cl_entityreport_sorted", "0", FCVAR_CHEAT, "For debugging, draw entity states to console in sorted order. [0 = disabled, 1 = average, 2 = current, 3 = peak" );

enum
{
	ENTITYSORT_NONE		= 0,
	ENTITYSORT_AVG		= 1,
	ENTITYSORT_CURRENT	= 2,
	ENTITYSORT_PEAK		= 3,
};

// How quickly to move rolling average for entityreport
#define BITCOUNT_AVERAGE 0.95f
// How long to flush item when something important happens
#define EFFECT_TIME  1.5f
// How long to latch peak bit count for item
#define PEAK_LATCH_TIME 2.0f;

//-----------------------------------------------------------------------------
// Purpose: Entity report event types
//-----------------------------------------------------------------------------
enum
{
	FENTITYBITS_ZERO = 0,
	FENTITYBITS_ADD = 0x01,
	FENTITYBITS_LEAVEPVS = 0x02,
	FENTITYBITS_DELETE = 0x04,
};

//-----------------------------------------------------------------------------
// Purpose: Data about an entity
//-----------------------------------------------------------------------------
typedef struct
{
	// Bits used for last message
	int				bits;
	// Rolling average of bits used
	float			average;
	// Last bit peak
	int				peak;
	// Time at which peak was last reset
	float			peaktime;
	// Event info
	int				flags;
	// If doing effect, when it will finish
	float			effectfinishtime;
	// If event was deletion, remember client class for a little bit
	ClientClass		*deletedclientclass;
} ENTITYBITS;

// List of entities we are keeping data bout
static ENTITYBITS s_EntityBits[ MAX_EDICTS ];

// Used to sort by average
int CompareEntityBits(const void* pIndexA, const void* pIndexB )
{
	int indexA = *(int*)pIndexA;
	int indexB = *(int*)pIndexB;

	ENTITYBITS *pEntryA = &s_EntityBits[indexA];
	ENTITYBITS *pEntryB = &s_EntityBits[indexB];

	/*
	if ( pEntryA->flags == FENTITYBITS_ZERO )
	{
		if ( pEntryB->flags == FENTITYBITS_ZERO )
		{
			return 0;
		}
		return 1;
	}
	else if ( pEntryB->flags == FENTITYBITS_ZERO )
	{
		return -1;
	}
	*/

	// sort dormant, out-of-pvs to the end
	IClientNetworkable *pNetA = entitylist->GetClientNetworkable( indexA );
	IClientNetworkable *pNetB = entitylist->GetClientNetworkable( indexB );

	bool bDormantA = pNetA == NULL || pNetA->IsDormant();
	bool bDormantB = pNetB == NULL || pNetB->IsDormant();

	if ( bDormantA != bDormantB )
	{
		return bDormantA ? 1 : -1;
	}

	switch ( cl_entityreport_sorted.GetInt() )
	{
	case ENTITYSORT_AVG:
		if ( pEntryA->average > pEntryB->average )
		{
			return -1;
		}
		if ( pEntryA->average < pEntryB->average )
		{
			return 1;
		}
		break;
	case ENTITYSORT_CURRENT:
		if ( pEntryA->bits > pEntryB->bits )
		{
			return -1;
		}
		if ( pEntryA->bits < pEntryB->bits )
		{
			return 1;
		}
		break;
	case ENTITYSORT_PEAK:
	default:
		if ( pEntryA->peak > pEntryB->peak )
		{
			return -1;
		}
		if ( pEntryA->peak < pEntryB->peak )
		{
			return 1;
		}
	}
	
	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Zero out structure ( level transition/startup )
//-----------------------------------------------------------------------------
void CL_ResetEntityBits( void )
{
	memset( s_EntityBits, 0, sizeof( s_EntityBits ) );
}

//-----------------------------------------------------------------------------
// Purpose: Record activity
// Input  : entnum - 
//			bitcount - 
//-----------------------------------------------------------------------------
void CL_RecordEntityBits( int entnum, int bitcount )
{
	if ( entnum < 0 || entnum >= MAX_EDICTS ) 
	{
		return;
	}

	ENTITYBITS *slot = &s_EntityBits[ entnum ];

	slot->bits = bitcount;
	// Update average
	slot->average = ( BITCOUNT_AVERAGE ) * slot->average + ( 1.f - BITCOUNT_AVERAGE ) * bitcount;

	// Recompute peak
	if ( realtime >= slot->peaktime )
	{
		slot->peak = 0.0f;
		slot->peaktime = realtime + PEAK_LATCH_TIME;
	}

	// Store off peak
	if ( bitcount > slot->peak )
	{
		slot->peak = bitcount;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Record entity add event
// Input  : entnum - 
//-----------------------------------------------------------------------------
void CL_RecordAddEntity( int entnum )
{
	if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
	{
		return;
	}

	ENTITYBITS *slot = &s_EntityBits[ entnum ];
	slot->flags = FENTITYBITS_ADD;
	slot->effectfinishtime = realtime + EFFECT_TIME;
}

//-----------------------------------------------------------------------------
// Purpose: record entity leave event
// Input  : entnum - 
//-----------------------------------------------------------------------------
void CL_RecordLeavePVS( int entnum )
{
	if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
	{
		return;
	}

	ENTITYBITS *slot = &s_EntityBits[ entnum ];
	slot->flags = FENTITYBITS_LEAVEPVS;
	slot->effectfinishtime = realtime + EFFECT_TIME;
}

//-----------------------------------------------------------------------------
// Purpose: record entity deletion event
// Input  : entnum - 
//			*pclass - 
//-----------------------------------------------------------------------------
void CL_RecordDeleteEntity( int entnum, ClientClass *pclass )
{
	if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
	{
		return;
	}

	ENTITYBITS *slot = &s_EntityBits[ entnum ];
	slot->flags = FENTITYBITS_DELETE;
	slot->effectfinishtime = realtime + EFFECT_TIME;
	slot->deletedclientclass = pclass;
}

//-----------------------------------------------------------------------------
// Purpose: Shows entity status report if cl_entityreport cvar is set
//-----------------------------------------------------------------------------
class CEntityReportPanel : public CBasePanel
{
	typedef CBasePanel BaseClass;
public:
	// Construction
					CEntityReportPanel( vgui::Panel *parent );
	virtual			~CEntityReportPanel( void );

	// Refresh
	virtual void	Paint();
	virtual void	ApplySchemeSettings( vgui::IScheme *pScheme );
	virtual bool	ShouldDraw( void );

	// Helpers
	void ApplyEffect( ENTITYBITS *entry, int& r, int& g, int& b );
	bool DrawEntry( int row, int col, int rowheight, int colwidth, int entityIdx );

private:
	// Font to use for drawing
	vgui::HFont		m_hFont;
};

static CEntityReportPanel *g_pEntityReportPanel = NULL;

//-----------------------------------------------------------------------------
// Purpose: Creates the CEntityReportPanel VGUI panel
// Input  : *parent - 
//-----------------------------------------------------------------------------
void CL_CreateEntityReportPanel( vgui::Panel *parent )
{
	g_pEntityReportPanel = new CEntityReportPanel( parent );
}

//-----------------------------------------------------------------------------
// Purpose: Instances the entity report panel
// Input  : *parent - 
//-----------------------------------------------------------------------------
CEntityReportPanel::CEntityReportPanel( vgui::Panel *parent ) :
	CBasePanel( parent, "CEntityReportPanel" )
{
	// Need parent here, before loading up textures, so getSurfaceBase 
	//  will work on this panel ( it's null otherwise )
	SetSize( videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight() );
	SetPos( 0, 0 );
	SetVisible( true );
	SetCursor( null );

	m_hFont = vgui::INVALID_FONT;

	SetFgColor( Color( 0, 0, 0, 255 ) );
	SetPaintBackgroundEnabled( false );
	SetPaintBorderEnabled(false);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CEntityReportPanel::~CEntityReportPanel( void )
{
}

void CEntityReportPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	// If you change this font, be sure to mark it with
	// $use_in_fillrate_mode in its .vmt file
	m_hFont = pScheme->GetFont( "DefaultVerySmall", false );
	Assert( m_hFont );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEntityReportPanel::ShouldDraw( void )
{
	if ( !cl_entityreport.GetInt() )
	{
		return false;
	}
	
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Helper to flash colors
// Input  : cycle - 
//			value - 
// Output : static int
//-----------------------------------------------------------------------------
static int MungeColorValue( float cycle, int& value )
{
	int midpoint;
	int remaining;
	bool invert = false;

	if ( value < 128 )
	{
		invert = true;
		value = 255 - value;
	}

	midpoint = value / 2;

	remaining = value - midpoint;
	midpoint = midpoint + remaining / 2;
		
	value = midpoint + ( remaining / 2 ) * cycle;
	if ( invert )
	{
		value = 255 - value;
	}

	value = max( 0, value );
	value = min( 255, value );
	return value;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : frac - 
//			r - 
//			g - 
//			b - 
//-----------------------------------------------------------------------------
void CEntityReportPanel::ApplyEffect( ENTITYBITS *entry, int& r, int& g, int& b )
{
	bool effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;
	if ( !effectactive )
		return;

	float frequency = 3.0f;

	float frac = ( EFFECT_TIME - ( entry->effectfinishtime - realtime ) ) / EFFECT_TIME;
	frac = min( 1.f, frac );
	frac = max( 0.f, frac );

	frac *= 2.0 * M_PI;
	frac = sin( frequency * frac );

	if ( entry->flags & FENTITYBITS_LEAVEPVS )
	{
		r = MungeColorValue( frac, r );
	}
	else if ( entry->flags & FENTITYBITS_ADD )
	{
		g = MungeColorValue( frac, g );
	}
	else if ( entry->flags & FENTITYBITS_DELETE )
	{
		r = MungeColorValue( frac, r );
		g = MungeColorValue( frac, g );
		b = MungeColorValue( frac, b );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CEntityReportPanel::DrawEntry( int row, int col, int rowheight, int colwidth, int entityIdx )
{
	IClientNetworkable *pNet;
	ClientClass			*pClientClass;
	bool				inpvs;
	int					r, g, b, a;
	bool				effectactive;
	ENTITYBITS			*entry;

	int top = 5;
	int left = 5;

	pNet	= entitylist->GetClientNetworkable( entityIdx );
	
	entry = &s_EntityBits[ entityIdx ];

	effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;

	if ( pNet && ((pClientClass = pNet->GetClientClass())) != NULL )
	{
		inpvs = !pNet->IsDormant();
		if ( inpvs )
		{
			if ( entry->average >= 5 )
			{
				r = 200; g = 200; b = 250;
				a = 255;
			}
			else
			{
				r = 200; g = 255; b = 100;
				a = 255;
			}
		}
		else
		{
			r = 255; g = 150; b = 100;
			a = 255;
		}

		ApplyEffect( entry, r, g, b );

		char	text[256];
		wchar_t unicode[ 256 ];

		Q_snprintf( text, sizeof(text), "(%i) %s", entityIdx, pClientClass->m_pNetworkName );
		
		g_pVGuiLocalize->ConvertANSIToUnicode( text, unicode, sizeof( unicode ) );

		DrawColoredText( m_hFont, left + col * colwidth, top + row * rowheight, r, g, b, a, unicode );

		if ( inpvs )
		{
			float fracs[ 3 ];
			fracs[ 0 ] = (float)( entry->bits >> 3 ) / 100.0f;
			fracs[ 1 ] = (float)( entry->peak >> 3 ) / 100.0f;
			fracs[ 2 ] = (float)( (int)entry->average >> 3 ) / 100.0f;

			for ( int j = 0; j < 3; j++ )
			{
				fracs[ j ] = max( 0.0f, fracs[ j ] );
				fracs[ j ] = min( 1.0f, fracs[ j ] );
			}

			int rcright =  left + col * colwidth + colwidth-2;
			int wide = colwidth / 3;
			int rcleft = rcright - wide;
			int rctop = top + row * rowheight;
			int rcbottom = rctop + rowheight - 1;

			vgui::surface()->DrawSetColor( 63, 63, 63, 127 );
			vgui::surface()->DrawFilledRect( rcleft, rctop, rcright, rcbottom );

			// draw a box around it
			vgui::surface()->DrawSetColor( 200, 200, 200, 127 );
			vgui::surface()->DrawOutlinedRect( rcleft, rctop, rcright, rcbottom );

			// Draw current as a filled rect
			vgui::surface()->DrawSetColor( 200, 255, 100, 192 );
			vgui::surface()->DrawFilledRect( rcleft, rctop + rowheight / 2, rcleft + wide * fracs[ 0 ], rcbottom - 1 );

			// Draw average a vertical bar
			vgui::surface()->DrawSetColor( 192, 192, 192, 255 );
			vgui::surface()->DrawFilledRect( rcleft + wide * fracs[ 2 ], rctop + rowheight / 2, rcleft + wide * fracs[ 2 ] + 1, rcbottom - 1 );

			// Draw peak as a vertical red tick
			vgui::surface()->DrawSetColor( 192, 0, 0, 255 );
			vgui::surface()->DrawFilledRect( rcleft + wide * fracs[ 1 ], rctop + 1, rcleft + wide * fracs[ 1 ] + 1, rctop + rowheight / 2 );
		}

		// drew something...
		return true;
	}
	/*else
	{
		r = 63; g = 63; b = 63;
		a = 220;

		ApplyEffect( entry, r, g, b );

		wchar_t unicode[ 256 ];
		g_pVGuiLocalize->ConvertANSIToUnicode( ( effectactive && entry->deletedclientclass ) ? 
			  entry->deletedclientclass->m_pNetworkName : "unused", unicode, sizeof( unicode ) );

		DrawColoredText( m_hFont, left + col * colwidth, top + row * rowheight, r, g, b, a, 
			L"(%i) %s", i, unicode );
	}*/

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEntityReportPanel::Paint() 
{
	VPROF( "CEntityReportPanel::Paint" );

	if ( !m_hFont )
		return;

	if ( !cl.IsActive() )
		return;

	if ( !entitylist )
		return;

	int top = 5;
	int left = 5;
	int row = 0;
	int col = 0;
	int colwidth = 160;
	int rowheight = vgui::surface()->GetFontTall( m_hFont );

	IClientNetworkable *pNet;
	bool				effectactive;
	ENTITYBITS			*entry;

	int lastused = entitylist->GetMaxEntities()-1;

	while ( lastused > 0 )
	{
		pNet	= entitylist->GetClientNetworkable( lastused );

		entry = &s_EntityBits[ lastused ];

		effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;

		if ( pNet && pNet->GetClientClass() )
		{
			break;
		}

		if ( effectactive )
			break;

		lastused--;
	}

 	int start = 0;
 	if ( cl_entityreport.GetInt() > 1 )
 	{
 		start = cl_entityreport.GetInt();
 	}

	// draw sorted
	if ( cl_entityreport_sorted.GetInt() != ENTITYSORT_NONE )
	{
		// copy and sort
		int entityIndices[MAX_EDICTS];
		int count = lastused - start + 1;
		for ( int i = 0, entityIdx = start; entityIdx <= lastused; ++i, ++entityIdx )
		{
			entityIndices[i] = entityIdx;
		}
		qsort( entityIndices, count, sizeof(int), CompareEntityBits );

		// now draw
		for ( int i = 0; i < count; ++i )
		{
			int entityIdx = entityIndices[i];

			if ( DrawEntry( row, col, rowheight, colwidth, entityIdx ) )
			{
				row++;
				if ( top + row * rowheight > videomode->GetModeStereoHeight() - rowheight )
				{
					row = 0;
					col++;
					// No more space anyway, give up
					if ( left + ( col + 1 ) * 200 > videomode->GetModeStereoWidth() )
						return;
				}
			}
		}
	}
	// not sorted, old method with items scattered across the screen
	else
	{
		for ( int i = start; i <= lastused; i++ )
		{
			DrawEntry( row, col, rowheight, colwidth, i );

			row++;
			if ( top + row * rowheight > videomode->GetModeStereoHeight() - rowheight )
			{
				row = 0;
				col++;
				// No more space anyway, give up
				if ( left + ( col + 1 ) * 200 > videomode->GetModeStereoWidth() )
					return;
			}
		}
	}
}