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

#ifndef GAMESTATS_H
#define GAMESTATS_H
#ifdef _WIN32
#pragma once
#endif

#include "tier1/utldict.h"
#include "tier1/utlbuffer.h"
#include "igamesystem.h"
//#include "steamworks_gamestats.h"

const int GAMESTATS_VERSION = 1;

enum StatSendType_t
{
	STATSEND_LEVELSHUTDOWN,
	STATSEND_APPSHUTDOWN,
	STATSEND_NOTENOUGHPLAYERS,
};

struct StatsBufferRecord_t
{
	float m_flFrameRate;									// fps
	float m_flServerPing;									// client ping to server

};

#define STATS_WINDOW_SIZE ( 60 * 10 )						// # of records to hold
#define STATS_RECORD_INTERVAL 1								// # of seconds between data grabs. 2 * 300 = every 10 minutes

class CGameStats;

void UpdatePerfStats( void );
void SetGameStatsHandler( CGameStats *pGameStats );

class CBasePlayer;
class CPropVehicleDriveable;
class CTakeDamageInfo;

#ifdef GAME_DLL

#define GAMESTATS_STANDARD_NOT_SAVED 0xFEEDBEEF

enum GameStatsVersions_t
{
	GAMESTATS_FILE_VERSION_OLD = 001,
	GAMESTATS_FILE_VERSION_OLD2,
	GAMESTATS_FILE_VERSION_OLD3,
	GAMESTATS_FILE_VERSION_OLD4,
	GAMESTATS_FILE_VERSION_OLD5,
	GAMESTATS_FILE_VERSION
};

struct BasicGameStatsRecord_t
{
public:
	BasicGameStatsRecord_t() :
	  m_nCount( 0 ),
		  m_nSeconds( 0 ),
		  m_nCommentary( 0 ),
		  m_nHDR( 0 ),
		  m_nCaptions( 0 ),
		  m_bSteam( true ),
		  m_bCyberCafe( false ),
		  m_nDeaths( 0 )
	  {
		  Q_memset( m_nSkill, 0, sizeof( m_nSkill ) );
	  }

	  void		Clear();

	  void		SaveToBuffer( CUtlBuffer& buf );
	  bool		ParseFromBuffer( CUtlBuffer& buf, int iBufferStatsVersion );

	  // Data
public:
	int			m_nCount;
	int			m_nSeconds;

	int			m_nCommentary;
	int			m_nHDR;
	int			m_nCaptions;
	int			m_nSkill[ 3 ];
	bool		m_bSteam;
	bool		m_bCyberCafe;
	int			m_nDeaths;
};

struct BasicGameStats_t
{
public:
	BasicGameStats_t() :
		  m_nSecondsToCompleteGame( 0 ),
		  m_nHL2ChaptureUnlocked( 0 ),
		  m_bSteam( true ),
		  m_bCyberCafe( false ),
		  m_nDXLevel( 0 )
	  {
	  }

	  void						Clear();

	  void						SaveToBuffer( CUtlBuffer& buf );
	  bool						ParseFromBuffer( CUtlBuffer& buf, int iBufferStatsVersion );

	  BasicGameStatsRecord_t	*FindOrAddRecordForMap( char const *mapname );

	  // Data
public:
	int							m_nSecondsToCompleteGame; // 0 means they haven't finished playing yet

	BasicGameStatsRecord_t		m_Summary;			// Summary record
	CUtlDict< BasicGameStatsRecord_t, unsigned short > m_MapTotals;
	bool						m_bSteam;
	bool						m_bCyberCafe;
	int							m_nHL2ChaptureUnlocked;
	int							m_nDXLevel;
};
#endif // GAME_DLL

class CBaseGameStats 
{
public:
	CBaseGameStats();

	// override this to declare what format you want to send.  New products should use new format.
	virtual bool UseOldFormat() 
	{ 
#ifdef GAME_DLL
		return true;		// servers by default send old format for backward compat
#else
		return false;		// clients never used old format so no backward compat issues, they use new format by default
#endif
	}

	// Implement this if you support new format gamestats.
	// Return true if you added data to KeyValues, false if you have no data to report
	virtual bool AddDataForSend( KeyValues *pKV, StatSendType_t sendType ) { return false; }

	// These methods used for new format gamestats only and control when data gets sent.
	virtual bool ShouldSendDataOnLevelShutdown()
	{
		// by default, servers send data at every level change and clients don't
#ifdef GAME_DLL
		return true;
#else
		return false;
#endif
	}
	virtual bool ShouldSendDataOnAppShutdown()
	{
		// by default, clients send data at app shutdown and servers don't
#ifdef GAME_DLL
		return false;
#else
		return true;
#endif
	}

	virtual void Event_Init( void );
	virtual void Event_Shutdown( void );
	virtual void Event_MapChange( const char *szOldMapName, const char *szNewMapName );
	virtual void Event_LevelInit( void );
	virtual void Event_LevelShutdown( float flElapsed );
	virtual void Event_SaveGame( void );
	virtual void Event_LoadGame( void );

	void		CollectData( StatSendType_t sendType );
	void		SendData();

	void StatsLog( PRINTF_FORMAT_STRING char const *fmt, ... );
	
	// This is the first call made, so that we can "subclass" the CBaseGameStats based on gamedir as needed (e.g., ep2 vs. episodic)
	virtual CBaseGameStats *OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir ) { return pCurrentGameStats; }

	// Frees up data from gamestats and resets it to a clean state.
	virtual void Clear( void );

	virtual bool StatTrackingEnabledForMod( void ) { return false; } //Override this to turn on the system. Stat tracking is disabled by default and will always be disabled at the user's request
	static bool StatTrackingAllowed( void ); //query whether stat tracking is possible and warranted by the user
	virtual bool HaveValidData( void ) { return true; } // whether we currently have an interesting enough data set to upload.  Called at upload time; if false, data is not uploaded.

	virtual bool ShouldTrackStandardStats( void ) { return true; } //exactly what was tracked for EP1 release
	
	//Get mod specific strings used for tracking, defaults should work fine for most cases
	virtual const char *GetStatSaveFileName( void );
	virtual const char *GetStatUploadRegistryKeyName( void );
	const char *GetUserPseudoUniqueID( void );

	virtual bool UserPlayedAllTheMaps( void ) { return false; } //be sure to override this to determine user completion time

#ifdef CLIENT_DLL
	virtual void Event_AchievementProgress( int achievementID, const char* achievementName ) {}
#endif

#ifdef GAME_DLL
	virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );	
	virtual void Event_PlayerConnected( CBasePlayer *pBasePlayer );
	virtual void Event_PlayerDisconnected( CBasePlayer *pBasePlayer );
	virtual void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
	virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
	virtual void Event_PlayerSuicide( CBasePlayer* pPlayer ) {}
	virtual void Event_Credits();
	virtual void Event_Commentary();
	virtual void Event_CrateSmashed();
	virtual void Event_Punted( CBaseEntity *pObject );
	virtual void Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting );
	virtual void Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName );
	virtual void Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info );
	virtual void Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle );
	virtual void Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame );
	virtual void Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer );
	virtual void Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer );
	virtual void Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer );
	virtual void Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount );

    //=============================================================================
    // HPE_BEGIN
    // [dwenger] Functions necessary for cs-specific stats
    //=============================================================================
    virtual void Event_WindowShattered( CBasePlayer *pPlayer );
    //=============================================================================
    // HPE_END
    //=============================================================================

	//custom data to tack onto existing stats if you're not doing a complete overhaul
	virtual void AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer ) { } //custom data you want thrown into the default save and upload path
	virtual void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer ) { }; //when loading the saved stats file, this will point to where you started saving data to the save buffer

	virtual void LoadingEvent_PlayerIDDifferentThanLoadedStats( void ); //Only called if you use the base SaveToFileNOW() and LoadFromFile() functions. Used in case you want to keep/invalidate data that was just loaded. 

	virtual bool LoadFromFile( void ); //called just before Event_Init()
	virtual bool SaveToFileNOW( bool bForceSyncWrite = false ); //saves buffers to their respective files now, returns success or failure
	virtual bool UploadStatsFileNOW( void ); //uploads data to the CSER now, returns success or failure

	static bool AppendLump( int nMaxLumpCount, CUtlBuffer &SaveBuffer, unsigned short iLump, unsigned short iLumpCount, size_t nSize, void *pData );
	static bool GetLumpHeader( int nMaxLumpCount, CUtlBuffer &LoadBuffer, unsigned short &iLump, unsigned short &iLumpCount, bool bPermissive = false );
	static void LoadLump( CUtlBuffer &LoadBuffer, unsigned short iLumpCount, size_t nSize, void *pData );

	//default save behavior is to save on level shutdown, and game shutdown
	virtual bool AutoSave_OnInit( void ) { return false; }
	virtual bool AutoSave_OnShutdown( void ) { return true; }
	virtual bool AutoSave_OnMapChange( void ) { return false; }
	virtual bool AutoSave_OnLevelInit( void ) { return false; }
	virtual bool AutoSave_OnLevelShutdown( void ) { return true; }

	//default upload behavior is to upload on game shutdown
	virtual bool AutoUpload_OnInit( void ) { return false; }
	virtual bool AutoUpload_OnShutdown( void ) { return true; }
	virtual bool AutoUpload_OnMapChange( void ) { return false; }
	virtual bool AutoUpload_OnLevelInit( void ) { return false; }
	virtual bool AutoUpload_OnLevelShutdown( void ) { return false; }

	// Helper for builtin stuff
	void SetSteamStatistic( bool bUsingSteam );
	void SetCyberCafeStatistic( bool bIsCyberCafeUser );
	void SetHDRStatistic( bool bHDREnabled );
	void SetCaptionsStatistic( bool bClosedCaptionsEnabled );
	void SetSkillStatistic( int iSkillSetting );
	void SetDXLevelStatistic( int iDXLevel );
	void SetHL2UnlockedChapterStatistic( void );
#endif // GAMEDLL
public:
#ifdef GAME_DLL
	BasicGameStats_t m_BasicStats; //exposed in case you do a complete overhaul and still want to save it
#endif
	bool			m_bLogging : 1;
	bool			m_bLoggingToFile : 1;
};

#ifdef GAME_DLL

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &SaveBuffer - 
//			iLump - 
//			iLumpCount - 
//-----------------------------------------------------------------------------
inline bool CBaseGameStats::AppendLump( int nMaxLumpCount, CUtlBuffer &SaveBuffer, unsigned short iLump, unsigned short iLumpCount, size_t nSize, void *pData )
{
	// Verify the lump index.
	Assert( ( iLump > 0 ) && ( iLump < nMaxLumpCount ) );

	if ( !( ( iLump > 0 ) && ( iLump < nMaxLumpCount ) ) )
		return false;

	// Check to see if we have any elements to save.
	if ( iLumpCount <= 0 )
		return false;

	// Write the lump id and element count.
	SaveBuffer.PutUnsignedShort( iLump );
	SaveBuffer.PutUnsignedShort( iLumpCount );

	size_t nTotalSize = iLumpCount * nSize;
	SaveBuffer.Put( pData, nTotalSize );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &LoadBuffer - 
//			&iLump - 
//			&iLumpCount - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
inline bool CBaseGameStats::GetLumpHeader( int nMaxLumpCount, CUtlBuffer &LoadBuffer, unsigned short &iLump, unsigned short &iLumpCount, bool bPermissive /*= false*/ )
{
	// Get the lump id and element count.
	iLump = LoadBuffer.GetUnsignedShort();
	if ( !LoadBuffer.IsValid() )
	{
		// check for EOF
		return false;
	}
	iLumpCount = LoadBuffer.GetUnsignedShort();

	if ( bPermissive )
		return true;

	// Verify the lump index.
	Assert( ( iLump > 0 ) && ( iLump < nMaxLumpCount ) );
	if ( !( ( iLump > 0 ) && ( iLump < nMaxLumpCount ) ) )
	{
		return false;
	}

	// Check to see if we have any elements to save.
	if ( iLumpCount <= 0 )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Loads 1 or more lumps of raw data
// Input  : &LoadBuffer - buffer to be read from
//			iLumpCount - # of lumps to read
//			nSize - size of each lump
//			pData - where to store the data
//-----------------------------------------------------------------------------
inline void CBaseGameStats::LoadLump( CUtlBuffer &LoadBuffer, unsigned short iLumpCount, size_t nSize, void *pData )
{
	LoadBuffer.Get( pData, iLumpCount * nSize );
}

#endif // GAME_DLL

// Moving the extern out of the GAME_DLL block so that the client can access it
extern CBaseGameStats *gamestats; //starts out pointing at a singleton of the class above, overriding this in any constructor should work for replacing it

//used to drive most of the game stat event handlers as well as track basic stats under the hood of CBaseGameStats
class CBaseGameStats_Driver : public CAutoGameSystemPerFrame
{
public:
	CBaseGameStats_Driver( void );

	typedef CAutoGameSystemPerFrame BaseClass;

	// IGameSystem overloads
	virtual bool Init();
	virtual void Shutdown();

	// Level init, shutdown
	virtual void LevelInitPreEntity();
	virtual void LevelShutdownPreEntity();
	virtual void LevelShutdownPreClearSteamAPIContext();
	virtual void LevelShutdown();
	// Called during game save
	virtual void OnSave();
	// Called during game restore, after the local player has connected and entities have been fully restored
	virtual void OnRestore();

	virtual void FrameUpdatePostEntityThink();

	void PossibleMapChange( void );

	void CollectData( StatSendType_t sendType );
	void SendData();
	void ResetData();
	bool AddBaseDataForSend( KeyValues *pKV, StatSendType_t sendType );

	StatsBufferRecord_t m_StatsBuffer[STATS_WINDOW_SIZE];
	bool m_bBufferFull;
	int m_nWriteIndex;
	float m_flLastRealTime;
	float m_flLastSampleTime;
	float m_flTotalTimeInLevels;
	int m_iNumLevels;
	bool m_bDidVoiceChat;	// Did the player use voice chat at ALL this map?

	template<class T> T AverageStat( T StatsBufferRecord_t::*field ) const
	{
		T sum = 0;
		for( int i = 0; i < STATS_WINDOW_SIZE; i++ )
			sum += m_StatsBuffer[i].*field;
		return sum / STATS_WINDOW_SIZE;
	}

	template<class T> T MaxStat( T StatsBufferRecord_t::*field ) const
	{
		T maxsofar = -16000000;
		for( int i = 0; i < STATS_WINDOW_SIZE; i++ )
			maxsofar = MAX( maxsofar, m_StatsBuffer[i].*field );
		return maxsofar;
	}

	template<class T> T MinStat( T StatsBufferRecord_t::*field ) const
	{
		T minsofar = 16000000;
		for( int i = 0; i < STATS_WINDOW_SIZE; i++ )
			minsofar = MIN( minsofar, m_StatsBuffer[i].*field );
		return minsofar;
	}

	inline void AdvanceIndex( void )
	{
		m_nWriteIndex++;
		if ( m_nWriteIndex == STATS_WINDOW_SIZE )
		{
			m_nWriteIndex = 0;
			m_bBufferFull = true;
		}
	}

	void UpdatePerfStats( void );

	CUtlString		m_PrevMapName; //used to track "OnMapChange" events
	int				m_iLoadedVersion;
	char			m_szLoadedUserID[ 17 ];	// GUID

	bool			m_bEnabled; //false if incapable of uploading or the user doesn't want to enable stat tracking
	bool			m_bShuttingDown;
	bool			m_bInLevel;
	bool			m_bFirstLevel;
	time_t			m_tLastUpload;

	float			m_flLevelStartTime;

	bool			m_bStationary;
	float			m_flLastMovementTime;
	CUserCmd		m_LastUserCmd;
	bool			m_bGamePaused;
	float			m_flPauseStartTime;

	CGamestatsData	*m_pGamestatsData;
};

#endif // GAMESTATS_H