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

#include "quakedef.h"
#include "dt.h"
#include "dt_encode.h"
#include "coordsize.h"

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

extern void DataTable_Warning( const char *pInMessage, ... );
extern bool ShouldWatchThisProp( const SendTable *pTable, int objectID, const char *pPropName );

// The engine implements this.
extern const char* GetObjectClassName( int objectID );

// Check for special flags like SPROP_COORD, SPROP_NOSCALE, and SPROP_NORMAL.
// Returns true if it encoded the float.
static inline bool EncodeSpecialFloat( const SendProp *pProp, float fVal, bf_write *pOut )
{
	int flags = pProp->GetFlags();
	if ( flags & SPROP_COORD )
	{
		pOut->WriteBitCoord( fVal );
		return true;
	}
	else if ( flags & SPROP_COORD_MP )
	{
		pOut->WriteBitCoordMP( fVal, false, false );
		return true;
	}
	else if ( flags & SPROP_COORD_MP_LOWPRECISION )
	{
		pOut->WriteBitCoordMP( fVal, false, true );
		return true;
	}
	else if ( flags & SPROP_COORD_MP_INTEGRAL )
	{
		pOut->WriteBitCoordMP( fVal, true, false );
		return true;
	}
	else if ( flags & SPROP_NOSCALE )
	{
		pOut->WriteBitFloat( fVal );
		return true;
	}
	else if ( flags & SPROP_NORMAL )
	{
		pOut->WriteBitNormal( fVal );
		return true;
	}
	return false;
}


static inline void EncodeFloat( const SendProp *pProp, float fVal, bf_write *pOut, int objectID )
{
	// Check for special flags like SPROP_COORD, SPROP_NOSCALE, and SPROP_NORMAL.
	if( EncodeSpecialFloat( pProp, fVal, pOut ) )
	{
		return;
	}

	uint32 ulVal;
	if( fVal < pProp->m_fLowValue )
	{
		// clamp < 0
		ulVal = 0;
		
		if(!(pProp->GetFlags() & SPROP_ROUNDUP))
		{
			DataTable_Warning("(class %s): Out-of-range value (%f) in SendPropFloat '%s', clamping.\n", GetObjectClassName( objectID ), fVal, pProp->m_pVarName );
		}
	}
	else if( fVal > pProp->m_fHighValue )
	{
		// clamp > 1
		ulVal = ((1 << pProp->m_nBits) - 1);

		if(!(pProp->GetFlags() & SPROP_ROUNDDOWN))
		{
			DataTable_Warning("%s: Out-of-range value (%f) in SendPropFloat '%s', clamping.\n", GetObjectClassName( objectID ), fVal, pProp->m_pVarName );
		}
	}
	else
	{
		float fRangeVal = (fVal - pProp->m_fLowValue) * pProp->m_fHighLowMul;
		ulVal = RoundFloatToUnsignedLong( fRangeVal );
	}
	
	pOut->WriteUBitLong(ulVal, pProp->m_nBits);
}


// Look for special flags like SPROP_COORD, SPROP_NOSCALE, and SPROP_NORMAL and
// decode if they're there. Fills in fVal and returns true if it decodes anything.
static inline bool DecodeSpecialFloat( SendProp const *pProp, bf_read *pIn, float &fVal )
{
	int flags = pProp->GetFlags();

	if ( flags & SPROP_COORD )
	{
		fVal = pIn->ReadBitCoord();
		return true;
	}
	else if ( flags & SPROP_COORD_MP )
	{
		fVal = pIn->ReadBitCoordMP( false, false );
		return true;
	}
	else if ( flags & SPROP_COORD_MP_LOWPRECISION )
	{
		fVal = pIn->ReadBitCoordMP( false, true );
		return true;
	}
	else if ( flags & SPROP_COORD_MP_INTEGRAL )
	{
		fVal = pIn->ReadBitCoordMP( true, false );
		return true;
	}
	else if ( flags & SPROP_NOSCALE )
	{
		fVal = pIn->ReadBitFloat();
		return true;
	}
	else if ( flags & SPROP_NORMAL )
	{
		fVal = pIn->ReadBitNormal();
		return true;
	}

	return false;
}


static float DecodeFloat(SendProp const *pProp, bf_read *pIn)
{
	float fVal;
	uint32 dwInterp;

	// Check for special flags..
	if( DecodeSpecialFloat( pProp, pIn, fVal ) )
	{
		return fVal;
	}

	dwInterp = pIn->ReadUBitLong(pProp->m_nBits);
	fVal = (float)dwInterp / ((1 << pProp->m_nBits) - 1);
	fVal = pProp->m_fLowValue + (pProp->m_fHighValue - pProp->m_fLowValue) * fVal;
	return fVal;
}

static inline void DecodeVector(SendProp const *pProp, bf_read *pIn, float *v)
{
	v[0] = DecodeFloat(pProp, pIn);
	v[1] = DecodeFloat(pProp, pIn);

	// Don't read in the third component for normals
	if ((pProp->GetFlags() & SPROP_NORMAL) == 0)
	{
		v[2] = DecodeFloat(pProp, pIn);
	}
	else
	{
		int signbit = pIn->ReadOneBit();

		float v0v0v1v1 = v[0] * v[0] +
			v[1] * v[1];
		if (v0v0v1v1 < 1.0f)
			v[2] = sqrtf( 1.0f - v0v0v1v1 );
		else
			v[2] = 0.0f;

		if (signbit)
			v[2] *= -1.0f;
	}
}

static inline void DecodeQuaternion(SendProp const *pProp, bf_read *pIn, float *v)
{
	v[0] = DecodeFloat(pProp, pIn);
	v[1] = DecodeFloat(pProp, pIn);
	v[2] = DecodeFloat(pProp, pIn);
	v[3] = DecodeFloat(pProp, pIn);
}

int	DecodeBits( DecodeInfo *pInfo, unsigned char *pOut )
{
	bf_read temp;

	// Read the property in (note: we don't return the bits from here because Decode returns
	// the decoded bits.. we're interested in getting the encoded bits).
	temp = *pInfo->m_pIn;
	pInfo->m_pRecvProp = NULL;
	pInfo->m_pData = NULL;
	g_PropTypeFns[pInfo->m_pProp->m_Type].Decode( pInfo );

	// Return the encoded bits.
	int nBits = pInfo->m_pIn->GetNumBitsRead() - temp.GetNumBitsRead();
	temp.ReadBits(pOut, nBits);
	return nBits;
}


// ---------------------------------------------------------------------------------------- //
// Most of the prop types can use this generic FastCopy version. Arrays are a bit of a pain.
// ---------------------------------------------------------------------------------------- //

inline void Generic_FastCopy( 
	const SendProp *pSendProp, 
	const RecvProp *pRecvProp, 
	const unsigned char *pSendData, 
	unsigned char *pRecvData,
	int objectID )
{
	// Get the data out of the ent.
	CRecvProxyData recvProxyData;

	pSendProp->GetProxyFn()( 
		pSendProp,
		pSendData, 
		pSendData + pSendProp->GetOffset(),
		&recvProxyData.m_Value,
		0,
		objectID
		);

	// Fill in the data for the recv proxy.
	recvProxyData.m_pRecvProp = pRecvProp;
	recvProxyData.m_iElement = 0;
	recvProxyData.m_ObjectID = objectID;
	pRecvProp->GetProxyFn()( &recvProxyData, pRecvData, pRecvData + pRecvProp->GetOffset() );
}


// ---------------------------------------------------------------------------------------- //
// DecodeInfo implementation.
// ---------------------------------------------------------------------------------------- //

void DecodeInfo::CopyVars( const DecodeInfo *pOther )
{
	m_pStruct = pOther->m_pStruct;
	m_pData = pOther->m_pData;
	
	m_pRecvProp = pOther->m_pRecvProp;
	m_pProp = pOther->m_pProp;
	m_pIn = pOther->m_pIn;
	m_ObjectID = pOther->m_ObjectID;
	m_iElement = pOther->m_iElement;
}


// ---------------------------------------------------------------------------------------- //
// Int property type abstraction.
// ---------------------------------------------------------------------------------------- //

void Int_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
	int nValue = pVar->m_Int;
	
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			pOut->WriteVarInt32( nValue );
		}
		else
		{
			pOut->WriteSignedVarInt32( nValue );
		}
	}
	else
	{
		// If signed, preserve lower bits and then re-extend sign if nValue < 0;
		// if unsigned, preserve all 32 bits no matter what. Bonus: branchless.
		int nPreserveBits = ( 0x7FFFFFFF >> ( 32 - pProp->m_nBits ) );
		nPreserveBits |= ( pProp->GetFlags() & SPROP_UNSIGNED ) ? 0xFFFFFFFF : 0;
		int nSignExtension = ( nValue >> 31 ) & ~nPreserveBits;

		nValue &= nPreserveBits;
		nValue |= nSignExtension;

#ifdef DBGFLAG_ASSERT
		// Assert that either the property is unsigned and in valid range,
		// or signed with a consistent sign extension in the high bits
		if ( pProp->m_nBits < 32 )
		{
			if ( pProp->GetFlags() & SPROP_UNSIGNED )
			{
				AssertMsg3( nValue == pVar->m_Int, "Unsigned prop %s needs more bits? Expected %i == %i", pProp->GetName(), nValue, pVar->m_Int );
			}
			else 
			{
				AssertMsg3( nValue == pVar->m_Int, "Signed prop %s needs more bits? Expected %i == %i", pProp->GetName(), nValue, pVar->m_Int );
			}
		}
		else
		{
			// This should never trigger, but I'm leaving it in for old-time's sake.
			Assert( nValue == pVar->m_Int );
		}
#endif

		pOut->WriteUBitLong( nValue, pProp->m_nBits, false );
	}
}


void Int_Decode( DecodeInfo *pInfo )
{
	const SendProp *pProp = pInfo->m_pProp;
	int flags = pProp->GetFlags();

	if ( flags & SPROP_VARINT )
	{
		if ( flags & SPROP_UNSIGNED )
		{
			pInfo->m_Value.m_Int = (int)pInfo->m_pIn->ReadVarInt32();
		}
		else
		{
			pInfo->m_Value.m_Int = pInfo->m_pIn->ReadSignedVarInt32();
		}
	}
	else
	{
		int bits = pProp->m_nBits;
		pInfo->m_Value.m_Int = pInfo->m_pIn->ReadUBitLong(bits);

		if( bits != 32 && (flags & SPROP_UNSIGNED) == 0 )
		{
			unsigned int highbit = 1ul << (pProp->m_nBits - 1);
			if ( pInfo->m_Value.m_Int & highbit )
			{
				pInfo->m_Value.m_Int -= highbit; // strip high bit...
				pInfo->m_Value.m_Int -= highbit; // ... then put it back with sign extension
			}
		}
	}

	if ( pInfo->m_pRecvProp )
	{
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
	}
}


int Int_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			return p1->ReadVarInt32() != p2->ReadVarInt32();
		}
		return p1->ReadSignedVarInt32() != p2->ReadSignedVarInt32();
	}

	return p1->CompareBits(p2, pProp->m_nBits);
}

const char* Int_GetTypeNameString()
{
	return "DPT_Int";
}


bool Int_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
	return (pVar->m_Int == 0);
}


void Int_DecodeZero( DecodeInfo *pInfo )
{
	pInfo->m_Value.m_Int = 0;

	if ( pInfo->m_pRecvProp )
	{
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
	}
}

bool Int_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			return pIn->ReadVarInt32() == 0;
		}
		return pIn->ReadSignedVarInt32() == 0;
	}
	return pIn->ReadUBitLong( pProp->m_nBits ) == 0;
}

void Int_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			pIn->ReadVarInt32();
		}
		else
		{
			pIn->ReadSignedVarInt32();
		}
	}
	else
	{
		pIn->SeekRelative( pProp->m_nBits );
	}
}

// ---------------------------------------------------------------------------------------- //
// Float type abstraction.
// ---------------------------------------------------------------------------------------- //

void Float_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
	EncodeFloat( pProp, pVar->m_Float, pOut, objectID );
}


void Float_Decode( DecodeInfo *pInfo )
{
	pInfo->m_Value.m_Float = DecodeFloat(pInfo->m_pProp, pInfo->m_pIn);

	if ( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}


int	Float_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	if ( pProp->GetFlags() & SPROP_COORD )
	{
		return p1->ReadBitCoord() != p2->ReadBitCoord();
	}
	else if ( pProp->GetFlags() & SPROP_COORD_MP )
	{
		return p1->ReadBitCoordMP( false, false ) != p2->ReadBitCoordMP( false, false );
	}
	else if ( pProp->GetFlags() & SPROP_COORD_MP_LOWPRECISION )
	{
		return p1->ReadBitCoordMP( false, true ) != p2->ReadBitCoordMP( false, true );
	}
	else if ( pProp->GetFlags() & SPROP_COORD_MP_INTEGRAL )
	{
		return p1->ReadBitCoordMP( true, false ) != p2->ReadBitCoordMP( true, false );
	}
	else if ( pProp->GetFlags() & SPROP_NOSCALE )
	{
		return p1->ReadUBitLong( 32 ) != p2->ReadUBitLong( 32 );
	}
	else if ( pProp->GetFlags() & SPROP_NORMAL )
	{
		return p1->ReadUBitLong( NORMAL_FRACTIONAL_BITS+1 ) != p2->ReadUBitLong( NORMAL_FRACTIONAL_BITS+1 );
	}
	else
	{
		return p1->ReadUBitLong( pProp->m_nBits ) != p2->ReadUBitLong( pProp->m_nBits );
	}
}

const char* Float_GetTypeNameString()
{
	return "DPT_Float";
}


bool Float_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
	return (pVar->m_Float == 0);
}


void Float_DecodeZero( DecodeInfo *pInfo )
{
	pInfo->m_Value.m_Float = 0;
	if ( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}

bool Float_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	return DecodeFloat( pProp, pIn ) == 0.0f;
}

void Float_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	// Check for special flags..
	if(pProp->GetFlags() & SPROP_COORD)
	{
		// Read the required integer and fraction flags
		unsigned int val = pIn->ReadUBitLong(2);
		// this reads two bits, the first bit (bit0 in this word) indicates integer part
		// the second bit (bit1 in this word) indicates the fractional part

		// If we got either parse them, otherwise it's a zero.
		if ( val )
		{
			// sign bit
			int seekDist = 1;

			// If there's an integer, read it in
			if ( val & 1 )
				seekDist += COORD_INTEGER_BITS;
		
			if ( val & 2 )
				seekDist += COORD_FRACTIONAL_BITS;

			pIn->SeekRelative( seekDist );
		}
	}
	else if ( pProp->GetFlags() & SPROP_COORD_MP )
	{
		pIn->ReadBitCoordMP( false, false );
	}
	else if ( pProp->GetFlags() & SPROP_COORD_MP_LOWPRECISION )
	{
		pIn->ReadBitCoordMP( false, true );
	}
	else if ( pProp->GetFlags() & SPROP_COORD_MP_INTEGRAL )
	{
		pIn->ReadBitCoordMP( true, false );
	}
	else if(pProp->GetFlags() & SPROP_NOSCALE)
	{
		pIn->SeekRelative( 32 );
	}
	else if(pProp->GetFlags() & SPROP_NORMAL)
	{
		pIn->SeekRelative( NORMAL_FRACTIONAL_BITS + 1 );
	}
	else
	{
		pIn->SeekRelative( pProp->m_nBits );
	}
}


// ---------------------------------------------------------------------------------------- //
// Vector type abstraction.
// ---------------------------------------------------------------------------------------- //

void Vector_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
	EncodeFloat(pProp, pVar->m_Vector[0], pOut, objectID);
	EncodeFloat(pProp, pVar->m_Vector[1], pOut, objectID);
	// Don't write out the third component for normals
	if ((pProp->GetFlags() & SPROP_NORMAL) == 0)
	{
		EncodeFloat(pProp, pVar->m_Vector[2], pOut, objectID);
	}
	else
	{
		// Write a sign bit for z instead!
		int	signbit = (pVar->m_Vector[2] <= -NORMAL_RESOLUTION);
		pOut->WriteOneBit( signbit );
	}
}


void Vector_Decode(DecodeInfo *pInfo)
{
	DecodeVector( pInfo->m_pProp, pInfo->m_pIn, pInfo->m_Value.m_Vector );
	
	if( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}


int	Vector_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	int c1 = Float_CompareDeltas( pProp, p1, p2 );
	int c2 = Float_CompareDeltas( pProp, p1, p2 );
	int c3;
	
	if ( pProp->GetFlags() & SPROP_NORMAL )
	{
		c3 = p1->ReadOneBit() != p2->ReadOneBit();
	}
	else
	{
		c3 = Float_CompareDeltas( pProp, p1, p2 );
	}

	return c1 | c2 | c3;
}

const char* Vector_GetTypeNameString()
{
	return "DPT_Vector";
}


bool Vector_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
	return ( pVar->m_Vector[0] == 0 ) && ( pVar->m_Vector[1] == 0 ) && ( pVar->m_Vector[2] == 0 );
}


void Vector_DecodeZero( DecodeInfo *pInfo )
{
	pInfo->m_Value.m_Vector[0] = 0;
	pInfo->m_Value.m_Vector[1] = 0;
	pInfo->m_Value.m_Vector[2] = 0;
	if ( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}

bool Vector_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	float v[3];
	
	DecodeVector( pProp, pIn, v );

	return ( v[0] == 0 ) && ( v[1] == 0 ) && ( v[2] == 0 );
}

void Vector_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	Float_SkipProp(pProp, pIn);
	Float_SkipProp(pProp, pIn);

	// Don't read in the third component for normals
	if ( pProp->GetFlags() & SPROP_NORMAL )
	{
		pIn->SeekRelative( 1 );
	}
	else
	{
		Float_SkipProp(pProp, pIn);
	}
}

// ---------------------------------------------------------------------------------------- //
// VectorXY type abstraction.
// ---------------------------------------------------------------------------------------- //

void VectorXY_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
	EncodeFloat(pProp, pVar->m_Vector[0], pOut, objectID);
	EncodeFloat(pProp, pVar->m_Vector[1], pOut, objectID);
}


void VectorXY_Decode(DecodeInfo *pInfo)
{
	pInfo->m_Value.m_Vector[0] = DecodeFloat(pInfo->m_pProp, pInfo->m_pIn);
	pInfo->m_Value.m_Vector[1] = DecodeFloat(pInfo->m_pProp, pInfo->m_pIn);
	
	if( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}


int	VectorXY_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	int c1 = Float_CompareDeltas( pProp, p1, p2 );
	int c2 = Float_CompareDeltas( pProp, p1, p2 );

	return c1 | c2;
}

const char* VectorXY_GetTypeNameString()
{
	return "DPT_VectorXY";
}


bool VectorXY_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
	return ( pVar->m_Vector[0] == 0 ) && ( pVar->m_Vector[1] == 0 );
}


void VectorXY_DecodeZero( DecodeInfo *pInfo )
{
	pInfo->m_Value.m_Vector[0] = 0;
	pInfo->m_Value.m_Vector[1] = 0;
	if ( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}

bool VectorXY_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	float v[2];
	
	v[0] = DecodeFloat(pProp, pIn);
	v[1] = DecodeFloat(pProp, pIn);

	return ( v[0] == 0 ) && ( v[1] == 0 );
}

void VectorXY_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	Float_SkipProp(pProp, pIn);
	Float_SkipProp(pProp, pIn);
}

#if 0 // We can't ship this since it changes the size of DTVariant to be 20 bytes instead of 16 and that breaks MODs!!!

// ---------------------------------------------------------------------------------------- //
// Quaternion type abstraction.
// ---------------------------------------------------------------------------------------- //

void Quaternion_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
	EncodeFloat(pProp, pVar->m_Vector[0], pOut, objectID);
	EncodeFloat(pProp, pVar->m_Vector[1], pOut, objectID);
	EncodeFloat(pProp, pVar->m_Vector[2], pOut, objectID);
	EncodeFloat(pProp, pVar->m_Vector[3], pOut, objectID);
}


void Quaternion_Decode(DecodeInfo *pInfo)
{
	DecodeQuaternion( pInfo->m_pProp, pInfo->m_pIn, pInfo->m_Value.m_Vector );
	
	if( pInfo->m_pRecvProp )
	{
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
	}
}


int	Quaternion_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	int c1 = Float_CompareDeltas( pProp, p1, p2 );
	int c2 = Float_CompareDeltas( pProp, p1, p2 );
	int c3 = Float_CompareDeltas( pProp, p1, p2 );
	int c4 = Float_CompareDeltas( pProp, p1, p2 );
	return c1 | c2 | c3 | c4;
}

const char* Quaternion_GetTypeNameString()
{
	return "DPT_Quaternion";
}


bool Quaternion_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
	return ( pVar->m_Vector[0] == 0 ) && ( pVar->m_Vector[1] == 0 ) && ( pVar->m_Vector[2] == 0 ) && ( pVar->m_Vector[3] == 0 );
}


void Quaternion_DecodeZero( DecodeInfo *pInfo )
{
	pInfo->m_Value.m_Vector[0] = 0;
	pInfo->m_Value.m_Vector[1] = 0;
	pInfo->m_Value.m_Vector[2] = 0;
	pInfo->m_Value.m_Vector[3] = 0;
	if ( pInfo->m_pRecvProp )
	{
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
	}
}

bool Quaternion_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	float v[4];
	
	DecodeQuaternion( pProp, pIn, v );

	return ( v[0] == 0 ) && ( v[1] == 0 ) && ( v[2] == 0 ) && ( v[3] == 0 );
}

void Quaternion_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	Float_SkipProp(pProp, pIn);
	Float_SkipProp(pProp, pIn);
	Float_SkipProp(pProp, pIn);
	Float_SkipProp(pProp, pIn);
}
#endif


// ---------------------------------------------------------------------------------------- //
// String type abstraction.
// ---------------------------------------------------------------------------------------- //

void String_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
	// First count the string length, then do one WriteBits call.
	int len;
	for ( len=0; len < DT_MAX_STRING_BUFFERSIZE-1; len++ )
	{
		if( pVar->m_pString[len] == 0 )
		{
			break;
		}
	}	
		
	// Optionally write the length here so deltas can be compared faster.
	pOut->WriteUBitLong( len, DT_MAX_STRING_BITS );
	pOut->WriteBits( pVar->m_pString, len * 8 );
}


void String_Decode(DecodeInfo *pInfo)
{
	// Read it in.
	int len = pInfo->m_pIn->ReadUBitLong( DT_MAX_STRING_BITS );

	char *tempStr = pInfo->m_TempStr;

	if ( len >= DT_MAX_STRING_BUFFERSIZE )
	{
		Warning( "String_Decode( %s ) invalid length (%d)\n", pInfo->m_pRecvProp->GetName(), len );
		len = DT_MAX_STRING_BUFFERSIZE - 1;
	}

	pInfo->m_pIn->ReadBits( tempStr, len*8 );
	tempStr[len] = 0;

	pInfo->m_Value.m_pString = tempStr;

	// Give it to the RecvProxy.
	if ( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}


// Compare the bits in pBuf1 and pBuf2 and return 1 if they are different.
// This must always seek both buffers to wherever they start at + nBits.
static inline int AreBitsDifferent( bf_read *pBuf1, bf_read *pBuf2, int nBits )
{
	int nDWords = nBits >> 5;

	int diff = 0;
	for ( int iDWord=0; iDWord < nDWords; iDWord++ )
	{
		diff |= (pBuf1->ReadUBitLong(32) != pBuf2->ReadUBitLong(32));
	}

	int nRemainingBits = nBits - (nDWords<<5);
	if (nRemainingBits > 0)
		diff |= pBuf1->ReadUBitLong( nRemainingBits ) != pBuf2->ReadUBitLong( nRemainingBits );
	
	return diff;
}


int String_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	int len1 = p1->ReadUBitLong( DT_MAX_STRING_BITS );
	int len2 = p2->ReadUBitLong( DT_MAX_STRING_BITS );

	if ( len1 == len2 )
	{
		// check if both strings are empty
		if (len1 == 0)
			return false;

		// Ok, they're short and fast.
		return AreBitsDifferent( p1, p2, len1*8 );
	}
	else
	{
		p1->SeekRelative( len1 * 8 );
		p2->SeekRelative( len2 * 8 );
		return true;
	}
}

const char* String_GetTypeNameString()
{
	return "DPT_String";
}


bool String_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
	return ( pVar->m_pString[0] == 0 );
}


void String_DecodeZero( DecodeInfo *pInfo )
{
	pInfo->m_Value.m_pString = pInfo->m_TempStr;
	pInfo->m_TempStr[0] = 0;
	if ( pInfo->m_pRecvProp )
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
}

bool String_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	// Read it in.
	int len = pIn->ReadUBitLong( DT_MAX_STRING_BITS );
	
	pIn->SeekRelative( len*8 );

	return len == 0;
}

void String_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	int len = pIn->ReadUBitLong( DT_MAX_STRING_BITS );
	pIn->SeekRelative( len*8 );
}


// ---------------------------------------------------------------------------------------- //
// Array abstraction.
// ---------------------------------------------------------------------------------------- //

int Array_GetLength( const unsigned char *pStruct, const SendProp *pProp, int objectID )
{
	// Get the array length from the proxy.
	ArrayLengthSendProxyFn proxy = pProp->GetArrayLengthProxy();
	
	if ( proxy )
	{
		int nElements = proxy( pStruct, objectID );
		
		// Make sure it's not too big.
		if ( nElements > pProp->GetNumElements() )
		{
			Assert( false );
			nElements = pProp->GetNumElements();
		}

		return nElements;
	}
	else
	{	
		return pProp->GetNumElements();
	}
}


void Array_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
	SendProp *pArrayProp = pProp->GetArrayProp();
	AssertMsg( pArrayProp, "Array_Encode: missing m_pArrayProp for SendProp '%s'.", pProp->m_pVarName );
	
	int nElements = Array_GetLength( pStruct, pProp, objectID );

	// Write the number of elements.
	pOut->WriteUBitLong( nElements, pProp->GetNumArrayLengthBits() );

	unsigned char *pCurStructOffset = (unsigned char*)pStruct + pArrayProp->GetOffset();
	for ( int iElement=0; iElement < nElements; iElement++ )
	{
		DVariant var;

		// Call the proxy to get the value, then encode.
		pArrayProp->GetProxyFn()( pArrayProp, pStruct, pCurStructOffset, &var, iElement, objectID );
		g_PropTypeFns[pArrayProp->GetType()].Encode( pStruct, &var, pArrayProp, pOut, objectID ); 
		
		pCurStructOffset += pProp->GetElementStride();
	}
}


void Array_Decode( DecodeInfo *pInfo )
{
	SendProp *pArrayProp = pInfo->m_pProp->GetArrayProp();
	AssertMsg( pArrayProp, ("Array_Decode: missing m_pArrayProp for a property.") );

	// Setup a DecodeInfo that is used to decode each of the child properties.
	DecodeInfo subDecodeInfo;
	subDecodeInfo.CopyVars( pInfo );
	subDecodeInfo.m_pProp = pArrayProp;

	int elementStride = 0;	
	ArrayLengthRecvProxyFn lengthProxy = 0;
	if ( pInfo->m_pRecvProp )
	{
		RecvProp *pArrayRecvProp = pInfo->m_pRecvProp->GetArrayProp();
		subDecodeInfo.m_pRecvProp = pArrayRecvProp;
		
		// Note we get the OFFSET from the array element property and the STRIDE from the array itself.
		subDecodeInfo.m_pData = (char*)pInfo->m_pData + pArrayRecvProp->GetOffset();
		elementStride = pInfo->m_pRecvProp->GetElementStride();
		Assert( elementStride != -1 ); // (Make sure it was set..)

		lengthProxy = pInfo->m_pRecvProp->GetArrayLengthProxy();
	}

	int nElements = pInfo->m_pIn->ReadUBitLong( pInfo->m_pProp->GetNumArrayLengthBits() );

	if ( lengthProxy )
		lengthProxy( pInfo->m_pStruct, pInfo->m_ObjectID, nElements );

	for ( subDecodeInfo.m_iElement=0; subDecodeInfo.m_iElement < nElements; subDecodeInfo.m_iElement++ )
	{
		g_PropTypeFns[pArrayProp->GetType()].Decode( &subDecodeInfo );
		subDecodeInfo.m_pData = (char*)subDecodeInfo.m_pData + elementStride;
	}
}


int Array_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	SendProp *pArrayProp = pProp->GetArrayProp();
	AssertMsg( pArrayProp, "Array_CompareDeltas: missing m_pArrayProp for SendProp '%s'.", pProp->m_pVarName );

	int nLengthBits = pProp->GetNumArrayLengthBits(); 
	int length1 = p1->ReadUBitLong( nLengthBits );
	int length2 = p2->ReadUBitLong( nLengthBits );

	int bDifferent = length1 != length2;
	
	// Compare deltas on the props that are the same.
	int nSame = min( length1, length2 );
	for ( int iElement=0; iElement < nSame; iElement++ )
	{
		bDifferent |= g_PropTypeFns[pArrayProp->GetType()].CompareDeltas( pArrayProp, p1, p2 );
	}

	// Now just eat up the remaining properties in whichever buffer was larger.
	if ( length1 != length2 )
	{
		bf_read *buffer = (length1 > length2) ? p1 : p2;

		int nExtra = max( length1, length2 ) - nSame;
		for ( int iEatUp=0; iEatUp < nExtra; iEatUp++ )
		{
			SkipPropData( buffer, pArrayProp );
		}
	}
	
	return bDifferent;
}


void Array_FastCopy( 
	const SendProp *pSendProp, 
	const RecvProp *pRecvProp, 
	const unsigned char *pSendData, 
	unsigned char *pRecvData, 
	int objectID )
{
	const RecvProp *pArrayRecvProp = pRecvProp->GetArrayProp();
	const SendProp *pArraySendProp = pSendProp->GetArrayProp();

	CRecvProxyData recvProxyData;
	recvProxyData.m_pRecvProp =	pArrayRecvProp;
	recvProxyData.m_ObjectID = objectID;

	// Find out the array length and call the RecvProp's array-length proxy.
	int nElements = Array_GetLength( pSendData, pSendProp, objectID );
	ArrayLengthRecvProxyFn lengthProxy = pRecvProp->GetArrayLengthProxy();
	if ( lengthProxy )
		lengthProxy( pRecvData, objectID, nElements );

	const unsigned char *pCurSendPos = pSendData + pArraySendProp->GetOffset();
	unsigned char *pCurRecvPos = pRecvData + pArrayRecvProp->GetOffset();
	for ( recvProxyData.m_iElement=0; recvProxyData.m_iElement < nElements; recvProxyData.m_iElement++ )
	{
		// Get this array element out of the sender's data.
		pArraySendProp->GetProxyFn()( pArraySendProp, pSendData, pCurSendPos, &recvProxyData.m_Value, recvProxyData.m_iElement, objectID );
		pCurSendPos += pSendProp->GetElementStride();
		
		// Write it into the receiver.
		pArrayRecvProp->GetProxyFn()( &recvProxyData, pRecvData, pCurRecvPos );
		pCurRecvPos += pRecvProp->GetElementStride();
	}
}

const char* Array_GetTypeNameString()
{
	return "DPT_Array";
}


bool Array_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
	int nElements = Array_GetLength( pStruct, pProp, -1 );
	return ( nElements == 0 );
}


void Array_DecodeZero( DecodeInfo *pInfo )
{
	ArrayLengthRecvProxyFn lengthProxy = pInfo->m_pRecvProp->GetArrayLengthProxy();
	if ( lengthProxy )
		lengthProxy( pInfo->m_pStruct, pInfo->m_ObjectID, 0 );
}

bool Array_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	SendProp *pArrayProp = pProp->GetArrayProp();
	AssertMsg( pArrayProp, ("Array_IsEncodedZero: missing m_pArrayProp for a property.") );

	int nElements = pIn->ReadUBitLong( pProp->GetNumArrayLengthBits() );

	for ( int i=0; i < nElements;  i++ )
	{
		// skip over data
		g_PropTypeFns[pArrayProp->GetType()].IsEncodedZero( pArrayProp, pIn );
	}
	
	return nElements == 0;;
}

void Array_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	SendProp *pArrayProp = pProp->GetArrayProp();
	AssertMsg( pArrayProp, ("Array_SkipProp: missing m_pArrayProp for a property.") );

	int nElements = pIn->ReadUBitLong( pProp->GetNumArrayLengthBits() );

	for ( int i=0; i < nElements;  i++ )
	{
		// skip over data
		g_PropTypeFns[pArrayProp->GetType()].SkipProp( pArrayProp, pIn );
	}
}


// ---------------------------------------------------------------------------------------- //
// Datatable type abstraction.
// ---------------------------------------------------------------------------------------- //

const char* DataTable_GetTypeNameString()
{
	return "DPT_DataTable";
}


// ---------------------------------------------------------------------------------------- //
// Int 64 property type abstraction.
// ---------------------------------------------------------------------------------------- //

void Int64_Encode( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp, bf_write *pOut, int objectID )
{
#ifdef SUPPORTS_INT64
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			pOut->WriteVarInt64( pVar->m_Int64 );
		}
		else
		{
			pOut->WriteSignedVarInt32( pVar->m_Int64 );
		}
	}
	else
	{
		bool bNeg = pVar->m_Int64 < 0;
		int64 iCopy = bNeg ? -pVar->m_Int64 : pVar->m_Int64;
		uint32 *pInt = (uint32*)&iCopy;
		uint32 lowInt = *pInt++;
		uint32 highInt = *pInt;
		if( pProp->IsSigned() )
		{
			pOut->WriteOneBit( bNeg );
			pOut->WriteUBitLong( (unsigned int)lowInt, 32 );
			pOut->WriteUBitLong( (unsigned int)highInt, pProp->m_nBits - 32 - 1 );	// For the sign bit
		}
		else
		{
			pOut->WriteUBitLong( (unsigned int)lowInt, 32 );
			pOut->WriteUBitLong( (unsigned int)highInt, pProp->m_nBits - 32 );
		}
	}
#endif
}


void Int64_Decode( DecodeInfo *pInfo )
{
#ifdef SUPPORTS_INT64
	if ( pInfo->m_pProp->GetFlags() & SPROP_VARINT )
	{
		if ( pInfo->m_pProp->GetFlags() & SPROP_UNSIGNED )
		{
			pInfo->m_Value.m_Int64 = (int64)pInfo->m_pIn->ReadVarInt64();
		}
		else
		{
			pInfo->m_Value.m_Int64 = pInfo->m_pIn->ReadSignedVarInt64();
		}
	}
	else
	{
		uint32 highInt = 0;
		uint32 lowInt = 0;
		bool bNeg = false;
		if(pInfo->m_pProp->IsSigned())
		{
			bNeg = pInfo->m_pIn->ReadOneBit() != 0;
			lowInt = pInfo->m_pIn->ReadUBitLong( 32 );
			highInt = pInfo->m_pIn->ReadUBitLong( pInfo->m_pProp->m_nBits - 32 - 1 );
		}
		else
		{
			lowInt = pInfo->m_pIn->ReadUBitLong( 32 );
			highInt = pInfo->m_pIn->ReadUBitLong( pInfo->m_pProp->m_nBits - 32 );
		}

		uint32 *pInt = (uint32*)&pInfo->m_Value.m_Int64;
		*pInt++ = lowInt;
		*pInt = highInt;

		if ( bNeg )
		{
			pInfo->m_Value.m_Int64 = -pInfo->m_Value.m_Int64;
		}
	}

	if ( pInfo->m_pRecvProp )
	{
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
	}
#endif
}


int Int64_CompareDeltas( const SendProp *pProp, bf_read *p1, bf_read *p2 )
{
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			return p1->ReadVarInt64() != p2->ReadVarInt64();
		}
		return p1->ReadSignedVarInt64() != p2->ReadSignedVarInt64();
	}

	uint32 highInt1 = p1->ReadUBitLong( pProp->m_nBits - 32 );
	uint32 lowInt1 = p1->ReadUBitLong( 32 );
	uint32 highInt2 = p2->ReadUBitLong( pProp->m_nBits - 32 );
	uint32 lowInt2 = p2->ReadUBitLong( 32 );
	return highInt1 != highInt2 || lowInt1 != lowInt2;
}

const char* Int64_GetTypeNameString()
{
	return "DPT_Int64";
}


bool Int64_IsZero( const unsigned char *pStruct, DVariant *pVar, const SendProp *pProp )
{
#ifdef SUPPORTS_INT64
	return (pVar->m_Int64 == 0);
#else
	return false;
#endif
}


void Int64_DecodeZero( DecodeInfo *pInfo )
{
#ifdef SUPPORTS_INT64
	pInfo->m_Value.m_Int64 = 0;

	if ( pInfo->m_pRecvProp )
	{
		pInfo->m_pRecvProp->GetProxyFn()( pInfo, pInfo->m_pStruct, pInfo->m_pData );
	}
#endif
}

bool Int64_IsEncodedZero( const SendProp *pProp, bf_read *pIn )
{
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			return pIn->ReadVarInt64() == 0;
		}
		return pIn->ReadSignedVarInt64() == 0;
	}

	uint32 highInt1 = pIn->ReadUBitLong( pProp->m_nBits - 32 );
	uint32 lowInt1 = pIn->ReadUBitLong( 32 );
	return (highInt1 == 0 && lowInt1 == 0);
}

void Int64_SkipProp( const SendProp *pProp, bf_read *pIn )
{
	if ( pProp->GetFlags() & SPROP_VARINT)
	{
		if ( pProp->GetFlags() & SPROP_UNSIGNED )
		{
			pIn->ReadVarInt64();
		}
		else
		{
			pIn->ReadSignedVarInt64();
		}
	}
	else
	{
		pIn->SeekRelative( pProp->m_nBits );
	}
}



PropTypeFns g_PropTypeFns[DPT_NUMSendPropTypes] =
{
	// DPT_Int
	{
		Int_Encode,
		Int_Decode,
		Int_CompareDeltas,
		Generic_FastCopy,
		Int_GetTypeNameString,
		Int_IsZero,
		Int_DecodeZero,
		Int_IsEncodedZero,
		Int_SkipProp,
	},

	// DPT_Float
	{
		Float_Encode,
		Float_Decode,
		Float_CompareDeltas,
		Generic_FastCopy,
		Float_GetTypeNameString,
		Float_IsZero,
		Float_DecodeZero,
		Float_IsEncodedZero,
		Float_SkipProp,
	},

	// DPT_Vector
	{
		Vector_Encode,
		Vector_Decode,
		Vector_CompareDeltas,
		Generic_FastCopy,
		Vector_GetTypeNameString,
		Vector_IsZero,
		Vector_DecodeZero,
		Vector_IsEncodedZero,
		Vector_SkipProp,
	},

	// DPT_VectorXY
	{
		VectorXY_Encode,
		VectorXY_Decode,
		VectorXY_CompareDeltas,
		Generic_FastCopy,
		VectorXY_GetTypeNameString,
		VectorXY_IsZero,
		VectorXY_DecodeZero,
		VectorXY_IsEncodedZero,
		VectorXY_SkipProp,
	},

	// DPT_String
	{
		String_Encode,
		String_Decode,
		String_CompareDeltas,
		Generic_FastCopy,
		String_GetTypeNameString,
		String_IsZero,
		String_DecodeZero,
		String_IsEncodedZero,
		String_SkipProp,
	},

	// DPT_Array
	{
		Array_Encode,
		Array_Decode,
		Array_CompareDeltas,
		Array_FastCopy,
		Array_GetTypeNameString,
		Array_IsZero,
		Array_DecodeZero,
		Array_IsEncodedZero,
		Array_SkipProp,
	},
	 
	// DPT_DataTable
	{
		NULL,
		NULL,
		NULL,
		NULL,
		DataTable_GetTypeNameString,
		NULL,
		NULL,
		NULL,
		NULL,
	},
#if 0 // We can't ship this since it changes the size of DTVariant to be 20 bytes instead of 16 and that breaks MODs!!!

	// DPT_Quaternion
	{
		Quaternion_Encode,
		Quaternion_Decode,
		Quaternion_CompareDeltas,
		Generic_FastCopy,
		Quaternion_GetTypeNameString,
		Quaternion_IsZero,
		Quaternion_DecodeZero,
		Quaternion_IsEncodedZero,
		Quaternion_SkipProp,
	},
#endif

#ifdef SUPPORTS_INT64
	// DPT_Int64
	{
		Int64_Encode,
		Int64_Decode,
		Int64_CompareDeltas,
		Generic_FastCopy,
		Int64_GetTypeNameString,
		Int64_IsZero,
		Int64_DecodeZero,
		Int64_IsEncodedZero,
		Int64_SkipProp,
	},
#endif

};