//======== (C) Copyright 2001 Charles G. Cleveland All rights reserved. =========
//
// The copyright to the contents herein is the property of Charles G. Cleveland.
// The contents may be used and/or copied only with the written permission of
// Charles G. Cleveland, or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: 
//
// $Workfile: NetworkMeter.cpp $
// $Date: 2002/11/15 19:09:23 $
//
//-------------------------------------------------------------------------------
// $Log: NetworkMeter.cpp,v $
// Revision 1.9  2002/11/15 19:09:23  Flayra
// - Reworked network metering to be easily toggleable
//
// Revision 1.8  2002/10/24 21:44:01  Flayra
// - Size was being calculated wrong (harmless, but gave wrong number in network log)
//
// Revision 1.7  2002/10/20 16:36:54  Flayra
// - Allow network metering to be compiled in or out easily
//
// Revision 1.6  2002/08/02 21:44:50  Flayra
// - Added code to enable/disable network metering, but there are problems when it's used
//
// Revision 1.5  2002/07/10 14:46:46  Flayra
// - More debug info to the log to track down overflows
//
// Revision 1.4  2002/07/08 17:22:56  Flayra
// - Allow disabling of network metering by setting rate to -1
//
// Revision 1.3  2002/07/01 21:20:51  Flayra
// - Added logging code to track down overflows
//
// Revision 1.2  2002/05/23 02:32:40  Flayra
// - Post-crash checkin.  Restored @Backup from around 4/16.  Contains changes for last four weeks of development.
//
// Revision 1.2  2002/05/01 02:34:41  Charlie
//===============================================================================
#include "mod/NetworkMeter.h"

////////////////////
// Hook functions //
////////////////////

void NetworkMeterMessageBegin(int msg_dest, int msg_type, const float* pOrigin, edict_t* ed)
{
	if(CVAR_GET_FLOAT("mp_networkdebug") > 0) 
	{
		char theDebugString[512];
		sprintf(theDebugString, "MessageBegin(%d, %d...)\n", msg_dest, msg_type);
		ALERT(at_logged, theDebugString); 
	}
	
#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->MessageBegin(msg_dest, msg_type, pOrigin, ed);
#else
	(*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed);
#endif
}

void MESSAGE_END()
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->MessageEnd();
	#else
	(*g_engfuncs.pfnMessageEnd)();
	#endif
}

void WRITE_BYTE(int inData)
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->WriteByte(inData);
	#else
	(*g_engfuncs.pfnWriteByte)(inData);
	#endif
}

void WRITE_CHAR(int inData)
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->WriteChar(inData);
	#else
	(*g_engfuncs.pfnWriteChar)(inData);
	#endif
}

void WRITE_SHORT(int inData)
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->WriteShort(inData);
	#else
	(*g_engfuncs.pfnWriteShort)(inData);
	#endif
}

void WRITE_LONG(int inData)
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->WriteLong(inData);
	#else
	(*g_engfuncs.pfnWriteLong)(inData);
	#endif
}

void WRITE_ANGLE(float inData)
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->WriteAngle(inData);
	#else
	(*g_engfuncs.pfnWriteAngle)(inData);
	#endif
}

void WRITE_COORD(float inData)
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->WriteCoord(inData);
	#else
	(*g_engfuncs.pfnWriteCoord)(inData);
	#endif
}

void WRITE_STRING(const char* inData)
{
	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->WriteString(inData);
	#else
	(*g_engfuncs.pfnWriteString)(inData);
	#endif
}

int REG_USER_MSG(const char* inMessageName, int inMessageSize)
{
	int theMessageID = (*g_engfuncs.pfnRegUserMsg)(inMessageName, inMessageSize);

	#ifdef USE_NETWORK_METERING
	NetworkMeter::Instance()->AddMessage(inMessageName, theMessageID);
	#endif

	return theMessageID;
}


//////////////////
// Network data //
//////////////////
NetworkData::NetworkData()
{
	this->mDataType = NETWORK_DATA_TYPE_INVALID;
	this->mIntData = 0;
	this->mFloatData = 0.0f;
	this->mStringData = "";
}

// Return size of data, in bytes
int NetworkData::GetDataSize()
{
	int theDataSize = 0;

	switch(this->mDataType)
	{
	case NETWORK_DATA_TYPE_INVALID:
		ASSERT(false);
		break;

	case NETWORK_DATA_TYPE_BYTE:
	case NETWORK_DATA_TYPE_CHAR:
		theDataSize = 1;
		break;

	case NETWORK_DATA_TYPE_SHORT:
	case NETWORK_DATA_TYPE_COORD:
		theDataSize = 2;
		break;

	case NETWORK_DATA_TYPE_LONG:
		theDataSize = 4;
		break;

	case NETWORK_DATA_TYPE_ANGLE:
		theDataSize = 4;
		break;
		
	case NETWORK_DATA_TYPE_STRING:
		//ASSERT(this->mStringData != "");
		theDataSize = 1*this->mStringData.length();
		break;
	}

	return theDataSize;
}

void NetworkData::Execute()
{
	switch(this->mDataType)
	{
	case NETWORK_DATA_TYPE_INVALID:
		ASSERT(false);
		break;
		
	case NETWORK_DATA_TYPE_BYTE:
		(*g_engfuncs.pfnWriteByte)(this->mIntData);
		break;

	case NETWORK_DATA_TYPE_CHAR:
		(*g_engfuncs.pfnWriteChar)(this->mIntData);
		break;
		
	case NETWORK_DATA_TYPE_SHORT:
		(*g_engfuncs.pfnWriteShort)(this->mIntData);
		break;
		
	case NETWORK_DATA_TYPE_LONG:
		(*g_engfuncs.pfnWriteLong)(this->mIntData);
		break;
		
	case NETWORK_DATA_TYPE_ANGLE:
		(*g_engfuncs.pfnWriteAngle)(this->mFloatData);
		break;

	case NETWORK_DATA_TYPE_COORD:
		(*g_engfuncs.pfnWriteCoord)(this->mFloatData);
		break;
		
	case NETWORK_DATA_TYPE_STRING:
		//ASSERT(this->mStringData != "");
		(*g_engfuncs.pfnWriteString)(this->mStringData.c_str());
		break;
	}
}

void NetworkData::SetTypeByte(int inData)
{
	this->mDataType = NETWORK_DATA_TYPE_BYTE;
	this->mIntData = inData;
}

void NetworkData::SetTypeChar(int inData)
{
	this->mDataType = NETWORK_DATA_TYPE_CHAR;
	this->mIntData = inData;
}

void NetworkData::SetTypeShort(int inData)
{
	this->mDataType = NETWORK_DATA_TYPE_SHORT;
	this->mIntData = inData;
}

void NetworkData::SetTypeLong(int inData)
{
	this->mDataType = NETWORK_DATA_TYPE_LONG;
	this->mIntData = inData;
}

void NetworkData::SetTypeAngle(float inData)
{
	this->mDataType = NETWORK_DATA_TYPE_ANGLE;
	this->mFloatData = inData;
}

void NetworkData::SetTypeCoord(float inData)
{
	this->mDataType = NETWORK_DATA_TYPE_COORD;
	this->mFloatData = inData;
}

void NetworkData::SetTypeString(const char* inData)
{
	this->mDataType = NETWORK_DATA_TYPE_STRING;
	this->mStringData = inData;
}



////////////////////
// NetworkMessage //
////////////////////
NetworkMessage::NetworkMessage()
{
	this->Clear();
}

void NetworkMessage::Clear()
{
	this->mMessageDest = -1;
	this->mMessageType = -1;
	this->mEdict = NULL;
	this->mOrigin[0] = this->mOrigin[1] = this->mOrigin[2] = 0.0f;
	this->mMessageData.clear();
	this->mMessagePending = false;
	this->mMessageSent = false;
	this->mTimeMessageSent = -1;
}

void NetworkMessage::MessageBegin(int inMessageDest, int inMessageType, const float* inOrigin, edict_t* inEd)
{
	this->Clear();

	// Set message header data
	this->mMessageDest = inMessageDest;
	this->mMessageType = inMessageType;
	if(inOrigin)
	{
		this->mOrigin[0] = inOrigin[0];
		this->mOrigin[1] = inOrigin[1];
		this->mOrigin[2] = inOrigin[2];
	}
	this->mEdict = inEd;

	this->mMessagePending = true;
}

void NetworkMessage::AddData(const NetworkData& inData)
{
	ASSERT(this->mMessagePending);

	this->mMessageData.push_back(inData);
}

void NetworkMessage::Execute(int theNumMessagesQueued)
{
	if(CVAR_GET_FLOAT("mp_networkdebug") > 0) 
	{
		string theMessageName = NetworkMeter::Instance()->LookupMessageID(this->mMessageType);
		string theMessageDest = "-";
		if(this->mEdict)
		{
			theMessageDest = STRING(VARS(this->mEdict)->netname);
		}
		UTIL_LogPrintf("NetworkMessage::Execute(\"%s\", \"%s\", %d bytes, %d queued, time: %f)\n", theMessageDest.c_str(), theMessageName.c_str(), this->GetDataSize(), theNumMessagesQueued, gpGlobals->time);
	}
	
	// Start the message
	(*g_engfuncs.pfnMessageBegin)(this->mMessageDest, this->mMessageType, this->mOrigin, this->mEdict);
	
	// Loop through the data
	for(NetworkMessageDataList::iterator theIter = this->mMessageData.begin(); theIter != this->mMessageData.end(); theIter++)
	{
		// Send each
		theIter->Execute();
	}

	// End the message
	(*g_engfuncs.pfnMessageEnd)();
	
	// Mark message as sent
	this->SetMessageSent();
}

void NetworkMessage::MessageEnd()
{
	ASSERT(this->mMessagePending);

	this->mMessagePending = false;
}

int	NetworkMessage::GetDataSize()
{
	// Estimated amount for header.  TODO: Ask Yahn to find out exactly?
	const int kNumBytesForMessageHeader = 4;

	int theNumBytes = kNumBytesForMessageHeader;

	for(NetworkMessageDataList::iterator theIter = this->mMessageData.begin(); theIter != this->mMessageData.end(); theIter++)
	{
		theNumBytes += theIter->GetDataSize();
	}

	return theNumBytes;
}

bool NetworkMessage::GetMessagePending() const
{
	return this->mMessagePending;
}

bool NetworkMessage::GetMessageSent() const
{
	return this->mMessageSent;
}

void NetworkMessage::SetMessageSent()
{
	this->mMessageSent = true;
	this->mTimeMessageSent = gpGlobals->time;
}

float NetworkMessage::GetTimeMessageSent() const
{
	return this->mTimeMessageSent;
}


////////////////////////
// PlayerNetworkMeter //
////////////////////////
PlayerNetworkMeter::PlayerNetworkMeter()
{
	this->mBytesSentInPastSecond = 0;
	this->mBytesPerSecond = 0;
}

void PlayerNetworkMeter::SetBufferAmount(int inBytesPerSecond)
{
	this->mBytesPerSecond = inBytesPerSecond;
}

void PlayerNetworkMeter::MessageBegin(int inMessageDest, int inMessageType, const float* inOrigin, edict_t* inEd)
{
	// Make sure we're not in the middle of sending a message
	ASSERT(!this->mMessage.GetMessagePending());
	
	// Start new message
	this->mMessage.MessageBegin(inMessageDest, inMessageType, inOrigin, inEd);
}

void PlayerNetworkMeter::MessageEnd()
{
	ASSERT(this->mMessage.GetMessagePending());
	
	this->mMessage.MessageEnd();
	
	// Add message to the (end of) list
	this->mMessageList.push_back(this->mMessage);
	
	// Clear message
	this->mMessage.Clear();
}

void PlayerNetworkMeter::WriteByte(int inByte)
{
	ASSERT(this->mMessage.GetMessagePending());
	
	NetworkData theNetworkData;
	theNetworkData.SetTypeByte(inByte);
	this->mMessage.AddData(theNetworkData);
}

void PlayerNetworkMeter::WriteChar(int inChar)
{
	ASSERT(this->mMessage.GetMessagePending());
	
	NetworkData theNetworkData;
	theNetworkData.SetTypeChar(inChar);
	this->mMessage.AddData(theNetworkData);
}

void PlayerNetworkMeter::WriteShort(int inShort)
{
	ASSERT(this->mMessage.GetMessagePending());
	
	NetworkData theNetworkData;
	theNetworkData.SetTypeShort(inShort);
	this->mMessage.AddData(theNetworkData);
}

void PlayerNetworkMeter::WriteLong(int inLong)
{
	ASSERT(this->mMessage.GetMessagePending());
	
	NetworkData theNetworkData;
	theNetworkData.SetTypeLong(inLong);
	this->mMessage.AddData(theNetworkData);
}

void PlayerNetworkMeter::WriteAngle(float inAngle)
{
	ASSERT(this->mMessage.GetMessagePending());
	
	NetworkData theNetworkData;
	theNetworkData.SetTypeAngle(inAngle);
	this->mMessage.AddData(theNetworkData);
}

void PlayerNetworkMeter::WriteCoord(float inCoord)
{
	ASSERT(this->mMessage.GetMessagePending());
	
	NetworkData theNetworkData;
	theNetworkData.SetTypeCoord(inCoord);
	this->mMessage.AddData(theNetworkData);
}

void PlayerNetworkMeter::WriteString(const char* inString)
{
	ASSERT(this->mMessage.GetMessagePending());
	
	NetworkData theNetworkData;
	theNetworkData.SetTypeString(inString);
	this->mMessage.AddData(theNetworkData);
}

void PlayerNetworkMeter::ProcessQueuedMessages()
{
	#ifdef USE_NETWORK_METERING
	
	//char theMessage[256];
	//sprintf(theMessage, "PlayerNetworkMeter::ProcessQueuedMessages(): %d messages\n", this->mMessageList.size());
	//ALERT(at_logged, "%s", theMessage);

	// Run through our list of network messages
	for(NetworkMessageList::iterator theIter = this->mMessageList.begin(); theIter != this->mMessageList.end(); )
	{
		// See how much data it requires
		int theMessageSize = theIter->GetDataSize();

		// For each, if message hasn't been sent
		if(!theIter->GetMessageSent())
		{
			bool theForceSend = (this->mBytesPerSecond < 0);
			int theBudget = this->mBytesPerSecond - this->mBytesSentInPastSecond;

			// This can be less then 0 when the variable is changed mid-game
			if((theBudget >= 0) || theForceSend)
			{
				// If we can afford to send this message
				if((theMessageSize <= theBudget) || theForceSend)
				{
					// Execute the message
					theIter->Execute(this->mMessageList.size());
				
					// Increment num bytes we've sent in past second
					this->mBytesSentInPastSecond += theMessageSize;
				
					// Continue processing
					theIter++;
				}
				else
				{
					// If not, stop looping
					break;
				}
			}
			else
			{
				// If not, stop looping
				break;
			}
		}
		else
		{
			// if time message sent was over a second ago
			float theMessageSendTime = theIter->GetTimeMessageSent();
			if(theMessageSendTime < (gpGlobals->time - 1.0f))
			{
				// get back the amount of bytes from it
				this->mBytesSentInPastSecond -= theMessageSize;

				// pop it off the list
				theIter = this->mMessageList.erase(theIter);
			}
			else
			{
				// Continue processing
				theIter++;
			}
		}
	}
	#endif
}


///////////////////
// Network meter //
///////////////////
NetworkMeter* NetworkMeter::sSingletonNetworkMeter = NULL;

// Singleton static accessor (see "Design Patterns")
NetworkMeter* NetworkMeter::Instance()
{
	if(!sSingletonNetworkMeter)
	{
		sSingletonNetworkMeter = new NetworkMeter();
	}

	// Out of memory condition
	ASSERT(sSingletonNetworkMeter);

	return sSingletonNetworkMeter;
}

NetworkMeter::NetworkMeter()
{
	this->mCurrentEntity = NULL;
	this->mBytesPerSecond = 0;
}

void NetworkMeter::SetBufferAmount(int inBytesPerSecond)
{
	this->mBytesPerSecond = inBytesPerSecond;
}

void NetworkMeter::AddMessage(const char* inMessageName, int inMessageID)
{
	this->mNetworkMessageTypes[inMessageID] = string(inMessageName);
}

string NetworkMeter::LookupMessageID(int inMessageID)
{
	string theMessageName = this->mNetworkMessageTypes[inMessageID];
	if(theMessageName == "")
	{
		theMessageName = "Unknown";
	}
	return theMessageName;
}


void NetworkMeter::MessageBegin(int inMessageDest, int inMessageType, const float* inOrigin, edict_t* inEd)
{
	this->mCurrentEntity = inEd;
	this->GetPlayerNetworkMeter(this->mCurrentEntity).MessageBegin(inMessageDest, inMessageType, inOrigin, inEd);
}

void NetworkMeter::MessageEnd()
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).MessageEnd();
}

void NetworkMeter::WriteByte(int inByte)
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).WriteByte(inByte);
}

void NetworkMeter::WriteChar(int inChar)
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).WriteChar(inChar);
}

void NetworkMeter::WriteShort(int inShort)
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).WriteShort(inShort);
}

void NetworkMeter::WriteLong(int inLong)
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).WriteLong(inLong);
}

void NetworkMeter::WriteAngle(float inAngle)
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).WriteAngle(inAngle);
}

void NetworkMeter::WriteCoord(float inCoord)
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).WriteCoord(inCoord);
}

void NetworkMeter::WriteString(const char* inString)
{
	this->GetPlayerNetworkMeter(this->mCurrentEntity).WriteString(inString);
}

void NetworkMeter::ProcessQueuedMessages()
{
	// Iterate through all player network meters
	for(PlayerNetworkMeterMap::iterator theIter = this->mPlayerList.begin(); theIter != this->mPlayerList.end(); theIter++)
	{
		theIter->second.SetBufferAmount(this->mBytesPerSecond);
		theIter->second.ProcessQueuedMessages();
	}
}

PlayerNetworkMeter&	NetworkMeter::GetPlayerNetworkMeter(edict_t* inEd)
{
	// Look up edict in map.  If it doesn't exist, this will create a new entry
	return this->mPlayerList[inEd];
}