#include "stdafx.h"
#include "Ctrl.h"
#include "Animation.h"

#if ENABLE_MSGTABLE_API

/***************************************************************************\
*****************************************************************************
*
* Global Functions
*
*****************************************************************************
\***************************************************************************/

//------------------------------------------------------------------------------
DUSER_API void WINAPI
DUserStopAnimation(Visual * pgvSubject, PRID pridAni)
{
    if (pgvSubject == NULL) {
        PromptInvalid("Invalid pgvSubject");
        return;
    }
    if (pridAni <= 0) {
        PromptInvalid("Invalid Animation pridAni");
        return;
    }

    DuExtension * pext = DuExtension::GetExtension(pgvSubject, pridAni);
    if (pext != NULL) {
        pext->GetStub()->OnRemoveExisting();
    }
}


/***************************************************************************\
*****************************************************************************
*
* class DuAnimation
*
*****************************************************************************
\***************************************************************************/

MSGID       DuAnimation::s_msgidComplete = 0;

//------------------------------------------------------------------------------
DuAnimation::~DuAnimation()
{
    Destroy(TRUE);

    SafeRelease(m_pipol);
    SafeRelease(m_pgflow);

#if DEBUG_TRACECREATION
    Trace("STOP  Animation  0x%p    @ %d  (%d frames)\n", this, GetTickCount(), m_DEBUG_cUpdates);
#endif // DEBUG_TRACECREATION


    //
    // Ensure proper destruction
    //

    AssertMsg(m_hact == NULL, "Action should already be destroyed");
}


/***************************************************************************\
*
* DuAnimation::InitClass
*
* InitClass() is called during startup and provides an opportunity to 
* initialize common data.
*
\***************************************************************************/

HRESULT
DuAnimation::InitClass()
{
    s_msgidComplete = RegisterGadgetMessage(&_uuidof(Animation::evComplete));
    if (s_msgidComplete == 0) {
        return (HRESULT) GetLastError();
    }

    return S_OK;
}

//------------------------------------------------------------------------------
HRESULT
DuAnimation::PreBuild(DUser::Gadget::ConstructInfo * pci)
{
    //
    // Validate parameters
    //

    Animation::AniCI * pDesc = reinterpret_cast<Animation::AniCI *>(pci);
    if ((pDesc->pipol == NULL) || (pDesc->pgflow == NULL)) {
        PromptInvalid("Must provide valid Interpolation and Flow objects");
        return E_INVALIDARG;
    }

    PRID pridExtension = 0;
    VerifyHR(pDesc->pgflow->GetPRID(&pridExtension));
    if (pridExtension == 0) {
        PromptInvalid("Flow must register PRID");
        return E_INVALIDARG;
    }

    return S_OK;
}


//------------------------------------------------------------------------------
HRESULT
DuAnimation::PostBuild(DUser::Gadget::ConstructInfo * pci)
{
    //
    // Check parameters.  This should be validated in PreBuild().
    //

    Animation::AniCI * pDesc = reinterpret_cast<Animation::AniCI *>(pci);

    Assert(pDesc->pipol != NULL);
    Assert(pDesc->pgflow != NULL);


    //
    // Setup the Action
    //

    GMA_ACTION gma;
    ZeroMemory(&gma, sizeof(gma));
    gma.cbSize      = sizeof(gma);
    gma.flDelay     = pDesc->act.flDelay;
    gma.flDuration  = pDesc->act.flDuration;
    gma.flPeriod    = pDesc->act.flPeriod;
    gma.cRepeat     = pDesc->act.cRepeat;
    gma.dwPause     = pDesc->act.dwPause;
    gma.pfnProc     = RawActionProc;
    gma.pvData      = this;

    m_hact = CreateAction(&gma);
    if (m_hact == NULL) {
        return (HRESULT) GetLastError();
    }

    PRID pridExtension;
    VerifyHR(pDesc->pgflow->GetPRID(&pridExtension));
    HRESULT hr = DuExtension::Create(pDesc->pgvSubject, pridExtension, DuExtension::oAsyncDestroy);
    if (FAILED(hr)) {
        return hr;
    }


    //
    // Store the related objects
    //

    pDesc->pipol->AddRef();
    pDesc->pgflow->AddRef();

    m_pipol     = pDesc->pipol;
    m_pgflow    = pDesc->pgflow;


    //
    // Animations need to be AddRef()'d again (have a reference count of 2) 
    // because they need to outlive the initial call to Release() after the 
    // called has setup the animation returned from BuildAnimation().  
    //
    // This is because the Animation continues to life until it has fully 
    // executed (or has been aborted).
    //

    AddRef();

    return S_OK;
}


//------------------------------------------------------------------------------
void
DuAnimation::Destroy(BOOL fFinal)
{
    //
    // Mark that we have already started the destruction process and don't need
    // to start again.  We only want to post the destruction message once.
    //

    if (m_fStartDestroy) {
        return;
    }
    m_fStartDestroy = TRUE;


    if (m_pgvSubject != NULL) {
#if DBG
        DuAnimation * paniExist = static_cast<DuAnimation *> (GetExtension(m_pgvSubject, m_pridListen));
        if (paniExist != NULL) {
            AssertMsg(paniExist == this, "Animations must match");
        }
#endif // DBG

        CleanupChangeGadget();
    }


    //
    // Destroy the Animation
    //

    AssertMsg(!fFinal, "Object is already being destructed");
    if (fFinal) {
        GetStub()->OnAsyncDestroy();
    } else {
        PostAsyncDestroy();
    }
}


//------------------------------------------------------------------------------
HRESULT
DuAnimation::ApiOnAsyncDestroy(Animation::OnAsyncDestroyMsg *)
{
    AssertMsg(m_fStartDestroy, "Must call Destroy() to start the destruction process.");
    AssertMsg(!m_fProcessing, "Should not be processing when start destruction");

    AssertMsg(m_pgvSubject == NULL, "Animation should already have detached from Gadget");
    HACTION hact = m_hact;

    //
    // Set everything to NULL now.
    //

    m_hact = NULL;
    if (hact != NULL) {
        ::DeleteHandle(hact);
        hact = NULL;
    }

    Release();

    return S_OK;
}


//------------------------------------------------------------------------------
HRESULT
DuAnimation::ApiSetTime(Animation::SetTimeMsg * pmsg)
{
    GMA_ACTIONINFO mai;

    //
    // TODO: Need to save these values from the last time so that they are 
    // valid.
    //

    mai.hact        = m_hact;
    mai.pvData      = this;
    mai.flDuration  = 0.0f;

    m_time = (Animation::ETime) pmsg->time;
    switch (pmsg->time)
    {
    case Animation::tComplete:
        // Don't do anything
        return S_OK;

    default:
    case Animation::tAbort:
    case Animation::tDestroy:
        goto Done;

    case Animation::tEnd:
        mai.flProgress  = 1.0f;
        break;

    case Animation::tReset:
        mai.flProgress  = 0.0f;
        break;
    }

    mai.cEvent      = 0;
    mai.cPeriods    = 0;
    mai.fFinished   = FALSE;

    m_fProcessing = TRUE;
    m_pgflow->OnAction(m_pgvSubject, m_pipol, mai.flProgress);
    Assert(m_fProcessing);
    m_fProcessing = FALSE;

Done:
    ::DeleteHandle(m_hact);
    
    return S_OK;    
}


//------------------------------------------------------------------------------
void
DuAnimation::CleanupChangeGadget()
{
    //
    // Give the derived Animation a chance to cleanup
    //
    // Check that we are still the Animation attached to this Gadget.  We need 
    // to remove this property immediately.  We can not wait for a posted 
    // message to be processed because we may need to set it right now if we are
    // creating a new Animation.
    //

    BOOL fStarted = FALSE;

    Animation::CompleteEvent msg;
    msg.cbSize  = sizeof(msg);
    msg.nMsg    = s_msgidComplete;
    msg.hgadMsg = GetHandle();
    msg.fNormal = IsStartDelete(m_pgvSubject->GetHandle(), &fStarted) && (!fStarted);

    DUserSendEvent(&msg, 0);


    Assert(m_pgvSubject != NULL);
    Assert(m_pridListen != 0);

    Verify(SUCCEEDED(m_pgvSubject->RemoveProperty(m_pridListen)));

    m_pgvSubject = NULL;
}

    
//------------------------------------------------------------------------------
void CALLBACK
DuAnimation::RawActionProc(
    IN  GMA_ACTIONINFO * pmai)
{
    //
    // Need to AddRef while processing the Animation to ensure that it does not
    // get destroyed from under us, for example, during one of the callbacks.
    //

    DuAnimation * pani = (DuAnimation *) pmai->pvData;
    pani->AddRef();

    Assert(!pani->m_fProcessing);

#if DEBUG_TRACECREATION
    Trace("START RawActionP 0x%p    @ %d\n", pani, GetTickCount());
#endif // DEBUG_TRACECREATION

    pani->ActionProc(pmai);

#if DEBUG_TRACECREATION
    Trace("STOP  RawActionP 0x%p    @ %d\n", pani, GetTickCount());
#endif // DEBUG_TRACECREATION

    Assert(!pani->m_fProcessing);

    pani->Release();
}


//------------------------------------------------------------------------------
void
DuAnimation::ActionProc(
    IN  GMA_ACTIONINFO * pmai)
{
#if DBG
    m_DEBUG_cUpdates++;
#endif // DBG

    if ((!m_fStartDestroy) && (m_pgvSubject != NULL)) {
        //
        // This ActionProc will be called when the Action is being destroyed, so
        // we only want to invoke the Action under certain circumstances.
        //

        switch (m_time)
        {
        case Animation::tComplete:
        case Animation::tEnd:
        case Animation::tReset:
            //
            // All of these are valid to complete.  If it isn't in this list, we
            // don't want to execute it during a shutdown.
            //

            m_fProcessing = TRUE;
            m_pgflow->OnAction(m_pgvSubject, m_pipol, pmai->flProgress);
            Assert(m_fProcessing);
            m_fProcessing = FALSE;
            break;
        }
    }

    if (pmai->fFinished) {
        m_hact = NULL;
        Destroy(FALSE);
    }
}


//------------------------------------------------------------------------------
HRESULT
DuAnimation::ApiOnRemoveExisting(Animation::OnRemoveExistingMsg *)
{
    GetStub()->SetTime(Animation::tDestroy);
    return S_OK;
}


//------------------------------------------------------------------------------
HRESULT
DuAnimation::ApiOnDestroySubject(Animation::OnDestroySubjectMsg *)
{
    AddRef();

    if (m_pgvSubject != NULL) {
        CleanupChangeGadget();

        //
        // The Gadget that we are modifying is being destroyed, so we need
        // to stop animating it.
        //

        m_time = Animation::tDestroy;
        Destroy(FALSE);
    }

    Release();

    return S_OK;
}

#else

//------------------------------------------------------------------------------
DUSER_API void WINAPI
DUserStopAnimation(Visual * pgvSubject, PRID pridAni)
{
    UNREFERENCED_PARAMETER(pgvSubject);
    UNREFERENCED_PARAMETER(pridAni);

    PromptInvalid("Not implemented without MsgTable support");
}

#endif // ENABLE_MSGTABLE_API