/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: smlprstime.cpp,v 1.2.12.1 2004/07/09 01:58:02 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 ***** */

// system
#include <time.h>
#ifdef _UNIX
#include <ctype.h>
#endif
// include
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxcom.h"
#include "hxxml.h"
#include "smiltype.h"
// pncont
#include "hxstring.h"
#include "hxslist.h"
// pnmisc
#include "nptime.h"
#include "smpte.h"
#include "hxwinver.h"
// rnxmllib
#include "hxxmlprs.h"
// rmasmil
#include "smlerror.h"
#include "smlparse.h"
#include "smlelem.h"
#include "smlprstime.h"
// pndebug
#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static const char HX_THIS_FILE[] = __FILE__;
#endif

SmilTimeValue::SmilTimeValue(IUnknown* pContext, UINT32 ulStartLine,
			     CSmilElement* pElement)
: m_pContext(pContext)
, m_pElement(pElement)
, m_ulStartLine(ulStartLine)
, m_type(SmilTimeNone)
, m_position(SMILEventSourceEnd)
, m_uRepeatIteration(0)
, m_bTreatSyncArcAsEvent(FALSE)
, m_lOffset(0)
, m_lOriginalOffset(0)
, m_year(-1)
, m_month(-1)
, m_day(-1)
, m_hour(0)
, m_min(0)
, m_sec(0)
, m_ms(0)
, m_UTCOffsetMin(0)
, m_bRelativeToUTC(FALSE)
, m_pszMarkerName(NULL)
, m_bIsExternalMarker(FALSE)
, m_pszExternalMarkerFileName(NULL)
, m_pszExternalMarkerName(NULL)
, m_ulMarkerTime(0)
, m_bUsedToBeMediaMarker(FALSE)
, m_pEventName(NULL)
, m_bTimeIsResolved(FALSE)
, m_lResolvedToTime(SMILTIME_INFINITY)
, m_lWhenTimeWasResolved(SMILTIME_INFINITY)
, m_lTimeOfPause(SMILTIME_INFINITY)
{
    m_pContext->AddRef();
}

SmilTimeValue::~SmilTimeValue()
{
    HX_RELEASE(m_pContext); // /fixes mem leak.
    HX_VECTOR_DELETE(m_pszMarkerName);
    HX_VECTOR_DELETE(m_pszExternalMarkerFileName);
    HX_VECTOR_DELETE(m_pszExternalMarkerName);
    HX_VECTOR_DELETE(m_pEventName);
}

// /This returns the resolved time, including any positive offset,
// and includes only the part of a negative offset that is after
// the resolved-AT time (which is m_lWhenTimeWasResolved).  In other
// words, this returns the time at which the element will actually
// begin or end based on this SmilTimeValue:
//
// Returns HXR_OK if resolved time is returned in lEffectiveResolveTime,
// else returns HXR_FAILED.
HX_RESULT
SmilTimeValue::getEffectiveResolvedTime(REF(INT32) lEffectiveResolvedTime)
{
    HX_RESULT retval = HXR_OK;
    lEffectiveResolvedTime = SMILTIME_NEGATIVE_INFINITY;
    switch (m_type)
    {
	case SmilTimeSyncBase:
#if defined(ENABLE_SYNC_TO_PREV)
	case SmilTimeSyncToPrev:
#endif
	case SmilTimeMediaMarker:
	case SmilTimeEvent:
	{
	    if (isTimeResolved())
	    {
		// /EH- the following should *always* be true:
		HX_ASSERT(m_lResolvedToTime >= m_lWhenTimeWasResolved);
		// /Note: resolvedToTime doesn't include offset:
		lEffectiveResolvedTime = m_lResolvedToTime;
#define XXXEH_OFFSET_NOT_ALREADY_ACCOUNTED_FOR
#if defined(XXXEH_OFFSET_NOT_ALREADY_ACCOUNTED_FOR)
		if (SmilTimeEvent == m_type
			// /XXXEH- TODO: handle SmilTimeMediaMarker here, too,
			// if and when we get internal markers (event-like
			// ones) working:
			||  (isSyncBaseTimeVal()  &&  m_bTreatSyncArcAsEvent))
		{
		    INT32 lEffectiveOffset = m_lOffset;
		    if (lEffectiveOffset < 0)
		    {
			lEffectiveOffset = m_lWhenTimeWasResolved -
				m_lResolvedToTime;
			if (lEffectiveOffset < m_lOffset)
			{
			    // /Resolved-at time was well before resolved-to
			    // time minus |offset|, so the entire offset is
			    // used:
			    lEffectiveOffset = m_lOffset;
			}
		    }
		    lEffectiveResolvedTime += lEffectiveOffset;
		}
#endif
	    }
	    else
	    {
		retval = HXR_FAILED;
	    }
	}
	break;

	// /XXXEH- TODO: figure out how to handle wallclock; I think
	// it should just work the same as clock values:
	case SmilTimeWallclock:
	case SmilTimeClockValue:
	case SmilTimeOffset:
	{
	    if (isTimeResolved())
	    {
		lEffectiveResolvedTime = m_lOffset;
	    }
	    else
	    {
		retval = HXR_FAILED;
		// /All wallclock, clock, and offset values should have
		// been set as resolved at parse time:
		HX_ASSERT(isTimeResolved());
	    }
	}
	break;

	default:
	{
	    retval = HXR_FAILED;
	}
	break;
    }
    return retval;
}


HX_RESULT
SmilTimeValue::parseValue(const char* pPos,
			  SMILSyncAttributeTag nTag,
			  // /Needed for self-references:
			  const char* pThisElementID)
{
    HX_RESULT ret = HXR_OK;

    if (pPos == (const char*)NULL)
    {
	ret = HXR_FAIL;
    }
    else if (*pPos == '+' || *pPos == '-')
    {
	HX_ASSERT(nTag == SMILSyncAttrBegin);
	if (nTag == SMILSyncAttrBegin)
	{
	    ret = parseOffset(pPos);
	    m_type = SmilTimeOffset;
	    if (SUCCEEDED(ret))
	    {
		m_bTimeIsResolved = TRUE;
	    }
	}
	else
	{
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadTimeValue, pPos, 
		m_ulStartLine);
	    return ret;
	}
    }
    else if (isdigit(*pPos)
	// /Fixes PR 42592:
	// "0.5s" was OK but ".5s" was being treated as unresolved:
	||  '.' == *pPos)
    {
	ret = parseOffset(pPos);
	if (SUCCEEDED(ret))
	{
	    m_bTimeIsResolved = TRUE;
	}

	// this is actually a Clock Value if it is an end
	// time.
	if (nTag == SMILSyncAttrBegin)
	{
	    m_type = SmilTimeOffset;
	}
	else // nTag == SMILSyncAttrEnd
	{
	    m_type = SmilTimeClockValue;
	}
    }
#if defined(ENABLE_SYNC_TO_PREV)
    else if (strncmp(pPos, "prev", 4) == 0)
    {
	// syncToPrev-value
	ret = parseSyncToPrev(pPos);
    }
#endif
    else if (strncmp(pPos, "wallclock", 9) == 0)
    {
	// wallclock-sync-value
	ret = parseWallClockValue(pPos);
    }
    else
    {
	const char* pDotPos = NULL;
	BOOL bEscape = FALSE;

	UINT32 len = strlen(pPos);
	char* idref = new char [len+1];
	char* idrefPos = idref;
	*idrefPos = '\0';
	char* command = new char [len+1];
	char* commandPos = command;
	*commandPos = '\0';

	const char* pOffset = NULL;

	enum { IDREF, ESCAPE, COMMAND, END, OFFSET } state = IDREF;

	const char* pBegin = pPos;

	while (*pPos)
	{
	    switch (state)
	    {
	    case ESCAPE:
		*idrefPos++ = *pPos;
		state = IDREF;
		break;
	    case IDREF:
		if (*pPos == '\\')
		{
		    // /Don't include escape char in idref; go to next char:
		    state = ESCAPE;
		}
		else if (*pPos == '.')
		{
		    *idrefPos = '\0';
		    state = COMMAND;
		}
		else if (isspace(*pPos))
		{
		    *idrefPos = '\0';
		    state = END;
		}
		else if (*pPos == '+' || *pPos == '-')
		{
		    *idrefPos++ = '\0';
		    pOffset = pPos;
		    state = OFFSET;
		}
		else
		{
		    *idrefPos++ = *pPos;
		}
		break;
	    case COMMAND:
	        if (isspace(*pPos))
		{
		    *commandPos = '\0';
		    state = END;
		}
		else if (*pPos == '+' || *pPos == '-')
		{
		    pOffset = pPos;
		    *commandPos = '\0';
		    state = OFFSET;
		}
		else
		{
		    *commandPos++ = *pPos;
		}
		break;
	    case END:
		if (*pPos == '+' || *pPos == '-')
		{
		    pOffset = pPos;
		    state = OFFSET;
		}
		break;
	    case OFFSET:
		break;
	    }
	    ++pPos;
	}

	if (state == COMMAND)
	{
	    *commandPos = '\0';
	}
	else if (state == IDREF || state == ESCAPE)
	{
	    *idrefPos = '\0';
	}

        // XXXMEH - if this is an animation element, then we
        // will handle the ".repeat(x)" syntax differently than
        // we treat sources. We will treat ".repeat(x)" as an
        // event rather than a sync-arc.
        BOOL bIsAnimate = FALSE;
        if (m_pElement && m_pElement->m_pNode &&
            (m_pElement->m_pNode->m_tag == SMILAnimate       ||
             m_pElement->m_pNode->m_tag == SMILSet           ||
             m_pElement->m_pNode->m_tag == SMILAnimateMotion ||
             m_pElement->m_pNode->m_tag == SMILAnimateColor))
        {
            bIsAnimate = TRUE;
        }
	if (*command == '\0')
	{
	    // /The SMIL Boston draft supports the fact that the name of the
	    // event ("Event-symbol") is required and is what is used when
	    // no "Eventbase-element." precedes it.  Thus, we need to
	    // treat the idref as the command string and use this 
	    // element's id for the idref:
	    // /HOWEVER, for PR 69849 (needed for PR 58290) fix, if event is
	    // "accesskey(...)", then the event is not self-generated and is
	    // not raised for another element, so give it a fake id here so
	    // special-case code isn't needed in multiple places, elsewhere:
	    if (!strncmp(idref, "accesskey(", 10))
	    {
		ret = parseEvent(
			// /Use this string (an invalid XML id) as the fake ID:
			ACCESSKEY_EVENT_FAKE_ID_STRING, // /fake ID.
			idref, // /<==actually the command (i.e., Event-symbol)
			pOffset);
	    }
	    else
	    {
		ret = parseEvent(pThisElementID,
			idref, // /<==actually the command (i.e., Event-symbol)
			pOffset);
	    }
	}
	else if (strcmp(command, "begin") == 0 ||
#if defined(XXXEH_REPEAT_VALUE_TIMING_SHOULD_BE_EVENT_BASED)
#else
		((0 == strncmp(command, "repeat(", 7)) && !bIsAnimate)  ||
#endif
		 strcmp(command, "end") == 0)
	{
			// base, memeber, offset
	    ret = parseSyncBase(idref, command, pOffset);
	}
	else if (strncmp(command, "marker(", 7) == 0)
	{
	    ret = parseMarker(idref, command, pOffset);
	}
	else // /everything else is an event (i.e., "Event-symbol") name:
	{
	    ret = parseEvent(idref, command, pOffset);
	}

	HX_VECTOR_DELETE(command);
	HX_VECTOR_DELETE(idref);
    }

    // /We need to make sure we set scheduled time values' resolved-to times
    // in case these times are used AFTER the element has already begun
    // playing or finished playing:
    if (SmilTimeOffset == m_type   || 
	SmilTimeClockValue == m_type  ||
	SmilTimeWallclock == m_type)
    {
	setResolvedToTime(0);
	setWhenTimeWasResolved(0);
    }

    return ret;
}

HX_RESULT
SmilTimeValue::setTimeOffset(time_t tRefTime)
{
    HX_RESULT ret = HXR_OK;
    if (m_type == SmilTimeWallclock)
    {
	struct tm ourtime;

	ourtime.tm_sec = m_sec; 
	ourtime.tm_min = m_min;
	ourtime.tm_hour = m_hour;
	if (m_day != -1 && m_year != -1 && m_month != -1)
	{
	    ourtime.tm_mday = m_day;
	    // /tm::tm_mon is 0-based, while m_month is 1-based, so we need
	    // to subtract 1 (and our parser already made sure m_month is
	    // greater than 0 and less than 13):
	    ourtime.tm_mon = m_month - 1;
	    ourtime.tm_year = m_year - 1900;
	}
	else
	{
	    time_t thetime;
	    time(&thetime);
	    struct tm* cur;
	    cur = localtime(&thetime);
	    ourtime.tm_mday = cur->tm_mday;
	    ourtime.tm_mon = cur->tm_mon;
	    ourtime.tm_year = cur->tm_year;
	    ourtime.tm_isdst = cur->tm_isdst;
	}

	time_t thisTime = mktime(&ourtime);

	double diff = 0.0;
	if (m_bRelativeToUTC)
	{
//XXXJHUG need a crossplatform way to get the timezone offset.
#ifdef _WIN32
	    diff = difftime(thisTime + _timezone, tRefTime + m_UTCOffsetMin);
#else
	    diff = difftime(thisTime, tRefTime + m_UTCOffsetMin);
#endif
	}
	else
	{
	    diff = difftime(thisTime, tRefTime);
	}

        double dDiffMS  = diff * 1000.0;
        double dMaxDiff = (double) 0x7FFFFFFF;
        double dMinDiff = -dMaxDiff; 

        // Clip at the min and max 
        if (dDiffMS > dMaxDiff)
        {
            dDiffMS = dMaxDiff - 10;
        }
        else if (dDiffMS < dMinDiff)
        {
            dDiffMS = dMinDiff +10;
        }

        // Now convert to INT32
        m_lOffset = (INT32) dDiffMS;
	m_lOriginalOffset = m_lOffset;
    }

    return ret;
}

HX_RESULT
SmilTimeValue::parseOffset(const char* pCh)
{
    HX_RESULT pnretval = HXR_OK;
    // eat whitespace
    while (*pCh && isspace(*pCh))
    {
	++pCh;
    }

    if (*pCh == '-')
    {
	// /Eat any whitespace following the '-'
	do
	{
	    ++pCh;
	} while (*pCh  &&  isspace(*pCh));
	UINT32 clockTime = 0;
	pnretval = parseClockValue(pCh, clockTime);
	m_lOffset = -(INT32)clockTime;
    }
    else if (*pCh == '+')
    {
	// /Eat any whitespace following the '+'
	do
	{
	    ++pCh;
	} while (*pCh  &&  isspace(*pCh));
	UINT32 clockTime = 0;
	pnretval = parseClockValue(pCh, clockTime);
	m_lOffset = (INT32)clockTime;
    }
    else
    {
	UINT32 clockTime = 0;
	pnretval = parseClockValue(pCh, clockTime);
	m_lOffset = (INT32)clockTime;
    }
    m_lOriginalOffset = m_lOffset;

    return pnretval;
}

HX_RESULT
SmilTimeValue::parseClockValue(const char* pValue, 
			       UINT32& ulTimeValue)
{
    if (!pValue  ||  strlen(pValue) < 1)
    {
	return HXR_FAILED;
    }
    // try npt
    char* pPtr = (char *)strstr(pValue, "npt=");
    if(pPtr)
    {
	pPtr += 4;  // point to beginning of clock value
	NPTime clockTime(pPtr);
	ulTimeValue = (UINT32)clockTime;
	return HXR_OK;
    }
    // try smpte
    pPtr = (char *)strstr(pValue, "smpte=");
    if(pPtr)
    {
	pPtr += 6;  // point to beginning of clock value
	SMPTETimeCode clockTime(pPtr);
	ulTimeValue = (UINT32)clockTime;
	return HXR_OK;
    }
    pPtr = (char *)strstr(pValue, "smpte-30-drop=");
    if(pPtr)
    {
	pPtr += 14;  // point to beginning of clock value
	SMPTETimeCode clockTime(pPtr);
	ulTimeValue = (UINT32)clockTime;
	return HXR_OK;
    }
    pPtr = (char *)strstr(pValue, "smpte-25=");
    if(pPtr)
    {
	pPtr += 9;  // point to beginning of clock value
	SMPTETimeCode clockTime;
        clockTime.m_framesPerSec = SMPTETimeCode::FPS_25;
        clockTime.fromString(pPtr);
	ulTimeValue = (UINT32)clockTime;
	return HXR_OK;
    }
    else
    {
	BOOL bAllow_h_min_s_ms_units = TRUE;
	BOOL bSucceeded = FALSE;
	// /First, get rid of trailing spaces:
	UINT32 ulStrlen = (UINT32)strlen(pValue);
	char* pEnd = (char*)(&pValue[ulStrlen-1]);
	while (isspace(*pEnd)  &&  pEnd>pValue)
	{
	    pEnd--;
	}
	pEnd[1] = '\0';
	// /This first tries just hh:mm:ss with no prefix/suffix
	// and then tries suffixes: {"h" | "min" | "s" | "ms"} with
	// the default (no ":" inside and no prefix or suffix) being
	// in seconds per the SMIL 1.0 spec ([SMIL 1.0 compliance]
	// Fixes PR 22673):
	NPTime clockTime(pValue, bAllow_h_min_s_ms_units, bSucceeded);
	if (bSucceeded)
	{
	    ulTimeValue = (UINT32)clockTime;
	    return HXR_OK;
	}
    }

    return HXR_FAIL;
}


HX_RESULT
SmilTimeValue::parseEvent(const char* pBase, const char* pEvent, 
			  const char* pOffset)
{
    HX_RESULT rslt = HXR_OK;

    m_type = SmilTimeEvent;
    UINT32 ulLen = 0;
    if (pBase)
    {
	ulLen = strlen(pBase);
	m_idRef = pBase;
    }
    
    if( !pEvent  ||  !strlen(pEvent))
    {
	// /XXXEH- not sure why we'd ever get this state so put assert here
	// to check it out:
	HX_ASSERT(pEvent  &&  strlen(pEvent));
	m_pEventName = NULL;
	rslt = HXR_UNEXPECTED; // /XXXEH- at least I *think* it's unexpected.
    }
    else
    {
	// /Handle "foo.\xyz" as meaning the "xyz" event that may have the
	// same name as a sync-arc, e.g., "foo.begin".  Treat them as
	// events that get resolved when the m_idRef element (renderer)
	// fires off a an event with the matching name:
	if ('\\' == *pEvent  &&  strlen(pEvent)>=2)
	{
	    // /Add -1 for removing the  '\', +1 for the terminating '\0':
	    m_pEventName = new char[strlen(pEvent)-1 + 1];
	    if (m_pEventName)
	    {
		strcpy(m_pEventName, &pEvent[1]); /* Flawfinder: ignore */
	    }
	    else
	    {
		rslt = HXR_OUTOFMEMORY;
	    }
	}
	else
	{
	    m_pEventName = new char[strlen(pEvent)+1];
	    if (m_pEventName)
	    {
		strcpy(m_pEventName, pEvent); /* Flawfinder: ignore */
	    }
	    else
	    {
		rslt = HXR_OUTOFMEMORY;
	    }
	}
    }

    if (SUCCEEDED(rslt)  &&  pOffset && *pOffset)
    {
	parseOffset(pOffset);
    }
    return rslt;
}

HX_RESULT
SmilTimeValue::parseSyncBase(const char* pBase, const char* pCommand, 
			     const char* pOffset)
{
    m_type = SmilTimeSyncBase;
    UINT32 ulLen = strlen(pBase);
    if (pBase  &&  pCommand)
    {
	m_idRef = pBase;
    }
    else
    {
	HX_ASSERT(FALSE);
	return HXR_FAIL;
    }

    BOOL bTimeValueOK = FALSE;
    BOOL bRepeatValueParsed = FALSE;
    if (strncmp(pCommand, "begin", 5) == 0)
    {
	m_position = SMILEventSourceBegin;
	bTimeValueOK = TRUE;
    }
    else if (strncmp(pCommand, "end", 3) == 0)
    {
	m_position = SMILEventSourceEnd;
	bTimeValueOK = TRUE;
    }
#if defined(XXXEH_REPEAT_VALUE_TIMING_SHOULD_BE_EVENT_BASED)
#else
    else if (strncmp(pCommand, "repeat(", 7) == 0)
    {
	bRepeatValueParsed = TRUE;
	INT32 strlenCommand = strlen(pCommand);
	if (strlenCommand >= 9  &&  ')' == pCommand[strlenCommand-1])
	{
	    m_position = SMILEventSourceBegin;
	    char* pTmp = (char*)&pCommand[7];
	    // /Make sure that every character inside the () is a number:
	    while (*pTmp  &&  ')' != *pTmp  &&  '0'<=*pTmp  &&  '9'>=*pTmp)
	    {
		pTmp++;
	    }
	    if (pTmp == (char*)&pCommand[strlenCommand-1])
	    {
		INT32 lNum = (INT32)atoi(&pCommand[7]);
		// /0 or negative repeat iterations are not allowed, even
		// though repeat(0) *could* have been interpreted as the
		// first play of foo (prior to it repeating), but wasn't:
		if (lNum > 0)
		{
		    *pTmp = '\0';
		    m_idRef += "_repeat_copy_";
		    m_idRef += &pCommand[7];
		    *pTmp = ')'; // /restore it.
		    m_uRepeatIteration = (UINT16)lNum;
		    bTimeValueOK = TRUE;
		}
	    }
	}
    }
#endif
    if (!bTimeValueOK)
    {
	if (!bRepeatValueParsed)
	{
	    HX_ASSERT(FALSE);
	}
	CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	errHandler.ReportError(SMILErrorBadTimeValue, pCommand, 
	    m_ulStartLine);
	return HXR_FAIL;
    }

    if (pOffset)
    {
	parseOffset(pOffset);
    }

    return HXR_OK;
}

HX_RESULT SmilTimeValue::parseMarker(const char* pBase, const char* pMarker, const char* pOffset)
{
    HX_RESULT retVal = HXR_FAIL;

    if (pBase && pMarker)
    {
        // Set the type
        m_type = SmilTimeMediaMarker;
        // Set the base
        m_idRef = pBase;
        // Set the offset (if we have one)
        if (pOffset)
        {
            parseOffset(pOffset);
        }
        // Move past the "marker.(", which we already
        // checked for before we were called
        pMarker += 7;
        // Allocate space for our string
        HX_VECTOR_DELETE(m_pszMarkerName);
        m_pszMarkerName = new char [strlen(pMarker) + 1];
        if (m_pszMarkerName)
        {
            // Look for the end of the marker name
            char* pEnd = (char *)strchr(pMarker, ')');
            if (pEnd)
            {
                // Copy the full marker name - might or
                // might not contain an external file reference
                strncpy(m_pszMarkerName, pMarker, pEnd - pMarker); /* Flawfinder: ignore */
                m_pszMarkerName[pEnd - pMarker] = '\0';
                // Now parse to see if there is an external file reference
                CHXString cMarker;
                CHXString cExtFileName;
                BOOL      bIsExternalMarker = m_bIsExternalMarker;
                retVal = CSmilParser::parseMarkerURI(m_pszMarkerName,
                                                     cMarker,
                                                     bIsExternalMarker,
                                                     cExtFileName);
                m_bIsExternalMarker = bIsExternalMarker;
                if (SUCCEEDED(retVal) && m_bIsExternalMarker)
                {
                    // Set the flag in the element that this
                    // element references an external media marker file
                    m_pElement->m_bUsesExternalMediaMarkerFile = TRUE;
                    // We just need to copy the marker string and
                    // the external file name string into our members
                    HX_VECTOR_DELETE(m_pszExternalMarkerFileName);
                    m_pszExternalMarkerFileName = new char [cExtFileName.GetLength() + 1];
                    if (m_pszExternalMarkerFileName)
                    {
                        strcpy(m_pszExternalMarkerFileName, (const char*) cExtFileName); /* Flawfinder: ignore */
                        HX_VECTOR_DELETE(m_pszExternalMarkerName);
                        m_pszExternalMarkerName = new char [cMarker.GetLength() + 1];
                        if (m_pszExternalMarkerName)
                        {
                            strcpy(m_pszExternalMarkerName, (const char*) cMarker); /* Flawfinder: ignore */
                        }
                        else
                        {
                            retVal = HXR_OUTOFMEMORY;
                        }
                    }
                    else
                    {
                        retVal = HXR_OUTOFMEMORY;
                    }
                }
            }
        }
    }

    if (FAILED(retVal))
    {
	HX_ASSERT(FALSE);
	CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	errHandler.ReportError(SMILErrorBadTimeValue, pMarker, m_ulStartLine);
    }

    return retVal;
}


#if defined(ENABLE_SYNC_TO_PREV)
HX_RESULT
SmilTimeValue::parseSyncToPrev(REF(const char*) pCh)
{
    HX_RESULT ret = HXR_OK;
    // move past "prev."
    pCh += 5;
    m_type = SmilTimeSyncToPrev;
    if (strncmp(pCh, "begin", 5) == 0)
    {
	m_position = SMILEventSourceBegin;
	pCh += 5;
    }
    else if (strncmp(pCh, "end", 3) == 0)
    {
	m_position = SMILEventSourceEnd;
	pCh += 3;
    }
    else
    {
	HX_ASSERT(FALSE);
	CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	errHandler.ReportError(SMILErrorBadTimeValue, pCh, 
	    m_ulStartLine);
	return HXR_FAIL;
    }

    // eat white space
    while (*pCh && isspace(*pCh))
    {
	++pCh;
    }

    if (*pCh == '+' || *pCh == '-')
    {
	ret = parseOffset(pCh);
    }
    
    return ret;
}
#endif


HX_RESULT
SmilTimeValue::parseWallClockValue(REF(const char*) pCh)
{
    HX_RESULT ret = HXR_OK;
    m_type = SmilTimeWallclock;
    m_bTimeIsResolved = TRUE;
    // move past "wallclock("
    pCh += 10;

    // eat whitespace.
    while (*pCh && isspace(*pCh))
    {
	++pCh;
    }

    // find the end ')'

    const char* begin = pCh;
    const char* end = NULL;
    const char* pT = NULL;
    const char* pTimeZone = NULL;
    const char* pTimePos = NULL;
    const char* pDatePos = NULL;

    INT32 year = -1;
    INT32 month = -1;
    INT32 day = -1;

    INT32 hour = 0;
    INT32 min = 0;
    INT32 sec = 0;
    INT32 ms = 0;

    char buf[10]; /* Flawfinder: ignore */
    
    // store offset in min.
    INT32 UTCOffset = 0;

    while (*pCh)
    {
	if (*pCh == ')')
	{
	    end = pCh;
	}
	else if (*pCh == 'T')
	{
	    pT = pCh;
	}
	else if (isspace(*pCh) || *pCh == '+' || *pCh == '-' || *pCh == 'Z')
	{
	    // this will find the last +, - or Z... which is what we want.
	    pTimeZone = pCh;
	}
	else if (!isdigit(*pCh) && *pCh != ':' && *pCh != '-' && *pCh != '.')
	{
	    ret = HXR_FAIL;

	}
	++pCh;
    }

    if (FAILED(ret))
    {
	CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	errHandler.ReportError(SMILErrorBadWallClockValue, begin, 
	    m_ulStartLine);
	goto cleanup;
    }

    // check for a 'T'
    if (pT)
    {
	//YYYY-MM-DDT
	if (*(begin+4) == '-' && *(begin+7) == '-' && pT == (begin+10))
	{
	    pDatePos = begin;
	    if (pTimeZone < begin+10)
	    {
		pTimeZone = NULL;
	    }
	}
	else
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, begin, 
		m_ulStartLine);
	    goto cleanup;
	}

	if (*(pT+3) == ':')
	{
	    pTimePos = pT+1;
	}
	else
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, begin, 
		m_ulStartLine);
	    goto cleanup;
	}
    }
    // else just the date or time.
    //YYYY-MM-DDT
    else if (*(begin+4) == '-' && *(begin+7) == '-' && pT == (begin+10))
    {
	// just date
	// there is a date.
	pDatePos = begin;
	if (pTimeZone < begin+10)
	{
	    pTimeZone = NULL;
	}
    }
    else if (*(begin+2) == ':')
    {
	pTimePos = begin;
    }
    else
    {
	ret = HXR_FAIL;
	CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	errHandler.ReportError(SMILErrorBadWallClockValue, begin, 
	    m_ulStartLine);
	goto cleanup;
    }

    // there is a date.
    if (pDatePos)
    {
	//YYYY-MM-DDT
	const char* pos = pDatePos;
	strncpy(buf, pos, 4); /* Flawfinder: ignore */
	buf[4] = '\0';
	pos += 5;
	year = HX_SAFEINT(strtol(buf, 0, 10));
	if (year < 0 || year > 9999)
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, pDatePos, 
		m_ulStartLine);
	    goto cleanup;
	}

	strncpy(buf, pos, 2); /* Flawfinder: ignore */
	buf[2] = '\0';
	pos += 3;
	month = HX_SAFEINT(strtol(buf, 0, 10));
	// /month can't be 0 or less or greater than 12 (i.e., it's
	// 1-based):
	if (month < 1 || month > 12)
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, pDatePos, 
		m_ulStartLine);
	    goto cleanup;
	}


	strncpy(buf, pos, 2); /* Flawfinder: ignore */
	buf[2] = '\0';
	pos += 3;
	day = HX_SAFEINT(strtol(buf, 0, 10));
	if (day < 0 || day > 31)
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, pDatePos, 
		m_ulStartLine);
	    goto cleanup;
	}

    }

    if (pTimePos)
    {
    //HH:MM...
	const char* pos = pTimePos;
	strncpy(buf, pos, 2); /* Flawfinder: ignore */
	buf[2] = '\0';
	pos += 3;
	hour = HX_SAFEINT(strtol(buf, 0, 10));
	if (hour < 0 || hour > 24)
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, pTimePos, 
		m_ulStartLine);
	    goto cleanup;
	}

	// just time

	strncpy(buf, pos, 2); /* Flawfinder: ignore */
	buf[2] = '\0';
	pos += 3;
	min = HX_SAFEINT(strtol(buf, 0, 10));
	if (min < 0 || min > 59)
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, pTimePos, 
		m_ulStartLine);
	    goto cleanup;
	}

	if (*(pos-1) == ':')
	{
	    strncpy(buf, pos, 2); /* Flawfinder: ignore */
	    buf[2] = '\0';
	    pos +=3;
	    sec = HX_SAFEINT(strtol(buf, 0, 10));
	    if (sec < 0 || sec > 59)
	    {
		ret = HXR_FAIL;
		CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
		errHandler.ReportError(SMILErrorBadWallClockValue, pTimePos, 
		    m_ulStartLine);
		goto cleanup;
	    }

	    if (*(pos-1) == '.')
	    {
		// find end.
		UINT32 len = 0;
		if (pTimeZone)
		{
		    len = pTimeZone - pos;
		}
		else
		{
		    len = end - pos;
		}
		strncpy(buf, pos, len); /* Flawfinder: ignore */
		buf[len] = '\0';
		pos += len;
		if (*buf)
		{
		    if (isdigit(*buf))
		    {
			ms = (*buf - '0') * 100;
			if (isdigit(*(buf+1)))
			{
			    ms += (*buf - '0') * 10;
			    if (isdigit(*(buf+2)))
			    {
				ms += (*buf - '0');
			    }
			}
		    }
		    else
		    {
			ret = HXR_FAIL;
			CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
			errHandler.ReportError(SMILErrorBadWallClockValue, pTimePos, 
			    m_ulStartLine);
			goto cleanup;
		    }
		}
	    }
	}
    }

    if (pTimeZone)
    {
	BOOL bSyntaxOK = FALSE;
	const char* begin = pTimeZone;

	if (*pTimeZone == 'Z')
	{
	    m_bRelativeToUTC = TRUE;
	    UTCOffset = 0;
	    bSyntaxOK = TRUE;
	}
	else if ((*pTimeZone == '+' || *pTimeZone == '-') && *(pTimeZone+3) == ':')
	{
	    m_bRelativeToUTC = TRUE;
	    int sign = 1;
	    if (*pTimeZone == '-')
	    {
		++pTimeZone;
		sign = -1;
	    }
	    else if (*pTimeZone == '+')
	    {
		++pTimeZone;
	    }
	    strncpy(buf, pTimeZone, 2); /* Flawfinder: ignore */
	    buf[2] = '\0';
	    pTimeZone += 3;
	    INT32 hour = HX_SAFEINT(strtol(buf, 0, 10));
	    if (hour < 0 || hour > 12)
	    {
		ret = HXR_FAIL;
		CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
		errHandler.ReportError(SMILErrorBadWallClockValue, begin, 
		    m_ulStartLine);
		goto cleanup;
	    }
	    UTCOffset = hour * 60;

	    strncpy(buf, pTimeZone, 2); /* Flawfinder: ignore */
	    buf[2] = '\0';
	    pTimeZone += 3;

	    INT32 min = HX_SAFEINT(strtol(buf, 0, 10));
	    if (min < 0 || min > 59)
	    {
		ret = HXR_FAIL;
		CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
		errHandler.ReportError(SMILErrorBadWallClockValue, begin, 
		    m_ulStartLine);
		goto cleanup;
	    }
	    UTCOffset += min;
	    UTCOffset *= sign;
	    bSyntaxOK = TRUE;
	}
	else if (isspace(*pTimeZone))
	{
	    bSyntaxOK = TRUE;
	    do
	    {
		*pTimeZone++;
		if (!(*pTimeZone)  ||  ')' == *pTimeZone)
		{
		    break;
		}
		if (!isspace(*pTimeZone))
		{
		    bSyntaxOK = FALSE;
		    break;
		}
	    } while (*pTimeZone);
	}

	if (!bSyntaxOK)
	{
	    ret = HXR_FAIL;
	    CSmilSMILSyntaxErrorHandler errHandler(m_pContext);
	    errHandler.ReportError(SMILErrorBadWallClockValue, begin, 
		m_ulStartLine);
	    goto cleanup;
	}
    }
    // else use the -1 value.

    m_UTCOffsetMin = (INT16)UTCOffset;
    m_ms = (UINT16)ms;
    m_sec = (UINT8)sec;
    m_min = (UINT8)min;
    m_hour = (UINT8)hour;
    m_day = (INT8)day;
    m_month = (INT8)month;
    m_year = (INT16)year;

cleanup:
    if (SUCCEEDED(ret))
    {
	m_bTimeIsResolved = TRUE;
    }

    return ret;
}

void SmilTimeValue::setIsTimeResolved(BOOL bIsResolved)
{
    m_bTimeIsResolved = bIsResolved;
}

HX_RESULT
SmilTimeValue::setPauseTime(LONG32 lTimeOfPause)
{
    HX_RESULT pnr = HXR_OK;
    if (!isResumeEvent())
    {
	pnr = HXR_UNEXPECTED;
    }
    else
    {
	m_lTimeOfPause = lTimeOfPause;
    }

    return pnr;
}



BOOL
SmilTimeValue::isSameTimeValue(SmilTimeValue* pOtherTimeVal)
{
    BOOL bIsSame = FALSE;

    // /Using this fixes freeze-up in use of strcmp(NULL,...) [PR 65817]
    // found while fixing Interop Timing #18.37 which has begin="0s;7s" :
    BOOL bHasSameEventNameOrBothHaveNoEventName = 
	    (NULL == m_pEventName  &&  NULL == pOtherTimeVal->m_pEventName);
    if (!bHasSameEventNameOrBothHaveNoEventName)
    {
	if (NULL != m_pEventName  &&  NULL != pOtherTimeVal->m_pEventName)
	{
	    bHasSameEventNameOrBothHaveNoEventName =
		    !strcmp(m_pEventName, pOtherTimeVal->m_pEventName);
	}
    }

    // /Now compare type, timing, event name, ...etc:
    if (pOtherTimeVal == this)
    {
	bIsSame = TRUE;
    }
    else if (pOtherTimeVal  &&
	    m_pElement == pOtherTimeVal->m_pElement  &&
	    m_type == pOtherTimeVal->m_type  &&
	    m_position == pOtherTimeVal->m_position  &&
	    m_uRepeatIteration == pOtherTimeVal->m_uRepeatIteration  &&
	    m_pszMarkerName == pOtherTimeVal->m_pszMarkerName  &&
	    bHasSameEventNameOrBothHaveNoEventName  &&
	    m_lOffset == pOtherTimeVal->m_lOffset  &&
	    m_bTimeIsResolved == pOtherTimeVal->m_bTimeIsResolved)
    {
	// /Note: if time isn't resolved, we don't care if times are same:
	if (m_bTimeIsResolved  &&
		m_lResolvedToTime  == pOtherTimeVal->m_lResolvedToTime)
	{
	    bIsSame = TRUE;
	}
	else
	{
	    switch (m_type)
	    {
		case SmilTimeOffset:
		case SmilTimeClockValue:
		case SmilTimeWallclock:
		case SmilTimeNone:
		{
		    bIsSame = TRUE;
		}
		break;

		case SmilTimeSyncBase:
#if defined(ENABLE_SYNC_TO_PREV)
		case SmilTimeSyncToPrev:
#endif
		case SmilTimeMediaMarker:
		case SmilTimeEvent:
		{
		    HX_ASSERT(m_idRef.GetLength());
		    HX_ASSERT(pOtherTimeVal->m_idRef.GetLength());
		    bIsSame = !strcmp(m_idRef, pOtherTimeVal->m_idRef);
		}
		break;
	    }
	}
    }

    return bIsSame;
}


BOOL
SmilTimeValue::deferUntil(LONG32 lNewStartTime)
{
    BOOL bSucceeded = FALSE;

    if (m_bTimeIsResolved  &&  m_lResolvedToTime < lNewStartTime)
    {
	// /XXXEH- TODO: determine if it's necessary to have a separate
	// variable that keeps track of the deferred amount, and that is
	// reset to 0 if and when this time is used to begin the element.
	// This might be needed if this SmilTimeValue gets reused due to
	// a repeat, seek, or ???
	switch (m_type)
	{
	    case SmilTimeWallclock:
	    case SmilTimeClockValue:
	    case SmilTimeOffset:
	    {
		// /Fixes PR 55936.  Add the amount deferred by to the
		// offset instead of just setting offset to newStartTime:
		m_lOffset += (lNewStartTime - m_lResolvedToTime);
		bSucceeded = TRUE;
	    }
	    break;

	    case SmilTimeSyncBase:
#if defined(ENABLE_SYNC_TO_PREV)
	    case SmilTimeSyncToPrev:
#endif
	    case SmilTimeMediaMarker:
	    case SmilTimeEvent:
	    {
		m_lResolvedToTime = lNewStartTime;
		bSucceeded = TRUE;
	    }
	    break;

	    default:
		HX_ASSERT(0);
	    break;
	}
    }

    return (bSucceeded);
}

void SmilTimeValue::setMarkerTime(UINT32 ulTime)
{
    // Only do this if either we aren't resolved
    if (!m_bTimeIsResolved && m_type == SmilTimeMediaMarker)
    {
        // Save our marker time
        m_ulMarkerTime = ulTime;
        // This time is an offset from the beginning
        // of the media. We need to add this offset to
        // any previously existing offset
        m_lOffset += (INT32) ulTime;
        // Convert ourselves to a syncbase and
        // set the flag which will tell us that
        // we used to be a media marker.
        m_type = SmilTimeSyncBase;
        m_bUsedToBeMediaMarker = TRUE;
        // By definition we are an offset from the beginning
        // of the media
        m_position = SMILEventSourceBegin;
        // Set the flag saying we are resolved
        m_bTimeIsResolved = TRUE;
    }
}
