//========= Copyright Valve Corporation, 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 "tier1/strtools.h"
#include "tier0/icommandline.h"
#include "dt_common_eng.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 )
#define SETUP_VISIT() class CDummyClass {} // Workaround for parser bug in VC7.1

// ------------------------------------------------------------------------------------ //
// 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;
		}
	}
}

struct MatchingProp_t
{
	SendProp	*m_pProp;
	RecvProp	*m_pMatchingRecvProp;

	static bool LessFunc( const MatchingProp_t& lhs, const MatchingProp_t& rhs )
	{
		return lhs.m_pProp < rhs.m_pProp;
	}
};

static bool MatchRecvPropsToSendProps_R( CUtlRBTree< MatchingProp_t, unsigned short >& lookup, char const *sendTableName, SendTable *pSendTable, RecvTable *pRecvTable, bool bAllowMismatches, bool *pAnyMismatches )
{
	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 ) )
			{
				Warning( "RecvProp type doesn't match server type for %s/%s\n", pSendTable->GetName(), pSendProp->GetName() );
				return false;
			}

			MatchingProp_t info;
			info.m_pProp = pSendProp;
			info.m_pMatchingRecvProp = pRecvProp;

			lookup.Insert( info );
		}
		else
		{
			if ( pAnyMismatches )
			{
				*pAnyMismatches = true;
			}

			Warning( "Missing RecvProp for %s - %s/%s\n", sendTableName, pSendTable->GetName(), pSendProp->GetName() );
			if ( !bAllowMismatches )
			{
				return false;
			}
		}

		// Recurse.
		if ( pSendProp->GetType() == DPT_DataTable )
		{
			if ( !MatchRecvPropsToSendProps_R( lookup, sendTableName, pSendProp->GetDataTable(), FindRecvTable( pSendProp->GetDataTable()->m_pNetTableName ), bAllowMismatches, pAnyMismatches ) )
				return false;
		}
	}

	return true;
}


// ------------------------------------------------------------------------------------ //
// Interface functions.
// ------------------------------------------------------------------------------------ //
bool RecvTable_Init( RecvTable **pTables, int nTables )
{
	SETUP_VISIT();

	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*/ )
{
	DTI_Term();

	SETUP_VISIT();

	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();
}

void RecvTable_FreeSendTable( SendTable *pTable )
{
	for ( int iProp=0; iProp < pTable->m_nProps; iProp++ )
	{
		SendProp *pProp = &pTable->m_pProps[iProp];

		delete [] pProp->m_pVarName;

		if ( pProp->m_pExcludeDTName )
			delete [] pProp->m_pExcludeDTName;
	}

	if ( pTable->m_pProps )
		delete [] pTable->m_pProps;

	delete pTable;
}


SendTable *RecvTable_ReadInfos( bf_read *pBuf, int nDemoProtocol )
{
	SendTable *pTable = new SendTable;

	pTable->m_pNetTableName = pBuf->ReadAndAllocateString();

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

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

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

		int nFlagsBits = PROPINFOBITS_FLAGS;
        
		// HACK to playback old demos. SPROP_NUMFLAGBITS was 11, now 13
		// old nDemoProtocol was 2 
		if ( nDemoProtocol == 2 )
		{
			nFlagsBits = 11;
		}

		pProp->SetFlags( pBuf->ReadUBitLong( nFlagsBits ) );

		if ( pProp->m_Type == DPT_DataTable )
		{
			pProp->m_pExcludeDTName = pBuf->ReadAndAllocateString();
		}
		else
		{
			if ( pProp->IsExcludeProp() )
			{
				pProp->m_pExcludeDTName = pBuf->ReadAndAllocateString();
			}
			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 pTable;
}

bool RecvTable_RecvClassInfos( bf_read *pBuf, bool bNeedsDecoder, int nDemoProtocol )
{
	SendTable *pSendTable = RecvTable_ReadInfos( pBuf, nDemoProtocol );

	if ( !pSendTable )
		return false;

	bool ret = DataTable_SetupReceiveTableFromSendTable( pSendTable, bNeedsDecoder );
		
	RecvTable_FreeSendTable( pSendTable );

	return ret;
}

static void CopySendPropsToRecvProps( 
	CUtlRBTree< MatchingProp_t, unsigned short >& lookup, 
	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];
		MatchingProp_t search;
		search.m_pProp = (SendProp *)pSendProp;
		int idx = lookup.Find( search );
		if ( idx == lookup.InvalidIndex() )
		{
			recvProps[iSendProp] = 0;
		}
		else
		{
			recvProps[iSendProp] = lookup[ idx ].m_pMatchingRecvProp;
		}
	}
}

bool RecvTable_CreateDecoders( const CStandardSendProxies *pSendProxies, bool bAllowMismatches, bool *pAnyMismatches )
{
	DTI_Init();

	SETUP_VISIT();

	if ( pAnyMismatches )
	{
		*pAnyMismatches = false;
	}

	// 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 );
		if ( !pDecoder->m_pClientSendTable )
			return false;


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

		CUtlRBTree< MatchingProp_t, unsigned short >	PropLookup( 0, 0, MatchingProp_t::LessFunc );

		// Now match RecvProp with SendProps.
		if ( !MatchRecvPropsToSendProps_R( PropLookup, pDecoder->GetSendTable()->m_pNetTableName, pDecoder->GetSendTable(), FindRecvTable( pDecoder->GetSendTable()->m_pNetTableName ), bAllowMismatches, pAnyMismatches ) )
			return false;
	
		// Now fill out the matching RecvProp array.
		CSendTablePrecalc *pPrecalc = &pDecoder->m_Precalc;
		CopySendPropsToRecvProps( PropLookup, pPrecalc->m_Props, pDecoder->m_Props );
		CopySendPropsToRecvProps( PropLookup, pPrecalc->m_DatatableProps, pDecoder->m_DatatableProps );
	
		DTI_HookRecvDecoder( pDecoder );
	}

	return true;
}


bool RecvTable_Decode( 
	RecvTable *pTable, 
	void *pStruct, 
	bf_read *pIn, 
	int objectID,
	bool updateDTI
	)
{
	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 );
	
	theStack.Init();
	int iStartBit = 0, nIndexBits = 0, iLastBit = pIn->GetNumBitsRead();
	unsigned int iProp;
	CDeltaBitsReader deltaBitsReader( pIn );
	while ( (iProp = deltaBitsReader.ReadNextPropIndex()) < MAX_DATATABLE_PROPS )
	{
		theStack.SeekToProp( iProp );
		
		const RecvProp *pProp = pDecoder->GetProp( iProp );

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

		DecodeInfo decodeInfo;
		decodeInfo.m_pStruct = theStack.GetCurStructBase();
		
		if ( pProp )
		{
			decodeInfo.m_pData = theStack.GetCurStructBase() + pProp->GetOffset();
		}
		else
		{
			// They're allowed to be missing props here if they're playing back a demo.
			// This allows us to change the datatables and still preserve old demos.
			decodeInfo.m_pData = NULL;
		}

		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[ decodeInfo.m_pProp->GetType() ].Decode( &decodeInfo );
		++g_nPropsDecoded;

		// Instrumentation (store # bits for the encoded property).
		if ( updateDTI && 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())
	);

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

	theStack.Init();

	for ( int iProp=0; iProp < pDecoder->GetNumProps(); iProp++ )
	{	
		theStack.SeekToProp( iProp );
	
		// They're allowed to be missing props here if they're playing back a demo.
		// This allows us to change the datatables and still preserve old demos.
		const RecvProp *pProp = pDecoder->GetProp( iProp );
		if ( !pProp )
			continue;

		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 = NULL;
		decodeInfo.m_ObjectID = objectID;

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



int RecvTable_MergeDeltas(
	RecvTable *pTable,

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

	bf_write *pOut,

	int objectID,
	int *pChangedProps,
	bool updateDTI
	)
{
	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()) );

	int nChanged = 0;
	
	// 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 );

	unsigned int iOldProp = ~0u;
	if ( pOldState )
		iOldProp = oldStateReader.ReadNextPropIndex();

	int iStartBit = 0, nIndexBits = 0, iLastBit = pNewState->GetNumBitsRead();

	unsigned int iNewProp = newStateReader.ReadNextPropIndex();
	
	while ( 1 )
	{
		// Write any properties in the previous state that aren't in the new state.
		while ( iOldProp < iNewProp )
		{
			deltaBitsWriter.WritePropIndex( iOldProp );
			oldStateReader.CopyPropData( deltaBitsWriter.GetBitBuf(), pDecoder->GetSendProp( iOldProp ) );
			iOldProp = oldStateReader.ReadNextPropIndex();
		}

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

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

		// Now write the new state's value.
		deltaBitsWriter.WritePropIndex( iNewProp );
		newStateReader.CopyPropData( deltaBitsWriter.GetBitBuf(), pDecoder->GetSendProp( iNewProp ) );
	
		if ( pChangedProps )
		{
			pChangedProps[nChanged] = iNewProp;
		}

		nChanged++;

		// Instrumentation (store # bits for the encoded property).
		if ( updateDTI && g_bDTIEnabled )
		{
			iLastBit = pNewState->GetNumBitsRead();
			DTI_HookDeltaBits( pDecoder, iNewProp, iLastBit - iStartBit, nIndexBits );
		}

		iNewProp = newStateReader.ReadNextPropIndex();
	}

	Assert( nChanged <= MAX_DATATABLE_PROPS );

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

	return nChanged;
}


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