/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

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

// must be first
#include <canvas/debug.hxx>
#include <canvas/verbosetrace.hxx>

#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/animations/XAnimate.hpp>
#include <com/sun/star/animations/AnimationNodeType.hpp>
#include <com/sun/star/presentation/EffectNodeType.hpp>
#include <com/sun/star/presentation/TextAnimationType.hpp>
#include <com/sun/star/animations/XAnimateSet.hpp>
#include <com/sun/star/animations/XIterateContainer.hpp>
#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
#include <com/sun/star/animations/XAnimateMotion.hpp>
#include <com/sun/star/animations/XAnimateColor.hpp>
#include <com/sun/star/animations/XAnimateTransform.hpp>
#include <com/sun/star/animations/AnimationTransformType.hpp>
#include <com/sun/star/animations/XTransitionFilter.hpp>
#include <com/sun/star/animations/XAudio.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <animations/animationnodehelper.hxx>
#include <basegfx/numeric/ftools.hxx>

#include "animationnodefactory.hxx"
#include "paralleltimecontainer.hxx"
#include "sequentialtimecontainer.hxx"
#include "propertyanimationnode.hxx"
#include "animationsetnode.hxx"
#include "animationpathmotionnode.hxx"
#include "animationcolornode.hxx"
#include "animationtransformnode.hxx"
#include "animationtransitionfilternode.hxx"
#include "animationaudionode.hxx"
#include "animationcommandnode.hxx"
#include "nodetools.hxx"
#include "tools.hxx"

#include <boost/bind.hpp>

using namespace ::com::sun::star;

namespace slideshow {
namespace internal {

namespace {

// forward declaration needed by NodeCreator
BaseNodeSharedPtr implCreateAnimationNode(
    const uno::Reference< animations::XAnimationNode >&  xNode,
    const BaseContainerNodeSharedPtr&                    rParent,
    const NodeContext&                                   rContext );

class NodeCreator
{
public:
    NodeCreator( BaseContainerNodeSharedPtr&    rParent,
                 const NodeContext&             rContext )
        : mrParent( rParent ), mrContext( rContext ) {}
    
    void operator()(
        const uno::Reference< animations::XAnimationNode >& xChildNode ) const
    {
        createChild( xChildNode, mrContext );
    }
    
protected:
    void createChild(
        const uno::Reference< animations::XAnimationNode >&   xChildNode,
        const NodeContext&                                    rContext ) const
    {
        BaseNodeSharedPtr pChild( implCreateAnimationNode( xChildNode,
                                                           mrParent,
                                                           rContext ) );
        
        OSL_ENSURE( pChild,
                    "NodeCreator::operator(): child creation failed" );
        
        // TODO(Q1): This yields circular references, which, it seems, is 
        // unavoidable here
        if( pChild )
            mrParent->appendChildNode( pChild );
    }
    
    BaseContainerNodeSharedPtr&     mrParent;
    const NodeContext&              mrContext;
};

/** Same as NodeCreator, only that NodeContext's
    SubsetShape is cloned for every child node.
    
    This is used for iterated animation node generation
*/
class CloningNodeCreator : private NodeCreator
{
public:
    CloningNodeCreator( BaseContainerNodeSharedPtr& rParent,
                        const NodeContext&          rContext )
        : NodeCreator( rParent, rContext ) {}
    
    void operator()(
        const uno::Reference< animations::XAnimationNode >& xChildNode ) const
    {
        NodeContext aContext( mrContext );
        
        // TODO(Q1): There's a catch here. If you clone a
        // subset whose actual subsetting has already been
        // realized (i.e. if enableSubsetShape() has been
        // called already), and the original of your clone
        // goes out of scope, then your subset will be
        // gone (SubsettableShapeManager::revokeSubset() be
        // called). As of now, this behaviour is not
        // triggered here (we either clone, XOR we enable
        // subset initially), but one might consider
        // reworking DrawShape/ShapeSubset to avoid this.
        
        // clone ShapeSubset, since each node needs their
        // own version of the ShapeSubset (otherwise,
        // e.g. activity counting does not work - subset
        // would be removed after first animation node
        // disables it). 
        //
        // NOTE: this is only a problem for animation
        // nodes that explicitely call
        // disableSubsetShape(). Independent shape subsets
        // (like those created for ParagraphTargets)
        // solely rely on the ShapeSubset destructor to
        // normalize things, which does the right thing
        // here: the subset is only removed after _the
        // last_ animation node releases the shared ptr.
        aContext.mpMasterShapeSubset.reset( 
            new ShapeSubset( *aContext.mpMasterShapeSubset ) );
        
        createChild( xChildNode, aContext );
    }
};

/** Create animation nodes for text iterations
    
    This method clones the animation nodes below xIterNode
    for every iterated shape entity.
*/
bool implCreateIteratedNodes(
    const uno::Reference< animations::XIterateContainer >&    xIterNode,
    BaseContainerNodeSharedPtr&                               rParent,
    const NodeContext&                                        rContext )
{
    ENSURE_OR_THROW( xIterNode.is(),
                      "implCreateIteratedNodes(): Invalid node" );
    
    const double nIntervalTimeout( xIterNode->getIterateInterval() );
    
    // valid iterate interval? We're ruling out monstrous
    // values here, to avoid pseudo 'hangs' in the
    // presentation
    if( nIntervalTimeout < 0.0 ||
        nIntervalTimeout > 1000.0 )
    {
        return false; // not an active iteration
    }
    
    if( ::basegfx::fTools::equalZero( nIntervalTimeout ) )
        OSL_TRACE( "implCreateIteratedNodes(): "
                   "iterate interval close to zero, there's "
                   "no point in defining such an effect "
                   "(visually equivalent to whole-shape effect)" );
    
    // Determine target shape (or subset)
    // ==================================
    
    // TODO(E1): I'm not too sure what to expect here...
    ENSURE_OR_RETURN_FALSE(
        xIterNode->getTarget().hasValue(),
        "implCreateIteratedNodes(): no target on ITERATE node" );
    
    uno::Reference< drawing::XShape > xTargetShape( xIterNode->getTarget(),
                                                    uno::UNO_QUERY );
    
    presentation::ParagraphTarget aTarget;
    sal_Int16                     nSubItem( xIterNode->getSubItem() );
    bool                          bParagraphTarget( false );
    
    if( !xTargetShape.is() )
    {
        // no shape provided. Maybe a ParagraphTarget?
        if( !(xIterNode->getTarget() >>= aTarget) )
            ENSURE_OR_RETURN_FALSE(
                false,
                "implCreateIteratedNodes(): could not extract any "
                "target information" );
        
        xTargetShape = aTarget.Shape;
        
        ENSURE_OR_RETURN_FALSE(
            xTargetShape.is(),
            "implCreateIteratedNodes(): invalid shape in ParagraphTarget" );
        
        // we've a paragraph target to iterate over, thus,
        // the whole animation container refers only to
        // the text
        nSubItem = presentation::ShapeAnimationSubType::ONLY_TEXT;
        
        bParagraphTarget = true;
    }
    
    // Lookup shape, and fill NodeContext
    // ==================================
    
    AttributableShapeSharedPtr  pTargetShape( 
        lookupAttributableShape( rContext.maContext.mpSubsettableShapeManager,
                                 xTargetShape ) );
    
    const DocTreeNodeSupplier& rTreeNodeSupplier(
        pTargetShape->getTreeNodeSupplier() );
    
    ShapeSubsetSharedPtr pTargetSubset;
    
    NodeContext aContext( rContext );
    
    // paragraph targets already need a subset as the
    // master shape (they're representing only a single
    // paragraph)
    if( bParagraphTarget )
    {
        ENSURE_OR_RETURN_FALSE( 
            aTarget.Paragraph >= 0 &&
            rTreeNodeSupplier.getNumberOfTreeNodes(
                DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) > aTarget.Paragraph,
            "implCreateIteratedNodes(): paragraph index out of range" );
        
        pTargetSubset.reset( 
            new ShapeSubset( 
                pTargetShape,
                // retrieve index aTarget.Paragraph of
                // type PARAGRAPH from this shape
                rTreeNodeSupplier.getTreeNode(
                    aTarget.Paragraph,
                    DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ),
                rContext.maContext.mpSubsettableShapeManager ) );
        
        // iterate target is not the whole shape, but only
        // the selected paragraph - subset _must_ be
        // independent, to be able to affect visibility
        // independent of master shape
        aContext.mbIsIndependentSubset = true;
        
        // already enable parent subset right here, to
        // make potentially generated subsets subtract
        // their content from the parent subset (and not
        // the original shape). Otherwise, already
        // subsetted parents (e.g. paragraphs) would not
        // have their characters removed, when the child
        // iterations start.
        // Furthermore, the setup of initial shape
        // attributes of course needs the subset shape
        // generated, to apply e.g. visibility changes.
        pTargetSubset->enableSubsetShape();
    }
    else
    {
        pTargetSubset.reset( 
            new ShapeSubset( pTargetShape,
                             rContext.maContext.mpSubsettableShapeManager ));
    }
    
    aContext.mpMasterShapeSubset = pTargetSubset;
    uno::Reference< animations::XAnimationNode > xNode( xIterNode,
                                                        uno::UNO_QUERY_THROW );
    
    // Generate subsets
    // ================
    
    if( bParagraphTarget ||
        nSubItem != presentation::ShapeAnimationSubType::ONLY_TEXT )
    {
        // prepend with animations for
        // full Shape (will be subtracted
        // from the subset parts within
        // the Shape::createSubset()
        // method). For ONLY_TEXT effects,
        // we skip this part, to animate
        // only the text.
        // 
        // OR
        //
        // prepend with subset animation for full
        // _paragraph_, from which the individual
        // paragraph subsets are subtracted. Note that the
        // subitem is superfluous here, we always assume
        // ONLY_TEXT, if a paragraph is referenced as the
        // master of an iteration effect.
        NodeCreator aCreator( rParent, aContext );
        if( !::anim::for_each_childNode( xNode, 
                                         aCreator ) )
        {
            ENSURE_OR_RETURN_FALSE(
                false, 
                "implCreateIteratedNodes(): iterated child node creation failed" );
        }                        
    }
    
    // TODO(F2): This does not do the correct
    // thing. Having nSubItem be set to ONLY_BACKGROUND
    // should result in the text staying unanimated in the
    // foreground, while the shape moves in the background
    // (this behaviour is perfectly possible with the
    // slideshow engine, only that the text won't be
    // currently visible, because animations are always in
    // the foreground)
    if( nSubItem != presentation::ShapeAnimationSubType::ONLY_BACKGROUND )
    {
        // determine type of subitem iteration (logical
        // text unit to animate)
        DocTreeNode::NodeType eIterateNodeType( 
            DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL );
        
        switch( xIterNode->getIterateType() )
        {
        case presentation::TextAnimationType::BY_PARAGRAPH:
            eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH;
            break;
            
        case presentation::TextAnimationType::BY_WORD:
            eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_WORD;
            break;
            
        case presentation::TextAnimationType::BY_LETTER:
            eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL;
            break;
            
        default:
            ENSURE_OR_THROW(
                false, "implCreateIteratedNodes(): "
                "Unexpected IterateType on XIterateContainer");
            break;
        }
        
        if( bParagraphTarget && 
            eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_WORD &&
            eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL )
        {
            // will not animate the whole paragraph, when
            // only the paragraph is animated at all.
            OSL_FAIL( "implCreateIteratedNodes(): Ignoring paragraph iteration for paragraph master" );
        }
        else
        {
            // setup iteration parameters
            // --------------------------
            
            // iterate target is the whole shape (or the
            // whole parent subshape), thus, can save
            // loads of subset shapes by generating them
            // only when the effects become active -
            // before and after the effect active
            // duration, all attributes are shared by
            // master shape and subset (since the iterated
            // effects are all the same).
            aContext.mbIsIndependentSubset = false;
            
            // determine number of nodes for given subitem
            // type
            sal_Int32 nTreeNodes( 0 );
            if( bParagraphTarget )
            {
                // create the iterated subset _relative_ to
                // the given paragraph index (i.e. animate the
                // given subset type, but only when it's part
                // of the given paragraph)
                nTreeNodes = rTreeNodeSupplier.getNumberOfSubsetTreeNodes(
                    pTargetSubset->getSubset(),
                    eIterateNodeType );
            }
            else
            {
                // generate normal subset
                nTreeNodes = rTreeNodeSupplier.getNumberOfTreeNodes( 
                    eIterateNodeType );
            }
            
            
            // iterate node, generate copies of the children for each subset
            // -------------------------------------------------------------
            
            // NodeContext::mnStartDelay contains additional node delay.
            // This will make the duplicated nodes for each iteration start
            // increasingly later.
            aContext.mnStartDelay = nIntervalTimeout; 
            
            for( sal_Int32 i=0; i<nTreeNodes; ++i )
            {
                // create subset with the corresponding tree nodes
                if( bParagraphTarget )
                {
                    // create subsets relative to paragraph subset
                    aContext.mpMasterShapeSubset.reset( 
                        new ShapeSubset(
                            pTargetSubset,
                            rTreeNodeSupplier.getSubsetTreeNode(
                                pTargetSubset->getSubset(),
                                i,
                                eIterateNodeType ) ) );
                }
                else
                {
                    // create subsets from main shape
                    aContext.mpMasterShapeSubset.reset( 
                        new ShapeSubset( pTargetSubset,
                                         rTreeNodeSupplier.getTreeNode(
                                             i,
                                             eIterateNodeType ) ) );
                }
                
                CloningNodeCreator aCreator( rParent, aContext );
                if( !::anim::for_each_childNode( xNode, 
                                                 aCreator ) )
                {
                    ENSURE_OR_RETURN_FALSE(
                        false, "implCreateIteratedNodes(): "
                        "iterated child node creation failed" );
                }
                
                aContext.mnStartDelay += nIntervalTimeout;
            }
        }
    }
    
    // done with iterate child generation
    return true;
}

BaseNodeSharedPtr implCreateAnimationNode(
    const uno::Reference< animations::XAnimationNode >&  xNode,
    const BaseContainerNodeSharedPtr&                    rParent,
    const NodeContext&                                   rContext )
{
    ENSURE_OR_THROW( xNode.is(),
                      "implCreateAnimationNode(): invalid XAnimationNode" );
    
    BaseNodeSharedPtr           pCreatedNode;
    BaseContainerNodeSharedPtr  pCreatedContainer;
    
    // create the internal node, corresponding to xNode
    switch( xNode->getType() )
    {
    case animations::AnimationNodeType::CUSTOM:
        OSL_FAIL( "implCreateAnimationNode(): "
                    "CUSTOM not yet implemented" );
        return pCreatedNode;
        
    case animations::AnimationNodeType::PAR:
        pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr(
            new ParallelTimeContainer( xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::ITERATE:
        // map iterate container to ParallelTimeContainer.
        // the iterating functionality is to be found
        // below, (see method implCreateIteratedNodes)
        pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr(
            new ParallelTimeContainer( xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::SEQ:
        pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr(
            new SequentialTimeContainer( xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::ANIMATE:
        pCreatedNode.reset( new PropertyAnimationNode(
                                xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::SET:
        pCreatedNode.reset( new AnimationSetNode(
                                xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::ANIMATEMOTION:
        pCreatedNode.reset( new AnimationPathMotionNode(
                                xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::ANIMATECOLOR:
        pCreatedNode.reset( new AnimationColorNode(
                                xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::ANIMATETRANSFORM:
        pCreatedNode.reset( new AnimationTransformNode(
                                xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::TRANSITIONFILTER:
        pCreatedNode.reset( new AnimationTransitionFilterNode(
                                xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::AUDIO:
        pCreatedNode.reset( new AnimationAudioNode(
                                xNode, rParent, rContext ) );
        break;
        
    case animations::AnimationNodeType::COMMAND:
        pCreatedNode.reset( new AnimationCommandNode(
                                xNode, rParent, rContext ) );
        break;
        
    default:
        OSL_FAIL( "implCreateAnimationNode(): "
                    "invalid AnimationNodeType" );
        return pCreatedNode;
    }
    
    // TODO(Q1): This yields circular references, which, it seems, is 
    // unavoidable here
    
    // HACK: node objects need shared_ptr to themselves, 
    // which we pass them here.
    pCreatedNode->setSelf( pCreatedNode );
    
    // if we've got a container node object, recursively add 
    // its children
    if( pCreatedContainer )
    {
        uno::Reference< animations::XIterateContainer > xIterNode(
            xNode, uno::UNO_QUERY );
        
        // when this node is an XIterateContainer with
        // active iterations, this method will generate
        // the appropriate children
        if( xIterNode.is() )
        {
            // note that implCreateIteratedNodes() might
            // choose not to generate any child nodes
            // (e.g. when the iterate timeout is outside
            // sensible limits). Then, no child nodes are
            // generated at all, since typically, child
            // node attribute are incomplete for iteration
            // children.
            implCreateIteratedNodes( xIterNode,
                                     pCreatedContainer, 
                                     rContext );
        }
        else
        {
            // no iterate subset node, just plain child generation now
            NodeCreator aCreator( pCreatedContainer, rContext );
            if( !::anim::for_each_childNode( xNode, aCreator ) )
            {
                OSL_FAIL( "implCreateAnimationNode(): "
                            "child node creation failed" );
                return BaseNodeSharedPtr();
            }
        }
    }                    
    
    return pCreatedNode;
}

} // anon namespace

AnimationNodeSharedPtr AnimationNodeFactory::createAnimationNode(
    const uno::Reference< animations::XAnimationNode >&   xNode,
    const ::basegfx::B2DVector&                           rSlideSize,
    const SlideShowContext&                               rContext )
{
    ENSURE_OR_THROW(
        xNode.is(),
        "AnimationNodeFactory::createAnimationNode(): invalid XAnimationNode" );
    
    return BaseNodeSharedPtr( implCreateAnimationNode(
                                  xNode, 
                                  BaseContainerNodeSharedPtr(), // no parent
                                  NodeContext( rContext,
                                               rSlideSize )));
}

#if defined(VERBOSE) && defined(DBG_UTIL)
void AnimationNodeFactory::showTree( AnimationNodeSharedPtr& pRootNode )
{
    if( pRootNode )
        DEBUG_NODES_SHOWTREE( boost::dynamic_pointer_cast<BaseContainerNode>(
                                  pRootNode).get() );
}
#endif

} // namespace internal
} // namespace slideshow

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
