/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_slideshow.hxx"

#include <canvas/debug.hxx>
#include <tools/diagnose_ex.h>
#include <canvas/elapsedtime.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>

#include <comphelper/anytostring.hxx>
#include <cppuhelper/exc_hlp.hxx>

#include <rtl/math.hxx>
#include <vcl/metric.hxx>
#include <vcl/salbtype.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/metaact.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/drawing/TextAnimationKind.hpp>
#include <com/sun/star/drawing/TextAnimationDirection.hpp>
#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
#include <com/sun/star/drawing/HomogenMatrix3.hpp>
#include <com/sun/star/awt/Rectangle.hpp>

#include "activity.hxx"
#include "wakeupevent.hxx"
#include "eventqueue.hxx"
#include "drawshapesubsetting.hxx"
#include "drawshape.hxx"
#include "shapesubset.hxx"
#include "shapeattributelayerholder.hxx"
#include "slideshowcontext.hxx"
#include "tools.hxx"
#include "gdimtftools.hxx"
#include "eventmultiplexer.hxx"
#include "intrinsicanimationactivity.hxx"
#include "intrinsicanimationeventhandler.hxx"

#include <boost/weak_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <vector>

using namespace com::sun::star;
using namespace ::slideshow::internal;

namespace {

class ScrollTextAnimNode
{
    sal_uInt32  mnDuration; // single duration
    sal_uInt32  mnRepeat; // 0 -> endless
    double      mfStart;
    double      mfStop;
    sal_uInt32  mnFrequency; // in ms
    // forth and back change at mnRepeat%2:
    bool        mbAlternate;
    
public:
    ScrollTextAnimNode(
        sal_uInt32 nDuration, sal_uInt32 nRepeat, double fStart, double fStop,
        sal_uInt32 nFrequency, bool bAlternate)
        :   mnDuration(nDuration),
            mnRepeat(nRepeat),
            mfStart(fStart),
            mfStop(fStop),
            mnFrequency(nFrequency),
            mbAlternate(bAlternate)
        {}
    
    sal_uInt32 GetDuration() const { return mnDuration; }
    sal_uInt32 GetRepeat() const { return mnRepeat; }
    sal_uInt32 GetFullTime() const { return mnDuration * mnRepeat; }
    double GetStart() const { return mfStart; }
    double GetStop() const { return mfStop; }
    sal_uInt32 GetFrequency() const { return mnFrequency; }
    bool DoAlternate() const { return mbAlternate; }
    
    double GetStateAtRelativeTime(sal_uInt32 nRelativeTime) const;
};

double ScrollTextAnimNode::GetStateAtRelativeTime(
    sal_uInt32 nRelativeTime) const
{
    // #151174# Avoid division by zero.
    if( mnDuration == 0 )
        return mfStop;

    if(mnRepeat) 
    {
        // ending
        const sal_uInt32 nRepeatCount(nRelativeTime / mnDuration);
        sal_uInt32 nFrameTime(nRelativeTime - (nRepeatCount * mnDuration));
        
        if(DoAlternate() && (nRepeatCount + 1L) % 2L) 
            nFrameTime = mnDuration - nFrameTime;
        
        return mfStart + ((mfStop - mfStart) *
                          (double(nFrameTime) / mnDuration));
    }
    else 
    {
        // endless
        sal_uInt32 nFrameTime(nRelativeTime % mnDuration);
        
        if(DoAlternate()) 
        {
            const sal_uInt32 nRepeatCount(nRelativeTime / mnDuration);
            
            if((nRepeatCount + 1L) % 2L)
                nFrameTime = mnDuration - nFrameTime;
        }
        
        return mfStart + ((mfStop - mfStart) * (double(nFrameTime) / mnDuration));
    }
}

class ActivityImpl : public Activity,
                     public boost::enable_shared_from_this<ActivityImpl>,
                     private boost::noncopyable
{
public:
    virtual ~ActivityImpl();
    
    ActivityImpl(
        SlideShowContext const& rContext,
        boost::shared_ptr<WakeupEvent> const& pWakeupEvent,
        boost::shared_ptr<DrawShape> const& pDrawShape );

    bool enableAnimations();
    
    // Disposable:
    virtual void dispose();
    // Activity:
    virtual double calcTimeLag() const;
    virtual bool perform();
    virtual bool isActive() const;
    virtual void dequeued();
    virtual void end();

private:    
    void updateShapeAttributes( double fTime,
                                basegfx::B2DRectangle const& parentBounds );
    
    // Access to VisibleWhenSTarted flags
    sal_Bool IsVisibleWhenStarted() const { return mbVisibleWhenStarted; }
    sal_Bool IsVisibleWhenStopped() const { return mbVisibleWhenStopped; }
    
    // scroll horizontal? if sal_False, scroll is vertical.
    bool ScrollHorizontal() const {
        return (drawing::TextAnimationDirection_LEFT == meDirection ||
                drawing::TextAnimationDirection_RIGHT == meDirection);
    }
    
    // Access to StepWidth in logical units
    sal_uInt32 GetStepWidthLogic() const;
    
    // is the animation direction opposite?
    bool DoScrollForward() const {
        return (drawing::TextAnimationDirection_RIGHT == meDirection ||
                drawing::TextAnimationDirection_DOWN == meDirection);
    }

    // do alternate text directions?
    bool DoAlternate() const { return mbAlternate; }
    
    // do scroll in?
    bool DoScrollIn() const { return mbScrollIn; }
    
    // Scroll helper methods
    void ImpForceScrollTextAnimNodes();
    ScrollTextAnimNode* ImpGetScrollTextAnimNode(
        sal_uInt32 nTime, sal_uInt32& rRelativeTime );
    sal_uInt32 ImpRegisterAgainScrollTextMixerState(
        sal_uInt32 nTime);

    // calculate the MixerState value for given time
    double GetMixerState(sal_uInt32 nTime);

    ////////////////////////////////////////////////////////////////////
    
    SlideShowContext                            maContext;
    boost::shared_ptr<WakeupEvent>              mpWakeupEvent;
    boost::weak_ptr<DrawShape>                  mpParentDrawShape;
    DrawShapeSharedPtr                          mpDrawShape;
    ShapeAttributeLayerHolder                   maShapeAttrLayer;
    GDIMetaFileSharedPtr                        mpMetaFile;
    IntrinsicAnimationEventHandlerSharedPtr     mpListener;
    canvas::tools::ElapsedTime                  maTimer;
    double                                      mfRotationAngle;
    bool                                        mbIsShapeAnimated;
    bool                                        mbIsDisposed;
    bool                                        mbIsActive;
    drawing::TextAnimationKind                  meAnimKind;
    
    // The blink frequency in ms
    sal_uInt32                                  mnFrequency;
    
    // The repeat count, init to 0L which means endless
    sal_uInt32                                  mnRepeat;
    
    // Flag to decide if text will be shown when animation has ended
    bool                                        mbVisibleWhenStopped;
    bool                                        mbVisibleWhenStarted;

    // Flag decides if TextScroll alternates. Default is sal_False.
    bool                                        mbAlternate;
    
    // Flag to remember if this is a simple scrollin text
    bool                                        mbScrollIn;
    
    // start time for this animation
    sal_uInt32                                  mnStartTime;
    
    // The AnimationDirection
    drawing::TextAnimationDirection             meDirection;
    
    // Get width per Step. Negative means pixel, positive logical units
    sal_Int32                                   mnStepWidth;
    
    // The single anim steps
    std::vector< ScrollTextAnimNode >           maVector;
    
    // the scroll rectangle
    Rectangle                                   maScrollRectangleLogic;
    
    // the paint rectangle
    Rectangle                                   maPaintRectangleLogic;
};

//////////////////////////////////////////////////////////////////////

class IntrinsicAnimationListener : public IntrinsicAnimationEventHandler,
                                   private boost::noncopyable
{
public:
    explicit IntrinsicAnimationListener( ActivityImpl& rActivity ) :
        mrActivity( rActivity )
    {}

private:

    virtual bool enableAnimations() { return mrActivity.enableAnimations(); }
    virtual bool disableAnimations() { mrActivity.end(); return true; }

    ActivityImpl& mrActivity;
};

//////////////////////////////////////////////////////////////////////

double ActivityImpl::GetMixerState( sal_uInt32 nTime )
{
    if( meAnimKind == drawing::TextAnimationKind_BLINK ) 
    {
        // from AInfoBlinkText:
        double fRetval(0.0);
        sal_Bool bDone(sal_False);
        const sal_uInt32 nLoopTime(2 * mnFrequency);
        
        if(mnRepeat) 
        {
            const sal_uInt32 nEndTime(mnRepeat * nLoopTime);
            
            if(nTime >= nEndTime) 
            {
                if(mbVisibleWhenStopped) 
                    fRetval = 0.0;
                else
                    fRetval = 1.0;
                
                bDone = sal_True;
            }
        }
        
        if(!bDone) 
        {
            sal_uInt32 nTimeInLoop(nTime % nLoopTime);
            fRetval = double(nTimeInLoop) / nLoopTime;
        }
        
        return fRetval;
    }
    else 
    {
        // from AInfoScrollText:
        double fRetval(0.0);
        ImpForceScrollTextAnimNodes();
        
        if(!maVector.empty()) 
        {
            sal_uInt32 nRelativeTime;
            ScrollTextAnimNode* pNode =
                ImpGetScrollTextAnimNode(nTime, nRelativeTime);
            
            if(pNode) 
            {
                // use node
                fRetval = pNode->GetStateAtRelativeTime(nRelativeTime);
            }
            else 
            {
                // end of animation, take last entry's end
                fRetval = maVector[maVector.size() - 1L].GetStop();
            }
        }
        
        return fRetval;
    }
}

// Access to StepWidth in logical units
sal_uInt32 ActivityImpl::GetStepWidthLogic() const
{
    // #i69847# Assuming higher DPI
    sal_uInt32 const PIXEL_TO_LOGIC = 30;
    
    sal_uInt32 nRetval(0L);
    
    if(mnStepWidth < 0L) 
    {
        // is in pixels, convert to logical units
        nRetval = (-mnStepWidth * PIXEL_TO_LOGIC);
    }
    else if(mnStepWidth > 0L) 
    {
        // is in logical units
        nRetval = mnStepWidth;
    }
    
    if(0L == nRetval) 
    {
        // step 1 pixel, canned value

        // #128389# with very high DPIs like in PDF export, this can
        // still get zero.  for that cases, set a default, too (taken
        // from ainfoscrolltext.cxx)
        nRetval = 100L;
    }
    
    return nRetval;
}

void ActivityImpl::ImpForceScrollTextAnimNodes()
{
    if(maVector.empty())
    {
        // prepare values
        sal_uInt32 nLoopTime;
        double fZeroLogic, fOneLogic, fInitLogic, fDistanceLogic;
        double fZeroLogicAlternate = 0.0, fOneLogicAlternate = 0.0;
        double fZeroRelative, fOneRelative, fInitRelative,fDistanceRelative;
        
        if(ScrollHorizontal())
        {
            if(DoAlternate())
            {
                if(maPaintRectangleLogic.GetWidth() >
                   maScrollRectangleLogic.GetWidth())
                {
                    fZeroLogicAlternate = maScrollRectangleLogic.Right() - maPaintRectangleLogic.GetWidth();
                    fOneLogicAlternate = maScrollRectangleLogic.Left();
                }
                else 
                {
                    fZeroLogicAlternate = maScrollRectangleLogic.Left();
                    fOneLogicAlternate = maScrollRectangleLogic.Right() - maPaintRectangleLogic.GetWidth();
                }
            }
            
            fZeroLogic = maScrollRectangleLogic.Left() - maPaintRectangleLogic.GetWidth();
            fOneLogic = maScrollRectangleLogic.Right();
            fInitLogic = maPaintRectangleLogic.Left();
        }
        else 
        {
            if(DoAlternate())
            {
                if(maPaintRectangleLogic.GetHeight() > maScrollRectangleLogic.GetHeight())
                {
                    fZeroLogicAlternate = maScrollRectangleLogic.Bottom() - maPaintRectangleLogic.GetHeight();
                    fOneLogicAlternate = maScrollRectangleLogic.Top();
                }
                else 
                {
                    fZeroLogicAlternate = maScrollRectangleLogic.Top();
                    fOneLogicAlternate = maScrollRectangleLogic.Bottom() - maPaintRectangleLogic.GetHeight();
                }
            }
            
            fZeroLogic = maScrollRectangleLogic.Top() - maPaintRectangleLogic.GetHeight();
            fOneLogic = maScrollRectangleLogic.Bottom();
            fInitLogic = maPaintRectangleLogic.Top();
        }
        
        fDistanceLogic = fOneLogic - fZeroLogic;
        fInitRelative = (fInitLogic - fZeroLogic) / fDistanceLogic;
        
        if(DoAlternate()) 
        {
            fZeroRelative =
                (fZeroLogicAlternate - fZeroLogic) / fDistanceLogic;
            fOneRelative =
                (fOneLogicAlternate - fZeroLogic) / fDistanceLogic;
            fDistanceRelative = fOneRelative - fZeroRelative;
        }
        else 
        {
            fZeroRelative = 0.0;
            fOneRelative = 1.0;
            fDistanceRelative = 1.0;
        }
        
        if(mnStartTime) 
        {
            // Start time loop
            ScrollTextAnimNode aStartNode(
                mnStartTime, 1L, 0.0, 0.0, mnStartTime, false);
            maVector.push_back(aStartNode);
        }
        
        if(IsVisibleWhenStarted()) 
        {
            double fRelativeStartValue, fRelativeEndValue,fRelativeDistance;
            
            if(DoScrollForward()) 
            {
                fRelativeStartValue = fInitRelative;
                fRelativeEndValue = fOneRelative;
                fRelativeDistance = fRelativeEndValue - fRelativeStartValue;
            }
            else 
            {
                fRelativeStartValue = fInitRelative;
                fRelativeEndValue = fZeroRelative;
                fRelativeDistance = fRelativeStartValue - fRelativeEndValue;
            }
            
            const double fNumberSteps =
                (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic();
            nLoopTime = FRound(fNumberSteps * mnFrequency);
            
            // init loop
            ScrollTextAnimNode aInitNode(
                nLoopTime, 1L,
                fRelativeStartValue, fRelativeEndValue,
                mnFrequency, false);
            maVector.push_back(aInitNode);
        }
        
        // prepare main loop values
        {
            double fRelativeStartValue, fRelativeEndValue, fRelativeDistance;
            
            if(DoScrollForward()) 
            {
                fRelativeStartValue = fZeroRelative;
                fRelativeEndValue = fOneRelative;
                fRelativeDistance = fRelativeEndValue - fRelativeStartValue;
            }
            else 
            {
                fRelativeStartValue = fOneRelative;
                fRelativeEndValue = fZeroRelative;
                fRelativeDistance = fRelativeStartValue - fRelativeEndValue;
            }
            
            const double fNumberSteps =
                (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic();
            nLoopTime = FRound(fNumberSteps * mnFrequency);
            
            if(0L == mnRepeat) 
            {
                if(!DoScrollIn()) 
                {
                    // endless main loop
                    ScrollTextAnimNode aMainNode(
                        nLoopTime, 0L,
                        fRelativeStartValue, fRelativeEndValue,
                        mnFrequency, DoAlternate());
                    maVector.push_back(aMainNode);
                }
            }
            else 
            {
                sal_uInt32 nNumRepeat(mnRepeat);
                
                if(DoAlternate() && (nNumRepeat + 1L) % 2L)
                    nNumRepeat += 1L;
                
                // ending main loop
                ScrollTextAnimNode aMainNode(
                    nLoopTime, nNumRepeat,
                    fRelativeStartValue, fRelativeEndValue,
                    mnFrequency, DoAlternate());
                maVector.push_back(aMainNode);
            }
        }
        
        if(IsVisibleWhenStopped()) 
        {
            double fRelativeStartValue, fRelativeEndValue, fRelativeDistance;
            
            if(DoScrollForward()) 
            {
                fRelativeStartValue = fZeroRelative;
                fRelativeEndValue = fInitRelative;
                fRelativeDistance = fRelativeEndValue - fRelativeStartValue;
            }
            else 
            {
                fRelativeStartValue = fOneRelative;
                fRelativeEndValue = fInitRelative;
                fRelativeDistance = fRelativeStartValue - fRelativeEndValue;
            }
            
            const double fNumberSteps =
                (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic();
            nLoopTime = FRound(fNumberSteps * mnFrequency);
            
            // exit loop
            ScrollTextAnimNode aExitNode(
                nLoopTime, 1L,
                fRelativeStartValue, fRelativeEndValue, mnFrequency, false);
            maVector.push_back(aExitNode);
        }
    }
}

ScrollTextAnimNode* ActivityImpl::ImpGetScrollTextAnimNode(
    sal_uInt32 nTime, sal_uInt32& rRelativeTime )
{
    ScrollTextAnimNode* pRetval = 0L;
    ImpForceScrollTextAnimNodes();
    
    if(!maVector.empty())
    {
        rRelativeTime = nTime;
        
        for(sal_uInt32 a(0L); !pRetval && a < maVector.size(); a++)
        {
            ScrollTextAnimNode & rNode = maVector[a];
            if(!rNode.GetRepeat()) 
            {
                // endless loop, use it
                pRetval = &rNode;
            }
            else if(rNode.GetFullTime() > rRelativeTime) 
            {
                // ending node
                pRetval = &rNode;
            }
            else 
            {
                // look at next
                rRelativeTime -= rNode.GetFullTime();
            }
        }
    }
    
    return pRetval;
}

sal_uInt32 ActivityImpl::ImpRegisterAgainScrollTextMixerState(sal_uInt32 nTime)
{
    sal_uInt32 nRetval(0L);
    ImpForceScrollTextAnimNodes();
    
    if(maVector.size())
    {
        sal_uInt32 nRelativeTime;
        ScrollTextAnimNode* pNode = ImpGetScrollTextAnimNode(nTime, nRelativeTime);
        
        if(pNode)
        {
            // take register time   
            nRetval = pNode->GetFrequency();
        }
    }
    else
    {
        // #i38135# not initialized, return default 
        nRetval = mnFrequency;
    }
    
    return nRetval;
}

void ActivityImpl::updateShapeAttributes( 
    double fTime, basegfx::B2DRectangle const& parentBounds )
{
    OSL_ASSERT( meAnimKind != drawing::TextAnimationKind_NONE );
    if( meAnimKind == drawing::TextAnimationKind_NONE )
        return;
    
    double const fMixerState = GetMixerState(
        static_cast<sal_uInt32>(fTime * 1000.0) );
    
    if( meAnimKind == drawing::TextAnimationKind_BLINK ) 
    {
        // show/hide text:
        maShapeAttrLayer.get()->setVisibility( fMixerState < 0.5 );
    }
    else if(mpMetaFile) // scroll mode:
    {
        //
        // keep care: the below code is highly sensible to changes...
        //
        
        // rectangle of the pure text:
        double const fPaintWidth = maPaintRectangleLogic.GetWidth();
        double const fPaintHeight = maPaintRectangleLogic.GetHeight();
        // rectangle where the scrolling takes place (-> clipping):
        double const fScrollWidth = maScrollRectangleLogic.GetWidth();
        double const fScrollHeight = maScrollRectangleLogic.GetHeight();
        
        basegfx::B2DPoint pos, clipPos;
        
        if(ScrollHorizontal()) 
        {
            double const fOneEquiv( fScrollWidth );
            double const fZeroEquiv( -fPaintWidth );

            pos.setX( fZeroEquiv + (fMixerState * (fOneEquiv - fZeroEquiv)) );
            
            clipPos.setX( -pos.getX() );
            clipPos.setY( -pos.getY() );

            // #i69844# Compensation for text-wider-than-shape case
            if( fPaintWidth > fScrollWidth )
                pos.setX( pos.getX() + (fPaintWidth-fScrollWidth) / 2.0 );
        }
        else
        { 
            // scroll vertical:
            double const fOneEquiv( fScrollHeight );
            double const fZeroEquiv( -fPaintHeight );

            pos.setY( fZeroEquiv + (fMixerState * (fOneEquiv - fZeroEquiv)) );

            clipPos.setX( -pos.getX() );
            clipPos.setY( -pos.getY() );

            // #i69844# Compensation for text-higher-than-shape case
            if( fPaintHeight > fScrollHeight )
                pos.setY( pos.getY() + (fPaintHeight-fScrollHeight) / 2.0 );
        }
        
        basegfx::B2DPolygon clipPoly(
            basegfx::tools::createPolygonFromRect(
                basegfx::B2DRectangle( clipPos.getX(),
                                       clipPos.getY(),
                                       clipPos.getX() + fScrollWidth,
                                       clipPos.getY() + fScrollHeight ) ) );
        
        if( !::basegfx::fTools::equalZero( mfRotationAngle )) 
        {
            maShapeAttrLayer.get()->setRotationAngle( mfRotationAngle );
            double const fRotate = (mfRotationAngle * M_PI / 180.0);
            basegfx::B2DHomMatrix aTransform;
            // position:
            aTransform.rotate( fRotate );
            pos *= aTransform;
        }
        
        pos += parentBounds.getCenter();
        maShapeAttrLayer.get()->setPosition( pos );
        maShapeAttrLayer.get()->setClip( basegfx::B2DPolyPolygon(clipPoly) );
    }
}

bool ActivityImpl::perform()
{
    if( !isActive() )
        return false;
    
    ENSURE_OR_RETURN_FALSE(
        mpDrawShape,
        "ActivityImpl::perform(): still active, but NULL draw shape" );
    
    DrawShapeSharedPtr const pParentDrawShape( mpParentDrawShape );
    if( !pParentDrawShape )
        return false; // parent has vanished
    
    if( pParentDrawShape->isVisible() )
    {
        if( !mbIsShapeAnimated )
        {
            mpDrawShape->setVisibility(true); // shape may be initially hidden
            maContext.mpSubsettableShapeManager->enterAnimationMode( mpDrawShape );
            maTimer.reset();
            mbIsShapeAnimated = true;
        }
        // update attributes related to current time:
        basegfx::B2DRectangle const parentBounds(
            pParentDrawShape->getBounds() );

        const double nCurrTime( maTimer.getElapsedTime() );
        updateShapeAttributes( nCurrTime, parentBounds );
    
        const sal_uInt32 nFrequency( 
            ImpRegisterAgainScrollTextMixerState(
                static_cast<sal_uInt32>(nCurrTime * 1000.0)) );

        if(nFrequency)
        {
            mpWakeupEvent->start();
            mpWakeupEvent->setNextTimeout( 
                std::max(0.1,nFrequency/1000.0) );
            maContext.mrEventQueue.addEvent( mpWakeupEvent );

            if( mpDrawShape->isContentChanged() )
                maContext.mpSubsettableShapeManager->notifyShapeUpdate( mpDrawShape );
        }
        // else: finished, not need to wake up again.
    }
    else
    {
        // busy-wait, until parent shape gets visible
        mpWakeupEvent->start();
        mpWakeupEvent->setNextTimeout( 2.0 );
    }

    // don't reinsert, WakeupEvent will perform that after the given timeout:
    return false;
}

ActivityImpl::ActivityImpl(
    SlideShowContext const& rContext,
    boost::shared_ptr<WakeupEvent> const& pWakeupEvent,
    boost::shared_ptr<DrawShape> const& pParentDrawShape )
    : maContext(rContext),
      mpWakeupEvent(pWakeupEvent),
      mpParentDrawShape(pParentDrawShape),
      mpListener( new IntrinsicAnimationListener(*this) ),
      maTimer(rContext.mrEventQueue.getTimer()),
      mbIsShapeAnimated(false),
      mbIsDisposed(false),
      mbIsActive(true),
      meAnimKind(drawing::TextAnimationKind_NONE),
      mnStartTime(0L)
{
    // get doctreenode:
    sal_Int32 const nNodes = pParentDrawShape->getNumberOfTreeNodes(
        DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH );

    DocTreeNode scrollTextNode(
        pParentDrawShape->getTreeNode(
            0, DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ));
    // xxx todo: remove this hack
    if( nNodes > 1 )
        scrollTextNode.setEndIndex(
            pParentDrawShape->getTreeNode(
                nNodes - 1,
                DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ).getEndIndex());
    
    // TODO(Q3): Doing this manually, instead of using
    // ShapeSubset. This is because of lifetime issues (ShapeSubset
    // generates circular references to parent shape)
    mpDrawShape = boost::dynamic_pointer_cast<DrawShape>(
        maContext.mpSubsettableShapeManager->getSubsetShape( 
            pParentDrawShape, 
            scrollTextNode ));

    mpMetaFile = mpDrawShape->forceScrollTextMetaFile();
    
    // make scroll text invisible for slide transition bitmaps
    mpDrawShape->setVisibility(false);    

    basegfx::B2DRectangle aScrollRect, aPaintRect;
    ENSURE_OR_THROW( getRectanglesFromScrollMtf( aScrollRect, 
                                                  aPaintRect,
                                                  mpMetaFile ),
                      "ActivityImpl::ActivityImpl(): Could not extract "
                      "scroll anim rectangles from mtf" );
    
    maScrollRectangleLogic = vcl::unotools::rectangleFromB2DRectangle(
        aScrollRect );
    maPaintRectangleLogic = vcl::unotools::rectangleFromB2DRectangle(
        aPaintRect );
        
    maShapeAttrLayer.createAttributeLayer(mpDrawShape);

    uno::Reference<drawing::XShape> const xShape( mpDrawShape->getXShape() );
    uno::Reference<beans::XPropertySet> const xProps( xShape, uno::UNO_QUERY_THROW );
    
    getPropertyValue( meAnimKind, xProps, OUSTR("TextAnimationKind") );
    OSL_ASSERT( meAnimKind != drawing::TextAnimationKind_NONE );    
    mbAlternate = (meAnimKind == drawing::TextAnimationKind_ALTERNATE);
    mbScrollIn = (meAnimKind == drawing::TextAnimationKind_SLIDE);
    
    // adopted from in AInfoBlinkText::ImplInit():
    sal_Int16 nRepeat(0);
    getPropertyValue( nRepeat, xProps, OUSTR("TextAnimationCount") );
    mnRepeat = nRepeat;
    
    if(mbAlternate) 
    {
        // force visible when started for scroll-forth-and-back, because
        // slide has been coming in with visible text in the middle:
        mbVisibleWhenStarted = true;
    }
    else 
    {
        getPropertyValue( mbVisibleWhenStarted, xProps,
                          OUSTR("TextAnimationStartInside") );
    }
    
    // set visible when stopped
    getPropertyValue( mbVisibleWhenStopped, xProps,
                      OUSTR("TextAnimatiogonStopInside") );
    // rotation:
    getPropertyValue( mfRotationAngle, xProps,
                      OUSTR("RotateAngle") );
    mfRotationAngle /= -100.0; // (switching direction)
    
    // set frequency
    sal_Int16 nDelay(0);
    getPropertyValue( nDelay, xProps, OUSTR("TextAnimationDelay") );
    // set delay if not automatic
    mnFrequency = (nDelay ? nDelay :
                   // default:
                   meAnimKind == drawing::TextAnimationKind_BLINK
                   ? 250L : 50L );
    
    // adopted from in AInfoScrollText::ImplInit():
    
    // If it is a simple m_bScrollIn, reset some parameters
    if( DoScrollIn() ) 
    {
        // most parameters are set correctly from the dialog logic, but
        // eg VisisbleWhenStopped is grayed out and needs to be corrected here.
        mbVisibleWhenStopped = true;
        mbVisibleWhenStarted = false;
        mnRepeat = 0L;
    }
    
    // Get animation direction
    getPropertyValue( meDirection, xProps, OUSTR("TextAnimationDirection") );
    
    // Get step width. Negative means pixel, positive logical units
    getPropertyValue( mnStepWidth, xProps, OUSTR("TextAnimationAmount") );

    maContext.mpSubsettableShapeManager->addIntrinsicAnimationHandler( 
        mpListener );
}

bool ActivityImpl::enableAnimations()
{
    mbIsActive = true;
    return maContext.mrActivitiesQueue.addActivity(
        shared_from_this() );
}

ActivityImpl::~ActivityImpl()
{
}

void ActivityImpl::dispose()
{
    if( !mbIsDisposed )
    {
        end();

        // only remove subset here, since end() is called on slide end
        // (and we must not spoil the slide preview bitmap with scroll
        // text)
        maShapeAttrLayer.reset();
        if( mpDrawShape )
        {
            // TODO(Q3): Doing this manually, instead of using
            // ShapeSubset. This is because of lifetime issues
            // (ShapeSubset generates circular references to parent
            // shape)
            DrawShapeSharedPtr pParent( mpParentDrawShape.lock() );
            if( pParent )
                maContext.mpSubsettableShapeManager->revokeSubset( 
                    pParent,
                    mpDrawShape );
        }

        mpMetaFile.reset();
        mpDrawShape.reset();
        mpParentDrawShape.reset();
        mpWakeupEvent.reset();
        maContext.dispose();
        mbIsDisposed = true;

        maContext.mpSubsettableShapeManager->removeIntrinsicAnimationHandler( 
            mpListener );
    }
}

double ActivityImpl::calcTimeLag() const
{
    return 0.0;
}

bool ActivityImpl::isActive() const
{
    return mbIsActive;
}

void ActivityImpl::dequeued()
{
    // not used here
}

void ActivityImpl::end()
{
    // not used here
    mbIsActive = false;

    if( mbIsShapeAnimated )
    {
        maContext.mpSubsettableShapeManager->leaveAnimationMode( mpDrawShape );
        mbIsShapeAnimated = false;
    }
}

} // anon namespace

namespace slideshow {
namespace internal {

boost::shared_ptr<Activity> createDrawingLayerAnimActivity(
    SlideShowContext const& rContext,
    boost::shared_ptr<DrawShape> const& pDrawShape )
{
    boost::shared_ptr<Activity> pActivity;

    try
    {
        boost::shared_ptr<WakeupEvent> const pWakeupEvent(
            new WakeupEvent( rContext.mrEventQueue.getTimer(),
                             rContext.mrActivitiesQueue ) );
        pActivity.reset( new ActivityImpl( rContext, pWakeupEvent, pDrawShape ) );
        pWakeupEvent->setActivity( pActivity );
    }
    catch( uno::RuntimeException& )
    {
        throw;
    }
    catch( uno::Exception& )
    {
        // translate any error into empty factory product.
        OSL_ENSURE( false,
                    rtl::OUStringToOString(
                        comphelper::anyToString( cppu::getCaughtException() ),
                        RTL_TEXTENCODING_UTF8 ).getStr() ); 
    }

    return pActivity;
}

} // namespace internal
} // namespace presentation

