//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
 
#include <stdarg.h>
#include "dt_send.h"
#include "dt.h"
#include "dt_recv.h"
#include "dt_encode.h"
#include "convar.h"
#include "commonmacros.h"
#include "tier1/strtools.h"
#include "tier0/dbg.h"
#include "dt_stack.h"

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


#define PROPINDEX_NUMBITS 12
#define MAX_TOTAL_SENDTABLE_PROPS	(1 << PROPINDEX_NUMBITS)


ConVar g_CV_DTWatchEnt( "dtwatchent", "-1", 0, "Watch this entities data table encoding." );
ConVar g_CV_DTWatchVar( "dtwatchvar", "", 0, "Watch the named variable." );
ConVar g_CV_DTWarning( "dtwarning", "0", 0, "Print data table warnings?" );
ConVar g_CV_DTWatchClass( "dtwatchclass", "", 0, "Watch all fields encoded with this table." );



// ----------------------------------------------------------------------------- //
//
// CBuildHierarchyStruct
//
// Used while building a CSendNode hierarchy.
//
// ----------------------------------------------------------------------------- //
class CBuildHierarchyStruct
{
public:
	const ExcludeProp	*m_pExcludeProps;
	int					m_nExcludeProps;

	const SendProp		*m_pDatatableProps[MAX_TOTAL_SENDTABLE_PROPS];
	int					m_nDatatableProps;
	
	const SendProp		*m_pProps[MAX_TOTAL_SENDTABLE_PROPS];
	unsigned char		m_PropProxyIndices[MAX_TOTAL_SENDTABLE_PROPS];
	int					m_nProps;

	unsigned char m_nPropProxies;
};


// ----------------------------------------------------------------------------- //
// CSendNode.
// ----------------------------------------------------------------------------- //

CSendNode::CSendNode()
{
	m_iDatatableProp = -1;
	m_pTable = NULL;
	
	m_iFirstRecursiveProp = m_nRecursiveProps = 0;

	m_DataTableProxyIndex = DATATABLE_PROXY_INDEX_INVALID; // set it to a questionable value.
}

CSendNode::~CSendNode()
{
	int c = GetNumChildren();
	for ( int i = c - 1 ; i >= 0 ; i-- )
	{
		delete GetChild( i );
	}
	m_Children.Purge();
}

// ----------------------------------------------------------------------------- //
// CSendTablePrecalc
// ----------------------------------------------------------------------------- //

bool PropOffsetLT( const unsigned short &a, const unsigned short &b )
{
	return a < b;
}

CSendTablePrecalc::CSendTablePrecalc() : 
	m_PropOffsetToIndexMap( 0, 0, PropOffsetLT )
{
	m_pDTITable = NULL;
	m_pSendTable = 0;
	m_nDataTableProxies = 0;
}


CSendTablePrecalc::~CSendTablePrecalc()
{
	if ( m_pSendTable )
		m_pSendTable->m_pPrecalc = 0;
}


const ExcludeProp* FindExcludeProp(
	char const *pTableName,
	char const *pPropName,
	const ExcludeProp *pExcludeProps, 
	int nExcludeProps)
{
	for ( int i=0; i < nExcludeProps; i++ )
	{
		if ( stricmp(pExcludeProps[i].m_pTableName, pTableName) == 0 && stricmp(pExcludeProps[i].m_pPropName, pPropName ) == 0 )
			return &pExcludeProps[i];
	}

	return NULL;
}


// Fill in a list of all the excluded props.
static bool SendTable_GetPropsExcluded( const SendTable *pTable, ExcludeProp *pExcludeProps, int &nExcludeProps, int nMaxExcludeProps )
{
	for(int i=0; i < pTable->m_nProps; i++)
	{
		SendProp *pProp = &pTable->m_pProps[i];

		if ( pProp->IsExcludeProp() )
		{
			char const *pName = pProp->GetExcludeDTName();

			ErrorIfNot( pName,
				("Found an exclude prop missing a name.")
			);
			
			ErrorIfNot( nExcludeProps < nMaxExcludeProps,
				("SendTable_GetPropsExcluded: Overflowed max exclude props with %s.", pName)
			);

			pExcludeProps[nExcludeProps].m_pTableName = pName;
			pExcludeProps[nExcludeProps].m_pPropName = pProp->GetName();
			nExcludeProps++;
		}
		else if ( pProp->GetDataTable() )
		{
			if( !SendTable_GetPropsExcluded( pProp->GetDataTable(), pExcludeProps, nExcludeProps, nMaxExcludeProps ) )
				return false;
		}
	}

	return true;
}


// Set the datatable proxy indices in all datatable SendProps.
static void SetDataTableProxyIndices_R( 
	CSendTablePrecalc *pMainTable, 
	CSendNode *pCurTable,
	CBuildHierarchyStruct *bhs )
{
	for ( int i=0; i < pCurTable->GetNumChildren(); i++ )
	{
		CSendNode *pNode = pCurTable->GetChild( i );
		const SendProp *pProp = bhs->m_pDatatableProps[pNode->m_iDatatableProp];

		if ( pProp->GetFlags() & SPROP_PROXY_ALWAYS_YES )
		{
			pNode->SetDataTableProxyIndex( DATATABLE_PROXY_INDEX_NOPROXY );
		}
		else
		{
			pNode->SetDataTableProxyIndex( pMainTable->GetNumDataTableProxies() );
			pMainTable->SetNumDataTableProxies( pMainTable->GetNumDataTableProxies() + 1 );
		}

		SetDataTableProxyIndices_R( pMainTable, pNode, bhs );
	}
}

// Set the datatable proxy indices in all datatable SendProps.
static void SetRecursiveProxyIndices_R( 
	SendTable *pBaseTable,
	CSendNode *pCurTable,
	int &iCurProxyIndex )
{
	if ( iCurProxyIndex >= CDatatableStack::MAX_PROXY_RESULTS )
		Error( "Too many proxies for datatable %s.", pBaseTable->GetName() );

	pCurTable->SetRecursiveProxyIndex( iCurProxyIndex );
	iCurProxyIndex++;
	
	for ( int i=0; i < pCurTable->GetNumChildren(); i++ )
	{
		CSendNode *pNode = pCurTable->GetChild( i );
		SetRecursiveProxyIndices_R( pBaseTable, pNode, iCurProxyIndex );
	}
}


void SendTable_BuildHierarchy( 
	CSendNode *pNode,
	const SendTable *pTable, 
	CBuildHierarchyStruct *bhs
	);


void SendTable_BuildHierarchy_IterateProps(
	CSendNode *pNode,
	const SendTable *pTable, 
	CBuildHierarchyStruct *bhs,
	const SendProp *pNonDatatableProps[MAX_TOTAL_SENDTABLE_PROPS],
	int &nNonDatatableProps )
{
	int i;
	for ( i=0; i < pTable->m_nProps; i++ )
	{
		const SendProp *pProp = &pTable->m_pProps[i];

		if ( pProp->IsExcludeProp() || 
			pProp->IsInsideArray() || 
			FindExcludeProp( pTable->GetName(), pProp->GetName(), bhs->m_pExcludeProps, bhs->m_nExcludeProps ) )
		{
			continue;
		}

		if ( pProp->GetType() == DPT_DataTable )
		{
			if ( pProp->GetFlags() & SPROP_COLLAPSIBLE )
			{
				// This is a base class.. no need to make a new CSendNode (and trigger a bunch of
				// unnecessary send proxy calls in the datatable stacks).
				SendTable_BuildHierarchy_IterateProps( 
					pNode,
					pProp->GetDataTable(), 
					bhs, 
					pNonDatatableProps, 
					nNonDatatableProps );
			}
			else
			{
				// Setup a child datatable reference.
				CSendNode *pChild = new CSendNode;

				// Setup a datatable prop for this node to reference (so the recursion
				// routines can get at the proxy).
				if ( bhs->m_nDatatableProps >= ARRAYSIZE( bhs->m_pDatatableProps ) )
					Error( "Overflowed datatable prop list in SendTable '%s'.", pTable->GetName() );
				
				bhs->m_pDatatableProps[bhs->m_nDatatableProps] = pProp;
				pChild->m_iDatatableProp = bhs->m_nDatatableProps;
				++bhs->m_nDatatableProps;

				pNode->m_Children.AddToTail( pChild );

				// Recurse into the new child datatable.
				SendTable_BuildHierarchy( pChild, pProp->GetDataTable(), bhs );
			}
		}
		else
		{
			if ( nNonDatatableProps >= MAX_TOTAL_SENDTABLE_PROPS )
				Error( "SendTable_BuildHierarchy: overflowed non-datatable props with '%s'.", pProp->GetName() );
			
			pNonDatatableProps[nNonDatatableProps] = pProp;
			++nNonDatatableProps;
		}
	}
}


void SendTable_BuildHierarchy( 
	CSendNode *pNode,
	const SendTable *pTable, 
	CBuildHierarchyStruct *bhs
	)
{
	pNode->m_pTable = pTable;
	pNode->m_iFirstRecursiveProp = bhs->m_nProps;
	
	Assert( bhs->m_nPropProxies < 255 );
	unsigned char curPropProxy = bhs->m_nPropProxies;
	++bhs->m_nPropProxies;

	const SendProp *pNonDatatableProps[MAX_TOTAL_SENDTABLE_PROPS];
	int nNonDatatableProps = 0;
	
	// First add all the child datatables.
	SendTable_BuildHierarchy_IterateProps(
		pNode,
		pTable,
		bhs,
		pNonDatatableProps,
		nNonDatatableProps );

	
	// Now add the properties.

	// Make sure there's room, then just copy the pointers from the loop above.
	ErrorIfNot( bhs->m_nProps + nNonDatatableProps < ARRAYSIZE( bhs->m_pProps ),
		("SendTable_BuildHierarchy: overflowed prop buffer.")
	);
	
	for ( int i=0; i < nNonDatatableProps; i++ )
	{
		bhs->m_pProps[bhs->m_nProps] = pNonDatatableProps[i];
		bhs->m_PropProxyIndices[bhs->m_nProps] = curPropProxy;
		++bhs->m_nProps;
	}

	pNode->m_nRecursiveProps = bhs->m_nProps - pNode->m_iFirstRecursiveProp;
}

void SendTable_SortByPriority(CBuildHierarchyStruct *bhs)
{
	int i, start = 0;

	while( true )
	{
		for ( i = start; i < bhs->m_nProps; i++ )
		{
			const SendProp *p = bhs->m_pProps[i];
			unsigned char c = bhs->m_PropProxyIndices[i];

			if ( p->GetFlags() & SPROP_CHANGES_OFTEN )
			{
				bhs->m_pProps[i] = bhs->m_pProps[start];
				bhs->m_PropProxyIndices[i] = bhs->m_PropProxyIndices[start];
				bhs->m_pProps[start] = p;
				bhs->m_PropProxyIndices[start] = c;
				start++;
				break;
			}
		}

		if ( i == bhs->m_nProps )
			return; 
	}
}


void CalcPathLengths_R( CSendNode *pNode, CUtlVector<int> &pathLengths, int curPathLength, int &totalPathLengths )
{
	pathLengths[pNode->GetRecursiveProxyIndex()] = curPathLength;
	totalPathLengths += curPathLength;
	
	for ( int i=0; i < pNode->GetNumChildren(); i++ )
	{
		CalcPathLengths_R( pNode->GetChild( i ), pathLengths, curPathLength+1, totalPathLengths );
	}
}


void FillPathEntries_R( CSendTablePrecalc *pPrecalc, CSendNode *pNode, CSendNode *pParent, int &iCurEntry )
{
	// Fill in this node's path.
	CSendTablePrecalc::CProxyPath &outProxyPath = pPrecalc->m_ProxyPaths[ pNode->GetRecursiveProxyIndex() ];
	outProxyPath.m_iFirstEntry = (unsigned short)iCurEntry;

	// Copy all the proxies leading to the parent.
	if ( pParent )
	{
		CSendTablePrecalc::CProxyPath &parentProxyPath = pPrecalc->m_ProxyPaths[pParent->GetRecursiveProxyIndex()];
		outProxyPath.m_nEntries = parentProxyPath.m_nEntries + 1;

		for ( int i=0; i < parentProxyPath.m_nEntries; i++ )
			pPrecalc->m_ProxyPathEntries[iCurEntry++] = pPrecalc->m_ProxyPathEntries[parentProxyPath.m_iFirstEntry+i];
		
		// Now add this node's own proxy.
		pPrecalc->m_ProxyPathEntries[iCurEntry].m_iProxy = pNode->GetRecursiveProxyIndex();
		pPrecalc->m_ProxyPathEntries[iCurEntry].m_iDatatableProp = pNode->m_iDatatableProp;
		++iCurEntry;
	}
	else
	{
		outProxyPath.m_nEntries = 0;
	}

	for ( int i=0; i < pNode->GetNumChildren(); i++ )
	{
		FillPathEntries_R( pPrecalc, pNode->GetChild( i ), pNode, iCurEntry );
	}
}


void SendTable_GenerateProxyPaths( CSendTablePrecalc *pPrecalc, int nProxyIndices )
{
	// Initialize the array.
	pPrecalc->m_ProxyPaths.SetSize( nProxyIndices );
	for ( int i=0; i < nProxyIndices; i++ )
		pPrecalc->m_ProxyPaths[i].m_iFirstEntry = pPrecalc->m_ProxyPaths[i].m_nEntries = 0xFFFF;
	
	// Figure out how long the path down the tree is to each node.
	int totalPathLengths = 0;
	CUtlVector<int> pathLengths;
	pathLengths.SetSize( nProxyIndices );
	memset( pathLengths.Base(), 0, sizeof( pathLengths[0] ) * nProxyIndices );
	CalcPathLengths_R( pPrecalc->GetRootNode(), pathLengths, 0, totalPathLengths );
	
	// 
	int iCurEntry = 0;
	pPrecalc->m_ProxyPathEntries.SetSize( totalPathLengths );
	FillPathEntries_R( pPrecalc, pPrecalc->GetRootNode(), NULL, iCurEntry );
}


bool CSendTablePrecalc::SetupFlatPropertyArray()
{
	SendTable *pTable = GetSendTable();

	// First go through and set SPROP_INSIDEARRAY when appropriate, and set array prop pointers.
	SetupArrayProps_R<SendTable, SendTable::PropType>( pTable );

	// Make a list of which properties are excluded.
	ExcludeProp excludeProps[MAX_EXCLUDE_PROPS];
	int nExcludeProps = 0;
	if( !SendTable_GetPropsExcluded( pTable, excludeProps, nExcludeProps, MAX_EXCLUDE_PROPS ) )
		return false;

	// Now build the hierarchy.
	CBuildHierarchyStruct bhs;
	bhs.m_pExcludeProps = excludeProps;
	bhs.m_nExcludeProps = nExcludeProps;
	bhs.m_nProps = bhs.m_nDatatableProps = 0;
	bhs.m_nPropProxies = 0;
	SendTable_BuildHierarchy( GetRootNode(), pTable, &bhs );

	SendTable_SortByPriority( &bhs );
	
	// Copy the SendProp pointers into the precalc.	
	MEM_ALLOC_CREDIT();
	m_Props.CopyArray( bhs.m_pProps, bhs.m_nProps );
	m_DatatableProps.CopyArray( bhs.m_pDatatableProps, bhs.m_nDatatableProps );
	m_PropProxyIndices.CopyArray( bhs.m_PropProxyIndices, bhs.m_nProps );

	// Assign the datatable proxy indices.
	SetNumDataTableProxies( 0 );
	SetDataTableProxyIndices_R( this, GetRootNode(), &bhs );
	
	int nProxyIndices = 0;
	SetRecursiveProxyIndices_R( pTable, GetRootNode(), nProxyIndices );

	SendTable_GenerateProxyPaths( this, nProxyIndices );
	return true;
}


// ---------------------------------------------------------------------------------------- //
// Helpers.
// ---------------------------------------------------------------------------------------- //

// Compares two arrays of bits.
// Returns true if they are equal.
bool AreBitArraysEqual(
	void const *pvBits1,
	void const *pvBits2,
	int nBits ) 
{
	unsigned int const *pBits1 = (unsigned int const *)pvBits1;
	unsigned int const *pBits2 = (unsigned int const *)pvBits2;

	// Compare words.
	int nWords = nBits >> 5;
	for ( int i = 0 ; i < nWords; ++i )
	{
		if ( pBits1[i] != pBits2[i] )
			return false;
	}

	if ( nBits & 31 )
	{
		// Compare remaining bits.
		unsigned int mask = (1 << (nBits & 31)) - 1;
		return ((pBits1[nWords] ^ pBits2[nWords]) & mask) == 0;
	}

	return true;
}


// Does a fast memcmp-based test to determine if the two bit arrays are different.
// Returns true if they are equal.
bool CompareBitArrays(
	void const *pPacked1,
	void const *pPacked2,
	int nBits1,
	int nBits2
	)
{
	if( nBits1 >= 0 && nBits1 == nBits2 )
	{
		if ( pPacked1 == pPacked2 )
		{
			return true;
		}
		else
		{
			return AreBitArraysEqual( pPacked1, pPacked2, nBits1 );
		}
	}
	else
		return false;
}

// Looks at the DTWatchEnt and DTWatchProp console variables and returns true
// if the user wants to watch this property.
bool ShouldWatchThisProp( const SendTable *pTable, int objectID, const char *pPropName )
{
	if(g_CV_DTWatchEnt.GetInt() != -1 &&
		g_CV_DTWatchEnt.GetInt() == objectID)
	{
		const char *pStr = g_CV_DTWatchVar.GetString();
		if ( pStr && pStr[0] != 0 )
		{
			return stricmp( pStr, pPropName ) == 0;
		}
		else
		{
			return true;
		}
	}

	if ( g_CV_DTWatchClass.GetString()[ 0 ] && Q_stristr( pTable->GetName(), g_CV_DTWatchClass.GetString() ) )
		return true;

	return false;
}

bool Sendprop_UsingDebugWatch()
{
	if ( g_CV_DTWatchEnt.GetInt() != -1 )
		return true;

	if ( g_CV_DTWatchClass.GetString()[ 0 ] )
		return true; 

	return false;
}


// Prints a datatable warning into the console.
void DataTable_Warning( const char *pInMessage, ... )
{
	char msg[4096];
	va_list marker;
	
#if 0
	#if !defined(_DEBUG)
		if(!g_CV_DTWarning.GetInt())
			return;
	#endif
#endif

	va_start(marker, pInMessage);
	Q_vsnprintf( msg, sizeof( msg ), pInMessage, marker);
	va_end(marker);

	Warning( "DataTable warning: %s", msg );
}