/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: fxmanagr.cpp,v 1.1.24.1 2004/07/09 01:51: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
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxresult.h"
#include "hxcom.h"
#include "hxwin.h"
#include "ihxpckts.h"
#include "hxvsurf.h"
#include "hxfiles.h"
#include "hxerror.h"

#ifdef _MACINTOSH
#include "hxwin.h"	/* for _HXFocusContext; needed for other platforms too? */
#endif

// pncont
#include "hxslist.h"
#include "hxmap.h"

// pnmisc
#include "unkimp.h"
#include "baseobj.h"

// pxcomlib
#include "pxcolor.h"
#include "pxrect.h"
#include "pxeffect.h"
#include "pximage.h"
#include "pxcmpmgr.h"
#include "pxrndcod.h"
#include "rpeffect.h"
#include "pxtime.h"

// pxrend
#include "hlinkmgr.h"
#include "imghelpr.h"
#include "rncodmgr.h"
#include "imagemgr.h"
#include "fxseshun.h"
#include "fxpkgmgr.h"
#include "fxmanagr.h"

// pndebug
#include "errdbg.h"
#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE     
static char HX_THIS_FILE[] = __FILE__;
#endif

BEGIN_INTERFACE_LIST(PXEffectsManager)
END_INTERFACE_LIST

PXEffectsManager::PXEffectsManager()
{
    m_pErrorMessages           = NULL;
    m_pImageManager            = NULL;
    m_pEffectsPackageManager   = NULL;
    m_pHyperlinkManager        = NULL;
    m_pEffectsList             = NULL;
    m_pEffectSessionList       = NULL;
    m_pPostDurationList        = NULL;
    m_pFadeLUT                 = NULL;
    m_pAlphaLUT                = NULL;
    m_bInitialized             = FALSE;
    m_bRedrawForced            = FALSE;
    m_ulNextEffectHandle       = 1;
    m_pLastEffect              = NULL;
    m_bCheckIndefiniteDuration = FALSE;
    m_bBackChannelNotify       = FALSE;
    m_ulBackChannelHandle      = 0;
    m_bIsLive                  = FALSE;
    ResetDamage();
}

PXEffectsManager::~PXEffectsManager()
{
    HX_RELEASE(m_pErrorMessages);
    HX_RELEASE(m_pImageManager);
    HX_RELEASE(m_pEffectsPackageManager);
    HX_RELEASE(m_pHyperlinkManager);
    DeleteEffectsList();
    DeleteEffectSessionList();
    DeletePostDurationList();
    HX_DELETE(m_pEffectsList);
    HX_DELETE(m_pEffectSessionList);
    HX_DELETE(m_pPostDurationList);
    HX_VECTOR_DELETE(m_pFadeLUT);
    HX_VECTOR_DELETE(m_pAlphaLUT);
    HX_RELEASE(m_pLastEffect);
}

HX_RESULT PXEffectsManager::Init(IHXErrorMessages*       pErrorMessages,
                                 PXImageManager*          pImageManager,
                                 PXEffectsPackageManager* pEffectsPackageManager,
                                 PXHyperlinkManager*      pHyperlinkManager)
{
    HX_RESULT retVal = HXR_OK;

    if (pImageManager)
    {
        // Reset the damage rect
        ResetDamage();

        // Reset the next effect handle
        m_ulNextEffectHandle = 1;

        // Save a copy of the IHXErrorMessages interface, if present
        HX_RELEASE(m_pErrorMessages);
        if (pErrorMessages)
        {
            m_pErrorMessages = pErrorMessages;
            m_pErrorMessages->AddRef();
        }

        // Save a copy of the image manager
        HX_RELEASE(m_pImageManager);
        m_pImageManager = pImageManager;
        m_pImageManager->AddRef();

        // Save a copy of the effects package manager
        HX_RELEASE(m_pEffectsPackageManager);
        m_pEffectsPackageManager = pEffectsPackageManager;
        m_pEffectsPackageManager->AddRef();

        // Save a copy of the hyperlink manager
        HX_RELEASE(m_pHyperlinkManager);
        m_pHyperlinkManager = pHyperlinkManager;
        m_pHyperlinkManager->AddRef();

        // Create the effect list
        DeleteEffectsList();
        HX_DELETE(m_pEffectsList);
        m_pEffectsList = new CHXSimpleList();
        if (m_pEffectsList)
        {
            // Create the effect session list
            DeleteEffectSessionList();
            HX_DELETE(m_pEffectSessionList);
            m_pEffectSessionList = new CHXSimpleList();
            if (m_pEffectSessionList)
            {
                // Create the post-duration list
                DeletePostDurationList();
                HX_DELETE(m_pPostDurationList);
                m_pPostDurationList = new CHXSimpleList();
                if (m_pPostDurationList)
                {
                    // Set up parameters for LUTs
                    m_ulLUTWidth      = 1 << m_pImageManager->GetMaxChannelDepth();
                    UINT32 ulNumBytes = m_ulLUTWidth * (kEffectLUTGranularity + 1);

                    // Allocate normal fade LUT
                    HX_VECTOR_DELETE(m_pFadeLUT);
                    m_pFadeLUT = new BYTE [ulNumBytes];
                    if (m_pFadeLUT)
                    {
                        // Set up the fade LUT
                        BYTE* pLUT = m_pFadeLUT;
                        for (UINT32 ulFrac = 0; ulFrac <= kEffectLUTGranularity; ulFrac++)
                        {
                            for (UINT32 ulPixVal = 0; ulPixVal < m_ulLUTWidth; ulPixVal++)
                            {
                                *pLUT++ = (BYTE) ((ulPixVal * ulFrac) / kEffectLUTGranularity);
                            }
                        }
                        // Allocate the alpha LUT
                        //
                        HX_VECTOR_DELETE(m_pAlphaLUT);
                        m_pAlphaLUT = new BYTE [256 * 256];
                        if (m_pAlphaLUT)
                        {
                            // Set up the alpha LUT
                            pLUT = m_pAlphaLUT;
                            for (UINT32 i = 0; i < 256; i++)
                            {
                                for (UINT32 j = 0; j < 256; j++)
                                {
                                    *pLUT++ = (BYTE) (j * i / 255);
                                }
                            }
                        }
                        else
                        {
                            retVal = HXR_OUTOFMEMORY;
                        }
                    }
                    else
                    {
                        retVal = HXR_OUTOFMEMORY;
                    }
                }
                else
                {
                    retVal = HXR_OUTOFMEMORY;
                }
            }
            else
            {
                retVal = HXR_OUTOFMEMORY;
            }
        }
        else
        {
            retVal = HXR_OUTOFMEMORY;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

void PXEffectsManager::ClearEffects()
{
    DeleteEffectsList();
    DeleteEffectSessionList();
    DeletePostDurationList();
}

HX_RESULT PXEffectsManager::AddEffect(PXEffect *pEffect)
{
    HX_RESULT retVal = HXR_OK;

    if (pEffect)
    {
        if (m_pEffectsList)
        {
            // Set the effect's ID handle
            pEffect->SetHandle(m_ulNextEffectHandle);

            // Increment the handle with 32-bit wrap (like we're really gonna need it)
            m_ulNextEffectHandle = (m_ulNextEffectHandle == 0xFFFFFFFF ? 1 : m_ulNextEffectHandle + 1);

            // Before we add it to the list, we adjust for zero values in the
            // dst rect. Zero values of width or height imply the entire
            // width or height of the appropriate image. First we adjust for zero
            // width and height in the display rect.
            PXRect cRect;
            cRect = pEffect->GetDstRect();
            cRect.AdjustForZeroValues(m_pImageManager->GetDisplayWidth(),
                                      m_pImageManager->GetDisplayHeight());
            pEffect->SetDstRect(cRect);

            // Do we have a src rect?
            if (pEffect->HasTarget())
            {
                // We do the same (adjust for zero-values) for src rects. We
                // *may* not have the target image yet, although we most likely will.
                PXImage* pTargetImage = NULL;
                m_pImageManager->GetImage(pEffect->GetTarget(), &pTargetImage);
                if (pTargetImage)
                {
                    cRect = pEffect->GetSrcRect();
                    cRect.AdjustForZeroValues((UINT32) pTargetImage->GetWidth(),
                                              (UINT32) pTargetImage->GetHeight());
                    pEffect->SetSrcRect(cRect);
                }
                HX_RELEASE(pTargetImage);
            }

            // If this is a viewchange effect, then we need to set the
            // starting src and dst rects
            //
            // XXXMEH - this is a design error in the .rp language - the starting
            // src and dst rects for the viewchange should be specified IN THAT effect
            // with new attributes rather than relying on whatever the last effect was.
            if (pEffect->GetEffectType() == PXEffect::kEffectTypeViewChange && m_pLastEffect)
            {
                pEffect->SetStartSrcRect(m_pLastEffect->GetSrcRect());
                pEffect->SetStartDstRect(m_pLastEffect->GetDstRect());
                if (m_pLastEffect->HasTarget())
                {
                    pEffect->SetTarget(m_pLastEffect->GetTarget());
                    if (m_pLastEffect->GetLastUse() == TRUE)
                    {
                        m_pLastEffect->SetLastUse(FALSE);
                        pEffect->SetLastUse(TRUE);
                    }
                }
            }

            // Update the last effect
            HX_RELEASE(m_pLastEffect);
            m_pLastEffect = pEffect;
            m_pLastEffect->AddRef();

            // Check for a few shortcuts before searching the whole list
            // If there aren't any elements in the queue, we know just to
            // add ourselves to the end of the list.
            if (m_pEffectsList->GetCount())
            {
                // Now we check the last element in the queue. If our start time is
                // greater than or equal to its start time, then we know just to
                // add ourselves at the tail of the queue.
                PXEffect *pTailEffect = (PXEffect *) m_pEffectsList->GetTail();
                if (pTailEffect)
                {
                    if (IsTimeLater(pEffect->GetStart(), pTailEffect->GetStart()))
                    {
                        // Now we have to search the list.
                        // Insert effect into list which is sorted by increasing start time
                        // We look for the first effect which has a start time greater
                        // than the one we're inserting
                        LISTPOSITION pos = m_pEffectsList->GetHeadPosition();
                        while (pos)
                        {
                            PXEffect *pListEffect = (PXEffect *) m_pEffectsList->GetAt(pos);
                            if (pListEffect)
                            {
                                if (IsTimeLater(pEffect->GetStart(), pListEffect->GetStart()))
                                {
                                    // Addref effect before insertion
                                    pEffect->AddRef();
                                    // Insert into the list
                                    m_pEffectsList->InsertBefore(pos, (void *) pEffect);
                                    break;
                                }
                                m_pEffectsList->GetNext(pos);
                            }
                            else
                            {
                                retVal = HXR_FAIL;
                                break;
                            }
                        }

                        // If we added ourselves in the above loop, then pos will be non-NULL.
                        // If we DIDN'T add ourselves in the above loop, then pos will be NULL.
                        if (!pos)
                        {
                            // We didn't find an effect with a start time greater, so
                            // this effect must be the new largest start time, so
                            // we add this effect to the tail of the list. 
                            //
                            pEffect->AddRef();
                            m_pEffectsList->AddTail((void *) pEffect);
                        }
                    }
                    else
                    {
                        // Addref before adding to the list
                        pEffect->AddRef();
                        // Add it to the list
                        m_pEffectsList->AddTail((void *) pEffect);
                    }
                }
                else
                {
                    retVal = HXR_FAIL;
                }
            }
            else
            {
                // Addref the effect object before adding to the list
                pEffect->AddRef();
                // Add it to the list
                m_pEffectsList->AddTail((void *) pEffect);
            }
        }
        else
        {
            retVal = HXR_UNEXPECTED;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    if (SUCCEEDED(retVal))
    {
        retVal = UpdatePostDurationExpirationTimes();
    }


    return retVal;
}

HX_RESULT PXEffectsManager::OnTimeSync(UINT32 ulTime)
{
    HX_RESULT retVal = HXR_OK;

    // First we check to see we have entered the execution
    // window for any new effects. If there are any such
    // effects, then we create an effect session for them
    // and put them in the session list, we also remove
    // them from the effect list
    retVal = ScanForNewEffects(ulTime);
    if (SUCCEEDED(retVal))
    {
        // Now that we've updated the effect session list, we
        // need to run through the session list and call Execute(ulTime)
        // on each of the sessions in the list
        retVal = ExecuteAllSessions(ulTime);
        if (SUCCEEDED(retVal))
        {
            // Now we need to remove all the sessions from the list
            // which are finished.
            retVal = UpdateSessionList(ulTime);
            if (SUCCEEDED(retVal))
            {
                // Retire some effects from the post duration list
                retVal = UpdatePostDurationList(ulTime);
            }
        }
    }

#ifdef XXXMEH_DEBUG_ASSERT
    // Debug-only assert
    HX_ASSERT(SUCCEEDED(retVal));
#endif

    return retVal;
}

HX_RESULT PXEffectsManager::GetNormalFadeLUT(UINT32 ulStart, UINT32 ulEnd, UINT32 ulTime,
                                             BYTE** ppStartImageLUT, BYTE** ppEndImageLUT)
{
    HX_RESULT retVal     = HXR_OK;
    UINT32    ulTimeInto = 0;
    UINT32    ulDuration = 0;

    if (IsTimeEqualOrLaterDiff(ulStart, ulTime, ulTimeInto) &&
        IsTimeEqualOrLater(ulTime,  ulEnd)                  &&
        IsTimeLaterDiff(ulStart, ulEnd, ulDuration)         &&
        ppStartImageLUT && ppEndImageLUT)
    {
        // Compute the scaled percentage done in the range [0, kEffectLUTGranularity]
        UINT32 ulPct = ulTimeInto * kEffectLUTGranularity / ulDuration;

        // Assign the LUTs The "end image" is the one we want to
        // show up 100% at the end. The "start image" is the one we
        // want to be 0% at the end. So the end image gets the straight
        // percentage done and the start image gets the remaining percentage.
        *ppStartImageLUT = m_pFadeLUT + (kEffectLUTGranularity - ulPct) * m_ulLUTWidth;
        *ppEndImageLUT   = m_pFadeLUT + ulPct * m_ulLUTWidth;
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::GetRecursiveFadeLUT(UINT32 ulStart,          UINT32 ulEnd,
                                                UINT32 ulTime,           UINT32 ulLastTime,
                                                BYTE** ppStartImageLUT,  BYTE** ppEndImageLUT)
{
    HX_RESULT retVal      = HXR_OK;
    UINT32    ulEndLast   = 0;
    UINT32    ulEndStart  = 0;
    UINT32    ulTimeStart = 0;
    UINT32    ulEndTime   = 0;
    UINT32    ulLastStart = 0;

    if (IsTimeLaterDiff(ulLastTime, ulEnd, ulEndLast)            &&
        IsTimeLaterDiff(ulStart, ulEnd, ulEndStart)              &&
        IsTimeEqualOrLaterDiff(ulStart, ulTime, ulTimeStart)     &&
        IsTimeEqualOrLaterDiff(ulTime, ulEnd, ulEndTime)         &&
        IsTimeEqualOrLaterDiff(ulStart, ulLastTime, ulLastStart) &&
        ppStartImageLUT && ppEndImageLUT)
    {
        // Compute the scaled percentage for the start image (which
        // in this case is the display image (recursively operated on)
        UINT32 ulStartPct = ulEndTime * kEffectLUTGranularity / ulEndLast;

        // Compute the scaled percentage for the end image
        UINT32 ulEndPct = (ulTimeStart*ulEndLast - ulEndTime*ulLastStart) * kEffectLUTGranularity /
                          (ulEndStart*ulEndLast);

        // Assign the LUTs
        *ppStartImageLUT = m_pFadeLUT + ulStartPct * m_ulLUTWidth;
        *ppEndImageLUT   = m_pFadeLUT + ulEndPct   * m_ulLUTWidth;
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::CanUseRecursive(PXEffect* pEffect, BOOL& rbRecursive)
{
    HX_RESULT retVal = HXR_OK;

    if (pEffect)
    {
        // Set the default
        rbRecursive = FALSE;

        // Here we need to check whether this effect overlaps
        // any other effect in time AND space. If it does NOT,
        // then we can use the recursive version of the effect.
        // If it DOES, then we have to use the "copying" version
        // of the effect.
        BOOL bOverlap = FALSE;
        retVal        = AnySpaceTimeOverlap(pEffect, &bOverlap);
        if (SUCCEEDED(retVal) && !bOverlap)
        {
            rbRecursive = TRUE;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::PacketLost()
{
    return HXR_OK;
}

HX_RESULT PXEffectsManager::GetEffectsPackageManager(PXEffectsPackageManager** ppEffectsPackageManager)
{
    HX_RESULT retVal = HXR_OK;

    if (ppEffectsPackageManager)
    {
        if (m_pEffectsPackageManager)
        {
            *ppEffectsPackageManager = m_pEffectsPackageManager;
            (*ppEffectsPackageManager)->AddRef();
        }
        else
        {
            retVal = HXR_UNEXPECTED;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

void PXEffectsManager::ResetDamage()
{
    // Reset the damage-related members
    m_bDisplayDamaged    = FALSE;
    m_cDamageRect.left   = 0;
    m_cDamageRect.top    = 0;
    m_cDamageRect.right  = 0;
    m_cDamageRect.bottom = 0;

    if (m_pEffectSessionList)
    {
        // Call PXEffectSession::ResetDamage() on all active effect sessions
        LISTPOSITION pos = m_pEffectSessionList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession* pSession = (PXEffectSession*) m_pEffectSessionList->GetNext(pos);
            if (pSession)
            {
                // Call ResetDamage() on this session
                pSession->ResetDamage();
            }
        }
    }
}

void PXEffectsManager::DeleteEffectsList()
{
    if (m_pEffectsList)
    {
        // Delete all the objects in the effect queue
        LISTPOSITION pos = m_pEffectsList->GetHeadPosition();
        while (pos)
        {
            PXEffect *pEffect = (PXEffect *) m_pEffectsList->GetNext(pos);
            HX_RELEASE(pEffect);
        }
        // Clear out the effect queue
        m_pEffectsList->RemoveAll();
    }
}

void PXEffectsManager::DeleteEffectSessionList()
{
    if (m_pEffectSessionList)
    {
        // Delete all the objects in the effect queue
        LISTPOSITION pos = m_pEffectSessionList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession *pEffectSession = (PXEffectSession *) m_pEffectSessionList->GetNext(pos);
            HX_RELEASE(pEffectSession);
        }
        // Clear out the effect queue
        m_pEffectSessionList->RemoveAll();
    }
}

void PXEffectsManager::DeletePostDurationList()
{
    if (m_pPostDurationList)
    {
        // Delete all the objects in the effect queue
        LISTPOSITION pos = m_pPostDurationList->GetHeadPosition();
        while (pos)
        {
            PXEffect *pEffect = (PXEffect*) m_pPostDurationList->GetNext(pos);
            HX_RELEASE(pEffect);
        }
        // Clear out the effect queue
        m_pPostDurationList->RemoveAll();
    }
}

HX_RESULT PXEffectsManager::ScanForNewEffects(UINT32 ulTime)
{
    HX_RESULT retVal = HXR_OK;

    if (m_pEffectsList && m_pEffectSessionList)
    {
        // Scan the effects list. Since we know the effect list is
        // sorted in time, we only need to scan until we hit
        // an effect whose start time is greater than the current
        // time.
        BOOL bDelayedInit = FALSE;
        LISTPOSITION pos = m_pEffectsList->GetHeadPosition();
        while (pos)
        {
            PXEffect* pEffect = (PXEffect*) m_pEffectsList->GetAt(pos);
            if (pEffect)
            {
                if (IsTimeEqualOrLater(pEffect->GetStart(), ulTime) ||
                    (m_bIsLive && pEffect->GetDisplayImmediately()))
                {
                    // If we got this effect because we were live and 
                    // the effect said to display immediately, then we
                    // need to reset the start time of the effect to 
                    // right now.
                    if (m_bIsLive && pEffect->GetDisplayImmediately())
                    {
                        pEffect->SetStart(ulTime);
                    }
                    // Do we need to check for indefinite duration effects?
                    if (m_bCheckIndefiniteDuration)
                    {
                        CheckIndefiniteDuration(pEffect);
                    }
                    // We found an effect, so we need to create an effect session
                    // by calling the factory method
                    PXEffectSession* pSession = NULL;
                    retVal                    = CreateEffectSession(pEffect, &pSession);
                    if (SUCCEEDED(retVal))
                    {
                        // Addref the session object
                        pSession->AddRef();
                        // Check if we should init this session now or delay it
                        // until the first exe. We only delay the init of a session
                        // if we know there are effects which are already past their
                        // duration.
                        HX_RESULT rv = HXR_OK;
                        if (bDelayedInit)
                        {
                            pSession->SetDelayedInit(TRUE);
                            pSession->SetEffect(pEffect);
                        }
                        else
                        {
                            // Init the effect session - note that if we can't init
                            // the effect session, then we can't do the effect. No need to
                            // halt the entire presentation - we just can't do this effect.
                            rv = pSession->Init(this, pEffect, m_pImageManager, m_pErrorMessages);
                        }
                        if (SUCCEEDED(rv))
                        {
#ifdef XXXMEH_DEBUG_OUT
                            DEBUG_OUT(m_pErrorMessages,
                                      DOL_REALPIX,
                                      (s, "Effect (start=%lu,dur=%lu,target=%lu,type=%lu,last=%lu) started.",
                                       pEffect->GetStart(), pEffect->GetDuration(), pEffect->GetTarget(),
                                       pEffect->GetEffectType(), (UINT32) pEffect->GetLastUse()));
#endif
                            // Add ref the session before adding to the list
                            pSession->AddRef();
                            // Now we need to add this session to the tail of the list
                            m_pEffectSessionList->AddTail((void*) pSession);

                            // If the effect has a clickthru URL, then tell the
                            // hyperlink manager about it
                            const char* pszURL = (const char*) pEffect->GetURL();
                            if (strlen(pszURL) > 0 &&
                                strspn(pszURL, " \r\n\t") < strlen(pszURL))
                            {
                                m_pHyperlinkManager->AddLink(pEffect->GetDstHXxRect(), pszURL);
                            }
                            else
                            {
                                m_pHyperlinkManager->AddLink(pEffect->GetDstHXxRect(), NULL);
                            }

                            // Check if this is a late effect (one that the current time is
                            // already past the effect's end time). If so, then set the ReInit
                            // flag for the rest of the effects
                            if (IsTimeLater(pEffect->GetEnd(), ulTime) && pEffect->GetDuration() > 0)
                            {
                                bDelayedInit = TRUE;
                            }
                        }
                        else
                        {
#ifdef XXXMEH_DEBUG_OUT
                            DEBUG_OUT(m_pErrorMessages,
                                      DOL_REALPIX,
                                       (s, "Effect (start=%lu,dur=%lu,target=%lu,type=%lu,last=%lu) session init FAILED - ignoring effect",
                                        pEffect->GetStart(), pEffect->GetDuration(), pEffect->GetTarget(),
                                        pEffect->GetEffectType(), (UINT32) pEffect->GetLastUse()));
#endif
                        }
                    }
                    HX_RELEASE(pSession);
                    // Remove the effect from the effects list
                    pos = m_pEffectsList->RemoveAt(pos);
                    // Release the list's ref on the effect
                    HX_RELEASE(pEffect);
                }
                else
                {
                    break;
                }
            }
            else
            {
                retVal = HXR_FAIL;
            }

            if (FAILED(retVal))
            {
                break;
            }
        }
    }
    else
    {
        retVal = HXR_UNEXPECTED;
    }

#ifdef XXXMEH_DEBUG_ASSERT
    // Debug-only assert
    HX_ASSERT(SUCCEEDED(retVal));
#endif

    return retVal;
}

HX_RESULT PXEffectsManager::CreateEffectSession(PXEffect* pEffect, PXEffectSession** ppSession)
{
    HX_RESULT retVal = HXR_OK;

    if (pEffect && ppSession)
    {
        // Set default
        *ppSession = NULL;

        switch (pEffect->GetEffectType())
        {
            case PXEffect::kEffectTypeFill:
                retVal = PXFillEffectSession::CreateObject((PXFillEffectSession**) ppSession);
                break;
            case PXEffect::kEffectTypeFadeOut:
                retVal = PXFadeOutEffectSession::CreateObject((PXFadeOutEffectSession**) ppSession);
                break;
            case PXEffect::kEffectTypeFadeIn:
            case PXEffect::kEffectTypeCrossFade:
                retVal = PXFadeInEffectSession::CreateObject((PXFadeInEffectSession**) ppSession);
                break;
            case PXEffect::kEffectTypeWipe:
                retVal = PXWipeEffectSession::CreateObject((PXWipeEffectSession**) ppSession);
                break;
            case PXEffect::kEffectTypeViewChange:
                retVal = PXViewchangeEffectSession::CreateObject((PXViewchangeEffectSession**) ppSession);
// This is a higher CPU but simpler viewchange implementation still in testing
//                retVal = PXSimpleViewchangeEffectSession::CreateObject((PXSimpleViewchangeEffectSession**) ppSession);
                break;
            case PXEffect::kEffectTypeExternal:
                retVal = PXExternalEffectSession::CreateObject((PXExternalEffectSession**) ppSession);
                break;
            case PXEffect::kEffectTypeAnimate:
                retVal = PXAnimationSession::CreateObject((PXAnimationSession**) ppSession);
                break;
            default:
                retVal = HXR_FAIL;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::ExecuteAllSessions(UINT32 ulTime)
{
    HX_RESULT retVal = HXR_OK;

    if (m_pEffectSessionList)
    {
        // Reset our damage rect
        m_bDisplayDamaged    = FALSE;
        m_cDamageRect.left   = 0;
        m_cDamageRect.top    = 0;
        m_cDamageRect.right  = 0;
        m_cDamageRect.bottom = 0;

        // Call PXEffectSession::Execute() on all active effect sessions
        LISTPOSITION pos = m_pEffectSessionList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession* pSession = (PXEffectSession*) m_pEffectSessionList->GetNext(pos);
            if (pSession)
            {
                // Reset the session's damage rect
                pSession->ResetDamage();
                // Do we need to do a delayed init on this session?
                HX_RESULT rv = HXR_OK;
                if (pSession->IsFirstExe() && pSession->GetDelayedInit())
                {
                    PXEffect* pSessionEffect = NULL;
                    pSession->GetEffect(&pSessionEffect);
                    if (pSessionEffect)
                    {
                        rv = pSession->Init(this, pSessionEffect, m_pImageManager, m_pErrorMessages);
                    }
                    HX_RELEASE(pSessionEffect);
                    pSession->SetDelayedInit(FALSE);
                }

                // Call Execute on this session
                if (SUCCEEDED(rv))
                {
                    rv = pSession->Execute(ulTime);
                    if (SUCCEEDED(rv))
                    {
                        // Check to see if this session damaged the display
                        if (pSession->IsDisplayDamaged())
                        {
                            // Get the session's damage rect
                            HXxRect cRect;
                            pSession->GetDamageRect(cRect);
                            // First time through we just set the flag and
                            // assign the current damage rect to the overall
                            // damage rect. Everytime after that, however, we
                            // must perform a simple rectangular convex hull
                            // computation.
                            if (m_bDisplayDamaged)
                            {
                                m_cDamageRect.left   = PXMIN(m_cDamageRect.left,   cRect.left);
                                m_cDamageRect.top    = PXMIN(m_cDamageRect.top,    cRect.top);
                                m_cDamageRect.right  = PXMAX(m_cDamageRect.right,  cRect.right);
                                m_cDamageRect.bottom = PXMAX(m_cDamageRect.bottom, cRect.bottom);
                            }
                            else
                            {
                                m_bDisplayDamaged = TRUE;
                                m_cDamageRect     = cRect;
                            }
                        }
                    }
                    else
                    {
                        // If an effect failed, we immediately set it to finished,
                        // so that it will be removed from the active queue
                        pSession->SetFinished(TRUE);
                    }
                }
            }
            else
            {
                retVal = HXR_FAIL;
            }

            if (FAILED(retVal))
            {
                break;
            }
        }
    }
    else
    {
        retVal = HXR_UNEXPECTED;
    }

#ifdef XXXMEH_DEBUG_ASSERT
    // Debug-only assert
    HX_ASSERT(SUCCEEDED(retVal));
#endif

    return retVal;
}

HX_RESULT PXEffectsManager::UpdateSessionList(UINT32 ulTime)
{
    HX_RESULT retVal = HXR_OK;

    if (m_pEffectSessionList && m_pPostDurationList)
    {
        // Reset the backchannel notify flag
        m_bBackChannelNotify = FALSE;

        LISTPOSITION pos = m_pEffectSessionList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession* pSession = (PXEffectSession*) m_pEffectSessionList->GetAt(pos);
            if (pSession)
            {
                if (pSession->IsFinished())
                {
                    PXEffect* pEffect = NULL;
                    pSession->GetEffect(&pEffect);
                    if (pEffect)
                    {
                        if (pEffect->GetEffectType() == PXEffect::kEffectTypeAnimate &&
                            pEffect->GetPostBackChannel())
                        {
                            m_bBackChannelNotify  = TRUE;
                            m_ulBackChannelHandle = pEffect->GetTarget();
                        }
                    }
                    // Release the effect
                    HX_RELEASE(pEffect);


                    // The effect is finished, but the image it used may have not
                    // been completely decoded due to lost or late packets. We check
                    // whether this session needs saving.
                    if (pSession->NeedsPostDurationUpdate())
                    {
                        // The image did not completely decode, so we will keep the
                        // session around in case we get more packets by moving it
                        // to the post-duration list
                        //
                        // First we compute the expiration time
                        UINT32 ulExpTime = 0;
                        HX_RESULT rv     = ComputeExpirationTime(pSession, &ulExpTime);
                        if (SUCCEEDED(rv))
                        {
                            // Set the expiration time
                            pSession->SetPostDurationExpirationTime(ulExpTime);
                            // Addref the session
                            pSession->AddRef();
                            // Move it to the post duration list
                            m_pPostDurationList->AddTail((void*) pSession);
                        }
                    }
                    else
                    {
                        // This effect either: a) did not have an image; or b) the image
                        // decoded completely. So we can release our hold on the image
                        // if there was one.
                        pSession->ReleaseTargetImage();
                    }

                    // Release the list's ref of the session
                    HX_RELEASE(pSession);
                    // Remove the session from the list
                    pos = m_pEffectSessionList->RemoveAt(pos);

                    // Check if we need to clear the indefinite duration flag
                    CheckClearIndefiniteDuration();
                }
                else
                {
                    m_pEffectSessionList->GetNext(pos);
                }
            }
            else
            {
                retVal = HXR_FAIL;
            }

            if (FAILED(retVal))
            {
                break;
            }
        }
    }
    else
    {
        retVal = HXR_UNEXPECTED;
    }

#ifdef XXXMEH_DEBUG_ASSERT
    // Debug-only assert
    HX_ASSERT(SUCCEEDED(retVal));
#endif

    return retVal;
}

HX_RESULT PXEffectsManager::AnySpaceTimeOverlap(PXEffect* pQueryEffect, BOOL* pbOverlap)
{
    HX_RESULT retVal = HXR_OK;

    if (pQueryEffect && pbOverlap)
    {
        // Set default
        *pbOverlap = FALSE;

        if (m_pEffectsList && m_pEffectSessionList)
        {
            // First we check active events for an overlap
            LISTPOSITION pos = m_pEffectSessionList->GetHeadPosition();
            while (pos)
            {
                PXEffectSession* pSession = (PXEffectSession*) m_pEffectSessionList->GetNext(pos);
                if (pSession)
                {
                    PXEffect* pEffect = NULL;
                    retVal            = pSession->GetEffect(&pEffect);
                    if (SUCCEEDED(retVal))
                    {
                        if (pEffect->IsOverlapped(pQueryEffect))
                        {
                            *pbOverlap = TRUE;
                            break;
                        }
                    }
                    HX_RELEASE(pEffect);
                }
                else
                {
                    retVal = HXR_FAIL;
                }
                if (FAILED(retVal))
                {
                    break;
                }
            }

            // Now if we haven't already found an overlap, then check the effects list
            if (SUCCEEDED(retVal) && *pbOverlap == FALSE)
            {
                pos = m_pEffectsList->GetHeadPosition();
                while (pos)
                {
                    PXEffect* pEffect = (PXEffect*) m_pEffectsList->GetNext(pos);
                    if (pEffect)
                    {
                        if (pEffect->IsOverlapped(pQueryEffect))
                        {
                            *pbOverlap = TRUE;
                            break;
                        }
                    }
                    else
                    {
                        retVal = HXR_FAIL;
                    }
                    if (FAILED(retVal))
                    {
                        break;
                    }
                }
            }
        }
        else
        {
            retVal = HXR_UNEXPECTED;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::CheckPostDurationPacket(UINT32 ulHandle)
{
    HX_RESULT retVal = HXR_OK;

    if (ulHandle)
    {
        if (m_pPostDurationList)
        {
            LISTPOSITION pos = m_pPostDurationList->GetHeadPosition();
            while (pos)
            {
                PXEffectSession* pSession = (PXEffectSession*) m_pPostDurationList->GetAt(pos);
                if (pSession)
                {
                    PXEffect* pEffect = NULL;
                    retVal            = pSession->GetEffect(&pEffect);
                    if (SUCCEEDED(retVal))
                    {
                        if (pEffect->HasTarget() && pEffect->GetTarget() == ulHandle)
                        {
                            // The image for this post-duration image has been updated,
                            // so we spring into action.
                            //
                            // Clear the finished flag, so the session will do something
                            pSession->SetFinished(FALSE);
                            // Now put it back on the active session list
                            m_pEffectSessionList->AddHead((void*) pSession);
                            // Remove it from the post duration list
                            pos = m_pPostDurationList->RemoveAt(pos);
                        }
                        else
                        {
                            m_pPostDurationList->GetNext(pos);
                        }
                    }
                    HX_RELEASE(pEffect);
                }
                else
                {
                    retVal = HXR_FAIL;
                }

                if (FAILED(retVal))
                {
                    break;
                }
            }
        }
        else
        {
            retVal = HXR_UNEXPECTED;
        }
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::UpdatePostDurationList(UINT32 ulTime)
{
    HX_RESULT retVal = HXR_OK;

    if (m_pPostDurationList)
    {
        LISTPOSITION pos = m_pPostDurationList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession* pSession = (PXEffectSession*) m_pPostDurationList->GetAt(pos);
            if (pSession)
            {
                if (IsTimeLater(pSession->GetPostDurationExpirationTime(), ulTime))
                {
                    // The time for this post-duration session has
                    // expired (i.e. - some other effect has covered up the screen)
                    // so we need to remove it from the list.
                    //
                    // First we release our hold on the target image, if there
                    // is one.
                    retVal = pSession->ReleaseTargetImage();
                    if (SUCCEEDED(retVal))
                    {
                        // Release the list's ref on the session
                        HX_RELEASE(pSession);
                        // Remove it from the list
                        pos = m_pPostDurationList->RemoveAt(pos);
                    }
                }
                else
                {
                    m_pPostDurationList->GetNext(pos);
                }
            }
            else
            {
                retVal = HXR_FAIL;
            }

            if (FAILED(retVal))
            {
                break;
            }
        }
    }
    else
    {
        retVal = HXR_UNEXPECTED;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::ComputeExpirationTime(PXEffectSession* pSession, UINT32* pulTime)
{
    HX_RESULT retVal = HXR_OK;

    if (pSession && pulTime)
    {
        // Set defaults
        *pulTime = 0xFFFFFFFF;

        // Init flag which says we have some expiration time
        BOOL bFoundExpTime = FALSE;

        // Get the effect for this session
        PXEffect* pEffect = NULL;
        HX_RESULT retVal  = pSession->GetEffect(&pEffect);
        if (SUCCEEDED(retVal))
        {
            // First we search the active effects (effect sessions)
            LISTPOSITION pos = m_pEffectSessionList->GetHeadPosition();
            while (pos)
            {
                PXEffectSession* pListSess = (PXEffectSession*) m_pEffectSessionList->GetNext(pos);
                if (pListSess)
                {
                    PXEffect* pListEffect = NULL;
                    retVal                = pListSess->GetEffect(&pListEffect);
                    if (SUCCEEDED(retVal))
                    {
                        if (IsTimeLater(pEffect->GetEnd(), pListEffect->GetEnd())  &&
                            pListEffect->GetDstRect().Contains(pEffect->GetDstRect()))
                        {
                            if (bFoundExpTime)
                            {
                                if (IsTimeLater(pListEffect->GetEnd(), *pulTime))
                                {
                                    *pulTime = pListEffect->GetEnd();
                                }
                            }
                            else
                            {
                                *pulTime      = pListEffect->GetEnd();
                                bFoundExpTime = TRUE;
                            }
                        }
                    }
                    HX_RELEASE(pListEffect);
                }
                else
                {
                    retVal = HXR_FAIL;
                }

                if (FAILED(retVal))
                {
                    break;
                }
            }

            if (SUCCEEDED(retVal))
            {
                // Now we search the effect list
                pos = m_pEffectsList->GetHeadPosition();
                while (pos)
                {
                    PXEffect* pListEffect = (PXEffect*) m_pEffectsList->GetNext(pos);
                    if (pListEffect)
                    {
                        if (IsTimeLater(pEffect->GetEnd(), pListEffect->GetEnd()) &&
                            pListEffect->GetDstRect().Contains(pEffect->GetDstRect()))
                        {
                            if (bFoundExpTime)
                            {
                                if (IsTimeLater(pListEffect->GetEnd(), *pulTime))
                                {
                                    *pulTime = pListEffect->GetEnd();
                                }
                            }
                            else
                            {
                                *pulTime      = pListEffect->GetEnd();
                                bFoundExpTime = TRUE;
                            }
                        }
                    }
                    else
                    {
                        retVal = HXR_FAIL;
                    }

                    if (FAILED(retVal))
                    {
                        break;
                    }
                }
            }
        }
        HX_RELEASE(pEffect);
    }
    else
    {
        retVal = HXR_INVALID_PARAMETER;
    }

    return retVal;
}

HX_RESULT PXEffectsManager::UpdatePostDurationExpirationTimes()
{
    HX_RESULT retVal = HXR_OK;

    if (m_pPostDurationList)
    {
        LISTPOSITION pos = m_pPostDurationList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession* pSession = (PXEffectSession*) m_pPostDurationList->GetNext(pos);
            if (pSession)
            {
                UINT32 ulExpTime = 0;
                retVal           = ComputeExpirationTime(pSession, &ulExpTime);
                if (SUCCEEDED(retVal))
                {
                    if (pSession->GetPostDurationExpirationTime() != ulExpTime)
                    {
#ifdef XXXMEH_DEBUG_LOG
                        PXEffect* pEffect = NULL;
                        pSession->GetEffect(&pEffect);
                        if (pEffect)
                        {
                            DEBUG_OUTF("c:\\realpix.log",
                                       (s, "0x%08x::PXEffectsManager::UpdatePostDurationExpirationTimes() - updating expiration time for effect session (start=%lu,type=%lu) to %lu\n",
                                       this, pEffect->GetStart(), pEffect->GetEffectType(), ulExpTime));
                        }
                        HX_RELEASE(pEffect);
#endif
                        // Set the expiration time
                        pSession->SetPostDurationExpirationTime(ulExpTime);
                    }
                }
            }
            else
            {
                retVal = HXR_FAIL;
            }

            if (FAILED(retVal))
            {
                break;
            }
        }
    }
    else
    {
        retVal = HXR_UNEXPECTED;
    }

    return retVal;
}

void PXEffectsManager::CheckIndefiniteDuration(PXEffect* pEffect)
{
    if (pEffect && m_pEffectSessionList)
    {
        // Run through the current list of effect sessions and find the 
        // animation session with indefinite duration
        LISTPOSITION pos = m_pEffectSessionList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession* pSession = (PXEffectSession*) m_pEffectSessionList->GetNext(pos);
            if (pSession)
            {
                // Get the effect from the session
                PXEffect* pListEffect = NULL;
                HX_RESULT retVal      = pSession->GetEffect(&pListEffect);
                if (SUCCEEDED(retVal))
                {
                    // Is this effect: a) an animate effect; and
                    //                 b) have indefinite duration?
                    if (pListEffect->GetEffectType() == PXEffect::kEffectTypeAnimate &&
                        pListEffect->GetDuration()   == 0xFFFFFFFF)
                    {
                        // This is an indefinite duration animation effect session (yow!),
                        // so we need to check if the dst rect of the effect to be added
                        // completely contains this effect's dst rect.
                        if (pEffect->GetDstRect().Contains(pListEffect->GetDstRect()))
                        {
                            // The new effect DOES completely contain the indefinite duration
                            // effect. XXXMEH - What we MAY want to check in the future is whether or
                            // not the effect to be added contains transparency in the first
                            // frame. If this is true, we may not want to remove the old effect - 
                            // it may be there intentionally. However, for now, we remove it. Actually,
                            // we set the status of the session to finished, which will cause:
                            // a) nothing to be executed this time sync; and b) cause it to be 
                            // removed from the session list this time sync.
                            pSession->SetFinished(TRUE);
                        }
                    }
                }
                HX_RELEASE(pListEffect);
            }
        }
    }
}

void PXEffectsManager::CheckClearIndefiniteDuration()
{
    // We don't need to clear it if it's not set
    if (m_bCheckIndefiniteDuration && m_pEffectSessionList)
    {
        // Run through the list of sessions and if there are
        // no more animate effects with indefinite durations, then we
        // can clear the flag
        BOOL         bIndefinitePresent = FALSE;
        LISTPOSITION pos                = m_pEffectSessionList->GetHeadPosition();
        while (pos)
        {
            PXEffectSession* pSession = (PXEffectSession*) m_pEffectSessionList->GetNext(pos);
            if (pSession)
            {
                PXEffect* pEffect = NULL;
                HX_RESULT retVal  = pSession->GetEffect(&pEffect);
                if (SUCCEEDED(retVal))
                {
                    if (pEffect->GetEffectType() == PXEffect::kEffectTypeAnimate &&
                        pEffect->GetDuration()   == 0xFFFFFFFF)
                    {
                        bIndefinitePresent = TRUE;
                    }
                }
                HX_RELEASE(pEffect);
            }
        }

        // If there were no indefinite duration animate sessions present,
        // then clear the flag
        if (!bIndefinitePresent)
        {
            m_bCheckIndefiniteDuration = FALSE;
        }
    }
}

