/* -*- 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 .
 */


// 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 <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/shared_ptr.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 explicitly 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( !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( !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( !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 OSL_DEBUG_LEVEL >= 2 && 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: */
