#include "priv.h"
#include "basesb.h"

#define DISPID_ONTRANSITIONFINISH     1
const GUID DIID_ICSSTransitionEvents = {0x8E64AA50L,0xDC42,0x11D0,0x99,0x49,0x00,0xA0,0xC9,0x0A,0x8F,0xF2};

//
// Notes:
//  - CTransitionSite object is always contained in a CBaseBrowser object,
//   therefore, it does not have its own reference count.
//  - The macro CONTAINERMAP maps the pointer to this object up to the
//   containing object.
//  - CTransitionSite is a friend class of CBaseBrowser class.
//
// Notes:
//  - Having anything be a friend of CBaseBrowser is not good.
//  - This object is completely contained by it's parent object,
//    so to avoid a reference loop we keep our pContainer pointer
//    with no reference. Do not QI off it or we will leak!
//
#define TF_TRSITE           0
#define TF_TRDRAW           TF_ALWAYS
#define TF_TREXTDRAW        0
#define TF_TRLIFE           0
#define TF_ADDREFRELEASE    0
#define TF_TRSPB            0
#define TF_DEBUGQI          0

/////////////////////////////////////////////////////////////////////////////
// Design constants
/////////////////////////////////////////////////////////////////////////////
#define MIN_TRANSITION_DURATION     0.0
#define MAX_TRANSITION_DURATION     100.0

#define MIN_ONVIEWCHANGE_DURATION   1500    // ms

#define TSPB_CREATE_INCR            4
    // CTransitionSitePropertyBag property list create size

/////////////////////////////////////////////////////////////////////////////
// Module variables
/////////////////////////////////////////////////////////////////////////////
static const TCHAR      c_szTransitionsKey[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CSSFilters");
static const OLECHAR    c_szDurationProp[] = OLESTR("Duration");
static OLECHAR *        s_szApplyMethod = OLESTR("Apply");
static OLECHAR *        s_szPlayMethod = OLESTR("Play");
static OLECHAR *        s_szStopMethod = OLESTR("Stop");

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite
/////////////////////////////////////////////////////////////////////////////
#undef THISCLASS

CTransitionSite::CTransitionSite
(
    IShellBrowser * pcont
) : _pContainer(pcont) // DO NOT ADDREF
{
    TraceMsg(TF_TRLIFE, "TRS::ctor called");

    ASSERT(pcont != NULL);

    _uState = TRSTATE_NONE;
    _ptiCurrent = NULL;
}

CTransitionSite::~CTransitionSite
(
)
{
    TraceMsg(TF_TRLIFE, "TRS::dtor called");

#ifdef DEBUG
    ASSERT(_pSite == NULL);
    ASSERT(_psvNew == NULL);
    ASSERT(_pvoNew == NULL);
    ASSERT(_hwndViewNew == NULL);
    ASSERT(_pTransition == NULL);
    ASSERT(_pDispTransition == NULL);
    ASSERT(_dwTransitionSink == NULL);
#endif  // DEBUG

    for (int te = teFirstEvent; te < teNumEvents; te++)
        SAFERELEASE(_tiEventInfo[te].pPropBag);
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_ApplyTransition
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_ApplyTransition
(
    BOOL bSiteChanging
)
{
    HRESULT hrResult = S_OK;

    TraceMsg(TF_TRSITE, "TRS::_ApplyTransition(%d) called", bSiteChanging);

    if (_ptiCurrent != NULL)
        _OnComplete();

    ASSERT(_ptiCurrent == NULL);

    if (bSiteChanging && (_tiEventInfo[teSiteEnter].clsid != CLSID_NULL))
        _ptiCurrent = &_tiEventInfo[teSiteEnter];
    else if (_tiEventInfo[tePageEnter].clsid != CLSID_NULL)
        _ptiCurrent = &_tiEventInfo[tePageEnter];
    else
        _ptiCurrent = NULL;

    // Allow the contatiner a chance to set a transition if we don't have one.
    if (_ptiCurrent == NULL)
    {
        // vaIn.vt = VT_BOOL; vaIn.boolVal = Site is changing;
        VARIANTARG  vaIn = { VT_BOOL, bSiteChanging };

        // vaOut[0].vt = VT_I4; vaOut[0].lVal = TransitionEvent
        // vaOut[1].vt = VT_BSTR; vaOut[1].bstrVal = Transition String.
        VARIANTARG  vaOut[2] = { 0 };

        if (SUCCEEDED(hrResult = IUnknown_Exec(_pContainer,
                                                    &CGID_ShellDocView,
                                                    SHDVID_GETTRANSITION,
                                                    OLECMDEXECOPT_DODEFAULT,
                                                    &vaIn,
                                                    vaOut)))
        {
            TRANSITIONINFO ti = { 0 };

            if  (
                (vaOut[0].vt == VT_I4)
                &&
                ((vaOut[0].lVal >= teFirstEvent) && (vaOut[0].lVal < teNumEvents))
                &&
                (vaOut[1].vt == VT_BSTR)
                &&
                (vaOut[1].bstrVal != NULL)
                &&
                ParseTransitionInfo((LPWSTR)vaOut[1].bstrVal, &ti)
                )
            {
                hrResult = _SetTransitionInfo((TransitionEvent)vaOut[0].lVal, &ti);
                _ptiCurrent = &_tiEventInfo[vaOut[0].lVal];
            }
            else
                hrResult = E_UNEXPECTED;

            SAFERELEASE(ti.pPropBag);
        }

        TraceMsg(TF_TRSITE, "hrResult = 0x%.8X after _pContainer->Exec()", hrResult);

        VariantClear(&vaOut[0]);
        VariantClear(&vaOut[1]);
    }

    if (SUCCEEDED(hrResult))
    {
        if (SUCCEEDED(hrResult = _LoadTransition()))
        {
            ASSERT(_hwndViewNew != NULL);

            // We need to hide the view window to draw on our own DC.
            _fViewIsVisible = ::IsWindowVisible(_hwndViewNew);
            if (_fViewIsVisible)
                ::ShowWindow(_hwndViewNew, SW_HIDE);

            _pContainer->EnableModelessSB(FALSE);
        }
    }
    else
    {
        // Make sure we don't fiddle with the window visibility
        // in _OnComplete if we didn't succeed.
        _hwndViewNew = NULL;

        _OnComplete();
    }

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_LoadTransition
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_LoadTransition
(
)
{
    HRESULT hrResult;

    TraceMsg(TF_TRSITE, "TRS::_LoadTransition called");

    if ((_ptiCurrent == NULL) || (_ptiCurrent->clsid == CLSID_NULL))
        return E_INVALIDARG;

    // Create the transition.
    for (;;)
    {
        ASSERT(_pTransition == NULL);
        if (FAILED(hrResult = CoCreateInstance( _ptiCurrent->clsid,
                                                NULL,
                                                CLSCTX_INPROC_SERVER,
                                                IID_IHTMLViewFilter,
                                                (void **)&_pTransition)))
        {
            TraceMsg(TF_ERROR, "TRS::_LoadTransition CoCreateInstance failed 0x%X", hrResult);
            break;
        }

        ASSERT(_pDispTransition == NULL);
        if (FAILED(hrResult = _pTransition->QueryInterface( IID_IDispatch,
                                                            (void **)&_pDispTransition)))
        {
            TraceMsg(TF_ERROR, "TRS::_LoadTransition QI::IDispatch failed 0x%X", hrResult);
            break;
        }

        ASSERT(_dwTransitionSink == 0);
        if (FAILED(hrResult = ConnectToConnectionPoint( SAFECAST(this, IDispatch *),
                                                        DIID_ICSSTransitionEvents,
                                                        TRUE,
                                                        _pTransition,
                                                        &_dwTransitionSink,
                                                        NULL)))
        {
            TraceMsg(TF_ERROR, "TRS::_LoadTransition ConnectToConnectionPoint failed 0x%X", hrResult);
            break;
        }

        // Supply the property bag to the transition (if needed)
        IPersistPropertyBag * pPersistPropBag;
        if  (
            (_ptiCurrent->pPropBag != NULL)
            &&
            SUCCEEDED(_pTransition->QueryInterface(IID_IPersistPropertyBag, (void **)&pPersistPropBag))
            )
        {
            EVAL(SUCCEEDED(pPersistPropBag->InitNew()));
            EVAL(SUCCEEDED(pPersistPropBag->Load(SAFECAST(_ptiCurrent->pPropBag, IPropertyBag *), NULL)));
            ATOMICRELEASE(pPersistPropBag);
        }

        if (FAILED(hrResult = _pTransition->SetSite(SAFECAST(this, IHTMLViewFilterSite *))))
        {
            TraceMsg(TF_ERROR, "TRS::_LoadTransition _pT->SetSite failed 0x%X", hrResult);
            break;
        }

        if (FAILED(hrResult = _pTransition->SetSource(SAFECAST(this, IHTMLViewFilter *))))
        {
            TraceMsg(TF_ERROR, "TRS::_LoadTransition _pT->SetSource failed 0x%X", hrResult);
            break;
        }

        if (FAILED(hrResult = _InitWait()))
        {
            TraceMsg(TF_ERROR, "TRS::_LoadTransition _InitWait failed 0x%X", hrResult);
            break;
        }

        hrResult = S_OK;
        break;
    }

    if (FAILED(hrResult))
        _OnComplete();

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_SetTransitionInfo
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_SetTransitionInfo
(
    TransitionEvent     te,
    TRANSITIONINFO *    pti
)
{
    TraceMsg(TF_TRSITE, "TRS::_SetTransitionInfo(%d)", te);
    
    ASSERT((te >= teFirstEvent) && (te < teNumEvents));

    SAFERELEASE(_tiEventInfo[te].pPropBag);        

    _tiEventInfo[te] = *pti;

    if (_tiEventInfo[te].pPropBag != NULL)
        _tiEventInfo[te].pPropBag->AddRef();

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_InitWait
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_InitWait()
{
    HRESULT hrResult;

    TraceMsg(TF_TRSITE, "TRS::_InitWait called");

    ASSERT(_pTransition != NULL);
    ASSERT(_pDispTransition != NULL);
    ASSERT(_uState == TRSTATE_NONE);

    for (;;)
    {
        RECT rc;
        IBrowserService2 *pbs2;
        if (SUCCEEDED(_pContainer->QueryInterface(IID_IBrowserService2, (void **)&pbs2)))
        {
            pbs2->GetViewRect(&rc);
            pbs2->Release();
        }

        EVAL(SUCCEEDED(hrResult = _pTransition->SetPosition(&rc)));
        TraceMsg(TF_TRSITE, "TRS::_InitWait called _pTransition->SetPosition 0x%X", hrResult);

        DISPID dispid;
        if (FAILED(hrResult = _pDispTransition->GetIDsOfNames(  IID_NULL,
                                                                &s_szApplyMethod,
                                                                1,
                                                                LOCALE_USER_DEFAULT,
                                                                &dispid)))
        {
            TraceMsg(TF_ERROR, "TRS::_InitWait _pDispTransition->GetIDsOfNames(Apply) failed 0x%X", hrResult);
            break;
        }

        unsigned int uArgError = (unsigned int)-1;
        DISPPARAMS dispparamsNoArgs = { NULL, NULL, 0, 0 };
        if (FAILED(hrResult = _pDispTransition->Invoke( dispid,
                                                        IID_NULL,
                                                        LOCALE_USER_DEFAULT,
                                                        DISPATCH_METHOD,
                                                        &dispparamsNoArgs,
                                                        NULL,
                                                        NULL,
                                                        &uArgError)))
        {
            TraceMsg(TF_ERROR, "TRS::_InitWait _pDispTransition->Invoke(Apply) failed 0x%X", hrResult);
            break;
        }

        break;
    }

    if (SUCCEEDED(hrResult))
        _uState = TRSTATE_INITIALIZING;

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_StartTransition
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_StartTransition
(
)
{
    HRESULT hrResult;
    
    TraceMsg(TF_TRSITE, "TRS::_StartTransition");

    ASSERT(_uState == TRSTATE_INITIALIZING);

    for (;;)
    {
        if (_pDispTransition == NULL)
        {
            hrResult = E_UNEXPECTED;
            break;
        }

        if  (
            FAILED(hrResult = _psvNew->QueryInterface(IID_IViewObject, (void **)&_pvoNew))
            ||
            FAILED(hrResult = _pvoNew->SetAdvise(   DVASPECT_CONTENT,
                                                    0,
                                                    SAFECAST(this, IAdviseSink *)))
            )
        {
            TraceMsg(TF_ERROR, "TRS::_StartTransition QI for IViewObject failed 0x%X", hrResult);
            break;
        }

        _uState = TRSTATE_STARTPAINTING;

        DISPID dispid;
        if (FAILED(hrResult = _pDispTransition->GetIDsOfNames(  IID_NULL,
                                                                &s_szPlayMethod,
                                                                1,
                                                                LOCALE_USER_DEFAULT,
                                                                &dispid)))
        {
            TraceMsg(TF_ERROR, "TRS::_StartTransition _pDispTransition->GetIDsOfNames(Play) failed 0x%X", hrResult);
            break;
        }

        unsigned int uArgError = (unsigned int)-1;
        DISPPARAMS dispparamsNoArgs = { NULL, NULL, 0, 0 };
        if (FAILED(hrResult = _pDispTransition->Invoke( dispid,
                                                        IID_NULL,
		                                                LOCALE_USER_DEFAULT,
                                                        DISPATCH_METHOD,
                                                        &dispparamsNoArgs,
                                                        NULL,
                                                        NULL,
                                                        &uArgError)))
        {
            TraceMsg(TF_ERROR, "TRS::_StartTransition _pDispTransition->Invoke(Play) failed 0x%X", hrResult);
            break;
        }

        _uState = TRSTATE_PAINTING;
        break;
    }

    if (FAILED(hrResult))
        _OnComplete();

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_StopTransition
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_StopTransition
(
)
{
    HRESULT hrResult;

    for (;;)
    {
        if (_pDispTransition == NULL)
        {
            hrResult = E_UNEXPECTED;
            break;
        }

        DISPID dispid;
        if (FAILED(hrResult = _pDispTransition->GetIDsOfNames(  IID_NULL,
                                                                &s_szStopMethod,
                                                                1,
                                                                LOCALE_USER_DEFAULT,
                                                                &dispid)))
        {
            TraceMsg(TF_ERROR, "TRS::_StopTransition _pDispTransition->GetIDsOfNames(Stop) failed 0x%X", hrResult);
            break;
        }

        unsigned int uArgError = (unsigned int)-1;
        DISPPARAMS dispparamsNoArgs = { NULL, NULL, 0, 0 };
        if (FAILED(hrResult = _pDispTransition->Invoke( dispid,
                                                        IID_NULL,
		                                                LOCALE_USER_DEFAULT,
                                                        DISPATCH_METHOD,
                                                        &dispparamsNoArgs,
                                                        NULL,
                                                        NULL,
                                                        &uArgError)))
        {
            TraceMsg(TF_ERROR, "TRS::_StopTransition _pDispTransition->Invoke(Stop) failed 0x%X", hrResult);
            break;
        }

        break;
    }

    _OnComplete();

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_UpdateEventList
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_UpdateEventList
(
)
{
    SAFERELEASE(_tiEventInfo[teSiteEnter].pPropBag);
    _tiEventInfo[teSiteEnter] = _tiEventInfo[teSiteExit];
    _tiEventInfo[teSiteExit].clsid = CLSID_NULL;
    _tiEventInfo[teSiteExit].pPropBag = NULL;

    SAFERELEASE(_tiEventInfo[tePageEnter].pPropBag);
    _tiEventInfo[tePageEnter] = _tiEventInfo[tePageExit];
    _tiEventInfo[tePageExit].clsid = CLSID_NULL;
    _tiEventInfo[tePageExit].pPropBag = NULL;

    _uState     = TRSTATE_NONE;
    _ptiCurrent = NULL;

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::_OnComplete
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::_OnComplete
(
)
{
    TraceMsg(TF_TRSITE, "TRS::_OnComplete() called");

    if (_dwTransitionSink != 0)
    {
        EVAL(SUCCEEDED(ConnectToConnectionPoint(SAFECAST(this, IDispatch *),
                                                DIID_ICSSTransitionEvents,
                                                FALSE,
                                                _pTransition,
                                                &_dwTransitionSink,
                                                NULL)));
        _dwTransitionSink = 0;
    }
    
    if (_pvoNew != NULL)
    {
        _pvoNew->SetAdvise(DVASPECT_CONTENT, 0, 0);
        ATOMICRELEASE(_pvoNew);
    }

    ATOMICRELEASE(_pDispTransition);

    if (_pTransition != NULL)
    {
        _pTransition->SetSource(NULL);
        _pTransition->SetSite(NULL);

        ASSERT(_pSite == NULL);

        ATOMICRELEASE(_pTransition);
    }

    if (_hwndViewNew != NULL)
    {
        if (_fViewIsVisible)
            ::ShowWindow(_hwndViewNew, SW_SHOW);

        _pContainer->EnableModelessSB(TRUE);
    }

    ATOMICRELEASE(_psvNew);
    _hwndViewNew = NULL;

    return _UpdateEventList();
}

/////////////////////////////////////////////////////////////////////////////
// IUnknown interface
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::QueryInterface(REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_IHTMLViewFilter))
    {
        *ppvObj = SAFECAST(this, IHTMLViewFilter *);
    }
    else if (IsEqualIID(riid, IID_IHTMLViewFilterSite))
    {
        *ppvObj = SAFECAST(this, IHTMLViewFilterSite *);
    }
    else if (IsEqualIID(riid, DIID_ICSSTransitionEvents) ||
            IsEqualIID(riid, IID_IDispatch))
    {
        *ppvObj = SAFECAST(this, IDispatch *);
    }
    else if (IsEqualIID(riid, IID_IServiceProvider))
    {
        return _pContainer->QueryInterface(IID_IServiceProvider, ppvObj);
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }
    
    AddRef();
    return S_OK;
}

ULONG CTransitionSite::AddRef()
{
    TraceMsg(TF_ADDREFRELEASE, "TRS::AddRef()");
    return _pContainer->AddRef();
}

ULONG CTransitionSite::Release()
{
    TraceMsg(TF_ADDREFRELEASE, "TRS::Release()");
    return _pContainer->Release();
}


/////////////////////////////////////////////////////////////////////////////
// IHTMLViewFilter interface
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::SetSource(IHTMLViewFilter * pFilter)
{
    ASSERT(0);
    return E_FAIL;
}

HRESULT CTransitionSite::GetSource
(
    IHTMLViewFilter ** ppFilter
)
{
    ASSERT(0);
    return E_FAIL;
}

HRESULT CTransitionSite::SetSite
(
    IHTMLViewFilterSite * pFilterSite
)
{
    TraceMsg(TF_TRSITE, "TRS::SetSite called with %x", pFilterSite);

    ATOMICRELEASE(_pSite);

    _pSite = pFilterSite;
    if (pFilterSite != NULL)
        pFilterSite->AddRef();

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::GetSite
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::GetSite
(
    IHTMLViewFilterSite ** ppFilterSite
)
{
    TraceMsg(TF_TRSITE, "TRS::GetSite called _pSite=%x", _pSite);
    *ppFilterSite = _pSite;

    if (_pSite != NULL)
    {
        _pSite->AddRef();
        return S_OK;
    }

    return S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::SetPosition
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::SetPosition
(
    LPCRECT prc
)
{
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::Draw
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::Draw
(
    HDC     hDC,
    LPCRECT prc
)
{
    HRESULT hrResult;
    HWND    hwndView = NULL;    // init to suppress bogus C4701 warning

    TraceMsg(TF_TREXTDRAW,
             "TRS::Draw called with hdc=%x (%d,%d)-(%d,%d)",
             hDC, prc->left, prc->top, prc->right, prc->bottom);

    if ((_uState == TRSTATE_STARTPAINTING) || (_uState == TRSTATE_PAINTING))
    {
        ASSERT(_pvoNew != NULL);

        RECTL rcl = { prc->left, prc->top, prc->right, prc->bottom };

        if (FAILED(hrResult = _pvoNew->Draw(DVASPECT_CONTENT,
                                            -1,
                                            NULL,
                                            NULL,
                                            NULL,
                                            hDC,
                                            &rcl,
                                            NULL,
                                            NULL,
                                            0)))
        {
            TraceMsg(TF_ERROR, "TRS::Draw IVO::Draw failed 0x%X", hrResult);
            hwndView = _hwndViewNew;
        }
    }
    else
    {
        IShellView * psv;
        hrResult = _pContainer->QueryActiveShellView(&psv);
        if (SUCCEEDED(hrResult))
        {
            hrResult = OleDraw(psv, DVASPECT_CONTENT, hDC, prc);
            psv->Release();
        }
        if (FAILED(hrResult))
        {
            IBrowserService2 *pbs;
            
            TraceMsg(TF_WARNING, "TRS:Draw OleDraw failed 0x%X", hrResult);
            
            if (SUCCEEDED(_pContainer->QueryInterface(IID_IBrowserService2, (void **)&pbs)))
            {
                pbs->GetViewWindow(&hwndView);
                pbs->Release();
            }
        }
    }

    // As a last resort, if IViewObject::Draw fails, try WM_PRINT
    if (FAILED(hrResult))
    {
        ASSERT((hwndView != NULL) && IsWindow(hwndView));

        // DrawEdge(..., EDGE_SUNKEN, BF_ADJUST|BF_RECT|BF_SOFT);

        ::SendMessage(hwndView, WM_PRINT, (WPARAM)hDC, PRF_NONCLIENT | PRF_CLIENT | PRF_CHILDREN | PRF_ERASEBKGND);
        hrResult = S_OK;
    }

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::GetStatusBits
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::GetStatusBits
(
    DWORD * pdwFlags
)
{
    *pdwFlags = FILTER_STATUS_OPAQUE;
    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// IHTMLViewFilterSite interface
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::GetDC
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::GetDC
(
    LPCRECT prc,
    DWORD   dwFlags,
    HDC *   phDC
)
{
    HWND hwnd;
    _pContainer->GetWindow(&hwnd);
    *phDC = ::GetDC(hwnd);
    
    TraceMsg(TF_TRSITE, "TRS::GetDC returning *phDC=%x", *phDC);

    return ((*phDC != NULL) ? S_OK : E_FAIL);
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::ReleaseDC
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::ReleaseDC
(
    HDC hDC
)
{
    TraceMsg(TF_TRSITE, "TRS::ReleaseDC called with %x", hDC);
    
    HWND hwnd;
    _pContainer->GetWindow(&hwnd);
    ::ReleaseDC(hwnd, hDC);

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::InvalidateRect
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::InvalidateRect
(
    LPCRECT prc,
    BOOL    fErase
)
{
#ifdef DEBUG
    if (prc) {
        TraceMsg(TF_TREXTDRAW, "TRS::InvalidateRect called with (%x, %x)-(%x, %x) fErase=%d",
             prc->left, prc->top, prc->right, prc->bottom, fErase);
    } else {
        TraceMsg(TF_TREXTDRAW, "TRS::InvalidateRect called prc=NULL, fErase=%d", fErase);
    }
#endif

    DWORD dwFlags = RDW_INVALIDATE | RDW_UPDATENOW;
    dwFlags |= (fErase ? RDW_ERASE : 0);

    HWND hwnd;
    _pContainer->GetWindow(&hwnd);
    ::RedrawWindow(hwnd, prc, NULL, dwFlags);

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::InvalidateRgn
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::InvalidateRgn
(
    HRGN hrgn,
    BOOL fErase
)
{
    TraceMsg(TF_TRSITE, "TRS::InvalidateRgn called");

    DWORD dwFlags = RDW_INVALIDATE | RDW_UPDATENOW;
    dwFlags |= (fErase ? RDW_ERASE : 0);

    HWND hwnd;
    _pContainer->GetWindow(&hwnd);
    ::RedrawWindow(hwnd, NULL, hrgn, dwFlags);

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::OnStatusBitsChange
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::OnStatusBitsChange
(
    DWORD dwFlags
)
{
    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// IAdviseSink interface
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::OnViewChange
/////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(void) CTransitionSite::OnViewChange(DWORD dwAspect, LONG lindex)
{
    static DWORD dwLastUpdate = 0;

    if  (
        ((GetTickCount() - dwLastUpdate) > MIN_ONVIEWCHANGE_DURATION)
        &&
        (_uState == TRSTATE_PAINTING)
        &&
        (dwAspect & DVASPECT_CONTENT)
        )
    {
        TraceMsg(TF_TRDRAW, "TRS::OnViewChange(%d)", lindex);

        _pSite->InvalidateRect(NULL, FALSE);
    }

    dwLastUpdate = GetTickCount();
}

/////////////////////////////////////////////////////////////////////////////
// IDispatch interface
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CTransitionSite::Invoke
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSite::Invoke
(
    DISPID          dispidMember,
    REFIID          riid,
    LCID            lcid,
    WORD            wFlags,
    DISPPARAMS *    pdispparams,
    VARIANT *       pvarResult,
    EXCEPINFO *     pexcepinfo,
    UINT *          puArgErr
)
{
    ASSERT(pdispparams != NULL);
    if (pdispparams == NULL)
        return E_INVALIDARG;

    if (!(wFlags & DISPATCH_METHOD))
        return E_INVALIDARG;

    switch(dispidMember)
    {
        case DISPID_ONTRANSITIONFINISH:
        {
            _OnComplete();
            break;
        }

        default:
            return E_INVALIDARG;
    }

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTransitionSitePropertyBag
/////////////////////////////////////////////////////////////////////////////
#undef CTransitionSite

CTransitionSitePropertyBag::CTransitionSitePropertyBag
(
) : _cRef(1)
{
    TraceMsg(TF_TRLIFE, "TRSPropBag::ctor called");

    // Implicit: _hdpaProperties = NULL;
}

CTransitionSitePropertyBag::~CTransitionSitePropertyBag()
{
    TraceMsg(TF_TRLIFE, "TRSPropBag::dtor called");

    if (_hdpaProperties != NULL)
    {
        DPA_DestroyCallback(_hdpaProperties, _DPA_FreeProperties, 0);
        _hdpaProperties = NULL;
    }
}

/////////////////////////////////////////////////////////////////////////////
// _DPA_FreeProperties
/////////////////////////////////////////////////////////////////////////////
int CTransitionSitePropertyBag::_DPA_FreeProperties(void *pv, void *pData)
{
    NAMEVALUE * pnv = (NAMEVALUE *)pv;

    ASSERT(pnv != NULL);
    ASSERT(pnv->pwszName != NULL);

    LocalFree(pnv->pwszName);
    pnv->pwszName = NULL;

    VariantClear(&pnv->varValue);

    LocalFree(pnv);
    pnv = NULL;

    return 1;
}

/////////////////////////////////////////////////////////////////////////////
// _AddProperty
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSitePropertyBag::_AddProperty(WCHAR *wszPropName, VARIANT *pvarValue)
{
    NAMEVALUE * pnv = NULL;
    HRESULT     hrResult = E_FAIL;

    // Create the list if needed
    if (_hdpaProperties == NULL)
        _hdpaProperties = DPA_Create(TSPB_CREATE_INCR);

    for (;;)
    {
        if (_hdpaProperties == NULL)
            break;

        // Alloc a new name value pair
        pnv = (NAMEVALUE *)LocalAlloc(LPTR, SIZEOF(NAMEVALUE));
        if (pnv == NULL)
        {
            TraceMsg(TF_WARNING, "TRSPB Unable to alloc memory for property");
            break;
        }

        // Copy the name
        UINT cb = (lstrlenW(wszPropName)+1) * SIZEOF(wszPropName[0]);
        pnv->pwszName = (LPWSTR)LocalAlloc(LPTR, cb);
        if (pnv->pwszName == NULL)
        {
            TraceMsg(TF_WARNING, "TRSPB Unable to alloc memory for property");
            break;
        }

        StrCpyNW(pnv->pwszName, wszPropName, cb / sizeof(wszPropName[0]));

        // Copy the value
        if (FAILED(hrResult = VariantCopy(&pnv->varValue, pvarValue)))
        {
            TraceMsg(TF_WARNING, "TRSPB VariantCopy failed");
            break;
        }

        // Add the name value pair to the list
        if (DPA_AppendPtr(_hdpaProperties, pnv) == DPA_ERR)
            break;

#ifdef DEBUG
        TCHAR szPropName[80];
        SHUnicodeToTChar(wszPropName, szPropName, SIZEOF(szPropName));

        VARIANT vVal = { 0 };
        TCHAR   szPropVal[80];

        EVAL(SUCCEEDED(VariantChangeType(&vVal, pvarValue, 0, VT_BSTR)));
        SHUnicodeToTChar(V_BSTR(&vVal), szPropVal, SIZEOF(szPropVal));
        EVAL(SUCCEEDED(VariantClear(&vVal)));

        TraceMsg(TF_TRSPB, "TRSPB::_AddProperty added '%s = %s'", szPropName, szPropVal);
#endif  // DEBUG        

        hrResult = S_OK;
        break;
    }

    // Cleanup on error
    if (FAILED(hrResult))
    {
        if (pnv != NULL)
        {
            if (pnv->pwszName != NULL)
            {
                LocalFree(pnv->pwszName);
                pnv->pwszName = NULL;
            }

            VariantClear(&pnv->varValue);

            LocalFree(pnv);
            pnv = NULL;
        }
    }

    return hrResult;
}

ULONG CTransitionSitePropertyBag::AddRef()
{
    _cRef++;
    return _cRef;
}

ULONG CTransitionSitePropertyBag::Release()
{
    ASSERT(_cRef > 0);
    _cRef--;

    if (_cRef > 0)
        return _cRef;

    delete this;
    return 0;
}

HRESULT CTransitionSitePropertyBag::QueryInterface(REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_IPropertyBag))
    {
        *ppvObj = SAFECAST(this, IPropertyBag *);
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// IPropertyBag interface
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CTransitionSitePropertyBag::Read
/////////////////////////////////////////////////////////////////////////////
HRESULT CTransitionSitePropertyBag::Read
(
    LPCOLESTR   pszPropName,
    VARIANT *   pVar,
    IErrorLog * pErrorLog
)
{
    HRESULT hrResult = E_INVALIDARG;

    if ((pszPropName == NULL) || (pVar == NULL))
        return E_POINTER;

#ifdef DEBUG
    TCHAR szPropName[80];
    SHUnicodeToTChar(pszPropName, szPropName, SIZEOF(szPropName));
    TraceMsg(TF_TRSPB, "TRSPB::Read(%s)", szPropName);
#endif  // DEBUG

    if (_hdpaProperties != NULL)
    {
        // Search for the property in the list.
        for (int i = 0; i < DPA_GetPtrCount(_hdpaProperties); i++)
        {
            NAMEVALUE * pnv = (NAMEVALUE *)DPA_GetPtr(_hdpaProperties, i);

            if (StrCmpIW(pszPropName, pnv->pwszName) == 0)
            {
                // Copy the variant property.
                hrResult = VariantChangeType(pVar, &pnv->varValue, 0, pVar->vt);
                break;
            }
        }
    }

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// Helper functions
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CLSIDFromTransitionName
/////////////////////////////////////////////////////////////////////////////
HRESULT CLSIDFromTransitionName
(
    LPCTSTR pszName,
    LPCLSID clsidName
)
{
    HKEY    hkeyTransitions = NULL;
    HRESULT hrResult;

    for (;;)
    {
        // Check for "{...CLSID...}"
        if (*pszName == TEXT('{'))
        {
            hrResult = SHCLSIDFromString(pszName, clsidName);
            break;
        }

        // Check for Transition Name
        if (RegCreateKey(   HKEY_LOCAL_MACHINE,
                            c_szTransitionsKey,
                            &hkeyTransitions) == ERROR_SUCCESS)
        {
            TCHAR   szCLSID[GUIDSTR_MAX];
            DWORD   cbBytes = SIZEOF(szCLSID);
            DWORD   dwType;

            if  (
                (RegQueryValueEx(   hkeyTransitions,
                                    pszName,
                                    NULL,
                                    &dwType,
                                    (BYTE *)szCLSID,
                                    &cbBytes) == ERROR_SUCCESS)
                &&
                (dwType == REG_SZ)
                )
            {
                hrResult = SHCLSIDFromString(szCLSID, clsidName);
                break;
            }
        }

        hrResult = E_FAIL;
        break;
    }

    if (hkeyTransitions != NULL)
        RegCloseKey(hkeyTransitions);

    return hrResult;
}

/////////////////////////////////////////////////////////////////////////////
// ParseTransitionInfo
//
// Purpose: Parse <META HTTP-EQUIV...> of the form:
//
//      <META
//          HTTP-EQUIV = transition-event
//          CONTENT = transition-description    
//      >
//
// where:
//
//      transition-event -> PAGE-ENTER
//                              | PAGE-EXIT
//                              | SITE-ENTER
//                              | SITE-EXIT
//
//      transition-description -> transition-name ( transition-properties ) 
//
//      transition-name -> IDENTIFIER
//                              | CLSID 
//                              | PROGID
//
//      transition-properties -> transition-property , transition-property
//      transition-properties -> transition-property
//
//      name-value-assignment -> = | :
//
//      transition-property -> NAME name-value-assignment VALUE
//
// examples:
//
//      <META
//          HTTP-EQUIV = "Page-Enter"
//          CONTENT = "Reveal(duration=500)"
//      >
//
//      <META
//          HTTP-EQUIV = "Page-Leave"
//          CONTENT = "Reveal(duration=500, type=checkerboard, size=10)"
//      >
//
/////////////////////////////////////////////////////////////////////////////
BOOL ParseTransitionInfo
(
    WCHAR *             pwz,
    TRANSITIONINFO *    pti
)
{
    enum
    {
        PTE_PARSE_TRANSITION_NAME,
        PTE_PARSE_NAME,
        PTE_PARSE_VALUE,
        PTE_FINISHED
    };

    #define MAX_TRANSITION_NAME_LEN GUIDSTR_MAX
    WCHAR wszTransitionName[MAX_TRANSITION_NAME_LEN];
    WCHAR * pwzTransitionName = wszTransitionName;

    #define MAX_PTINAME_LEN     32
    WCHAR   wszName[MAX_PTINAME_LEN];
    WCHAR * pwzName = wszName;

    #define MAX_PTIVALUE_LEN    32
    WCHAR   wszValue[MAX_PTIVALUE_LEN];
    WCHAR * pwzValue = wszValue;

    WCHAR   wch;
    UINT    cch         = 0;
    UINT    uiState     = PTE_PARSE_TRANSITION_NAME;
    BOOL    bSucceeded  = FALSE;

    ASSERT(pti != NULL);

    do
    {
        wch = *pwz;

        switch (uiState)
        {
            case PTE_PARSE_TRANSITION_NAME:
            {
                if (wch == TEXT('('))  // Open paren
                {
                    cch     = 0;
                    uiState = PTE_PARSE_NAME;

                    *pwzTransitionName = '\0';
                    pwzTransitionName = wszTransitionName;

                    TCHAR szTransitionName[MAX_TRANSITION_NAME_LEN];
                    SHUnicodeToTChar(wszTransitionName, szTransitionName, ARRAYSIZE(szTransitionName));

                    TraceMsg(DM_TRACE, "ParseTransitionInfo(%s)", szTransitionName);

                    // Resolve the Transition Name
                    EVAL(SUCCEEDED(CLSIDFromTransitionName(szTransitionName, &(pti->clsid))));
                }
                else if (
                        !ISSPACE(wch)
                        &&
                        (cch++ < (MAX_TRANSITION_NAME_LEN-1))
                        )
                {
                    *pwzTransitionName++ = wch;
                }
                else
                    ;   // Ignore
                    
                break;
            }

            case PTE_PARSE_NAME:
            {
                if  (
                    (wch == TEXT('='))  // Equal
                    ||
                    (wch == TEXT(':'))  // Semicolon
                    )
                {
                    cch     = 0;
                    uiState = PTE_PARSE_VALUE;

                    *pwzName = '\0';
                    pwzName = wszName;
                }
                else if (
                        !ISSPACE(wch)
                        &&
                        (cch++ < (MAX_PTINAME_LEN-1))
                        )
                {
                    *pwzName++ = wch;
                }
                else
                    ;   // Ignore

                break;
            }

            case PTE_PARSE_VALUE:
            {
                if  (
                    (wch == TEXT(','))  // Comma
                    ||
                    (wch == TEXT(')'))  // Close paren
                    )
                {
                    cch = 0;
                    if (wch == TEXT(','))
                        uiState = PTE_PARSE_NAME;
                    else
                        uiState = PTE_FINISHED;

                    *pwzValue = '\0';
                    pwzValue = wszValue;

                    // Initialize the property bag class
                    if (pti->pPropBag == NULL)
                        pti->pPropBag = new CTransitionSitePropertyBag;

                    if (pti->pPropBag)
                    {
                        VARIANT v;
                        if (SUCCEEDED(InitVariantFromStr(&v, wszValue)))
                        {
                            VARIANT vDuration = { 0 };
                            VARIANT* pvarToAdd = &v;

                            // Limit the duration of the transition.
                            if (StrCmpIW(wszName, c_szDurationProp) == 0)
                            {
                                if (SUCCEEDED(VariantChangeTypeEx(&vDuration, &v, MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, VT_R4)))
                                {
                                    ASSERT(V_VT(&vDuration) == VT_R4);

                                    if (V_R4(&vDuration) < MIN_TRANSITION_DURATION)
                                        V_R4(&vDuration) = MIN_TRANSITION_DURATION;
                                    else if (V_R4(&vDuration) > MAX_TRANSITION_DURATION)
                                        V_R4(&vDuration) = MAX_TRANSITION_DURATION;

                                    // don't need to VariantClear() vDuration since it's VT_R4 by the above ASSERT
                                    pvarToAdd = &vDuration;
                                }
                            }

                            // Add the property to the property bag
                            pti->pPropBag->_AddProperty(wszName, pvarToAdd);

                            VariantClear(&v);
                            // vDuration is VT_EMPTY or VT_R4, neither of which nead VariantClear
                        }
                    }
                }
                else if (
                        !ISSPACE(wch)
                        &&
                        (cch++ < (MAX_PTIVALUE_LEN-1))
                        )
                {
                    *pwzValue++ = wch;
                }
                else
                    ;   // Ignore

                break;
            }

            case PTE_FINISHED:
            {
                bSucceeded = TRUE;
                break;
            }

            default:
                break;
        }

        pwz++;
    } while (wch && !bSucceeded);

    return bSucceeded;
} // ParseTransitionInfo

