//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "dt.h"
#include "dt_recv_eng.h"
#include "dt_encode.h"
#include "dt_instrumentation.h"
#include "dt_stack.h"
#include "utllinkedlist.h"
#include "tier0/dbg.h"
#include "dt_recv_decoder.h"
#include "vstdlib/strtools.h"

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


class CClientSendTable;


// Testing out this pattern.. you can write simple code blocks inside of
// codeToRun. The thing that sucks is that you can't access your function's
// local variables inside of codeToRun.
//
// If it used an iterator class, it could access local function variables,
// but the iterator class might be more trouble than it's worth.
#define FOR_EACH_PROP_R( TableType, pTablePointer, tableCode, propCode )	\
	class CPropVisitor 										\
	{ 														\
		public:												\
		static void Visit_R( TableType *pTable )					\
		{													\
			tableCode;										\
															\
			for ( int i=0; i < pTable->GetNumProps(); i++ )	\
			{												\
				TableType::PropType *pProp = pTable->GetProp( i );	\
															\
				propCode;									\
															\
				if ( pProp->GetType() == DPT_DataTable )	\
					Visit_R( pProp->GetDataTable() );		\
			}												\
		}													\
	};														\
	CPropVisitor::Visit_R( pTablePointer );				

#define SENDPROP_VISIT( pTablePointer, tableCode, propCode )  FOR_EACH_PROP_R( SendTable, pTablePointer, tableCode, propCode )
#define RECVPROP_VISIT( pTablePointer, tableCode, propCode )  FOR_EACH_PROP_R( RecvTable, pTablePointer, tableCode, propCode )



// ------------------------------------------------------------------------------------ //
// Globals.
// ------------------------------------------------------------------------------------ //

CUtlLinkedList< RecvTable*, unsigned short > g_RecvTables;
CUtlLinkedList< CRecvDecoder *, unsigned short > g_RecvDecoders;
CUtlLinkedList< CClientSendTable*, unsigned short > g_ClientSendTables;

int g_nPropsDecoded = 0;


// ------------------------------------------------------------------------------------ //
// Static helper functions.
// ------------------------------------------------------------------------------------ //

RecvTable* FindRecvTable( const char *pName )
{
	FOR_EACH_LL( g_RecvTables, i )
	{
		if ( stricmp( g_RecvTables[i]->GetName(), pName ) == 0 )
			return g_RecvTables[i];
	}
	return 0;
}


static CClientSendTable* FindClientSendTable( const char *pName )
{
	FOR_EACH_LL( g_ClientSendTables, i )
	{
		CClientSendTable *pTable = g_ClientSendTables[i];

		if ( stricmp( pTable->GetName(), pName ) == 0 )
			return pTable;
	}

	return NULL;
}


// Find all child datatable properties for the send tables.
bool SetupClientSendTableHierarchy()
{
	FOR_EACH_LL( g_ClientSendTables, iClientTable )
	{
		CClientSendTable *pTable = g_ClientSendTables[iClientTable];
		
		// For each datatable property, find the table it references.
		for ( int iProp=0; iProp < pTable->GetNumProps(); iProp++ )
		{
			CClientSendProp *pClientProp = pTable->GetClientProp( iProp );
			SendProp *pProp = &pTable->m_SendTable.m_pProps[iProp];

			if ( pProp->m_Type == DPT_DataTable )
			{
				const char *pTableName = pClientProp->GetTableName();
				ErrorIfNot( pTableName,
					("SetupClientSendTableHierarchy: missing table name for prop '%s'.", pProp->GetName())
				);

				CClientSendTable *pChild = FindClientSendTable( pTableName );
				if ( !pChild )
				{
					DataTable_Warning( "SetupClientSendTableHierarchy: missing SendTable '%s' (referenced by '%s').\n", pTableName, pTable->GetName() );
					return false;
				}

				pProp->SetDataTable( &pChild->m_SendTable );
			}
		}
	}
	
	return true;
}


static RecvProp* FindRecvProp( RecvTable *pTable, const char *pName )
{
	for ( int i=0; i < pTable->GetNumProps(); i++ )
	{
		RecvProp *pProp = pTable->GetProp( i );

		if ( stricmp( pProp->GetName(), pName ) == 0 )
			return pProp;
	}
	
	return NULL;
}


// See if the RecvProp is fit to receive the SendProp's data.
bool CompareRecvPropToSendProp( const RecvProp *pRecvProp, const SendProp *pSendProp )
{
	while ( 1 )
	{
		ErrorIfNot( pRecvProp && pSendProp,
			("CompareRecvPropToSendProp: missing a property.")
		);

		if ( pRecvProp->GetType() != pSendProp->GetType() || pRecvProp->IsInsideArray() != pSendProp->IsInsideArray() )
		{
			return false;
		}

		if ( pRecvProp->GetType() == DPT_Array )
		{
			if ( pRecvProp->GetNumElements() != pSendProp->GetNumElements() )
				return false;
			
			pRecvProp = pRecvProp->GetArrayProp();
			pSendProp = pSendProp->GetArrayProp();
		}
		else
		{
			return true;
		}
	}
}


static bool MatchRecvPropsToSendProps_R( char const *sendTableName, SendTable *pSendTable, RecvTable *pRecvTable )
{
	for ( int i=0; i < pSendTable->m_nProps; i++ )
	{
		SendProp *pSendProp = &pSendTable->m_pProps[i];

		if ( pSendProp->IsExcludeProp() || pSendProp->IsInsideArray() )
			continue;

		// Find a RecvProp by the same name and type.
		RecvProp *pRecvProp = 0;
		if ( pRecvTable )
			pRecvProp = FindRecvProp( pRecvTable, pSendProp->GetName() );

		if ( pRecvProp )
		{
			if ( !CompareRecvPropToSendProp( pRecvProp, pSendProp ) )
			{
				DataTable_Warning( "RecvProp type doesn't match server type for %s/%s\n", pSendTable->GetName(), pSendProp->GetName() );
				return false;
			}

			pSendProp->m_pMatchingRecvProp = pRecvProp;
		}
		else
		{
			DataTable_Warning( "Missing RecvProp for %s - %s/%s\n", sendTableName, pSendTable->GetName(), pSendProp->GetName() );
		}

		// Recurse.
		if ( pSendProp->GetType() == DPT_DataTable )
		{
			if ( !MatchRecvPropsToSendProps_R( sendTableName, pSendProp->GetDataTable(), pRecvProp ? pRecvProp->GetDataTable() : 0 ) )
				return false;
		}
	}

	return true;
}


// ------------------------------------------------------------------------------------ //
// Interface functions.
// ------------------------------------------------------------------------------------ //

bool RecvTable_Init( RecvTable **pTables, int nTables )
{
	for ( int i=0; i < nTables; i++ )
	{
		RECVPROP_VISIT( pTables[i], 
			{
				if ( pTable->IsInMainList() )
					return;
				
				// Shouldn't have a decoder yet.
				ErrorIfNot( !pTable->m_pDecoder,
					("RecvTable_Init: table '%s' has a decoder already.", pTable->GetName())
				);
				
				pTable->SetInMainList( true );
				g_RecvTables.AddToTail( pTable );
			},

			{}
		);
	}

	return true;
}


void RecvTable_Term( bool clearall /*= true*/ )
{
	FOR_EACH_LL( g_RecvTables, i )
	{
		RECVPROP_VISIT( g_RecvTables[i],
			{
				if ( !pTable->IsInMainList() )
					return;

				pTable->SetInMainList( false );
				pTable->m_pDecoder = 0;
			},

			{}
		);
	}

	if ( clearall )
	{
		g_RecvTables.Purge();
	}
	g_RecvDecoders.PurgeAndDeleteElements();
	g_ClientSendTables.PurgeAndDeleteElements();
}


bool RecvTable_RecvInfo( bf_read *pBuf, bool bNeedsDecoder )
{
	CClientSendTable *pClientSendTable = new CClientSendTable;
	SendTable *pTable = &pClientSendTable->m_SendTable;
	g_ClientSendTables.AddToTail( pClientSendTable );

	// Read the name.
	pTable->m_pNetTableName = pBuf->ReadAndAllocateString();

	// Create a decoder for it if necessary.
	if ( bNeedsDecoder )
	{
		// Make a decoder for it.
		CRecvDecoder *pDecoder = new CRecvDecoder;
		g_RecvDecoders.AddToTail( pDecoder );
		
		RecvTable *pRecvTable = FindRecvTable( pTable->m_pNetTableName );
		if ( !pRecvTable )
		{
			DataTable_Warning( "No matching RecvTable for SendTable '%s'.\n", pTable->m_pNetTableName );
			return false;
		}

		pRecvTable->m_pDecoder = pDecoder;
		pDecoder->m_pTable = pRecvTable;

		pDecoder->m_pClientSendTable = pClientSendTable;
		pDecoder->m_Precalc.m_pSendTable = pClientSendTable->GetSendTable();
		pClientSendTable->GetSendTable()->m_pPrecalc = &pDecoder->m_Precalc;

		// Initialize array properties.
		SetupArrayProps_R<RecvTable, RecvTable::PropType>( pRecvTable );
	}


	// Read the property list.
	pTable->m_nProps = pBuf->ReadUBitLong( PROPINFOBITS_NUMPROPS );
	pTable->m_pProps = pTable->m_nProps ? new SendProp[ pTable->m_nProps ] : 0;
	pClientSendTable->m_Props.SetSize( pTable->m_nProps );

	for ( int iProp=0; iProp < pTable->m_nProps; iProp++ )
	{
		CClientSendProp *pClientProp = &pClientSendTable->m_Props[iProp];
		SendProp *pProp = &pTable->m_pProps[iProp];

		pProp->m_Type = (SendPropType)pBuf->ReadUBitLong( PROPINFOBITS_TYPE );
		pProp->m_pVarName = pBuf->ReadAndAllocateString();
		pProp->SetFlags( pBuf->ReadUBitLong( PROPINFOBITS_FLAGS ) );

		if ( pProp->m_Type == DPT_DataTable )
		{
			pClientProp->SetTableName( pBuf->ReadAndAllocateString() );
		}
		else
		{
			if ( pProp->IsExcludeProp() )
			{
				pProp->m_pExcludeDTName = pBuf->ReadAndAllocateString();
			}
			else if ( pProp->GetType() == DPT_String )
			{
				pProp->m_StringBufferLen = pBuf->ReadUBitLong( PROPINFOBITS_STRINGBUFFERLEN );
			}
			else if ( pProp->GetType() == DPT_Array )
			{
				pProp->SetNumElements( pBuf->ReadUBitLong( PROPINFOBITS_NUMELEMENTS ) );
			}
			else
			{
				pProp->m_fLowValue = pBuf->ReadBitFloat();
				pProp->m_fHighValue = pBuf->ReadBitFloat();
				pProp->m_nBits = pBuf->ReadUBitLong( PROPINFOBITS_NUMBITS );
			}
		}
	}

	return true;
}


static void CopySendPropsToRecvProps( 
	const CUtlVector<const SendProp*> &sendProps, 
	CUtlVector<const RecvProp*> &recvProps 
	)
{
	recvProps.SetSize( sendProps.Count() );
	for ( int iSendProp=0; iSendProp < sendProps.Count(); iSendProp++ )
	{
		const SendProp *pSendProp = sendProps[iSendProp];
		recvProps[iSendProp] = pSendProp->m_pMatchingRecvProp;
	}
}


bool RecvTable_CreateDecoders()
{
	// First, now that we've supposedly received all the SendTables that we need,
	// set their datatable child pointers.
	if ( !SetupClientSendTableHierarchy() )
		return false;

	FOR_EACH_LL( g_RecvDecoders, i )
	{
		CRecvDecoder *pDecoder = g_RecvDecoders[i];


		// It should already have been linked to its ClientSendTable.
		Assert( pDecoder->m_pClientSendTable );


		// For each decoder, precalculate the SendTable's flat property list.
		if ( !pDecoder->m_Precalc.SetupFlatPropertyArray() )
			return false;


		// Clear the SendProp matches from any previous call to MatchRecvPropsToSendProps_R.
		SENDPROP_VISIT( pDecoder->GetSendTable(), {}, pProp->m_pMatchingRecvProp = NULL )
				
		// Now match RecvProp with SendProps.
		if ( !MatchRecvPropsToSendProps_R( pDecoder->GetSendTable()->m_pNetTableName, pDecoder->GetSendTable(), pDecoder->GetRecvTable() ) )
			return false;
	

		// Now fill out the matching RecvProp array.
		CSendTablePrecalc *pPrecalc = &pDecoder->m_Precalc;
		CopySendPropsToRecvProps( pPrecalc->m_Props, pDecoder->m_Props );
		CopySendPropsToRecvProps( pPrecalc->m_DatatableProps, pDecoder->m_DatatableProps );
	
		DTI_HookRecvDecoder( pDecoder );
	}

	return true;
}


bool RecvTable_Decode( 
	RecvTable *pTable, 
	void *pStruct, 
	bf_read *pIn, 
	int objectID
	)
{
	CRecvDecoder *pDecoder = pTable->m_pDecoder;
	ErrorIfNot( pDecoder,
		("RecvTable_Decode: table '%s' missing a decoder.", pTable->GetName())
	);

	// While there are properties, decode them.. walk the stack as you go.
	CClientDatatableStack theStack( pDecoder, (unsigned char*)pStruct, objectID );

	int iStartBit = 0, nIndexBits = 0, iLastBit = pIn->GetNumBitsRead();
	
	int iProp;
	CDeltaBitsReader deltaBitsReader( pIn );
	while ( deltaBitsReader.ReadNextPropIndex( &iProp ) )
	{
		theStack.SeekToProp( iProp );
		
		const RecvProp *pProp = pDecoder->GetProp( iProp );
		Assert( pProp );

		// Instrumentation (store the # bits for the prop index).
		if ( g_bDTIEnabled )
		{
			iStartBit = pIn->GetNumBitsRead();
			nIndexBits = iStartBit - iLastBit;
		}

		DecodeInfo decodeInfo;
		decodeInfo.m_pStruct = theStack.GetCurStructBase();
		decodeInfo.m_pData = theStack.GetCurStructBase() + pProp->GetOffset();
		decodeInfo.m_pRecvProp = theStack.IsCurProxyValid() ? pProp : NULL; // Just skip the data if the proxies are screwed.
		decodeInfo.m_pProp = pDecoder->GetSendProp( iProp );
		decodeInfo.m_pIn = pIn;
		decodeInfo.m_ObjectID = objectID;

		g_PropTypeFns[pProp->GetType()].Decode( &decodeInfo );
		++g_nPropsDecoded;

		// Instrumentation (store # bits for the encoded property).
		if ( g_bDTIEnabled )
		{
			iLastBit = pIn->GetNumBitsRead();
			DTI_HookDeltaBits( pDecoder, iProp, iLastBit - iStartBit, nIndexBits );
		}
	}
	
	return !pIn->IsOverflowed();	
}


void RecvTable_DecodeZeros( RecvTable *pTable, void *pStruct, int objectID )
{
	CRecvDecoder *pDecoder = pTable->m_pDecoder;
	ErrorIfNot( pDecoder,
		("RecvTable_DecodeZeros: table '%s' missing a decoder.", pTable->GetName())
	);

	char zeroBuf[64]; // This should be more space than any property type uses (if it starts with zeros).
	Q_memset( zeroBuf, 0, sizeof( zeroBuf ) );
	bf_read buf( zeroBuf, sizeof( zeroBuf ) );

	// While there are properties, decode them.. walk the stack as you go.
	CClientDatatableStack theStack( pDecoder, (unsigned char*)pStruct, objectID );
	for ( int iProp=0; iProp < pDecoder->GetNumProps(); iProp++ )
	{	
		theStack.SeekToProp( iProp );
	
		const RecvProp *pProp = pDecoder->GetProp( iProp );

		buf.m_iCurBit = 0;

		DecodeInfo decodeInfo;
		decodeInfo.m_pStruct = theStack.GetCurStructBase();
		decodeInfo.m_pData = theStack.GetCurStructBase() + pProp->GetOffset();
		decodeInfo.m_pRecvProp = theStack.IsCurProxyValid() ? pProp : NULL; // Just skip the data if the proxies are screwed.
		decodeInfo.m_pProp = pDecoder->GetSendProp( iProp );
		decodeInfo.m_pIn = &buf;
		decodeInfo.m_ObjectID = objectID;

		g_PropTypeFns[pProp->GetType()].Decode( &decodeInfo );
	}
}


// Copies pProp's state from pIn to pOut. pDecodeInfo MUST be setup by calling InitDecodeInfoForSkippingProps
// with pIn.
//
// NOTE: this routine isn't optimal. If it shows up on the profiles, then it's easy to
// make this fast by adding a special routine to copy a property's state to PropTypeFns.
static void CopyPropState( 
	CRecvDecoder *pDecoder, 
	int iSendProp, 
	bf_read *pIn, 
	CDeltaBitsWriter *pOut, 
	DecodeInfo *pDecodeInfo 
	)
{
	const SendProp *pProp = pDecoder->GetSendProp( iSendProp );

	// Sanity check.
	Assert( pDecodeInfo->m_pData == 0 && pDecodeInfo->m_pStruct == 0 );
	Assert( pDecodeInfo->m_pIn == pIn );

	// Decode the data.
	int iStartBit = pIn->GetNumBitsRead();
	
	pDecodeInfo->m_pProp = pProp;
	g_PropTypeFns[ pProp->GetType() ].Decode( pDecodeInfo );

	// Figure out how many bits it took.
	int nBits = pIn->GetNumBitsRead() - iStartBit;
	
	// Read the data 
	pIn->Seek( iStartBit );

	pOut->WritePropIndex( iSendProp );
	pOut->GetBitBuf()->WriteBitsFromBuffer( pIn, nBits );
}



bool RecvTable_MergeDeltas(
	RecvTable *pTable,

	bf_read *pOldState,		// this can be null
	bf_read *pNewState,

	bf_write *pOut,

	int objectID,
	bool bDebugWatchInfo
	)
{
	ErrorIfNot( pTable && pNewState && pOut,
		("RecvTable_MergeDeltas: invalid parameters passed.")
	);

	CRecvDecoder *pDecoder = pTable->m_pDecoder;
	ErrorIfNot( pDecoder, ("RecvTable_MergeDeltas: table '%s' is missing its decoder.", pTable->GetName()) );
	

	// Setup to read the delta bits from each buffer.
	CDeltaBitsReader oldStateReader( pOldState );
	CDeltaBitsReader newStateReader( pNewState );

	// Setup to write delta bits into the output.
	CDeltaBitsWriter deltaBitsWriter( pOut );

	// Setup to skip over the old state's properties if necessary.
	DecodeInfo oldDataSkipper, newDataSkipper;
	InitDecodeInfoForSkippingProps( &oldDataSkipper, pOldState, objectID );
	InitDecodeInfoForSkippingProps( &newDataSkipper, pNewState, objectID );

	int iOldProp = PROP_SENTINEL;
	if ( pOldState )
		iOldProp = NextProp( &oldStateReader );

	int iNewProp = NextProp( &newStateReader );
	
	while ( 1 )
	{
		// Write any properties in the previous state that aren't in the new state.
		while ( iOldProp < iNewProp )
		{
			CopyPropState( pDecoder, iOldProp, pOldState, &deltaBitsWriter, &oldDataSkipper );
			iOldProp = NextProp( &oldStateReader );
		}

		// Check if we're at the end here so the while() statement above can seek the old buffer
		// to its end too.
		if ( iNewProp == PROP_SENTINEL )
			break;
		
		// If the old state has this property too, then just skip over its data.
		if ( iOldProp == iNewProp )
		{
			SkipPropData( &oldDataSkipper, pDecoder->GetSendProp( iOldProp ) );
			iOldProp = NextProp( &oldStateReader );
		}

		// Now write the new state's value.
		CopyPropState( pDecoder, iNewProp, pNewState, &deltaBitsWriter, &newDataSkipper );
		iNewProp = NextProp( &newStateReader );
	}

	ErrorIfNot( 
		!(pOldState && pOldState->IsOverflowed()) && !pNewState->IsOverflowed() && !pOut->IsOverflowed(),
		("RecvTable_MergeDeltas: overflowed in RecvTable '%s'.", pTable->GetName())
		);

	return true;
}


bool RecvTable_CopyEncoding( RecvTable *pTable, bf_read *pIn, bf_write *pOut, int objectID )
{
	return RecvTable_MergeDeltas( pTable, NULL, pIn, pOut, objectID, false );
}



