/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
 
#include "effectrewinder.hxx"
#include <eventqueue.hxx>
#include <usereventqueue.hxx>
#include <basecontainernode.hxx>
#include <delayevent.hxx>
 
#include <com/sun/star/animations/Event.hpp>
#include <com/sun/star/animations/EventTrigger.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <utility>
 
using ::com::sun::star::uno::Reference;
using namespace ::com::sun::star;
 
namespace slideshow::internal {
 
 
namespace {
 
class RewinderEventHandler : public EventHandler
{
public:
    typedef ::std::function<bool ()> Action;
    explicit RewinderEventHandler (Action aAction) : maAction(std::move(aAction)) {}
 
private:
    const Action maAction;
    virtual bool handleEvent() override { return maAction(); }
};
 
 
class RewinderAnimationEventHandler : public AnimationEventHandler
{
public:
    typedef ::std::function<bool (const AnimationNodeSharedPtr& rpNode)> Action;
    explicit RewinderAnimationEventHandler (Action aAction) : maAction(std::move(aAction)) {}
 
private:
    const Action maAction;
    virtual bool handleAnimationEvent (const AnimationNodeSharedPtr& rpNode) override
        { return maAction(rpNode); }
};
 
 
} // end of anonymous namespace
 
 
//----- EffectRewinder --------------------------------------------------------------
 
EffectRewinder::EffectRewinder (
    EventMultiplexer& rEventMultiplexer,
    EventQueue& rEventQueue,
    UserEventQueue& rUserEventQueue)
    : mrEventMultiplexer(rEventMultiplexer),
      mrEventQueue(rEventQueue),
      mrUserEventQueue(rUserEventQueue),
      mpSlideStartHandler(),
      mpSlideEndHandler(),
      mpAnimationStartHandler(),
      mnMainSequenceEffectCount(0),
      mpAsynchronousRewindEvent(),
      mxCurrentAnimationRootNode(),
      mxCurrentSlide(),
      mbNonUserTriggeredMainSequenceEffectSeen(false),
      mbHasAdvancedTimeSetting(false)
{
    initialize();
}
 
 
void EffectRewinder::initialize()
{
    // Add some event handlers so that we are informed when
    // a) an animation is started (we then check whether that belongs to a
    // main sequence effect and if so, increase the respective counter),
    // b,c) a slide was started or ended (in which case the effect counter
    // is reset.
 
    mpAnimationStartHandler =
        std::make_shared<RewinderAnimationEventHandler>(
            [this]( const AnimationNodeSharedPtr& pNode)
            { return this->notifyAnimationStart( pNode ); } );
    mrEventMultiplexer.addAnimationStartHandler(mpAnimationStartHandler);
 
    mpSlideStartHandler =
        std::make_shared<RewinderEventHandler>(
            [this]() { return this->resetEffectCount(); } );
    mrEventMultiplexer.addSlideStartHandler(mpSlideStartHandler);
 
    mpSlideEndHandler =
        std::make_shared<RewinderEventHandler>(
            [this]() { return this->resetEffectCount(); } );
    mrEventMultiplexer.addSlideEndHandler(mpSlideEndHandler);
}
 
 
EffectRewinder::~EffectRewinder()
{
    dispose();
}
 
 
void EffectRewinder::dispose()
{
    if (mpAsynchronousRewindEvent)
    {
        mpAsynchronousRewindEvent->dispose();
        mpAsynchronousRewindEvent.reset();
    }
 
    if (mpAnimationStartHandler)
    {
        mrEventMultiplexer.removeAnimationStartHandler(mpAnimationStartHandler);
        mpAnimationStartHandler.reset();
    }
 
    if (mpSlideStartHandler)
    {
        mrEventMultiplexer.removeSlideStartHandler(mpSlideStartHandler);
        mpSlideStartHandler.reset();
    }
 
    if (mpSlideEndHandler)
    {
        mrEventMultiplexer.removeSlideEndHandler(mpSlideEndHandler);
        mpSlideEndHandler.reset();
    }
}
 
 
void EffectRewinder::setRootAnimationNode (
    const uno::Reference<animations::XAnimationNode>& xRootNode)
{
    mxCurrentAnimationRootNode = xRootNode;
}
 
void EffectRewinder::setCurrentSlide (
    const uno::Reference<drawing::XDrawPage>& xSlide)
{
    mxCurrentSlide = xSlide;
 
    // Check if the current slide has advance time setting or not
    uno::Reference< beans::XPropertySet > xPropSet( mxCurrentSlide, uno::UNO_QUERY );
    sal_Int32 nChange(0);
 
    if( xPropSet.is())
        getPropertyValue( nChange, xPropSet, u"Change"_ustr);
 
    mbHasAdvancedTimeSetting = nChange;
}
 
bool EffectRewinder::rewind (
    const ::std::shared_ptr<ScreenUpdater::UpdateLock>& rpPaintLock,
    const ::std::function<void ()>& rSlideRewindFunctor,
    const ::std::function<void ()>& rPreviousSlideFunctor)
{
    mpPaintLock = rpPaintLock;
 
    // Do not allow nested rewinds.
    if (mpAsynchronousRewindEvent)
    {
        OSL_ASSERT( ! mpAsynchronousRewindEvent);
        return false;
    }
 
    // Abort (and skip over the rest of) any currently active animation.
    mrUserEventQueue.callSkipEffectEventHandler();
 
    if (!mbHasAdvancedTimeSetting)
        mrEventQueue.forceEmpty();
 
    const int nSkipCount (mnMainSequenceEffectCount - 1);
    if (nSkipCount < 0)
    {
        if ( ! rPreviousSlideFunctor)
        {
            OSL_ASSERT(rPreviousSlideFunctor);
            return false;
        }
 
        // No main sequence effects to rewind on the current slide.
        // Go back to the previous slide.
        mpAsynchronousRewindEvent = makeEvent(
            ::std::bind(
                &EffectRewinder::asynchronousRewindToPreviousSlide,
                this,
                rPreviousSlideFunctor),
            u"EffectRewinder::asynchronousRewindToPreviousSlide"_ustr);
    }
    else
    {
        // The actual rewinding is done asynchronously so that we can safely
        // call other methods.
        mpAsynchronousRewindEvent = makeEvent(
            ::std::bind(
                &EffectRewinder::asynchronousRewind,
                this,
                nSkipCount,
                true,
                rSlideRewindFunctor),
            u"EffectRewinder::asynchronousRewind"_ustr);
    }
 
    if (mpAsynchronousRewindEvent)
        mrEventQueue.addEvent(mpAsynchronousRewindEvent);
 
    return bool(mpAsynchronousRewindEvent);
}
 
 
void EffectRewinder::skipAllMainSequenceEffects()
{
    // Do not allow nested rewinds.
    if (mpAsynchronousRewindEvent)
    {
        OSL_ASSERT(!mpAsynchronousRewindEvent);
        return;
    }
 
    const int nTotalMainSequenceEffectCount (countMainSequenceEffects());
    mpAsynchronousRewindEvent = makeEvent(
        ::std::bind(
            &EffectRewinder::asynchronousRewind,
            this,
            nTotalMainSequenceEffectCount,
            false,
            ::std::function<void ()>()),
        u"EffectRewinder::asynchronousRewind"_ustr);
    mrEventQueue.addEvent(mpAsynchronousRewindEvent);
}
 
 
sal_Int32 EffectRewinder::countMainSequenceEffects()
{
    // Determine the number of main sequence effects.
    sal_Int32 nMainSequenceNodeCount (0);
 
    ::std::queue<uno::Reference<animations::XAnimationNode> > aNodeQueue;
    aNodeQueue.push(mxCurrentAnimationRootNode);
    while ( ! aNodeQueue.empty())
    {
        const uno::Reference<animations::XAnimationNode> xNode (aNodeQueue.front());
        aNodeQueue.pop();
 
        // Does the current node belong to the main sequence?
        if (xNode.is())
        {
            animations::Event aEvent;
            if (xNode->getBegin() >>= aEvent)
                if (aEvent.Trigger == animations::EventTrigger::ON_NEXT)
                    ++nMainSequenceNodeCount;
        }
 
        // If the current node is a container then prepare its children for investigation.
        uno::Reference<container::XEnumerationAccess> xEnumerationAccess (xNode, uno::UNO_QUERY);
        if (xEnumerationAccess.is())
        {
            uno::Reference<container::XEnumeration> xEnumeration (
                xEnumerationAccess->createEnumeration());
            if (xEnumeration.is())
                while (xEnumeration->hasMoreElements())
                {
                    aNodeQueue.push(
                        uno::Reference<animations::XAnimationNode>(
                            xEnumeration->nextElement(), uno::UNO_QUERY));
                }
        }
    }
 
    return nMainSequenceNodeCount;
}
 
 
void EffectRewinder::skipSingleMainSequenceEffects()
{
    // This basically just starts the next effect and then skips over its
    // animation.
    mrEventMultiplexer.notifyNextEffect();
    mrEventQueue.forceEmpty();
    mrUserEventQueue.callSkipEffectEventHandler();
    mrEventQueue.forceEmpty();
}
 
 
bool EffectRewinder::resetEffectCount()
{
    mnMainSequenceEffectCount = 0;
    return false;
}
 
 
bool EffectRewinder::notifyAnimationStart (const AnimationNodeSharedPtr& rpNode)
{
    // This notification is only relevant for us when the rpNode belongs to
    // the main sequence.
    BaseNodeSharedPtr pBaseNode (::std::dynamic_pointer_cast<BaseNode>(rpNode));
    if ( ! pBaseNode)
        return false;
 
    BaseContainerNodeSharedPtr pParent (pBaseNode->getParentNode());
    if ( ! (pParent && pParent->isMainSequenceRootNode()))
        return false;
 
    // This notification is only relevant for us when the effect is user
    // triggered.
    bool bIsUserTriggered (false);
 
    Reference<animations::XAnimationNode> xNode (rpNode->getXAnimationNode());
    if (xNode.is())
    {
        animations::Event aEvent;
        if (xNode->getBegin() >>= aEvent)
            bIsUserTriggered = (aEvent.Trigger == animations::EventTrigger::ON_NEXT);
    }
 
    if (bIsUserTriggered)
        ++mnMainSequenceEffectCount;
    else
        mbNonUserTriggeredMainSequenceEffectSeen = true;
 
    return false;
}
 
 
void EffectRewinder::asynchronousRewind (
    sal_Int32 nEffectCount,
    const bool bRedisplayCurrentSlide,
    const std::function<void ()>& rSlideRewindFunctor)
{
    OSL_ASSERT(mpAsynchronousRewindEvent);
 
    if (bRedisplayCurrentSlide)
    {
        mpPaintLock->Activate();
        // Re-display the current slide.
        if (rSlideRewindFunctor)
            rSlideRewindFunctor();
        mpAsynchronousRewindEvent = makeEvent(
            ::std::bind(
                &EffectRewinder::asynchronousRewind,
                this,
                nEffectCount,
                false,
                rSlideRewindFunctor),
            u"EffectRewinder::asynchronousRewind"_ustr);
        mrEventQueue.addEvent(mpAsynchronousRewindEvent);
    }
    else
    {
        // Process initial events and skip any animations that are started
        // when the slide is shown.
        mbNonUserTriggeredMainSequenceEffectSeen = false;
 
        if (!mbHasAdvancedTimeSetting)
            mrEventQueue.forceEmpty();
 
        if (mbNonUserTriggeredMainSequenceEffectSeen)
        {
            mrUserEventQueue.callSkipEffectEventHandler();
            mrEventQueue.forceEmpty();
        }
 
        while (--nEffectCount >= 0)
            skipSingleMainSequenceEffects();
 
        mpAsynchronousRewindEvent.reset();
        mpPaintLock.reset();
    }
}
 
 
void EffectRewinder::asynchronousRewindToPreviousSlide (
    const ::std::function<void ()>& rSlideRewindFunctor)
{
    OSL_ASSERT(mpAsynchronousRewindEvent);
 
    mpAsynchronousRewindEvent.reset();
    rSlideRewindFunctor();
}
 
 
} // end of namespace ::slideshow::internal
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V547 Expression 'mbNonUserTriggeredMainSequenceEffectSeen' is always false.