/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: CHXClientSink.cpp,v 1.18.2.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 "CHXClientSink.h"
#include "CHXClientDebug.h"

#include "enter_hx_headers.h"
#include "hxerror.h"
#include "hxcore.h"
#include "ihxpckts.h"

#include "hxsmartptr.h"
HX_SMART_POINTER_INLINE( SPIHXBuffer, IHXBuffer );
HX_SMART_POINTER_INLINE( SPIHXValues, IHXValues );
HX_SMART_POINTER_INLINE( SPIHXPersistentComponentManager, IHXPersistentComponentManager );
HX_SMART_POINTER_INLINE( SPIHXPersistentComponent, IHXPersistentComponent );
HX_SMART_POINTER_INLINE( SPIHXAudioPlayer, IHXAudioPlayer );
HX_SMART_POINTER_INLINE( SPIHXErrorSinkControl, IHXErrorSinkControl );
HX_SMART_POINTER_INLINE( SPIHXErrorMessages, IHXErrorMessages );
HX_SMART_POINTER_INLINE( SPIHXScheduler, IHXScheduler );

#ifdef HELIX_FEATURE_REGISTRY
#include "hxcomm.h"  // IHXRegistryID
#include "hxmon.h"   // IHXRegistry
HX_SMART_POINTER_INLINE( SPIHXRegistry, IHXRegistry );
HX_SMART_POINTER_INLINE( SPIHXRegistryID, IHXRegistryID );
#endif

#include "exit_hx_headers.h"

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

UINT32 const kInvalidSeekPosition = 0xFFFFFFFF;

static const char* const kURLPropertyName    = "url";
static const char* const kSourcePropertyName = "src";
static const char* const kPersistentComponentIDPropertyName = "PersistentComponentID";

#pragma mark -

static int
Unhex( char c )
{
	return (c >= '0' && c <= '9' ? c - '0'
	    : c >= 'A' && c <= 'F' ? c - 'A' + 10
	    : c - 'a' + 10 );
}

static void
UnescapeURL( char* s )
{
/*
 * Remove URL hex escapes from s... done in place.  The basic concept for
 * this routine is borrowed from the WWW library HTUnEscape() routine.
 */
	char* p = NULL;
	bool bProcessingOptionsPastQuestionMark = false;

	for ( p = s; *s != '\0'; ++s )
	{
		if ( ( !bProcessingOptionsPastQuestionMark ) && ( *s == '%' ) )
		{
			if ( *++s != '\0' ) 
			{
				*p = Unhex( *s ) << 4;
			}

			if ( *++s != '\0' ) 
			{
				*p++ += Unhex( *s );
			}
		} 
		else 
		{
			if ( *s == '?' )
			{
				bProcessingOptionsPastQuestionMark = true;
			}
			*p++ = *s;
		}
	}
	*p = '\0';
}

CHXClientSink::~CHXClientSink( void )
{
	delete [] m_pRPURLTarget;
	delete [] m_pRPURL;
	delete [] m_pContextURL;
	delete [] m_pTrackURL;
	delete [] m_pMetafileURL;
	delete [] m_pTitle;
}

CHXClientSink::CHXClientSink( IHXPlayer* pIHXPlayer, void* userInfo, const HXClientCallbacks* pClientCallbacks )
	: m_UserInfo( userInfo )
	, m_pClientCallbacks( pClientCallbacks )
	, m_pIHXCorePlayer( pIHXPlayer )
	, m_hScheduler( 0 )
#ifdef HELIX_FEATURE_REGISTRY
	, m_TitlePropID( 0 )
	, m_ClipBandwidthPropID( 0 )
#endif
	, m_TitleSize( 0 )
	, m_pTitle( NULL )
	, m_pMetafileURL( NULL )
	, m_pTrackURL( NULL )
	, m_pContextURL( NULL )
	, m_pRPURL( NULL )
	, m_pRPURLTarget( NULL )
	, m_ClipBandwidth( 0 )
	, m_ContentState( kContentStateStopped ) // If this object is loaded, kContentStateNotLoaded is not applicable
	, m_BufferPercent( 100 )
	, m_Position( 0 )
	, m_Length( 0 )
	, m_OnPlayingPositionThreshold( 0 )
	, m_PendingBeginPosition( kInvalidSeekPosition )
	, m_IsLive( false )
	, m_IsGroupsListDirty( false )
	, m_HasContentBegun( false )
{
	CHXASSERT( m_pClientCallbacks ); // What's the point in observing if we cannot tell anyone about it.
	CHXASSERT( m_pIHXCorePlayer ); // Need a valid core player.
}

BEGIN_INTERFACE_LIST_NOCREATE( CHXClientSink )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXClientAdviseSink )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXGroupSink )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXVolumeAdviseSink )
#ifdef HELIX_FEATURE_REGISTRY
    INTERFACE_LIST_ENTRY_SIMPLE( IHXPropWatchResponse )
#endif
	INTERFACE_LIST_ENTRY_SIMPLE( IHXErrorSink )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXCallback )
END_INTERFACE_LIST

void
CHXClientSink::Init( void )
{
	SetUpPropWatcher();
	
	SPIHXAudioPlayer spAudioPlayer = m_pIHXCorePlayer;
	if ( spAudioPlayer.IsValid() )
	{
		IHXVolume* pIClientVolume = spAudioPlayer->GetDeviceVolume();
		if ( pIClientVolume )
		{
			pIClientVolume->AddAdviseSink( this );
			pIClientVolume->Release();
		}
	}
	SPIHXErrorSinkControl spErrorSinkControl = m_pIHXCorePlayer;
	if ( spErrorSinkControl.IsValid() )
	{
		spErrorSinkControl->AddErrorSink( this, HXLOG_EMERG, HXLOG_INFO );
	}
}

void
CHXClientSink::Destroy( void )
{
	if ( m_hScheduler != 0 )
	{
		SPIHXScheduler spScheduler = m_pIHXCorePlayer;
		if ( spScheduler.IsValid() )
		{
			m_hScheduler = spScheduler->Remove( m_hScheduler );
		}
	}
	SPIHXErrorSinkControl spErrorSinkControl = m_pIHXCorePlayer;
	if ( spErrorSinkControl.IsValid() )
	{
		spErrorSinkControl->RemoveErrorSink( this );
	}
	SPIHXAudioPlayer spAudioPlayer = m_pIHXCorePlayer;
	if ( spAudioPlayer.IsValid() )
	{
		IHXVolume* pIClientVolume = spAudioPlayer->GetDeviceVolume();
		if ( pIClientVolume )
		{
			pIClientVolume->RemoveAdviseSink( this );
			pIClientVolume->Release();
		}
	}
	DestroyPropWatcher();
}

#pragma mark -

void
CHXClientSink::SetBeginPosition( UINT32 beginPosition )
{
	if ( m_ContentState == kContentStateStopped )
	{
		// XXXSEH: This has issues with various pieces of content including http clips and complex clips whose timeline is not known at the beginning.
		// m_PendingBeginPosition = beginPosition; // Need to wait for an OnBegin() callback before we can change the clip's position.
	}
	else
	{
		m_PendingBeginPosition = kInvalidSeekPosition;
		( void ) m_pIHXCorePlayer->Seek( beginPosition );
	}
}

void
CHXClientSink::DoGroupsListUpdate( void )
{
	// we defer calling OnGroupsChanged until a playback transition, since at the times
	// the group changes really occur, the information about the track names isn't yet
	// available
	if ( m_IsGroupsListDirty )
	{
		m_IsGroupsListDirty = false;
		if ( m_pClientCallbacks->OnGroupsChanged )
		{
			m_pClientCallbacks->OnGroupsChanged( m_UserInfo );
		}
	}
}

void
CHXClientSink::UpdateContentState( int newContentState )
{
	if ( m_ContentState != newContentState )
	{
		if ( m_ContentState == kContentStateContacting )
		{
			if ( m_pClientCallbacks->OnContacting )
			{
				m_pClientCallbacks->OnContacting( m_UserInfo, NULL );
			}
		}
		int oldContentState = m_ContentState;
		m_ContentState = newContentState;
		if ( m_pClientCallbacks->OnContentStateChanged )
		{
			m_pClientCallbacks->OnContentStateChanged( m_UserInfo, oldContentState, m_ContentState );
		}
	}
}

STDMETHODIMP
CHXClientSink::OnPresentationOpened( void )
{
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnBegin( ULONG32 ulTime )
{
	m_HasContentBegun = true;
	m_Position = ulTime;
	m_OnPlayingPositionThreshold = ulTime;

	DoGroupsListUpdate();  // updates group information for second and later clip
	
	if ( m_PendingBeginPosition != kInvalidSeekPosition )
	{
		UINT32 beginPosition = m_PendingBeginPosition;
		m_PendingBeginPosition = kInvalidSeekPosition;
		( void ) m_pIHXCorePlayer->Seek( beginPosition );
	}
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnContacting( const char* pHostName )
{
	UpdateContentState( kContentStateContacting );
	if ( m_pClientCallbacks->OnContacting )
	{
		m_pClientCallbacks->OnContacting( m_UserInfo, pHostName );
	}
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnBuffering( ULONG32 ulFlags, UINT16 unPercentComplete )
{
	if ( m_BufferPercent != unPercentComplete )
	{
		m_OnPlayingPositionThreshold = m_pIHXCorePlayer->GetCurrentPlayTime();
		m_BufferPercent = unPercentComplete;
		if ( m_BufferPercent < 100 )
		{
			DoGroupsListUpdate(); // update information on first clip
			
			// I've noticed that this callback is being made even when the content is paused,
			// so let's prevent the content state from changing in this case.
			if ( m_HasContentBegun )
			{
				UpdateContentState( kContentStateLoading );
			}
		}
		if ( m_pClientCallbacks->OnBuffering )
		{
			m_pClientCallbacks->OnBuffering( m_UserInfo, ulFlags, m_BufferPercent );
		}
	}
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnPosLength( UINT32 ulPosition, UINT32 ulLength )
{
	bool wasLive = m_IsLive;
	m_Position = ulPosition;
	m_IsLive = ( 0 != m_pIHXCorePlayer->IsLive() );
	if ( ( m_Length != ulLength ) || ( !wasLive != !m_IsLive ) )
	{
		m_Length = ulLength;
		if ( m_pClientCallbacks->OnLengthChanged )
		{
			m_pClientCallbacks->OnLengthChanged( m_UserInfo, m_Length );
		}
	}
	if ( m_Position > m_OnPlayingPositionThreshold )
	{
		DoGroupsListUpdate(); // update information on first clip

		m_OnPlayingPositionThreshold = 0xFFFFFFFF;
		UpdateContentState( kContentStatePlaying );
	}
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnPause( ULONG32 ulTime )
{
	m_HasContentBegun = false;
	
	m_Position = ulTime;
	m_OnPlayingPositionThreshold = ulTime;
	UpdateContentState( kContentStatePaused );
	return HXR_OK;
}
	
STDMETHODIMP
CHXClientSink::OnStop( void )
{
	m_HasContentBegun = false;
	
	m_Position = 0; // XXXSEH: Might be nice to retain the last position.
	m_OnPlayingPositionThreshold = 0;
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnPresentationClosed( void )
{
	if ( m_ContentState != kContentStateStopped )
	{
		// I'd prefer to send this out in OnStop(), but unfortunately it and this callback are called
		// prior to Helix being ready to immediately accept a new url for playback.
		// This kludge solves this problem, providing support for custom playlist managers for example.
		if ( m_hScheduler == 0 )
		{
			SPIHXScheduler spScheduler = m_pIHXCorePlayer;
			if ( spScheduler.IsValid() )
			{
				m_hScheduler = spScheduler->RelativeEnter( this, 0 );
			}
			// Couldn't schedule the callback to update the state, so let's do it now.
			// Not ideal, but better than never altering the state to stopped.
			if ( m_hScheduler == 0 )
			{
				UpdateContentState( kContentStateStopped );
			}
		}
	}
	return HXR_OK;
}

void
CHXClientSink::ProcessPendingStateChange( void )
{
	if ( m_hScheduler != 0 )
	{
		SPIHXScheduler spScheduler = m_pIHXCorePlayer;
		if ( spScheduler.IsValid() )
		{
			m_hScheduler = spScheduler->Remove( m_hScheduler );
		}
		UpdateContentState( kContentStateStopped );
	}
}

STDMETHODIMP
CHXClientSink::Func( void )
{
	m_hScheduler = 0;
	
	UpdateContentState( kContentStateStopped );
	if ( m_pClientCallbacks->OnContentConcluded )
	{
		m_pClientCallbacks->OnContentConcluded( m_UserInfo );
	}

	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnStatisticsChanged( void )
{
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnPreSeek( ULONG32 ulOldTime, ULONG32 ulNewTime )
{
	m_Position = ulNewTime;
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnPostSeek( ULONG32 ulOldTime, ULONG32 ulNewTime )
{
	m_Position = ulNewTime;
	m_OnPlayingPositionThreshold = ulNewTime;
	return HXR_OK;
}

#pragma mark -

void
CHXClientSink::GetURLsFromTrackProperties( IHXValues* pTrackProperties )
{
	delete [] m_pMetafileURL;
	m_pMetafileURL = NULL;
	delete [] m_pTrackURL;
	m_pTrackURL = NULL;
	
	if ( pTrackProperties )
	{
		SPIHXBuffer spTrackURLBuffer;
		pTrackProperties->GetPropertyCString( kURLPropertyName, *spTrackURLBuffer.AsInOutParam() );
		if ( !spTrackURLBuffer.IsValid() )
		{
			pTrackProperties->GetPropertyCString( kSourcePropertyName, *spTrackURLBuffer.AsInOutParam() );
		}
		if ( spTrackURLBuffer.IsValid() )
		{
			UINT32 bufferSize = spTrackURLBuffer->GetSize();
			m_pTrackURL = new char[ bufferSize ];
			if ( m_pTrackURL )
			{
				memcpy( m_pTrackURL, spTrackURLBuffer->GetBuffer(), bufferSize );
			}
		}
		ULONG32 persistentComponentID = 0;
		if ( SUCCEEDED( pTrackProperties->GetPropertyULONG32( kPersistentComponentIDPropertyName, persistentComponentID ) ) )
		{
			SPIHXPersistentComponentManager spPersistentComponentManager = m_pIHXCorePlayer;
			if ( spPersistentComponentManager.IsValid() )
			{
				SPIHXPersistentComponent spPersistentComponent;
				spPersistentComponentManager->GetPersistentComponent( persistentComponentID, *spPersistentComponent.AsInOutParam() );
				if ( spPersistentComponent.IsValid() )
				{
					SPIHXValues spPersistentComponentProperties;
					spPersistentComponent->GetPersistentProperties( *spPersistentComponentProperties.AsInOutParam() );
					if ( spPersistentComponentProperties.IsValid() )
					{
						SPIHXBuffer spMetafileURLBuffer;
						spPersistentComponentProperties->GetPropertyCString( kURLPropertyName, *spMetafileURLBuffer.AsInOutParam() );
						if ( !spMetafileURLBuffer.IsValid() )
						{
							spPersistentComponentProperties->GetPropertyCString( kSourcePropertyName, *spMetafileURLBuffer.AsInOutParam() );
						}
						if ( spMetafileURLBuffer.IsValid() )
						{
							UINT32 bufferSize = spMetafileURLBuffer->GetSize();
							m_pMetafileURL = new char[ bufferSize ];
							if ( m_pMetafileURL )
							{
								memcpy( m_pMetafileURL, spMetafileURLBuffer->GetBuffer(), bufferSize );
							}
						}
					}
				}
			}
		}
	}
}

static UINT32
CopyURLArgument( char* pDest, const char* pSource, UINT32 length )
{
	if ( length == 0 ) return length;
	
	UINT32 copyLength = length;
	const char* pSourceToCopy = pSource;
	if ( pSourceToCopy[ 0 ] == '\"' )
	{
		pSourceToCopy++;
		copyLength--;
	}
	if ( copyLength == 0 ) return copyLength;
	
	if ( pSourceToCopy[ copyLength - 1 ] == '\"' )
	{
		copyLength--;
	}
	if ( copyLength == 0 ) return copyLength;

	memcpy( pDest, pSourceToCopy, copyLength );
	return copyLength;
}

void
CHXClientSink::UpdateContextURL( void )
{
	static const char* const kContextURLName    = "rpcontexturl=";
	static const char* const kContextWidthName  = "rpcontextwidth=";
	static const char* const kContextHeightName = "rpcontextheight=";
	static const char* const kContextParamsName = "rpcontextparams=";
	
	char* pNewContextURL = NULL;
	
	const char* pContextURL = NULL;
	const char* pURLToParse = ( m_pTrackURL    && ( NULL != ( pContextURL = strstr( m_pTrackURL,    kContextURLName ) ) ) ) ? m_pTrackURL    :
							  ( m_pMetafileURL && ( NULL != ( pContextURL = strstr( m_pMetafileURL, kContextURLName ) ) ) ) ? m_pMetafileURL : NULL;
	if ( pURLToParse )
	{
		const char* pArgumentDelim;
		UINT32 urlToParseLength = strlen( pURLToParse );
		const char* pEndOfURLToParse = pURLToParse + urlToParseLength;
		
		pContextURL += strlen( kContextURLName );
		
		// If the context URL has its own arguments, denoted by a '?', then we assume the context URL runs the remainder of the URL,
		// since we have no way of knowing when it actually stops.
		bool doesContextURLHaveArguments = ( NULL != strchr( pContextURL, '?' ) );
		pArgumentDelim = doesContextURLHaveArguments ? NULL : strchr( pContextURL, '&' );
		UINT32 contextURLLength = pArgumentDelim ? ( pArgumentDelim - pContextURL ) : ( pEndOfURLToParse - pContextURL );
		
		UINT32 contextParamsLength = 0;
		const char* pContextParams = strstr( pURLToParse, kContextParamsName );
		if ( pContextParams )
		{
			pContextParams += strlen( kContextParamsName );
			pArgumentDelim = strchr( pContextParams, '&' );
			contextParamsLength = pArgumentDelim ? ( pArgumentDelim - pContextParams ) : ( pEndOfURLToParse - pContextParams );
		}
		UINT32 contextWidthLength = 0;
		const char* pContextWidth = strstr( pURLToParse, kContextWidthName );
		if ( pContextWidth )
		{
			pContextWidth += strlen( kContextWidthName );
			pArgumentDelim = strchr( pContextWidth, '&' );
			contextWidthLength = pArgumentDelim ? ( pArgumentDelim - pContextWidth ) : ( pEndOfURLToParse - pContextWidth );
		}
		UINT32 contextHeightLength = 0;
		const char* pContextHeight = strstr( pURLToParse, kContextHeightName );
		if ( pContextHeight )
		{
			pContextHeight += strlen( kContextHeightName );
			pArgumentDelim = strchr( pContextHeight, '&' );
			contextHeightLength = pArgumentDelim ? ( pArgumentDelim - pContextHeight ) : ( pEndOfURLToParse - pContextHeight );
		}
		
		// <pContextURL>?<pContextParams>&rptarget=_rpcontextwin&rpcontextwidth=<pContextWidth>&rpcontextheight=<pContextHeight>
		const UINT32 kContextURLWithArgsLength = contextURLLength + 1 + 
												 ( pContextParams ? ( contextParamsLength + 1 ) : 0 ) +
												 ( 8 + 1 + 13 ) +
												 ( pContextWidth  ? ( 1 + strlen( kContextWidthName  ) + 1 + contextWidthLength  ) : 0 ) +
												 ( pContextHeight ? ( 1 + strlen( kContextHeightName ) + 1 + contextHeightLength ) : 0 );
		
		pNewContextURL = new char[ kContextURLWithArgsLength + 1 ];
		if ( pNewContextURL )
		{
			contextURLLength = CopyURLArgument( pNewContextURL, pContextURL, contextURLLength );
			UINT32 copyIndex = contextURLLength;
			pNewContextURL[ copyIndex++ ] = doesContextURLHaveArguments ? '&' : '?';
			
			if ( pContextParams )
			{
				contextParamsLength = CopyURLArgument( &pNewContextURL[ copyIndex ], pContextParams, contextParamsLength );
				pNewContextURL[ copyIndex + contextParamsLength ] = '\0';
				UnescapeURL( &pNewContextURL[ copyIndex ] ); // Unescape the params here. Since they follow the '?', they'll be ignored in the final Unescape.
				copyIndex += strlen( &pNewContextURL[ copyIndex ] );
				pNewContextURL[ copyIndex++ ] = '&';
			}
			sprintf( &pNewContextURL[ copyIndex ], "%s", "rptarget=_rpcontextwin" );
			copyIndex += strlen( &pNewContextURL[ copyIndex ] );
			
			if ( pContextWidth )
			{
				sprintf( &pNewContextURL[ copyIndex ], "%c%s", '&', kContextWidthName );
				copyIndex += strlen( &pNewContextURL[ copyIndex ] );
				contextWidthLength = CopyURLArgument( &pNewContextURL[ copyIndex ], pContextWidth, contextWidthLength );
				copyIndex += contextWidthLength;
			}
			if ( pContextHeight )
			{
				sprintf( &pNewContextURL[ copyIndex ], "%c%s", '&', kContextHeightName );
				copyIndex += strlen( &pNewContextURL[ copyIndex ] );
				contextHeightLength = CopyURLArgument( &pNewContextURL[ copyIndex ], pContextHeight, contextHeightLength );
				copyIndex += contextHeightLength;
			}
			pNewContextURL[ copyIndex ] = '\0';
			
			UnescapeURL( pNewContextURL );
		}
	}
	delete [] m_pContextURL;
	m_pContextURL = pNewContextURL;
}

void
CHXClientSink::UpdateRPURLAndTarget( void )
{
	static const char* const kRPURLName		  = "rpurl=";
	static const char* const kRPURLTargetName = "rpurltarget=";
	
	char* pNewRPURL = NULL;
	char* pNewRPURLTarget = NULL;
	
	const char* pRPURL = NULL;
	const char* pURLToParse = ( m_pTrackURL    && ( NULL != ( pRPURL = strstr( m_pTrackURL,    kRPURLName ) ) ) ) ? m_pTrackURL    :
							  ( m_pMetafileURL && ( NULL != ( pRPURL = strstr( m_pMetafileURL, kRPURLName ) ) ) ) ? m_pMetafileURL : NULL;
	if ( pURLToParse )
	{
		const char* pArgumentDelim;
		UINT32 urlToParseLength = strlen( pURLToParse );
		const char* pEndOfURLToParse = pURLToParse + urlToParseLength;
		
		pRPURL += strlen( kRPURLName );
		
		// If the RPURL has its own arguments, denoted by a '?', then we assume the RPURL runs the remainder of the URL,
		// since we have no way of knowing when it actually stops.
		bool doesRPURLHaveArguments = ( NULL != strchr( pRPURL, '?' ) );
		pArgumentDelim = doesRPURLHaveArguments ? NULL : strchr( pRPURL, '&' );
		UINT32 rpURLLength = pArgumentDelim ? ( pArgumentDelim - pRPURL ) : ( pEndOfURLToParse - pRPURL );
		
		pNewRPURL = new char[ rpURLLength + 1 ];
		if ( pNewRPURL )
		{
			rpURLLength = CopyURLArgument( pNewRPURL, pRPURL, rpURLLength );
			pNewRPURL[ rpURLLength ] = '\0';
			UnescapeURL( pNewRPURL );
		
			UINT32 rpURLTargetLength = 0;
			const char* pRPURLTarget = strstr( pURLToParse, kRPURLTargetName );
			if ( pRPURLTarget )
			{
				pRPURLTarget += strlen( kRPURLTargetName );
				pArgumentDelim = strchr( pRPURLTarget, '&' );
				rpURLTargetLength = pArgumentDelim ? ( pArgumentDelim - pRPURLTarget ) : ( pEndOfURLToParse - pRPURLTarget );

				pNewRPURLTarget = new char[ rpURLTargetLength + 1 ];
				if ( pNewRPURLTarget )
				{
					rpURLTargetLength = CopyURLArgument( pNewRPURLTarget, pRPURLTarget, rpURLTargetLength );
					pNewRPURLTarget[ rpURLTargetLength ] = '\0';
				}
			}
		}
	}
	delete [] m_pRPURL;
	m_pRPURL = pNewRPURL;
	delete [] m_pRPURLTarget;
	m_pRPURLTarget = pNewRPURLTarget;
}

STDMETHODIMP
CHXClientSink::TrackStarted( UINT16 uGroupIndex, UINT16 uTrackIndex, IHXValues* pTrack )
{
	GetURLsFromTrackProperties( pTrack );
	UpdateContextURL();
	UpdateRPURLAndTarget();
	if ( m_pClientCallbacks->OnGroupStarted )
	{
		m_pClientCallbacks->OnGroupStarted( m_UserInfo, uGroupIndex );
	}
	if ( m_pContextURL && m_pClientCallbacks->GoToURL )
	{
		( void ) m_pClientCallbacks->GoToURL( m_UserInfo, m_pContextURL, NULL, false );
	}
	if ( m_pRPURL && m_pClientCallbacks->GoToURL )
	{
		( void ) m_pClientCallbacks->GoToURL( m_UserInfo, m_pRPURL, m_pRPURLTarget, false );
	}
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::TrackStopped( UINT16 uGroupIndex, UINT16 uTrackIndex, IHXValues* pTrack )
{
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::GroupAdded( UINT16 uGroupIndex, IHXGroup* pGroup )
{
	m_IsGroupsListDirty = true;
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::GroupRemoved( UINT16 uGroupIndex, IHXGroup* pGroup )
{
	m_IsGroupsListDirty = true;
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::AllGroupsRemoved( void )
{
	m_IsGroupsListDirty = true;
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::TrackAdded( UINT16 uGroupIndex, UINT16 uTrackIndex, IHXValues* pTrack )
{
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::TrackRemoved( UINT16 uGroupIndex, UINT16 uTrackIndex, IHXValues* pTrack )
{
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::CurrentGroupSet( UINT16 uGroupIndex, IHXGroup* pGroup )
{
	// with a new group open, we may now have access to the clip name
	// for the group (though it won't be available yet from the core, that
	// slacker; we'll wait until OnBegin happens to update our info)
	m_IsGroupsListDirty = true;
	return HXR_OK;
}

#pragma mark -

STDMETHODIMP
CHXClientSink::OnVolumeChange( const UINT16 uVolume )
{
	if ( m_pClientCallbacks->OnVolumeChanged )
	{
		m_pClientCallbacks->OnVolumeChanged( m_UserInfo, uVolume );
	}
	return HXR_OK;
}

STDMETHODIMP
CHXClientSink::OnMuteChange( const HXBOOL bMute )
{
	if ( m_pClientCallbacks->OnMuteChanged )
	{
		bool isMuted = ( ( 0 != bMute ) ? true : false );
		m_pClientCallbacks->OnMuteChanged( m_UserInfo, isMuted );
	}
	return HXR_OK;
}

#pragma mark -

void
CHXClientSink::SetUpPropWatcher( void )
{
#ifdef HELIX_FEATURE_REGISTRY
	if (  m_spPropWatch.IsValid() ) return;
	
	// Get the name of our registry.
	SPIHXRegistry spRegistry = m_pIHXCorePlayer;
	SPIHXRegistryID spRegistryID = m_pIHXCorePlayer;
	if ( !spRegistry.IsValid() || !spRegistryID.IsValid() ) return;
	
	UINT32 playerId;
	SPIHXBuffer spPlayerNameBuffer;
	spRegistryID->GetID( playerId );
	spRegistry->GetPropName( playerId, *spPlayerNameBuffer.AsInOutParam() );
	if ( spPlayerNameBuffer.IsValid() && ( spPlayerNameBuffer->GetSize() > 0 ) )
	{
		HX_RESULT result = spRegistry->CreatePropWatch( *m_spPropWatch.AsInOutParam() );
		if ( SUCCEEDED( result ) )
		{
			result = m_spPropWatch->Init( this );
			if ( SUCCEEDED( result ) )
			{
				char propName[ 256 ] = "";
				sprintf( propName, "%s.%s", ( const char* ) spPlayerNameBuffer->GetBuffer(), "Title" );
				m_TitlePropID = spRegistry->GetId( propName );
				if ( m_TitlePropID > 0 )
				{
					m_spPropWatch->SetWatchById( m_TitlePropID );
				}
				sprintf( propName, "%s.%s", ( const char* ) spPlayerNameBuffer->GetBuffer(), "ClipBandwidth" );
				m_ClipBandwidthPropID = spRegistry->GetId( propName );
				if ( m_ClipBandwidthPropID > 0 )
				{
					m_spPropWatch->SetWatchById( m_ClipBandwidthPropID );
				}
			}
			else
			{
				m_spPropWatch.Clear();
			}
		}
	}
#endif
}

#ifdef HELIX_FEATURE_REGISTRY
STDMETHODIMP
CHXClientSink::AddedProp( const UINT32 ulId, const HXPropType propType, const UINT32 ulParentID )
{
	return HXR_OK; // Called when a child property to a watched property is added.
}

STDMETHODIMP
CHXClientSink::ModifiedProp( const UINT32 ulId, const HXPropType propType, const UINT32 ulParentID )
{
	SPIHXRegistry spRegistry = m_pIHXCorePlayer;
	CHXASSERT( spRegistry.IsValid() ); // Wouldn't be getting this callback if the Player didn't support IHXRegistry.
	if ( ulId == m_TitlePropID )
	{
		SPIHXBuffer spPropStringBuffer;
		spRegistry->GetStrById( ulId, *spPropStringBuffer.AsInOutParam() );
		if ( spPropStringBuffer.IsValid() )
		{
			UINT32 bufferSize = spPropStringBuffer->GetSize();
			if ( ( m_TitleSize != bufferSize ) ||
				 ( 0 != memcmp( m_pTitle, spPropStringBuffer->GetBuffer(), m_TitleSize ) ) )
			{
				delete [] m_pTitle;
				m_pTitle = new char[ bufferSize ];
				if ( m_pTitle )
				{
					memcpy( m_pTitle, spPropStringBuffer->GetBuffer(), bufferSize );
					m_TitleSize = bufferSize;
				}
				if ( m_pClientCallbacks->OnTitleChanged )
				{
					m_pClientCallbacks->OnTitleChanged( m_UserInfo, m_pTitle );
				}
			}
		}
		return HXR_OK;
	}
	if ( ulId == m_ClipBandwidthPropID )
	{
		INT32 oldClipBandwidth = m_ClipBandwidth;
		if ( SUCCEEDED( spRegistry->GetIntById( ulId, m_ClipBandwidth ) ) )
		{
			if ( m_pClientCallbacks->OnClipBandwidthChanged && ( oldClipBandwidth != m_ClipBandwidth ) )
			{
				m_pClientCallbacks->OnClipBandwidthChanged( m_UserInfo, m_ClipBandwidth );
			}
		}
		return HXR_OK;
	}
	return HXR_FAIL;
}

STDMETHODIMP
CHXClientSink::DeletedProp( const UINT32 ulId, const UINT32 ulParentID )
{
	if ( ulId == m_TitlePropID )
	{
		m_TitlePropID = 0;
		delete [] m_pTitle;
		m_pTitle = NULL;

		return m_spPropWatch->ClearWatchById( ulId );
	}
	if ( ulId == m_ClipBandwidthPropID )
	{
		m_ClipBandwidthPropID = 0;
		m_ClipBandwidth = 0;
		return m_spPropWatch->ClearWatchById( ulId );
	}
	return HXR_FAIL;
}
#endif

void
CHXClientSink::DestroyPropWatcher( void )
{
#ifdef HELIX_FEATURE_REGISTRY
	if ( !m_spPropWatch.IsValid() ) return;

	if ( m_ClipBandwidthPropID > 0 )
	{
		m_spPropWatch->ClearWatchById( m_ClipBandwidthPropID );
		m_ClipBandwidthPropID = 0;
	}
	m_ClipBandwidth = 0;
	if ( m_TitlePropID > 0 )
	{
		m_spPropWatch->ClearWatchById( m_TitlePropID );
		m_TitlePropID = 0;
	}
	delete [] m_pTitle;
	m_pTitle = NULL;

	m_spPropWatch.Clear();
#endif
}

#pragma mark -

STDMETHODIMP
CHXClientSink::ErrorOccurred( const UINT8	unSeverity,  
							  const ULONG32	ulHXCode,
							  const ULONG32	ulUserCode,
							  const char*	pUserString,
							  const char*	pMoreInfoURL )
{
	// don't report warnings & logging info
	if ( unSeverity <= HXLOG_ERR )
	{
		DoGroupsListUpdate();
		if ( m_pClientCallbacks->OnErrorOccurred )
		{
			IHXBuffer* pIErrorString = NULL;
			SPIHXErrorMessages spErrorMessages = m_pIHXCorePlayer;
			if ( spErrorMessages.IsValid() )
			{
				pIErrorString = spErrorMessages->GetErrorText( ulHXCode );
			}
			m_pClientCallbacks->OnErrorOccurred( m_UserInfo, ulHXCode, ulUserCode, ( pIErrorString ? ( const char* ) pIErrorString->GetBuffer() : NULL ), pUserString, pMoreInfoURL );
			HX_RELEASE( pIErrorString );
		}
	}
	return HXR_OK;
}
