//////////////////////////////////////////////////////////////////////////////
//
// Asura_MessageHeader.cpp
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// Description:
//
//     code to pack / unpack network message headers
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

#include "Network/Asura_MessageHeader.h"

#include "Core/Asura_BitPacker.h"
#include "Core/Asura_Maths.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Logfile.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Network/Asura_NetMsg.h"
#include "Network/Asura_NetTransport_System.h"

const u_int uFIELD_PRESENT_BITS = 5;	// entity classification, to guid, from guid, sent time, data size

// ARC - 04 August 2006 15:33:51 - Allow project specific number of message ID bits
#if !defined(ASURA_NETWORK_MESSAGE_ID_BITS)
#define ASURA_NETWORK_MESSAGE_ID_BITS (8)
#endif
const u_int uMSG_ID_BITS = ASURA_NETWORK_MESSAGE_ID_BITS;
const u_int uENTITY_CLASSIFICATION_BITS = 8;
const u_int uSENT_TIME_BITS = 32;
const u_int uDATA_SIZE_BITS = 11;

// 0-3 gives us ASURA_GUID_SERVER, ASURA_GUID_CLIENT, ASURA_GUID_MY_CLIENT_ENTITIES, ASURA_GUID_PLAYER
// 0-2047 gives us everything up to the likely end of player Guids
// 0-262143 should work for all entities actually used in most games.
// the remainder is to cope with games that go on for so long that they go over the
// 62143 game-created entities allowed by the 18-bit range.
// This guid range ID takes 2 bits.
enum GuidRange
{
	GuidRange_0_to_3,			// 2 bits
	GuidRange_0_to_2047,		// 12 bits
	GuidRange_0_to_262143,		// 18 bits
#if defined(ASURA_USE_STREAMING)
	GuidRange_0_to_4billion,	// 32 bits
#else
	GuidRange_0_to_16million,	// 24 bits
#endif	// defined(ASURA_USE_STREAMING)
	NumGuidRanges
};

const u_int uGUID_RANGE_BITS = 2;
#if defined(ASURA_USE_STREAMING)
const u_int auGUID_RANGE_BITS[NumGuidRanges] = {2, 12, 18, 32};
const u_int uMAX_GUID_BITS = uGUID_RANGE_BITS + 32;
#else
const u_int auGUID_RANGE_BITS[NumGuidRanges] = {2, 12, 18, 24};
const u_int uMAX_GUID_BITS = uGUID_RANGE_BITS + 24;
#endif	// defined(ASURA_USE_STREAMING)

const u_int uMAX_BYTES_FOR_HEADER = (uFIELD_PRESENT_BITS + uMSG_ID_BITS + uENTITY_CLASSIFICATION_BITS + uMAX_GUID_BITS + uMAX_GUID_BITS + uSENT_TIME_BITS + uDATA_SIZE_BITS + 7) / 8;

// GetBytesRequiredForMessage returns the number of bytes required to pack the message.
u_int Asura_MessagePacking::GetBytesRequiredForMessage(u_int uEntityClassification, u_int uToGuid, u_int uFromGuid, float fSentTime, u_int uDataSize)
{
	// add up the number of bits we will use
	u_int uBitsNeeded = uFIELD_PRESENT_BITS + uMSG_ID_BITS;
	
	if (uEntityClassification != AsuraEntityClass_NotSet)
	{
		uBitsNeeded += uENTITY_CLASSIFICATION_BITS;
	}
	uBitsNeeded += GetBitsRequiredForGuid(uToGuid);
	uBitsNeeded += GetBitsRequiredForGuid(uFromGuid);
	// TODO: sent time might not be used
	uBitsNeeded += uSENT_TIME_BITS;
	if (uDataSize)
	{
		++uBitsNeeded; // extra bit for whether data is compressed
		uBitsNeeded += uDATA_SIZE_BITS;
	}

	// convert that to bytes and add the data size
	u_int uBytesNeeded = ((uBitsNeeded + 7) / 8) + uDataSize;
	return uBytesNeeded;
}

// GetBytesRequiredForMessage returns the number of bytes required to pack the message.
u_int Asura_MessagePacking::GetBytesRequiredForMessage( const Asura_MessageHeader& xMsgHeader )
{
	u_int uEntityClassification = xMsgHeader.GetEntityClassification();
	u_int uToGuid = xMsgHeader.GetToGuid();
	u_int uFromGuid = xMsgHeader.GetFromGuid();
	float fSentTime = xMsgHeader.GetSentTime();
	u_int uDataSize = xMsgHeader.GetDataSize();
	return GetBytesRequiredForMessage(uEntityClassification, uToGuid, uFromGuid, fSentTime, uDataSize);
}

// PackMessageToBuffer() function takes buffer ptr and size, and asura message and 
// writes the various fields with a bit to say if each field is present (except for 
// msgID which is always present). It also copies the  the message data (if any) 
// into the buffer.
// It returns the number of bytes used. This is zero if there is not enough room.
u_int Asura_MessagePacking::PackMessageToBuffer( u_char* pucBuffer, u_int uBufferSize, const Asura_Message& xMsg )
{
	// initialise local variables to the data we need from the message.
	const u_short usEntityClassification = xMsg.GetEntityClassification();
	const u_int uToGuid = xMsg.GetToGuid();
	const u_int uFromGuid = xMsg.GetFromGuid();
	const float fSentTime = xMsg.GetSentTime();
	const u_int uDataSize = xMsg.GetDataSize();
	u_int uPackedDataSize = 0;
	const void* pPackedData = NULL;
	bool bMessageDataWasPacked;
#if defined(ASURA_NETMSG_PACK_ALL_DATA)
	bool bMessageDataShouldBePacked = !Asura_Network::IsMessageDataInNetworkByteOrder(xMsg.GetMsgID(), usEntityClassification);
	if (bMessageDataShouldBePacked && Asura_NetMsg_System::PackMessageData(xMsg, uPackedDataSize, pPackedData))
	{
		bMessageDataWasPacked = true;
	}
	else
#endif
	{
		// we will send unpacked data in this case. If bMessageDataShouldBePacked is true, 
		// this will break cross-endian games.
#if defined(ASURA_NETMSG_PACK_ALL_DATA)
		Asura_Assertf(!bMessageDataShouldBePacked, ("missing netmsg registration for message %s (%s)\n", 
			Asura_Network::GetMsgIDDesc(xMsg.GetMsgID()), 
			Asura_Network::GetEntityClassificationDesc(xMsg.GetEntityClassification())));
#endif
		bMessageDataWasPacked = false;
		uPackedDataSize = uDataSize;
		pPackedData = xMsg.GetData();
	}

	u_int uBytesNeeded = GetBytesRequiredForMessage(usEntityClassification, uToGuid, uFromGuid, fSentTime, uPackedDataSize);
	if (uBytesNeeded > uBufferSize)
	{
		return 0;
	}

	Asura_BitPacker xBitPacker(uMAX_BYTES_FOR_HEADER);

	// msg ID
	u_short usMsgID = xMsg.GetMsgID();
	PackMsgID( usMsgID, &xBitPacker );

	// entity classification
	if (usEntityClassification != AsuraEntityClass_NotSet)
	{
		xBitPacker.PackBool(true);
		PackEntityClassification( usEntityClassification, &xBitPacker );
	}
	else
	{
		xBitPacker.PackBool(false);
	}

	// to guid
	if (uToGuid != ASURA_GUID_UNREGISTERED)
	{
		xBitPacker.PackBool(true);
		PackGuid(uToGuid, &xBitPacker);
	}
	else
	{
		xBitPacker.PackBool(false);
	}

	// from guid
	if (uFromGuid != ASURA_GUID_UNREGISTERED)
	{
		xBitPacker.PackBool(true);
		PackGuid(uFromGuid, &xBitPacker);
	}
	else
	{
		xBitPacker.PackBool(false);
	}

	// sent time - sent only if it has changed from what the last value packed in was.
	// This avoids duplicating the sent time in the same packet.
	// TODO - add this functionality
	Asura_Maths::Asura_IntOrFloat xSentTime;
	xSentTime.F = fSentTime;
	xBitPacker.PackBool(true);
	xBitPacker.Pack(xSentTime.I, uSENT_TIME_BITS);

	if (uDataSize)
	{
//		Asura_OutputToDebugger(("pack %s (%s): data %spacked\n", Asura_Network::GetMsgIDDesc(usMsgID), 
//			Asura_Network::GetEntityClassificationDesc(usEntityClassification), bMessageDataWasPacked ? "" : "not "));

		xBitPacker.PackBool(true);
		xBitPacker.PackBool(bMessageDataWasPacked);
		xBitPacker.Pack(uPackedDataSize, uDATA_SIZE_BITS);
	}
	else
	{
//		Asura_OutputToDebugger(("pack %s (%s): no data\n", Asura_Network::GetMsgIDDesc(usMsgID), 
//			Asura_Network::GetEntityClassificationDesc(usEntityClassification)));
		xBitPacker.PackBool(false);
	}

	u_int uPackerDataLen;
	const void* pPackerData = xBitPacker.GetResult(uPackerDataLen);

	// copy the packer data to the provided buffer
	Asura_Mem::Copy(pucBuffer, pPackerData, uPackerDataLen);

	// copy the message data after the packer data
	Asura_Mem::Copy(pucBuffer + uPackerDataLen, pPackedData, uPackedDataSize);

	// return total amount of data written.
	Asura_Assert(uBytesNeeded == (uPackerDataLen + uPackedDataSize), "bytes written doesn't match predicted value");
	return uBytesNeeded;
}

// UncompressToMessageHeader takes the compressed buffer and fills out the Asura_MessageHeader.
// It returns a ptr to the first byte after the packed data, which is the message data block
// (if any) or it could be the start of the next packed header.
const void* Asura_MessagePacking::UnpackBufferToMessageHeader( const void* pData, bool bGuaranteed, 
															  Asura_MessageHeader& xMsgHeader, u_int& uSizeOfPackedData )
{
	const u_char* pucData = reinterpret_cast<const u_char*>(pData);
	Asura_BitUnpacker xBitUnpacker(pucData, uMAX_BYTES_FOR_HEADER);
	
	// message ID
	xMsgHeader.m_usMsgID = UnpackMsgID( &xBitUnpacker );

	// entity classification
	if (xBitUnpacker.UnpackBool())
	{
		xMsgHeader.m_usEntityClassification = UnpackEntityClassification( &xBitUnpacker );
	}
	else
	{
		xMsgHeader.m_usEntityClassification = AsuraEntityClass_NotSet;
	}

	// to guid
	if (xBitUnpacker.UnpackBool())
	{
		xMsgHeader.m_uToGuid = UnpackGuid(&xBitUnpacker);
	}
	else
	{
		xMsgHeader.m_uToGuid = ASURA_GUID_UNREGISTERED;
	}

	// from guid
	if (xBitUnpacker.UnpackBool())
	{
		xMsgHeader.m_uFromGuid = UnpackGuid(&xBitUnpacker);
	}
	else
	{
		xMsgHeader.m_uFromGuid = ASURA_GUID_UNREGISTERED;
	}

	// sent time
#if defined ASURA_ASSERT
	bool bHasSentTime =
#endif
	xBitUnpacker.UnpackBool();

	Asura_Assert(bHasSentTime, "omitting sent time not supported yet");
	Asura_Maths::Asura_IntOrFloat xSentTime;
	xSentTime.I = xBitUnpacker.Unpack(uSENT_TIME_BITS);
	xMsgHeader.m_fSentTime = xSentTime.F;

	// msg data size and ptr to data
	if (xBitUnpacker.UnpackBool())
	{
		const bool bDataIsPacked = xBitUnpacker.UnpackBool();
		if (bDataIsPacked)
		{
//			Asura_OutputToDebugger(("unpack %s (%s): data packed\n", Asura_Network::GetMsgIDDesc(xMsgHeader.GetMsgID()),
//				Asura_Network::GetEntityClassificationDesc(xMsgHeader.GetEntityClassification())));

			// recover the message data size from the netmsg
			const Asura_NetMsg* pxNetMsg = Asura_NetMsg::FindNetMsgObject(xMsgHeader.GetMsgID(), xMsgHeader.GetEntityClassification());
			if (!pxNetMsg)
			{
				pxNetMsg = Asura_Network::RegisterNetMsg(xMsgHeader.GetMsgID(), xMsgHeader.GetEntityClassification());
			}
			Asura_Assert(pxNetMsg, "failed to find netmsg for decompression. Server & client build out of sync?");
			if (pxNetMsg)
			{
				xMsgHeader.SetDataSize(pxNetMsg->GetDataSize());
				uSizeOfPackedData = xBitUnpacker.Unpack(uDATA_SIZE_BITS);
			}
			else
			{
				// abort!
				uSizeOfPackedData = 0;
				xMsgHeader.SetDataSize(0);
			}
		}
		else
		{
//			Asura_OutputToDebugger(("unpack %s (%s): data not packed\n", Asura_Network::GetMsgIDDesc(xMsgHeader.GetMsgID()),
//				Asura_Network::GetEntityClassificationDesc(xMsgHeader.GetEntityClassification())));

			xMsgHeader.SetDataSize(xBitUnpacker.Unpack(uDATA_SIZE_BITS));
		}
	}
	else
	{
//		Asura_OutputToDebugger(("unpack %s (%s): no data\n", Asura_Network::GetMsgIDDesc(xMsgHeader.GetMsgID()),
//			Asura_Network::GetEntityClassificationDesc(xMsgHeader.GetEntityClassification())));

		uSizeOfPackedData = 0;
		xMsgHeader.SetDataSize(0);
	}

	xMsgHeader.SetGuaranteed(bGuaranteed);

	// return ptr to first byte boundary after header
	u_int uBitsRead = xBitUnpacker.GetNextBitToRead();
	u_int uNumBytes = (uBitsRead + 7) / 8;
	return (pucData + uNumBytes);
}



// private static function to find the enum value for the required range for this guid
u_int Asura_MessagePacking::GetGuidRange(Asura_Guid uGuid)
{
	enum GuidRange eGuidRange;
	if (uGuid <= 3)
	{
		eGuidRange = GuidRange_0_to_3;
	}
	else if (uGuid <= 2047)
	{
		eGuidRange = GuidRange_0_to_2047;
	}
	else if (uGuid <= 262143)
	{
		eGuidRange = GuidRange_0_to_262143;
	}
	else
	{
#if defined(ASURA_USE_STREAMING)
		eGuidRange = GuidRange_0_to_4billion;
#else
		eGuidRange = GuidRange_0_to_16million;
#endif	// defined(ASURA_USE_STREAMING)
	}
	return eGuidRange;
}

// public static function to calculate num bits that will be used by a Guid
u_int Asura_MessagePacking::GetBitsRequiredForGuid(Asura_Guid uGuid)
{
	if (uGuid == ASURA_GUID_UNREGISTERED)
	{
		return 0;
	}
	else
	{
		return uGUID_RANGE_BITS + auGUID_RANGE_BITS[GetGuidRange(uGuid)];
	}
}

// public static functions to compress / uncompress Guid into 4-26 bits
void Asura_MessagePacking::PackGuid(u_int uGuid, Asura_BitPacker* pxPacker)
{
	enum GuidRange eGuidRange = static_cast<enum GuidRange>(GetGuidRange(uGuid));
	pxPacker->Pack(eGuidRange, uGUID_RANGE_BITS);
	pxPacker->Pack(uGuid, auGUID_RANGE_BITS[eGuidRange]);
}

u_int Asura_MessagePacking::UnpackGuid(class Asura_BitUnpacker* pxUnpacker)
{
	u_int uGuidRange = pxUnpacker->Unpack(uGUID_RANGE_BITS);
	Asura_Assert(uGuidRange < NumGuidRanges, "invalid guid range");
	u_int uGuid = pxUnpacker->Unpack(auGUID_RANGE_BITS[uGuidRange]);
	return uGuid;
}


// public static functions to pack msgID and entity classification in/out of a BitPacker.
void Asura_MessagePacking::PackMsgID( u_short usMsgID, class Asura_BitPacker* pxPacker )
{
	// convert project range so it is continuous with engine range.
	if (usMsgID >= ASURA_MSG_PROJECT_SPECIFIC)
	{
		usMsgID -= (ASURA_MSG_PROJECT_SPECIFIC - ASURA_MSG_END_OF_ENGINE_MESSAGES);
	}
	pxPacker->Pack(usMsgID, uMSG_ID_BITS);
}

u_short Asura_MessagePacking::UnpackMsgID( class Asura_BitUnpacker* pxUnpacker )
{
	u_int uMsgID = pxUnpacker->Unpack(uMSG_ID_BITS);
	if (uMsgID >= ASURA_MSG_END_OF_ENGINE_MESSAGES)
	{
		uMsgID += ASURA_MSG_PROJECT_SPECIFIC - ASURA_MSG_END_OF_ENGINE_MESSAGES;
	}
	return static_cast<u_short>(uMsgID);
}

u_int Asura_MessagePacking::GetBitsRequiredForMsgID( u_short usMsgID )
{
	return uMSG_ID_BITS;
}

void Asura_MessagePacking::PackEntityClassification( u_short usEntityClassification, class Asura_BitPacker* pxPacker )
{
	// convert project range so it is continuous with engine range.
	if (usEntityClassification >= AsuraEntityClass_ProjectSpecific)
	{
		usEntityClassification -= (AsuraEntityClass_ProjectSpecific - AsuraEntityClass_LastEngineClassification);
	}
	pxPacker->Pack(usEntityClassification, uENTITY_CLASSIFICATION_BITS);
}

u_short Asura_MessagePacking::UnpackEntityClassification( class Asura_BitUnpacker* pxUnpacker )
{
	u_int uEntityClassification = pxUnpacker->Unpack(uENTITY_CLASSIFICATION_BITS);
	if (uEntityClassification >= AsuraEntityClass_LastEngineClassification)
	{
		uEntityClassification += (AsuraEntityClass_ProjectSpecific - AsuraEntityClass_LastEngineClassification);
	}
	return static_cast<u_short>(uEntityClassification);
}

u_int Asura_MessagePacking::GetBitsRequiredForEntityClassification( u_short usEntityClassification )
{
	return uENTITY_CLASSIFICATION_BITS;
}

void Asura_MessageHeader::ChangeByteOrder()
{
	ASURA_NTS_CHANGE_BYTE_ORDER(m_usMsgID);
	ASURA_NTS_CHANGE_BYTE_ORDER(m_usEntityClassification);
	ASURA_NTS_CHANGE_BYTE_ORDER(m_uToGuid);
	ASURA_NTS_CHANGE_BYTE_ORDER(m_uFromGuid);
	ASURA_NTS_CHANGE_BYTE_ORDER(m_fSentTime);
	ASURA_NTS_CHANGE_BYTE_ORDER(m_uDataSizeAndGuaranteedBit);
}

#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
