//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//===========================================================================//

#include "pch_tier0.h"
#include "tier0/minidump.h"

#if defined( _WIN32 ) && !defined( _X360 )
#include "tier0/valve_off.h"
#define WIN_32_LEAN_AND_MEAN
#include <windows.h>				// Currently needed for IsBadReadPtr and IsBadWritePtr
#pragma comment(lib,"user32.lib")	// For MessageBox
#include <time.h>
#endif

#include <assert.h>
#ifdef OSX
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include "Color.h"
#include "tier0/dbg.h"
#include "tier0/threadtools.h"
#include "tier0/icommandline.h"
#include <math.h>
#if defined( _X360 )
#include "xbox/xbox_console.h"
#endif

#ifdef ANDROID
#include <android/log.h>
#endif

#include "tier0/etwprof.h"

#ifndef STEAM
#define PvRealloc realloc
#define PvAlloc malloc
#endif

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

extern const tchar* GetProcessorArchName();

#define MAX_MSGS 4196

class CDbgLogger : public IDbgLogger
{
public:
	CDbgLogger();
	~CDbgLogger();

	void Init(const char *logfile);
	void Write(const char *data);
	void Disable();

private:
	FILE *file;
	float flStartTime;
	bool bShouldLog;

	char *pMsgs[MAX_MSGS];
	size_t iMsg;
};


CDbgLogger::CDbgLogger()
{
	bShouldLog = true;
	flStartTime = Plat_FloatTime();
	file = NULL;
	iMsg = 0;
}

void CDbgLogger::Disable()
{
	bShouldLog = false;

	while( iMsg > 0 )
	{
		delete[] pMsgs[iMsg];
		iMsg--;
	}
}

void CDbgLogger::Init(const char *logfile)
{
	time_t timeCur;
	struct tm tmStruct;

	char szTime[256];

	bShouldLog = true;

	time( &timeCur );
	Plat_gmtime( &timeCur, &tmStruct );
	Plat_ctime( &timeCur, szTime, sizeof(szTime) );

	file = fopen(logfile, "w+");
	if( file )
	{
#ifdef GIT_COMMIT_HASH
		fprintf(file, ">>> Engine(arch:%s commit:" GIT_COMMIT_HASH ") started at %s\n", GetProcessorArchName(), szTime);
#else
		fprintf(file, ">>> Engine(arch:%s) started at %s\n", GetProcessorArchName(), szTime);
#endif

#ifdef GNUC
		fprintf(file, "Compiler version: %s\n", __VERSION__);
#endif
		fprintf(file, "Compiler CFLAGS: %s\n", WAF_CFLAGS);
		fprintf(file, "Compiler LDFLAGS: %s\n", WAF_LDFLAGS);
		fflush(file);

		for( int i = 0; i < iMsg; i++ )
		{
			Write(pMsgs[i]);
			delete[] pMsgs[i];
		}
		iMsg = 0;
	}
}

CDbgLogger::~CDbgLogger()
{
	while( iMsg > 0 )
	{
		delete[] pMsgs[iMsg];
		iMsg--;
	}

	if( !file )
		return;

	time_t timeCur;
	struct tm tmStruct;

	char szTime[256];

	time( &timeCur );
	Plat_gmtime( &timeCur, &tmStruct );
	Plat_ctime( &timeCur, szTime, sizeof(szTime) );

	fprintf(file, "\n>>> Engine closed at %s\n", szTime);
	fclose(file);
}

void CDbgLogger::Write(const char *data)
{
	if( !bShouldLog )
		return;

	size_t len = strlen(data);

	if( file )
	{
		fprintf(file, "[%.4f] ", Plat_FloatTime() - flStartTime);
		fprintf(file, "%s", data);
		fflush(file);
	}
	else if( iMsg < MAX_MSGS )
	{
		pMsgs[iMsg] = new char[len+8];
		memcpy(pMsgs[iMsg], data, len);
		pMsgs[iMsg][len] = 0;
		iMsg++;
	}
}

static CDbgLogger g_DbgLogger;
IDbgLogger *DebugLogger() { return &g_DbgLogger; }

//-----------------------------------------------------------------------------
// internal structures
//-----------------------------------------------------------------------------
enum
{
	MAX_GROUP_NAME_LENGTH = 48 
};

struct SpewGroup_t
{
	tchar	m_GroupName[MAX_GROUP_NAME_LENGTH];
	int		m_Level;
};

// Skip forward past the directory
static const char *SkipToFname( const tchar* pFile )
{
	if ( pFile == NULL )
		return "unknown";
	const tchar* pSlash = _tcsrchr( pFile, '\\' );
	const tchar* pSlash2 = _tcsrchr( pFile, '/' );
	if (pSlash < pSlash2) pSlash = pSlash2;
	return pSlash ? pSlash + 1: pFile;
}


//-----------------------------------------------------------------------------
DBG_INTERFACE SpewRetval_t DefaultSpewFunc( SpewType_t type, const tchar *pMsg )
{
#ifdef _X360
	if ( XBX_IsConsoleConnected() )
	{
		// send to console
		XBX_DebugString( XMAKECOLOR( 0,0,0 ), pMsg );
	}
	else
#endif
	{
		_tprintf( _T("%s"), pMsg );
#ifdef _WIN32
		Plat_DebugString( pMsg );
#endif
	}
	if ( type == SPEW_ASSERT )
	{
#ifndef WIN32
		// Non-win32
		bool bRaiseOnAssert = getenv( "RAISE_ON_ASSERT" ) || !!CommandLine()->FindParm( "-raiseonassert" );
#elif defined( _DEBUG )
		// Win32 debug
		bool bRaiseOnAssert = true;
#else
		// Win32 release
		bool bRaiseOnAssert = !!CommandLine()->FindParm( "-raiseonassert" );
#endif

		return bRaiseOnAssert ? SPEW_DEBUGGER : SPEW_CONTINUE;
	}
	else if ( type == SPEW_ERROR )
		return SPEW_ABORT;
	else
		return SPEW_CONTINUE;
}

//-----------------------------------------------------------------------------
DBG_INTERFACE SpewRetval_t DefaultSpewFuncAbortOnAsserts( SpewType_t type, const tchar *pMsg )
{
	SpewRetval_t r = DefaultSpewFunc( type, pMsg );
	if ( type == SPEW_ASSERT )
		r = SPEW_ABORT;
	return r;
}


//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
static SpewOutputFunc_t   s_SpewOutputFunc = DefaultSpewFunc;

static AssertFailedNotifyFunc_t	s_AssertFailedNotifyFunc = NULL;

static const tchar*	s_pFileName;
static int			s_Line;
static SpewType_t	s_SpewType;

static SpewGroup_t* s_pSpewGroups = 0;
static int			s_GroupCount = 0;
static int			s_DefaultLevel = 0;
#if !defined( _X360 )
static Color		s_DefaultOutputColor( 255, 255, 255, 255 );
#else
static Color		s_DefaultOutputColor( 0, 0, 0, 255 );
#endif

// Only useable from within a spew function
struct SpewInfo_t
{
	const Color*	m_pSpewOutputColor;
	const tchar*	m_pSpewOutputGroup;
	int				m_nSpewOutputLevel;
};

CTHREADLOCALPTR(SpewInfo_t) g_pSpewInfo;


// Standard groups
static const tchar* s_pDeveloper = _T("developer");
static const tchar* s_pConsole = _T("console");
static const tchar* s_pNetwork = _T("network");

enum StandardSpewGroup_t
{
	GROUP_DEVELOPER = 0,
	GROUP_CONSOLE,
	GROUP_NETWORK,

	GROUP_COUNT,
};

static int s_pGroupIndices[GROUP_COUNT] = { -1, -1, -1 };
static const char *s_pGroupNames[GROUP_COUNT] = { s_pDeveloper, s_pConsole, s_pNetwork };


//-----------------------------------------------------------------------------
// Spew output management.
//-----------------------------------------------------------------------------
void SpewOutputFunc( SpewOutputFunc_t func )
{
	s_SpewOutputFunc = func ? func : DefaultSpewFunc;
}

SpewOutputFunc_t GetSpewOutputFunc( void )
{
	if( s_SpewOutputFunc )
		return s_SpewOutputFunc;
	return DefaultSpewFunc;
}

void _ExitOnFatalAssert( const tchar* pFile, int line )
{
	_SpewMessage( _T("Fatal assert failed: %s, line %d.  Application exiting.\n"), pFile, line );

	// only write out minidumps if we're not in the debugger
	if ( !Plat_IsInDebugSession() )
	{
		char rgchSuffix[512];
		_snprintf( rgchSuffix, sizeof(rgchSuffix), "fatalassert_%s_%d", SkipToFname( pFile ), line );
		WriteMiniDump( rgchSuffix );
	}

	DevMsg( 1, _T("_ExitOnFatalAssert\n") );
	exit( EXIT_FAILURE );
}


//-----------------------------------------------------------------------------
// Templates to assist in validating pointers:
//-----------------------------------------------------------------------------


DBG_INTERFACE void _AssertValidReadPtr( void* ptr, int count/* = 1*/ )
{
	Assert( !count || ptr );
}

DBG_INTERFACE void _AssertValidWritePtr( void* ptr, int count/* = 1*/ )
{
	Assert( !count || ptr );
}

DBG_INTERFACE void _AssertValidReadWritePtr( void* ptr, int count/* = 1*/ )
{
	Assert( !count || ptr );
}

#undef AssertValidStringPtr
DBG_INTERFACE void AssertValidStringPtr( const tchar* ptr, int maxchar/* = 0xFFFFFF */ )
{
	Assert( ptr );
}

//-----------------------------------------------------------------------------
// Should be called only inside a SpewOutputFunc_t, returns groupname, level, color
//-----------------------------------------------------------------------------
const tchar* GetSpewOutputGroup( void )
{
	SpewInfo_t *pSpewInfo = g_pSpewInfo;
	assert( pSpewInfo );
	if ( pSpewInfo )
		return pSpewInfo->m_pSpewOutputGroup;
	return NULL;
}

int GetSpewOutputLevel( void )
{
	SpewInfo_t *pSpewInfo = g_pSpewInfo;
	assert( pSpewInfo );
	if ( pSpewInfo )
		return pSpewInfo->m_nSpewOutputLevel;
	return -1;
}

const Color* GetSpewOutputColor( void )
{
	SpewInfo_t *pSpewInfo = g_pSpewInfo;
	assert( pSpewInfo );
	if ( pSpewInfo )
		return pSpewInfo->m_pSpewOutputColor;
	return &s_DefaultOutputColor;
}


//-----------------------------------------------------------------------------
// Spew functions
//-----------------------------------------------------------------------------
DBG_INTERFACE void  _SpewInfo( SpewType_t type, const tchar* pFile, int line )
{
	// Only grab the file name. Ignore the path.
	s_pFileName = SkipToFname( pFile );
	s_Line = line;
	s_SpewType = type;
}


static SpewRetval_t _SpewMessage( SpewType_t spewType, const char *pGroupName, int nLevel, const Color *pColor, const tchar* pMsgFormat, va_list args )
{
	tchar pTempBuffer[8192];

	assert( _tcslen( pMsgFormat ) < sizeof( pTempBuffer) ); // check that we won't artifically truncate the string

	/* Printf the file and line for warning + assert only... */
	int len = 0;
	if ( spewType == SPEW_ASSERT )
	{
		len = _sntprintf( pTempBuffer, sizeof( pTempBuffer ) - 1, _T("%s (%d) : "), s_pFileName, s_Line );
	}

	if ( len == -1 )
		return SPEW_ABORT;

	/* Create the message.... */
	int val= _vsntprintf( &pTempBuffer[len], sizeof( pTempBuffer ) - len - 1, pMsgFormat, args );
	if ( val == -1 )
		return SPEW_ABORT;

	len += val;
	assert( len * sizeof(*pMsgFormat) < sizeof(pTempBuffer) ); /* use normal assert here; to avoid recursion. */

	// Add \n for warning and assert
	if ( spewType == SPEW_ASSERT )
	{
		len += _stprintf( &pTempBuffer[len], _T("\n") ); 
	}

	assert( len < sizeof(pTempBuffer)/sizeof(pTempBuffer[0]) - 1 ); /* use normal assert here; to avoid recursion. */
	assert( s_SpewOutputFunc );

	/* direct it to the appropriate target(s) */
	SpewRetval_t ret;
	assert( (void*)g_pSpewInfo == (void*)NULL );
	SpewInfo_t spewInfo =
	{
		pColor,
		pGroupName,
		nLevel
	};

#ifdef ANDROID
	__android_log_print( ANDROID_LOG_INFO, "SRCENG", "%s", pTempBuffer );
#endif
	g_DbgLogger.Write( pTempBuffer );

	g_pSpewInfo = &spewInfo;
	ret = s_SpewOutputFunc( spewType, pTempBuffer );
	g_pSpewInfo = NULL;

	switch (ret)
	{
// Asserts put the break into the macro so it occurs in the right place
	case SPEW_DEBUGGER:
		if ( spewType != SPEW_ASSERT )
		{
			DebuggerBreak();
		}
		break;
		
	case SPEW_ABORT:
	{
//		MessageBox(NULL,"Error in _SpewMessage","Error",MB_OK);
//		ConMsg( _T("Exiting on SPEW_ABORT\n") );
		exit(1);
	}
	}

	return ret;
}

#include "tier0/valve_off.h"

FORCEINLINE SpewRetval_t _SpewMessage( SpewType_t spewType, const tchar* pMsgFormat, va_list args )
{
	return _SpewMessage( spewType, "", 0, &s_DefaultOutputColor, pMsgFormat, args );
}


//-----------------------------------------------------------------------------
// Find a group, return true if found, false if not. Return in ind the
// index of the found group, or the index of the group right before where the
// group should be inserted into the list to maintain sorted order.
//-----------------------------------------------------------------------------
bool FindSpewGroup( const tchar* pGroupName, int* pInd )
{
	int s = 0;
	if (s_GroupCount)
	{
		int e = (int)(s_GroupCount - 1);
		while ( s <= e )
		{
			int m = (s+e) >> 1;
			int cmp = _tcsicmp( pGroupName, s_pSpewGroups[m].m_GroupName );
			if ( !cmp )
			{
				*pInd = m;
				return true;
			}
			if ( cmp < 0 )
				e = m - 1;
			else
				s = m + 1;
		}
	}
	*pInd = s;
	return false;
}

//-----------------------------------------------------------------------------
// True if -hushasserts was passed on command line.
//-----------------------------------------------------------------------------
bool HushAsserts()
{
#ifdef DBGFLAG_ASSERT
	static bool s_bHushAsserts = !!CommandLine()->FindParm( "-hushasserts" );
	return s_bHushAsserts;
#else
	return true;
#endif
}

//-----------------------------------------------------------------------------
// Tests to see if a particular spew is active
//-----------------------------------------------------------------------------
bool IsSpewActive( const tchar* pGroupName, int level )
{
	// If we don't find the spew group, use the default level.
	int ind;
	if ( FindSpewGroup( pGroupName, &ind ) )
		return s_pSpewGroups[ind].m_Level >= level;
	else
		return s_DefaultLevel >= level;
}

inline bool IsSpewActive( StandardSpewGroup_t group, int level )
{
	// If we don't find the spew group, use the default level.
	if ( s_pGroupIndices[group] >= 0 )
		return s_pSpewGroups[ s_pGroupIndices[group] ].m_Level >= level;
	return s_DefaultLevel >= level;
}

SpewRetval_t  _SpewMessage( const tchar* pMsgFormat, ... )
{
	va_list args;
	va_start( args, pMsgFormat );
	SpewRetval_t ret = _SpewMessage( s_SpewType, pMsgFormat, args );
	va_end(args);
	return ret;
}

SpewRetval_t _DSpewMessage( const tchar *pGroupName, int level, const tchar* pMsgFormat, ... )
{
	if( !IsSpewActive( pGroupName, level ) )
		return SPEW_CONTINUE;

	va_list args;
	va_start( args, pMsgFormat );
	SpewRetval_t ret = _SpewMessage( s_SpewType, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
	return ret;
}

DBG_INTERFACE SpewRetval_t ColorSpewMessage( SpewType_t type, const Color *pColor, const tchar* pMsgFormat, ... )
{
	va_list args;
	va_start( args, pMsgFormat );
	SpewRetval_t ret = _SpewMessage( type, "", 0, pColor, pMsgFormat, args );
	va_end(args);
	return ret;
}

void Msg( const tchar* pMsgFormat, ... )
{
	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, pMsgFormat, args );
	va_end(args);
}

void DMsg( const tchar *pGroupName, int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( pGroupName, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void MsgV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist )
{
	_SpewMessage( SPEW_MESSAGE, pMsg, arglist );
}


void Warning( const tchar *pMsgFormat, ... )
{
	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, pMsgFormat, args );
	va_end(args);
}

void DWarning( const tchar *pGroupName, int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( pGroupName, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void WarningV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist )
{
	_SpewMessage( SPEW_WARNING, pMsg, arglist );
}


void Log( const tchar *pMsgFormat, ... )
{
	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, pMsgFormat, args );
	va_end(args);
}

void DLog( const tchar *pGroupName, int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( pGroupName, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void LogV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist )
{
	_SpewMessage( SPEW_LOG, pMsg, arglist );
}


void Error( const tchar *pMsgFormat, ... )
{
	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_ERROR, pMsgFormat, args );
	va_end(args);
}

void ErrorV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist )
{
	_SpewMessage( SPEW_ERROR, pMsg, arglist );
}

//-----------------------------------------------------------------------------
// A couple of super-common dynamic spew messages, here for convenience 
// These looked at the "developer" group, print if it's level 1 or higher 
//-----------------------------------------------------------------------------
void DevMsg( int level, const tchar* pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_DEVELOPER, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pDeveloper, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void DevWarning( int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_DEVELOPER, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, s_pDeveloper, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void DevLog( int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_DEVELOPER, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, s_pDeveloper, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void DevMsg( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_DEVELOPER, 1 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pDeveloper, 1, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void DevWarning( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_DEVELOPER, 1 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, s_pDeveloper, 1, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void DevLog( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_DEVELOPER, 1 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, s_pDeveloper, 1, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}


//-----------------------------------------------------------------------------
// A couple of super-common dynamic spew messages, here for convenience 
// These looked at the "console" group, print if it's level 1 or higher 
//-----------------------------------------------------------------------------
void ConColorMsg( int level, const Color& clr, const tchar* pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pConsole, level, &clr, pMsgFormat, args );
	va_end(args);
}

void ConMsg( int level, const tchar* pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pConsole, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void ConWarning( int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, s_pConsole, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void ConLog( int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, s_pConsole, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void ConColorMsg( const Color& clr, const tchar* pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 1 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pConsole, 1, &clr, pMsgFormat, args );
	va_end(args);
}

void ConMsg( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 1 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pConsole, 1, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void ConWarning( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 1 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, s_pConsole, 1, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void ConLog( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 1 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, s_pConsole, 1, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}


void ConDColorMsg( const Color& clr, const tchar* pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 2 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pConsole, 2, &clr, pMsgFormat, args );
	va_end(args);
}

void ConDMsg( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 2 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pConsole, 2, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void ConDWarning( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 2 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, s_pConsole, 2, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void ConDLog( const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_CONSOLE, 2 ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, s_pConsole, 2, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}


//-----------------------------------------------------------------------------
// A couple of super-common dynamic spew messages, here for convenience 
// These looked at the "network" group, print if it's level 1 or higher 
//-----------------------------------------------------------------------------
void NetMsg( int level, const tchar* pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_NETWORK, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_MESSAGE, s_pNetwork, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void NetWarning( int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_NETWORK, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_WARNING, s_pNetwork, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

void NetLog( int level, const tchar *pMsgFormat, ... )
{
	if( !IsSpewActive( GROUP_NETWORK, level ) )
		return;

	va_list args;
	va_start( args, pMsgFormat );
	_SpewMessage( SPEW_LOG, s_pNetwork, level, &s_DefaultOutputColor, pMsgFormat, args );
	va_end(args);
}

#include "tier0/valve_on.h"


//-----------------------------------------------------------------------------
// Sets the priority level for a spew group
//-----------------------------------------------------------------------------
void SpewActivate( const tchar* pGroupName, int level )
{
	Assert( pGroupName );
	
	// check for the default group first...
	if ((pGroupName[0] == '*') && (pGroupName[1] == '\0'))
	{
		s_DefaultLevel = level;
		return;
	}
	
	// Normal case, search in group list using binary search.
	// If not found, grow the list of groups and insert it into the
	// right place to maintain sorted order. Then set the level.
	int ind;
	if ( !FindSpewGroup( pGroupName, &ind ) )
	{
		// not defined yet, insert an entry.
		++s_GroupCount;
		if ( s_pSpewGroups )
		{
			s_pSpewGroups = (SpewGroup_t*)PvRealloc( s_pSpewGroups, 
				s_GroupCount * sizeof(SpewGroup_t) );
			
			// shift elements down to preserve order
			int numToMove = s_GroupCount - ind - 1;
			memmove( &s_pSpewGroups[ind+1], &s_pSpewGroups[ind], 
				numToMove * sizeof(SpewGroup_t) );

			// Update standard groups
			for ( int i = 0; i < GROUP_COUNT; ++i )
			{   
				if ( ( ind <= s_pGroupIndices[i] ) && ( s_pGroupIndices[i] >= 0 ) )
				{
					++s_pGroupIndices[i];
				}
			}
		}
		else
		{
			s_pSpewGroups = (SpewGroup_t*)PvAlloc( s_GroupCount * sizeof(SpewGroup_t) ); 
		}
		
		Assert( _tcslen( pGroupName ) < MAX_GROUP_NAME_LENGTH );
		_tcscpy( s_pSpewGroups[ind].m_GroupName, pGroupName );

		// Update standard groups
		for ( int i = 0; i < GROUP_COUNT; ++i )
		{
			if ( ( s_pGroupIndices[i] < 0 ) && !_tcsicmp( s_pGroupNames[i], pGroupName ) )
			{
				s_pGroupIndices[i] = ind;
				break;
			}
		}
	}
	s_pSpewGroups[ind].m_Level = level;
}


// If we don't have a function from math.h, then it doesn't link certain floating-point
// functions in and printfs with %f cause runtime errors in the C libraries.
DBG_INTERFACE float CrackSmokingCompiler( float a )
{
	return (float)fabs( a );
}

void* Plat_SimpleLog( const tchar* file, int line )
{
	FILE* f = _tfopen( _T("simple.log"), _T("at+") );
	_ftprintf( f, _T("%s:%i\n"), file, line );
	fclose( f );

	return NULL;
}

#ifdef DBGFLAG_VALIDATE
void ValidateSpew( CValidator &validator )
{
	validator.Push( _T("Spew globals"), NULL, _T("Global") );

	validator.ClaimMemory( s_pSpewGroups );

	validator.Pop( );
}
#endif // DBGFLAG_VALIDATE

//-----------------------------------------------------------------------------
// Purpose: For debugging startup times, etc.
// Input  : *fmt - 
//			... - 
//-----------------------------------------------------------------------------
void COM_TimestampedLog( char const *fmt, ... )
{
	static float s_LastStamp = 0.0;
	static bool s_bShouldLog = false;
	static bool s_bShouldLogToETW = false;
	static bool s_bChecked = false;
	static bool	s_bFirstWrite = false;

	if ( !s_bChecked )
	{
		s_bShouldLog = ( IsX360() || CommandLine()->CheckParm( "-profile" ) ) ? true : false;
		s_bShouldLogToETW = ( CommandLine()->CheckParm( "-etwprofile" ) ) ? true : false;
		if ( s_bShouldLogToETW )
		{
			s_bShouldLog = true;
		}
		s_bChecked = true;
	}
	if ( !s_bShouldLog )
	{
		return;
	}

	char string[1024];
	va_list argptr;
	va_start( argptr, fmt );
	_vsnprintf( string, sizeof( string ), fmt, argptr );
	va_end( argptr );

	float curStamp = Plat_FloatTime();

#if defined( _X360 )
	XBX_rTimeStampLog( curStamp, string );
#endif

	if ( IsPC() )
	{
		// If ETW profiling is enabled then do it only.
		if ( s_bShouldLogToETW )
		{
			ETWMark( string );
		}
		else
		{
			if ( !s_bFirstWrite )
			{
				unlink( "timestamped.log" );
				s_bFirstWrite = true;
			}

			FILE* fp = fopen( "timestamped.log", "at+" );
			fprintf( fp, "%8.4f / %8.4f:  %s\n", curStamp, curStamp - s_LastStamp, string );
			fclose( fp );
		}
	}

	s_LastStamp = curStamp;
}

//-----------------------------------------------------------------------------
// Sets an assert failed notify handler
//-----------------------------------------------------------------------------
void SetAssertFailedNotifyFunc( AssertFailedNotifyFunc_t func )
{
	s_AssertFailedNotifyFunc = func;
}


//-----------------------------------------------------------------------------
// Calls the assert failed notify handler if one has been set
//-----------------------------------------------------------------------------
void CallAssertFailedNotifyFunc( const char *pchFile, int nLine, const char *pchMessage )
{
	if ( s_AssertFailedNotifyFunc )
		s_AssertFailedNotifyFunc( pchFile, nLine, pchMessage );
}