/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: CHXStatisticTracker.cpp,v 1.7.6.4 2004/07/09 01:49:47 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "CHXStatisticTracker.h"
#include "HXClientConstants.h"
#include "CHXFlatArray.h"
#include "CHXClientDebug.h"

#include "enter_hx_headers.h"
#include "ihxpckts.h"
#include "hxcore.h"
#include "hxmon.h"   // IHXRegistry
#include "hxcomm.h"  // IHXRegistryID

#include "hxsmartptr.h"
HX_SMART_POINTER_INLINE( SPIHXRegistry, IHXRegistry );
HX_SMART_POINTER_INLINE( SPIHXRegistryID, IHXRegistryID );
HX_SMART_POINTER_INLINE( SPIHXBuffer, IHXBuffer );
HX_SMART_POINTER_INLINE( SPIHXValues, IHXValues );
#include "exit_hx_headers.h"

#include "hlxclib/string.h"
#include "hlxclib/stdlib.h"


static char const kRegistryPropertySeparator = '.';
static char const kStatisticKeyWildcard = '#';
static UINT32 const kInvalidRegistryPropID = 0;

#ifdef LOG_STATISTICS_DEBUG_INFO
static void
ExamineProperty( IHXRegistry* pIRegistry, const UINT32 ulId, const char* pMsg )
{
	SPIHXBuffer spPropNameBuffer;
	pIRegistry->GetPropName( ulId, *spPropNameBuffer.AsInOutParam() );
#ifdef _MAC_MACHO
	CFStringRef debugStr = CFStringCreateWithFormat( kCFAllocatorDefault, nil, CFSTR( "%s: ID = %d, Name = %s" ), pMsg, ulId, ( const char* ) spPropNameBuffer->GetBuffer() );
	CFShow( debugStr );
	CFRelease( debugStr );
#endif
}
#endif

class CHXStatisticProcessor
{
public:
	CHXStatisticProcessor( void ) {}
	virtual ~CHXStatisticProcessor( void ) {}
	virtual bool operator() ( int valueType, const unsigned char* pValue ) = 0;
};

static bool
ProcessStatistic( IHXRegistry* pIRegistry, UINT32 statisticPropID, CHXStatisticProcessor& Processor )
{
	CHXASSERT( pIRegistry );
	
	if ( statisticPropID != kInvalidRegistryPropID )
	{
		HXPropType propType = pIRegistry->GetTypeById( statisticPropID );
		switch ( propType )
		{
			case PT_INTEGER:
			{
				INT32 intProperty;
				if ( SUCCEEDED( pIRegistry->GetIntById( statisticPropID, intProperty ) ) )
				{
					return Processor( kValueType32BitSignedInt, ( const unsigned char* ) &intProperty );
				}
			}
			break;
			
			case PT_STRING:
			{
				SPIHXBuffer spPropStringBuffer;
				if ( SUCCEEDED( pIRegistry->GetStrById( statisticPropID, *spPropStringBuffer.AsInOutParam() ) ) && spPropStringBuffer.IsValid() )
				{
					return Processor( kValueTypeString, spPropStringBuffer->GetBuffer() );
				}
			}
			break;
			
			default:
			break;
		}
	}
	return false;
}

static const char*
GetPropSubString( const char* pStatisticKey, int propIndex, UINT32* pLength, bool* pIsLeafKey )
{
	if ( !pStatisticKey ) return NULL;

	const char* pLocation = pStatisticKey;
	for ( int index = 0; index < propIndex; ++index )
	{
		pLocation = strchr( pLocation, kRegistryPropertySeparator );
		if ( !pLocation )
		{
			return NULL;
		}
		++pLocation;
	}
	const char* pNextLocation = strchr( pLocation, kRegistryPropertySeparator );
	if ( pNextLocation )
	{
		*pLength = pNextLocation - pLocation;
		*pIsLeafKey = false;
	}
	else
	{
		*pLength = strlen( pLocation );
		*pIsLeafKey = true;
	}
	return pLocation;
}

static bool
ShouldObserveProperty( const char* pStatisticKey, const char* pPropName, bool shouldObserveJustMe )
{
	int propIndex = 0;
	bool isLeafNode, isLeafKey = ( !pStatisticKey || !*pStatisticKey );
	UINT32 nodeLength, keyLength;
	const char* pNodeSubString;
	while ( NULL != ( pNodeSubString = GetPropSubString( pPropName, propIndex, &nodeLength, &isLeafNode ) ) )
	{
		const char* pKeySubString = GetPropSubString( pStatisticKey, propIndex, &keyLength, &isLeafKey );
		if ( !pKeySubString ) return false;
		
		// Wildcard matches all subsequent characters in node prop name.
		if ( pKeySubString[ keyLength - 1 ] == kStatisticKeyWildcard )
		{
			if ( nodeLength < keyLength ) return false;
			if ( 0 != strncasecmp( pKeySubString, pNodeSubString, ( keyLength - 1 ) ) ) return false;
		}
		else
		{
			if ( nodeLength != keyLength ) return false;
			if ( 0 != strncasecmp( pKeySubString, pNodeSubString, keyLength ) ) return false;
		}
		++propIndex;
	}
	return shouldObserveJustMe ? isLeafKey : true;
}

// ***** CHXStatisticTrackerNode *****
CHXStatisticTrackerNode::CHXStatisticTrackerNode( IHXRegistry* pIRegistry, CHXStatisticTracker* pParentTracker, UINT32 propID, const char* pFullPropName )
	: m_pRegistry( pIRegistry )
	, m_pPropWatch( NULL )
	, m_PropName( NULL )
	, m_pParentTracker( pParentTracker )
	, m_PropID( propID )
{
	CHXASSERT( m_pRegistry );
	m_pRegistry->AddRef();
	if ( m_pParentTracker )
	{
		CHXASSERT( pFullPropName );
		
		// Skip the Statistics.Player#. portion.
		const char* pNodePropName = pFullPropName;
		int separatorsLeftToSkip = 2;
		while ( *pNodePropName )
		{
			if ( *pNodePropName == kRegistryPropertySeparator )
			{
				--separatorsLeftToSkip;
			}
			pNodePropName++;
			if ( separatorsLeftToSkip <= 0 )
			{
				break;
			}
		}
		m_PropName = ( char* ) malloc( strlen( pNodePropName ) + 1 );
		strcpy( m_PropName, pNodePropName );
	}
}

CHXStatisticTrackerNode::~CHXStatisticTrackerNode( void )
{
	if ( m_PropName )
	{
		free( m_PropName );
		m_PropName = NULL;
	}
	if ( m_pPropWatch )
	{
		m_pPropWatch->Release();
		m_pPropWatch = NULL;
	}
	m_pRegistry->Release();
}

BEGIN_INTERFACE_LIST_NOCREATE( CHXStatisticTrackerNode )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXPropWatchResponse )
END_INTERFACE_LIST

bool
CHXStatisticTrackerNode::IsWatchingMe( void ) const
{
	return( NULL != m_pPropWatch );
}

bool
CHXStatisticTrackerNode::StartWatchingMe( void )
{
	if (  m_pPropWatch ) return true;

	CHXASSERT( m_pRegistry );

	HX_RESULT result = m_pRegistry->CreatePropWatch( m_pPropWatch );
	if ( SUCCEEDED( result ) )
	{
		result = m_pPropWatch->Init( this );
		if ( SUCCEEDED( result ) )
		{
			m_pPropWatch->SetWatchById( m_PropID );
		}
		else
		{
			m_pPropWatch->Release();
			m_pPropWatch = NULL;
		}
	}
	return( 0 != SUCCEEDED( result ) );
}

void
CHXStatisticTrackerNode::StopWatchingMe( void )
{
	if ( !m_pPropWatch ) return;

	m_pPropWatch->ClearWatchById( m_PropID );
	m_pPropWatch->Release();
	m_pPropWatch = NULL;
}

class CopyStatisticProcessor : public CHXStatisticProcessor
{
private:
	unsigned char* m_pValueBuffer;
	UINT32 m_BufferLength;
	int* m_pValueType;
	UINT32* m_pUsedBufferLength;
public:
	CopyStatisticProcessor( unsigned char* pValueBuffer, UINT32 bufferLength, int* pValueType, UINT32* pUsedBufferLength )
		: m_pValueBuffer( pValueBuffer )
		, m_BufferLength( bufferLength )
		, m_pValueType( pValueType )
		, m_pUsedBufferLength( pUsedBufferLength )
	{}
	virtual ~CopyStatisticProcessor( void ) {}
	virtual bool operator() ( int valueType, const unsigned char* pValue )
	{
		*m_pValueType = valueType;
		UINT32 desiredBufferLength = 0;
		switch ( valueType )
		{
			case kValueType32BitSignedInt:
				desiredBufferLength = sizeof( INT32 );
			break;
			case kValueTypeString:
				desiredBufferLength = strlen( ( const char* ) pValue ) + 1;
			break;
		}
		if ( !m_pValueBuffer || ( m_BufferLength == 0 ) )
		{
			if ( m_pUsedBufferLength )
			{
				*m_pUsedBufferLength = desiredBufferLength;
			}
			return false;
		}
		bool outCopied = false;
		UINT32 usedBufferLength = 0;
		if ( desiredBufferLength <= m_BufferLength )
		{
			usedBufferLength = desiredBufferLength;
			memcpy( m_pValueBuffer, pValue, usedBufferLength );
			outCopied = true;
		}
		else
		{
			switch ( valueType )
			{
				case kValueType32BitSignedInt:
				break;
				
				case kValueTypeString:
				{
					usedBufferLength = m_BufferLength;
					memcpy( m_pValueBuffer, pValue, usedBufferLength );
					m_pValueBuffer[ usedBufferLength - 1 ] = '\0';
					outCopied = true;
				}
				break;
			}
		}
		if ( m_pUsedBufferLength )
		{
			*m_pUsedBufferLength = usedBufferLength;
		}
		return outCopied;
	}
};

bool
CHXStatisticTrackerNode::GetStatisticsFor( const char* pStatisticKey, unsigned char* pValueBuffer, UINT32 bufferLength, int* pValueType, UINT32* pUsedBufferLength )
{
	bool outGotStatistic = false;
	
	CHXStatisticTrackerNode* pPlayerTracker = this;
	CHXStatisticTracker* pParentTracker = GetParentTracker();
	while ( pParentTracker )
	{
		pPlayerTracker = pParentTracker;
		pParentTracker = pParentTracker->GetParentTracker();
	}
	UINT32 playerId = pPlayerTracker->GetPropID();
	
	SPIHXBuffer spPlayerNameBuffer;
	m_pRegistry->GetPropName( playerId, *spPlayerNameBuffer.AsInOutParam() );
	if ( spPlayerNameBuffer.IsValid() && ( spPlayerNameBuffer->GetSize() > 0 ) )
	{
		UINT32 fullPropNameSize = spPlayerNameBuffer->GetSize() + 1 + strlen( pStatisticKey ) + 1;
		char* pFullPropName = new char[ fullPropNameSize ];
		if ( pFullPropName )
		{
			sprintf( pFullPropName, "%s%c%s", spPlayerNameBuffer->GetBuffer(), kRegistryPropertySeparator, pStatisticKey );
			UINT32 propID = m_pRegistry->GetId( pFullPropName );
			if ( propID != kInvalidRegistryPropID )
			{
				CopyStatisticProcessor Processor( pValueBuffer, bufferLength, pValueType, pUsedBufferLength );
				outGotStatistic = ProcessStatistic( m_pRegistry, propID, Processor );
			}
			delete [] pFullPropName;
		}
	}
	return outGotStatistic;
}

#ifdef LOG_STATISTICS_DEBUG_INFO
void
CHXStatisticTrackerNode::ExamineProperty( const UINT32 ulId, const char* pMsg )
{
	::ExamineProperty( m_pRegistry, ulId, pMsg );
}

static void
ExamineStatisticsByName( IHXRegistry* pIRegistry, const char* pFullEntryName )
{
	SPIHXValues spRegistryValues;
	if ( SUCCEEDED( pIRegistry->GetPropListByName( pFullEntryName, *spRegistryValues.AsInOutParam() ) ) )
	{
		const char* pPropertyName = NULL;
		ULONG32 propertyID;
		if ( SUCCEEDED( spRegistryValues->GetFirstPropertyULONG32( pPropertyName, propertyID ) ) )
		{
			do
			{
				INT32 registryIntValue;
				SPIHXBuffer spRegistryStrValue;
				if ( SUCCEEDED( pIRegistry->GetStrByName( pPropertyName, *spRegistryStrValue.AsInOutParam() ) ) )
				{
					const char* pRegistryStrValue = ( const char* ) spRegistryStrValue->GetBuffer();
#ifdef _MAC_MACHO
					CFStringRef debugStr = CFStringCreateWithFormat( kCFAllocatorDefault, nil, CFSTR( "String: %s = %s" ), pPropertyName, pRegistryStrValue );
					CFShow( debugStr );
					CFRelease( debugStr );
#endif
				}
				else if ( SUCCEEDED( pIRegistry->GetIntByName( pPropertyName, registryIntValue ) ) )
				{
#ifdef _MAC_MACHO
					CFStringRef debugStr = CFStringCreateWithFormat( kCFAllocatorDefault, nil, CFSTR( "Int: %s = %d" ), pPropertyName, registryIntValue );
					CFShow( debugStr );
					CFRelease( debugStr );
#endif
				}
				else
				{
					ExamineStatisticsByName( pIRegistry, pPropertyName );
				}
			}
			while ( SUCCEEDED( spRegistryValues->GetNextPropertyULONG32( pPropertyName, propertyID ) ) );
		}
	}
}

void
CHXStatisticTrackerNode::ExamineStatistics( void )
{
	CHXASSERT( m_pRegistry );

	SPIHXBuffer spFullPropName;
	m_pRegistry->GetPropName( m_PropID, *spFullPropName.AsInOutParam() );
	if ( spFullPropName.IsValid() )
	{
		ExamineStatisticsByName( m_pRegistry, ( const char* ) spFullPropName->GetBuffer() );
		
		CFShow( CFSTR( "*************** End of set ***************" ) );
	}
}
#endif

// ***** CHXStatisticTracker *****
CHXStatisticTracker*
CHXStatisticTracker::CreatePlayerStatisticTracker( IHXPlayer* pIHXPlayer )
{
	SPIHXRegistry spRegistry = pIHXPlayer;
	CHXASSERT( spRegistry.IsValid() ); // Client player must support IHXRegistry.
	SPIHXRegistryID spRegistryID = pIHXPlayer;
	CHXASSERT( spRegistryID.IsValid() ); // Client player must support IHXRegistryID.
	
	UINT32 playerId;
	SPIHXBuffer spPlayerNameBuffer;
	spRegistryID->GetID( playerId );
	spRegistry->GetPropName( playerId, *spPlayerNameBuffer.AsInOutParam() );
	if ( spPlayerNameBuffer.IsValid() && ( spPlayerNameBuffer->GetSize() > 0 ) )
	{
		CHXStatisticTracker* pPlayerStatisticTracker = new CHXStatisticTracker( spRegistry.Ptr(), NULL, playerId, ( const char* ) spPlayerNameBuffer->GetBuffer() );
		if ( pPlayerStatisticTracker )
		{
			pPlayerStatisticTracker->AddRef();
			if ( pPlayerStatisticTracker->StartWatchingMe() )
			{
				return pPlayerStatisticTracker;
			}
			pPlayerStatisticTracker->Release();
			pPlayerStatisticTracker = NULL;
		}
	}
	return NULL;
}

void
CHXStatisticTracker::DestroyPlayerStatisticTracker( CHXStatisticTracker* pPlayerStatisticTracker )
{
	if ( pPlayerStatisticTracker )
	{
		pPlayerStatisticTracker->StopWatchingMe();
		pPlayerStatisticTracker->Release();
		pPlayerStatisticTracker = NULL;
	}
}

CHXStatisticTracker::CHXStatisticTracker( IHXRegistry* pIRegistry, CHXStatisticTracker* pParentTracker, UINT32 propID, const char* pFullPropName )
	: CHXStatisticTrackerNode( pIRegistry, pParentTracker, propID, pFullPropName )
	, m_pStatisticTrackerNodes( NULL )
	, m_pObservers( NULL )
{
}

CHXStatisticTracker::~CHXStatisticTracker( void )
{
	if ( m_pStatisticTrackerNodes )
	{
		CHXStatisticTrackerNode* pStatisticTrackerNode = NULL;
		while ( m_pStatisticTrackerNodes->Pop( &pStatisticTrackerNode ) )
		{
			pStatisticTrackerNode->Release();
			pStatisticTrackerNode = NULL;
		}
		delete m_pStatisticTrackerNodes;
		m_pStatisticTrackerNodes = NULL;
	}
	if ( m_pObservers )
	{
		UINT32 numOfObservers = m_pObservers->GetCount();
		for ( UINT32 index = 0; index < numOfObservers; ++index )
		{
			SHXTrackerObserverData observerData;
			m_pObservers->GetAt( index, &observerData );
			free( observerData.m_StatisticKey );
		}
		delete m_pObservers;
		m_pObservers = NULL;
	}
}

BEGIN_INTERFACE_LIST_NOCREATE( CHXStatisticTracker )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXInterruptSafe )
END_INTERFACE_LIST_BASE( CHXStatisticTrackerNode )

bool
CHXStatisticTracker::StartWatchingMe( void )
{
	if ( IsWatchingMe() ) return true;
	
	bool isWatchingMe = CHXStatisticTrackerNode::StartWatchingMe();
	if ( isWatchingMe )
	{
		// This property may already have children. If so, we won't get an AddedProp() call for them.
		IHXRegistry* pRegistry = GetRegistry();
		CHXASSERT( pRegistry );
		
		SPIHXValues spRegistryValues;
		UINT32 parentPropID = GetPropID();
		if ( SUCCEEDED( pRegistry->GetPropListById( parentPropID, *spRegistryValues.AsInOutParam() ) ) )
		{
			const char* pChildPropertyName = NULL;
			ULONG32 childPropertyID;
			if ( SUCCEEDED( spRegistryValues->GetFirstPropertyULONG32( pChildPropertyName, childPropertyID ) ) )
			{
				do
				{
					( void ) AddedProp( childPropertyID, pRegistry->GetTypeById( childPropertyID ), parentPropID );
				}
				while ( SUCCEEDED( spRegistryValues->GetNextPropertyULONG32( pChildPropertyName, childPropertyID ) ) );
			}
		}
	}
	return isWatchingMe;
}

CHXStatisticTrackerNode*
CHXStatisticTracker::FindStatisticTrackerNode( const UINT32 ulId, UINT32* pOutIndex ) const
{
	CHXASSERT( pOutIndex );
	if ( !m_pStatisticTrackerNodes ) return NULL;
	
	UINT32 numOfNodes = m_pStatisticTrackerNodes->GetCount();
	for ( UINT32 index = 0; index < numOfNodes; ++index )
	{
		CHXStatisticTrackerNode* pStatisticTrackerNode = NULL;
		m_pStatisticTrackerNodes->GetAt( index, &pStatisticTrackerNode );
		if ( ulId == pStatisticTrackerNode->GetPropID() )
		{
			*pOutIndex = index;
			return pStatisticTrackerNode;
		}
	}
	return NULL;
}

STDMETHODIMP_( HXBOOL )
CHXStatisticTracker::IsInterruptSafe( void )
{
	// XXXSEH: This should be removed. It's present to correct a problem in Helix.
	// If a composite node is added from another thread, the AddedProp() call will be cued unless we return 1 here.
	// If it's cued, then subsequent child properties added to it won't result in AddedProp()'s because the cued
	// AddedProp call is required to set up the watcher for them.
	return 1;
}

STDMETHODIMP
CHXStatisticTracker::AddedProp( const UINT32 ulId, const HXPropType propType, const UINT32 ulParentID )
{
	IHXRegistry* pRegistry = GetRegistry();
	CHXASSERT( pRegistry );

	HXPropType actualPropType = pRegistry->GetTypeById( ulId );

#ifdef LOG_STATISTICS_DEBUG_INFO
	char msg[ 32 ];
	sprintf( msg, "Added property type %d", actualPropType );
	ExamineProperty( ulId, msg );
#endif

	// It's not uncommon for the CORE to create "Duplicate" properties.
	UINT32 index;
	if ( NULL == FindStatisticTrackerNode( ulId, &index ) )
	{
		if ( !m_pStatisticTrackerNodes )
		{
			m_pStatisticTrackerNodes = new CHXFlatArray(sizeof(CHXStatisticTrackerNode*));
		}
		if (  m_pStatisticTrackerNodes )
		{
			SPIHXBuffer spFullPropName;
			pRegistry->GetPropName( ulId, *spFullPropName.AsInOutParam() );
			if ( spFullPropName.IsValid() && ( spFullPropName->GetSize() > 0 ) )
			{
				CHXStatisticTrackerNode* pStatisticTrackerNode = NULL;
				switch ( actualPropType )
				{
					case PT_COMPOSITE:
					{
						pStatisticTrackerNode = new CHXStatisticTracker( pRegistry, this, ulId, ( const char* ) spFullPropName->GetBuffer() );
						if ( pStatisticTrackerNode )
						{
							pStatisticTrackerNode->AddRef();
							if ( !pStatisticTrackerNode->StartWatchingMe() )
							{
								pStatisticTrackerNode->Release();
								pStatisticTrackerNode = NULL;
							}
						}
					}
					break;
					default:
					{
						pStatisticTrackerNode = new CHXStatisticTrackerEntry( pRegistry, this, ulId, ( const char* ) spFullPropName->GetBuffer() );
						if ( pStatisticTrackerNode )
						{
							pStatisticTrackerNode->AddRef();
						}
					}
					break;
				}
				if ( pStatisticTrackerNode )
				{
					m_pStatisticTrackerNodes->Push( &pStatisticTrackerNode );
					if ( m_pObservers )
					{
						UINT32 numOfObservers = m_pObservers->GetCount();
						for ( UINT32 index = 0; index < numOfObservers; ++index )
						{
							SHXTrackerObserverData observerData;
							m_pObservers->GetAt( index, &observerData );
							pStatisticTrackerNode->AddObserver( observerData.m_StatisticKey, observerData.m_pStatisticsCallbacks, observerData.m_ObserverInfo );
						}
					}
				}
			}
		}
	}
	return HXR_OK; // XXXSEH: What's the return value mean?
}

STDMETHODIMP
CHXStatisticTracker::ModifiedProp( const UINT32 ulId, const HXPropType propType, const UINT32 ulParentID )
{
	CHXASSERT( !"Why is ModifiedProp being called on a parent property?" );
	
	return HXR_OK; // XXXSEH: What's the return value mean?
}

STDMETHODIMP
CHXStatisticTracker::DeletedProp( const UINT32 ulId, const UINT32 ulParentID )
{
	if ( ulId == GetPropID() )
	{
#ifdef LOG_STATISTICS_DEBUG_INFO
		ExamineProperty( ulId, "Deleted parent property" );
#endif
		if ( m_pStatisticTrackerNodes )
		{
			CHXStatisticTrackerNode* pStatisticTrackerNode = NULL;
			while ( m_pStatisticTrackerNodes->Pop( &pStatisticTrackerNode ) )
			{
#ifdef LOG_STATISTICS_DEBUG_INFO
				pStatisticTrackerNode->ExamineProperty( pStatisticTrackerNode->GetPropID(), "Delete child property" );
#endif
				pStatisticTrackerNode->DeletedProp( pStatisticTrackerNode->GetPropID(), GetPropID() );
				pStatisticTrackerNode->Release();
				pStatisticTrackerNode = NULL;
			}
			delete m_pStatisticTrackerNodes;
			m_pStatisticTrackerNodes = NULL;
		}
		if ( m_pObservers )
		{
			UINT32 numOfObservers = m_pObservers->GetCount();
			for ( UINT32 index = 0; index < numOfObservers; ++index )
			{
				SHXTrackerObserverData observerData;
				m_pObservers->GetAt( index, &observerData );
				HXOnDeletedStatisticProcPtr OnDeletedStatistic = observerData.m_pStatisticsCallbacks->OnDeletedStatistic;
				if ( OnDeletedStatistic && ShouldObserveProperty( observerData.m_StatisticKey, GetPropName(), true ) )
				{
					OnDeletedStatistic( GetPropName(), observerData.m_ObserverInfo );
				}
			}
		}
		StopWatchingMe();
	}
	else
	{
		CHXASSERT( m_pStatisticTrackerNodes );
		
#ifdef LOG_STATISTICS_DEBUG_INFO
		ExamineProperty( ulId, "Deleted property from parent" );
#endif
		UINT32 index;
		CHXStatisticTrackerNode* pStatisticTrackerNode = FindStatisticTrackerNode( ulId, &index );
		if ( pStatisticTrackerNode )
		{
			pStatisticTrackerNode->Release();
			m_pStatisticTrackerNodes->Remove( index );
		}
	}
	return HXR_OK; // XXXSEH: What's the return value mean?
}

static bool
AreTrackerObserverDataElementsEqual( const void* item1Ptr, const void* item2Ptr )
{
	const SHXTrackerObserverData* data1 = ( const SHXTrackerObserverData* ) item1Ptr;
	const SHXTrackerObserverData* data2 = ( const SHXTrackerObserverData* ) item2Ptr;
	return( ( 0 == strcmp( data1->m_StatisticKey, data2->m_StatisticKey	   ) ) &&
			( data1->m_pStatisticsCallbacks == data2->m_pStatisticsCallbacks ) &&
			( data1->m_ObserverInfo			== data2->m_ObserverInfo		 ) );
}

bool
CHXStatisticTracker::AddObserver( const char* pStatisticKey, const HXStatisticsCallbacks* pStatisticsCallbacks, void* observerInfo )
{
	CHXASSERT( pStatisticsCallbacks );
	CHXASSERT( pStatisticKey && *pStatisticKey );
	
	if ( ShouldObserveProperty( pStatisticKey, GetPropName(), false ) )
	{
		if ( !m_pObservers )
		{
			m_pObservers = new CHXFlatArray(sizeof(SHXTrackerObserverData));
		}
		if (  m_pObservers )
		{
			SHXTrackerObserverData observerData = { ( char* ) pStatisticKey, pStatisticsCallbacks, observerInfo };
			if ( m_pObservers->HasRecord( &observerData, AreTrackerObserverDataElementsEqual ) ) return true;

			observerData.m_StatisticKey = ( char* ) malloc( strlen( pStatisticKey ) + 1 );
			strcpy( observerData.m_StatisticKey, pStatisticKey );
			m_pObservers->Push( &observerData );
			
			HXOnAddedStatisticProcPtr OnAddedStatistic = pStatisticsCallbacks->OnAddedStatistic;
			if ( OnAddedStatistic && ShouldObserveProperty( pStatisticKey, GetPropName(), true ) )
			{
				OnAddedStatistic( GetPropName(), kValueTypeInternalUse, NULL, observerInfo );
			}
			if ( m_pStatisticTrackerNodes )
			{
				UINT32 numOfNodes = m_pStatisticTrackerNodes->GetCount();
				for ( UINT32 index = 0; index < numOfNodes; ++index )
				{
					CHXStatisticTrackerNode* pStatisticTrackerNode = NULL;
					m_pStatisticTrackerNodes->GetAt( index, &pStatisticTrackerNode );
					pStatisticTrackerNode->AddObserver( pStatisticKey, pStatisticsCallbacks, observerInfo );
				}
			}
			return true;
		}
	}
	return false;
}

void
CHXStatisticTracker::RemoveObserver( const char* pStatisticKey, const HXStatisticsCallbacks* pStatisticsCallbacks, void* observerInfo )
{
	if ( m_pObservers )
	{
		UINT32 recordNumToRemove;
		SHXTrackerObserverData observerData = { ( char* ) pStatisticKey, pStatisticsCallbacks, observerInfo };
		if ( m_pObservers->FindRecord( &observerData, AreTrackerObserverDataElementsEqual, 0, &recordNumToRemove, &observerData ) )
		{
			free( observerData.m_StatisticKey );
			m_pObservers->Remove( recordNumToRemove );
			if ( m_pStatisticTrackerNodes )
			{
				UINT32 numOfNodes = m_pStatisticTrackerNodes->GetCount();
				for ( UINT32 index = 0; index < numOfNodes; ++index )
				{
					CHXStatisticTrackerNode* pStatisticTrackerNode = NULL;
					m_pStatisticTrackerNodes->GetAt( index, &pStatisticTrackerNode );
					pStatisticTrackerNode->RemoveObserver( pStatisticKey, pStatisticsCallbacks, observerInfo );
				}
			}
		}
	}
}

// ***** CHXStatisticTrackerEntry *****
CHXStatisticTrackerEntry::CHXStatisticTrackerEntry( IHXRegistry* pIRegistry, CHXStatisticTracker* pParentTracker, UINT32 propID, const char* pFullPropName )
	: CHXStatisticTrackerNode( pIRegistry, pParentTracker, propID, pFullPropName )
	, m_pEntryObservers( NULL )
{
}

CHXStatisticTrackerEntry::~CHXStatisticTrackerEntry( void )
{
	delete m_pEntryObservers;
	m_pEntryObservers = NULL;
}

BEGIN_INTERFACE_LIST_NOCREATE( CHXStatisticTrackerEntry )
END_INTERFACE_LIST_BASE( CHXStatisticTrackerNode )

class StatisticCallbackProcessor : public CHXStatisticProcessor
{
private:
	const char* m_pStatisticName;
	void* m_ObserverInfo;
	const HXStatisticsCallbacks* m_pStatisticsCallbacks;
	bool m_IsNewStatistic;
public:
	StatisticCallbackProcessor( const char* pStatisticName, const HXStatisticsCallbacks* pStatisticsCallbacks, void* observerInfo, bool isNewStatistic )
		: m_pStatisticName( pStatisticName )
		, m_ObserverInfo( observerInfo )
		, m_pStatisticsCallbacks( pStatisticsCallbacks )
		, m_IsNewStatistic( isNewStatistic )
	{}
	virtual ~StatisticCallbackProcessor( void ) {}
	virtual bool operator() ( int valueType, const unsigned char* pValue )
	{
		if ( m_pStatisticsCallbacks )
		{
			if ( m_IsNewStatistic )
			{
				if ( m_pStatisticsCallbacks->OnAddedStatistic )
				{
					m_pStatisticsCallbacks->OnAddedStatistic( m_pStatisticName, valueType, pValue, m_ObserverInfo );
				}
			}
			else
			{
				if ( m_pStatisticsCallbacks->OnModifiedStatistic )
				{
					m_pStatisticsCallbacks->OnModifiedStatistic( m_pStatisticName, valueType, pValue, m_ObserverInfo );
				}
			}
		}
		return true;
	}
};

static bool
AreTrackerEntryObserverDataElementsEqual( const void* item1Ptr, const void* item2Ptr )
{
	const SHXTrackerEntryObserverData* data1 = ( const SHXTrackerEntryObserverData* ) item1Ptr;
	const SHXTrackerEntryObserverData* data2 = ( const SHXTrackerEntryObserverData* ) item2Ptr;
	return( ( data1->m_pStatisticsCallbacks	== data2->m_pStatisticsCallbacks ) &&
			( data1->m_ObserverInfo		 	== data2->m_ObserverInfo         ) );
}

bool
CHXStatisticTrackerEntry::AddObserver( const char* pStatisticKey, const HXStatisticsCallbacks* pStatisticsCallbacks, void* observerInfo )
{
	CHXASSERT( pStatisticsCallbacks );
	
	if ( ShouldObserveProperty( pStatisticKey, GetPropName(), true ) )
	{
		if ( !m_pEntryObservers )
		{
			m_pEntryObservers = new CHXFlatArray(sizeof(SHXTrackerEntryObserverData));
		}
		if ( m_pEntryObservers )
		{
			if ( StartWatchingMe() )
			{
				SHXTrackerEntryObserverData observerData = { pStatisticsCallbacks, observerInfo };
				if ( m_pEntryObservers->HasRecord( &observerData, AreTrackerEntryObserverDataElementsEqual ) )
				{
					return true;
				}
				// Add new observer.
				m_pEntryObservers->Push( &observerData );
				StatisticCallbackProcessor Processor( GetPropName(), pStatisticsCallbacks, observerInfo, true );
				( void ) ProcessStatistic( GetRegistry(), GetPropID(), Processor );
				return true;
			}
		}
	}
	return false;
}

void
CHXStatisticTrackerEntry::RemoveObserver( const char* pStatisticKey, const HXStatisticsCallbacks* pStatisticsCallbacks, void* observerInfo )
{
	// XXXSEH: Note - pStatisticKey ignored. If the observer is present, then it can be assumed that ShouldObserveProperty() returned true for it.
	CHXASSERT( pStatisticsCallbacks );

	if ( m_pEntryObservers )
	{
		UINT32 recordNumToRemove;
		SHXTrackerEntryObserverData observerData = { pStatisticsCallbacks, observerInfo };
		if ( m_pEntryObservers->FindRecord( &observerData, AreTrackerEntryObserverDataElementsEqual, 0, &recordNumToRemove ) )
		{
			m_pEntryObservers->Remove( recordNumToRemove );
			if ( m_pEntryObservers->IsEmpty() )
			{
				delete m_pEntryObservers;
				m_pEntryObservers = NULL;
				StopWatchingMe();
			}
		}
	}
}

STDMETHODIMP
CHXStatisticTrackerEntry::AddedProp( const UINT32 ulId, const HXPropType propType, const UINT32 ulParentID )
{
	CHXASSERT( !"Why is AddedProp being called on a non parent property?" );

	return HXR_OK; // XXXSEH: What's the return value mean?
}

STDMETHODIMP
CHXStatisticTrackerEntry::ModifiedProp( const UINT32 ulId, const HXPropType propType, const UINT32 ulParentID )
{
#ifdef LOG_STATISTICS_DEBUG_INFO
	ExamineProperty( ulId, "Modified property" );
#endif
	if ( m_pEntryObservers )
	{
		UINT32 numOfObservers = m_pEntryObservers->GetCount();
		for ( UINT32 index = 0; index < numOfObservers; ++index )
		{
			SHXTrackerEntryObserverData observerData;
			m_pEntryObservers->GetAt( index, &observerData );
			StatisticCallbackProcessor Processor( GetPropName(), observerData.m_pStatisticsCallbacks, observerData.m_ObserverInfo, false );
			( void ) ProcessStatistic( GetRegistry(), GetPropID(), Processor );
		}
	}
	return HXR_OK; // XXXSEH: What's the return value mean?
}

STDMETHODIMP
CHXStatisticTrackerEntry::DeletedProp( const UINT32 ulId, const UINT32 ulParentID )
{
#ifdef LOG_STATISTICS_DEBUG_INFO
	ExamineProperty( ulId, "Deleted property" );
#endif
	if ( m_pEntryObservers )
	{
		UINT32 numOfObservers = m_pEntryObservers->GetCount();
		for ( UINT32 index = 0; index < numOfObservers; ++index )
		{
			SHXTrackerEntryObserverData observerData;
			m_pEntryObservers->GetAt( index, &observerData );
			HXOnDeletedStatisticProcPtr OnDeletedStatistic = observerData.m_pStatisticsCallbacks->OnDeletedStatistic;
			if ( OnDeletedStatistic )
			{
				OnDeletedStatistic( GetPropName(), observerData.m_ObserverInfo );
			}
		}
	}
	StopWatchingMe();
	
	return HXR_OK; // XXXSEH: What's the return value mean?
}
