//====== Copyright ©, Valve Corporation, All rights reserved. =======
//
// Purpose: Maps message types to strings and vice versa
//
//=============================================================================

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

// Protobuf headers interfere with the valve min/max/malloc overrides. so we need to do all
// this funky wrapping to make the include happy.
#include <tier0/valve_minmax_off.h>
#include "google/protobuf/descriptor.h"
#include <tier0/valve_minmax_on.h>
#include "gcsdk/jobtime.h"

namespace GCSDK
{

extern CGCEmitGroup g_EGMessages;
extern const char *PchMsgNameFromEMsg( MsgType_t eMsg );

//-----------------------------------------------------------------------------
// message type flags
//-----------------------------------------------------------------------------

static const int MT_GC	= 0x01;		// this message is sent to or from a Game Coordinator (will be proxied by servers along the way)
static const int MT_GC_SYSTEM = 0x02; // this message was sent to or from the steam servers as a system message. Clients can't send these

//-----------------------------------------------------------------------------
// Various info about each message type
//-----------------------------------------------------------------------------
struct MsgInfo_t
{
	MsgType_t eMsg;
	int	nFlags;
	const char *pchMsgName;

	struct Stats_t
	{
		Stats_t() : nSourceMask(0), nCount( 0 ), uBytes( 0 ) {}
		uint32 nSourceMask;
		uint32 nCount;
		uint64 uBytes;
	};

	enum EStatsType
	{
		k_EStatsTypeSent,
		k_EStatsTypeReceived,
		k_EStatsTypeMultiplexedSends,
		k_EStatsTypeMultiplexedSendsRaw,

		k_EStatsType_Count
	};

	enum EStatsGroup
	{
		k_EStatsGroupGlobal,
		k_EStatsGroupProfile,
		k_EStatsGroupWindow,

		k_EStatsGroup_Count
	};


	Stats_t stats[ k_EStatsGroup_Count ][ k_EStatsType_Count ];
};

//-----------------------------------------------------------------------------
// Purpose: Using protobuf reflection, bind them into a message list
//-----------------------------------------------------------------------------
void MsgRegistrationFromEnumDescriptor( const ::google::protobuf::EnumDescriptor *pEnumDescriptor, int nTypeMask );

//-----------------------------------------------------------------------------
// manages a hashed list of messages, allowing fast tests for validity and
// info lookup.
//-----------------------------------------------------------------------------

class CMessageList
{
public:
	CMessageList();
	~CMessageList();

	bool BInit( );

	// returns false if a message isn't valid or isn't one of the types specified
	//   or true if the message is valid. ppMsgName can be NULL.
	bool GetMessage( MsgType_t eMsg, const char **ppMsgName, int nTypeMask );

	// make stats about sending messages
	void TallySendMessage( MsgType_t eMsg, uint32 uMsgSize, uint32 nSourceMask = 0 );
	void TallyReceiveMessage( MsgType_t eMsg, uint32 uMsgSize, uint32 nSourceMask = 0);
	void TallyMultiplexedMessage( MsgType_t eMsg, uint32 uSent, uint32 cRecipients, uint32 uMsgSize, uint32 nSourceMask = 0 );

	// profiling
	void EnableProfiling( bool bEnableProfiling );

	// print out our stats
	void PrintStats( bool bShowAll, bool bSortByFrequency, MsgInfo_t::EStatsGroup eGroup, MsgInfo_t::EStatsType eType, uint32 nSourceMask = 0 ) const; 
	void PrintMultiplexStats( MsgInfo_t::EStatsGroup eGroup, bool bSortByFrequency, uint32 nSourceMask = 0 ) const;

	// Window management - This is similar to profiling in many ways, but is separate so that the base system can monitor traffic rates without
	// interfering with profiles.
	
	//called to obtain the totals for the timing window
	const MsgInfo_t::Stats_t& GetWindowTotal( MsgInfo_t::EStatsType eType ) const;
	//called to reset the window timings
	void ResetWindow();
	//returns how long this window has been running in microseconds
	uint64 GetWindowDuration() const		{ return GetGroupDuration( MsgInfo_t::k_EStatsGroupWindow ); }
	

private:
	void TallyMessageInternal( MsgInfo_t &msgInfo, MsgInfo_t::EStatsType eBucket, uint32 unMsgSize, uint32 nSourceMask, uint32 cMessages = 1 );

	void AssureBucket( int nBucket );

	//-----------------------------------------------------------------------------
	// given a particular message ID, find out what bucket it would be in,
	// as well as which slot in that bucket.
	//-----------------------------------------------------------------------------
	static int HashMessage( MsgType_t eMsg, int &nSlot )
	{
		// hash is everything except the lowest nibble,
		// because buckets are 16 entries
		int nBucket = eMsg / m_kcBucketSize;
		nSlot = eMsg % m_kcBucketSize;
		return nBucket;
	}

	short GetMessageIndex( MsgType_t eMsg );

	//given a group, this will return the time that the stats have been collected over
	uint64 GetGroupDuration( MsgInfo_t::EStatsGroup eGroup ) const;

private:

	//totalled stats for the current window. It would be too costly to total these all the time
	MsgInfo_t::Stats_t		m_WindowTotals[ MsgInfo_t::k_EStatsType_Count ];

	//the time that we have been collecting each of these buckets
	CJobTime				m_sCollectTime[ MsgInfo_t::k_EStatsGroup_Count ];	
	
	//are we currently actively tracking a profile?
	bool m_bProfiling;
	//the duration of the last finished profile (if it is no longer running, otherwise use the timer)
	uint64 m_ulProfileMicrosecs;

	CUtlVector< short* > m_vecMessageInfoBuckets;
	CUtlVector<MsgInfo_t> m_vecMsgInfo;
	static const int m_kcBucketSize = 16;
};

extern CMessageList g_theMessageList;

//-----------------------------------------------------------------------------
// Purpose: Returns the true if the specified message is a valid GC system message.
// Input  : eMsg -		message type to test
//			ppMsgName - Optional pointer to receive message name
//-----------------------------------------------------------------------------
inline bool BIsValidSystemMsg( MsgType_t eMsg, const char **ppMsgName )
{
	return g_theMessageList.GetMessage( eMsg, ppMsgName, MT_GC_SYSTEM );
}


} // namespace GCSDK

#endif // MESSAGELIST_H