//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include "dt_stack.h"
#include "client.h"
#include "host.h"
#include "utllinkedlist.h"
#include "server.h"
#include "server_class.h"
#include "eiface.h"
#include "demo.h"
#include "sv_packedentities.h"
#include "tier0/icommandline.h"

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

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

RecvTable* FindRecvTable( const char *pName );

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

	// Read the name.
	pTable->m_pNetTableName = COM_StringCopy( sendTable->m_pNetTableName );

	// 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 = sendTable->m_nProps;
	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];
		const SendProp *pSendTableProp = &sendTable->m_pProps[ iProp ];

		pProp->m_Type = (SendPropType)pSendTableProp->m_Type;
		pProp->m_pVarName = COM_StringCopy( pSendTableProp->GetName() );
		pProp->SetFlags( pSendTableProp->GetFlags() );
		pProp->SetPriority( pSendTableProp->GetPriority() );


		if ( CommandLine()->FindParm("-dti" ) && pSendTableProp->GetParentArrayPropName() )
		{
			pProp->m_pParentArrayPropName = COM_StringCopy( pSendTableProp->GetParentArrayPropName() );
		}

		if ( pProp->m_Type == DPT_DataTable )
		{
			char *pDTName = pSendTableProp->m_pExcludeDTName; // HACK

			if ( pSendTableProp->GetDataTable() )
				pDTName = pSendTableProp->GetDataTable()->m_pNetTableName;

			Assert( pDTName && Q_strlen(pDTName) > 0 );

			pClientProp->SetTableName( COM_StringCopy( pDTName ) );
			
			// Normally we wouldn't care about this but we need to compare it against 
			// proxies in the server DLL in SendTable_BuildHierarchy.
			pProp->SetDataTableProxyFn( pSendTableProp->GetDataTableProxyFn() );
			pProp->SetOffset( pSendTableProp->GetOffset() );
		}
		else
		{
			if ( pProp->IsExcludeProp() )
			{
				pProp->m_pExcludeDTName = COM_StringCopy( pSendTableProp->GetExcludeDTName() );
			}
			else if ( pProp->GetType() == DPT_Array )
			{
				pProp->SetNumElements( pSendTableProp->GetNumElements() );
			}
			else
			{
				pProp->m_fLowValue = pSendTableProp->m_fLowValue;
				pProp->m_fHighValue = pSendTableProp->m_fHighValue;
				pProp->m_nBits = pSendTableProp->m_nBits;
			}
		}
	}

	return true;
}

// If the table's ID is -1, writes its info into the buffer and increments curID.
void DataTable_MaybeCreateReceiveTable( CUtlVector< SendTable * >& visited, SendTable *pTable, bool bNeedDecoder )
{
	// Already sent?
	if ( visited.Find( pTable ) != visited.InvalidIndex() )
		return;

	visited.AddToTail( pTable );

	DataTable_SetupReceiveTableFromSendTable( pTable, bNeedDecoder );
}


void DataTable_MaybeCreateReceiveTable_R( CUtlVector< SendTable * >& visited, SendTable *pTable )
{
	DataTable_MaybeCreateReceiveTable( visited, pTable, false );

	// Make sure we send child send tables..
	for(int i=0; i < pTable->m_nProps; i++)
	{
		SendProp *pProp = &pTable->m_pProps[i];

		if( pProp->m_Type == DPT_DataTable )
		{
			DataTable_MaybeCreateReceiveTable_R( visited, pProp->GetDataTable() );
		}
	}
}

void DataTable_CreateClientTablesFromServerTables()
{
	if ( !serverGameDLL )
	{
		Sys_Error( "DataTable_CreateClientTablesFromServerTables:  No serverGameDLL loaded!" );
	}

	ServerClass *pClasses = serverGameDLL->GetAllServerClasses();
	ServerClass *pCur;

	CUtlVector< SendTable * > visited;

	// First, we send all the leaf classes. These are the ones that will need decoders
	// on the client.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		DataTable_MaybeCreateReceiveTable( visited, pCur->m_pTable, true );
	}

	// Now, we send their base classes. These don't need decoders on the client
	// because we will never send these SendTables by themselves.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		DataTable_MaybeCreateReceiveTable_R( visited, pCur->m_pTable );
	}
}

void DataTable_CreateClientClassInfosFromServerClasses( CBaseClientState *pState )
{
	if ( !serverGameDLL )
	{
		Sys_Error( "DataTable_CreateClientClassInfosFromServerClasses:  No serverGameDLL loaded!" );
	}

	ServerClass *pClasses = serverGameDLL->GetAllServerClasses();

	// Count the number of classes.
	int nClasses = 0;
	for ( ServerClass *pCount=pClasses; pCount; pCount=pCount->m_pNext )
	{
		++nClasses;
	}

	// Remove old
	if ( pState->m_pServerClasses )
	{
		delete [] pState->m_pServerClasses;
	}

	Assert( nClasses > 0 );

	pState->m_nServerClasses = nClasses;
	pState->m_pServerClasses = new C_ServerClassInfo[ pState->m_nServerClasses ];
	if ( !pState->m_pServerClasses )
	{
		Host_EndGame(true, "CL_ParseClassInfo: can't allocate %d C_ServerClassInfos.\n", pState->m_nServerClasses);
		return;
	}

	// Now fill in the entries
	int curID = 0;
	for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext )
	{
		Assert( pClass->m_ClassID >= 0 && pClass->m_ClassID < nClasses );

		pClass->m_ClassID = curID++;

		pState->m_pServerClasses[ pClass->m_ClassID ].m_ClassName = COM_StringCopy( pClass->m_pNetworkName );
		pState->m_pServerClasses[ pClass->m_ClassID ].m_DatatableName = COM_StringCopy( pClass->m_pTable->GetName() );
	}
}

// If the table's ID is -1, writes its info into the buffer and increments curID.
static void DataTable_MaybeWriteSendTableBuffer( SendTable *pTable, bf_write *pBuf, bool bNeedDecoder )
{
	// Already sent?
	if ( pTable->GetWriteFlag() )
		return;

	pTable->SetWriteFlag( true );

	SendTable_WriteInfos( pTable, *pBuf, bNeedDecoder, false );
}

// Calls DataTable_MaybeWriteSendTable recursively.
void DataTable_MaybeWriteSendTableBuffer_R( SendTable *pTable, bf_write *pBuf )
{
	DataTable_MaybeWriteSendTableBuffer( pTable, pBuf, false );

	// Make sure we send child send tables..
	for(int i=0; i < pTable->m_nProps; i++)
	{
		SendProp *pProp = &pTable->m_pProps[i];

		if( pProp->m_Type == DPT_DataTable )
		{
			DataTable_MaybeWriteSendTableBuffer_R( pProp->GetDataTable(), pBuf );
		}
	}
}

#ifdef _DEBUG
// If the table's ID is -1, writes its info into the buffer and increments curID.
void DataTable_MaybeDumpSendTable( SendTable *pTable, bool bNeedDecoder )
{
	// Already sent?
	if ( pTable->GetWriteFlag() )
		return;

	pTable->SetWriteFlag( true );

	Msg( "\t%s has %d props:\n", pTable->GetName(), pTable->GetNumProps() );
	for ( int iProp=0; iProp < pTable->m_nProps; iProp++ )
	{
		const SendProp *pProp = &pTable->m_pProps[iProp];
		Msg( "\t\t%s\n", pProp->GetName() );
	}
}

// Calls DataTable_MaybeWriteSendTable recursively.
void DataTable_MaybeDumpSendTable_R( SendTable *pTable )
{
	DataTable_MaybeDumpSendTable( pTable, false );

	// Make sure we send child send tables..
	for(int i=0; i < pTable->m_nProps; i++)
	{
		SendProp *pProp = &pTable->m_pProps[i];

		if( pProp->m_Type == DPT_DataTable )
		{
			DataTable_MaybeDumpSendTable_R( pProp->GetDataTable() );
		}
	}
}
#endif

void DataTable_ClearWriteFlags_R( SendTable *pTable )
{
	pTable->SetWriteFlag( false );

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

		if( pProp->m_Type == DPT_DataTable )
		{
			DataTable_ClearWriteFlags_R( pProp->GetDataTable() );
		}
	}
}

void DataTable_ClearWriteFlags( ServerClass *pClasses )
{
	for ( ServerClass *pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		DataTable_ClearWriteFlags_R( pCur->m_pTable );
	}
}

void DataTable_WriteSendTablesBuffer( ServerClass *pClasses, bf_write *pBuf )
{
	ServerClass *pCur;

	DataTable_ClearWriteFlags( pClasses );

	// First, we send all the leaf classes. These are the ones that will need decoders
	// on the client.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		DataTable_MaybeWriteSendTableBuffer( pCur->m_pTable, pBuf, true );
	}

	// Now, we send their base classes. These don't need decoders on the client
	// because we will never send these SendTables by themselves.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		DataTable_MaybeWriteSendTableBuffer_R( pCur->m_pTable, pBuf );
	}

	// Signal no more send tables.
	SendTable_WriteInfos( NULL, *pBuf, false, true );
}

#ifdef _DEBUG
void DataTable_DumpSendTables( ServerClass *pClasses )
{
	ServerClass *pCur;

	DataTable_ClearWriteFlags( pClasses );

	// First, we send all the leaf classes. These are the ones that will need decoders
	// on the client.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		DataTable_MaybeDumpSendTable( pCur->m_pTable, true );
	}

	// Now, we send their base classes. These don't need decoders on the client
	// because we will never send these SendTables by themselves.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		DataTable_MaybeDumpSendTable_R( pCur->m_pTable );
	}
}
#endif

void DataTable_WriteClassInfosBuffer(ServerClass *pClasses, bf_write *pBuf )
{
	int count = 0;

	ServerClass *pClass = pClasses;
	
	// first count total number of classes in list
	while ( pClass != NULL )
	{
		pClass=pClass->m_pNext;
		count++;
	}

	// write number of classes
	pBuf->WriteShort( count );	

	pClass = pClasses; // go back to first class

	// write each class info
	while ( pClass != NULL )
	{
		pBuf->WriteShort( pClass->m_ClassID );
		pBuf->WriteString( pClass->m_pNetworkName );
		pBuf->WriteString( pClass->m_pTable->GetName() );
		pClass=pClass->m_pNext;
	}
}

#ifdef _DEBUG
void DataTable_DumpClassInfos(ServerClass *pClasses )
{
	int count = 0;

	ServerClass *pClass = pClasses;

	// first count total number of classes in list
	while ( pClass != NULL )
	{
		pClass=pClass->m_pNext;
		count++;
	}

	// write number of classes
	Msg( "%d ClassInfos\n", count );

	pClass = pClasses; // go back to first class

	// write each class info
	while ( pClass != NULL )
	{
		Msg( "\t%d: %s / %s\n", pClass->m_ClassID, pClass->m_pNetworkName, pClass->m_pTable->GetName() );
		pClass=pClass->m_pNext;
	}
}
#endif

bool DataTable_ParseClassInfosFromBuffer( CClientState *pState, bf_read *pBuf )
{
	if(pState->m_pServerClasses)
	{
		delete [] pState->m_pServerClasses;
	}

	pState->m_nServerClasses = pBuf->ReadShort();

	Assert( pState->m_nServerClasses );
	pState->m_pServerClasses = new C_ServerClassInfo[pState->m_nServerClasses];

	if ( !pState->m_pServerClasses )
	{
		Host_EndGame(true, "CL_ParseClassInfo: can't allocate %d C_ServerClassInfos.\n", pState->m_nServerClasses);
		return false;
	}
	
	for ( int i = 0; i < pState->m_nServerClasses; i++ )
	{
		int classID = pBuf->ReadShort();

		if( classID >= pState->m_nServerClasses )
		{
			Host_EndGame(true, "DataTable_ParseClassInfosFromBuffer: invalid class index (%d).\n", classID);
			return false;
		}

		pState->m_pServerClasses[classID].m_ClassName = pBuf->ReadAndAllocateString();
		pState->m_pServerClasses[classID].m_DatatableName = pBuf->ReadAndAllocateString();
	}

	return true;
}

bool DataTable_LoadDataTablesFromBuffer( bf_read *pBuf, int nDemoProtocol )
{
	// Okay, read them out of the buffer since they weren't recorded into the main network stream during recording
	CSVCMsg_SendTable_t msg;

	// Create all of the send tables locally
	// was DataTable_ParseClientTablesFromBuffer()
	while ( 1 )
	{
		int type = pBuf->ReadVarInt32();
		
		if( !msg.ReadFromBuffer( *pBuf ) )
		{
			Host_Error( "DataTable_ParseClientTablesFromBuffer ReadFromBuffer failed.\n" );
			return false;
		}

		int msgType = msg.GetType();
		if ( msgType != type )
		{
			Host_Error( "DataTable_ParseClientTablesFromBuffer ReadFromBuffer failed.\n" );
			return false;
		}

		if( msg.is_end() )
			break;

		if ( !RecvTable_RecvClassInfos( msg, nDemoProtocol ) )
		{
			Host_Error( "DataTable_ParseClientTablesFromBuffer failed.\n" );
			return false;
		}
	}
	
#ifndef DEDICATED
	// Now create all of the server classes locally, too
	return DataTable_ParseClassInfosFromBuffer( &GetBaseLocalClient(), pBuf );
#else
	return false;
#endif
}