/**************************************************************
 * 
 * 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 <comphelper/anytostring.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>

#include <com/sun/star/animations/TransitionType.hpp>
#include <com/sun/star/animations/TransitionSubType.hpp>

#include "transitionfactory.hxx"
#include "transitiontools.hxx"
#include "parametricpolypolygonfactory.hxx"
#include "animationfactory.hxx"
#include "clippingfunctor.hxx"

#include <boost/bind.hpp>


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

namespace slideshow {
namespace internal {

/***************************************************
 ***                                             ***
 ***          Shape Transition Effects           ***
 ***                                             ***
 ***************************************************/

namespace {

class ClippingAnimation : public NumberAnimation
{
public:
    ClippingAnimation(
        const ParametricPolyPolygonSharedPtr&   rPolygon,
        const ShapeManagerSharedPtr&            rShapeManager,
        const TransitionInfo&                   rTransitionInfo,
        bool                                    bDirectionForward,
        bool                                    bModeIn );

    ~ClippingAnimation();
    
    // Animation interface
    // -------------------
    virtual void prefetch( const AnimatableShapeSharedPtr&     rShape,
                           const ShapeAttributeLayerSharedPtr& rAttrLayer );
    virtual void start( const AnimatableShapeSharedPtr& 	rShape,
                        const ShapeAttributeLayerSharedPtr& rAttrLayer );
    virtual void end();
    
    // NumberAnimation interface
    // -----------------------
    virtual bool operator()( double nValue );
    virtual double getUnderlyingValue() const;
        
private:
    void end_();

    AnimatableShapeSharedPtr           mpShape;
    ShapeAttributeLayerSharedPtr       mpAttrLayer;
    ShapeManagerSharedPtr              mpShapeManager;
    ClippingFunctor					   maClippingFunctor;
    bool							   mbSpriteActive;
};

ClippingAnimation::ClippingAnimation(
    const ParametricPolyPolygonSharedPtr&   rPolygon,
    const ShapeManagerSharedPtr&            rShapeManager,
    const TransitionInfo&                   rTransitionInfo,
    bool                                    bDirectionForward,
    bool                                    bModeIn ) :
        mpShape(),
        mpAttrLayer(),
        mpShapeManager( rShapeManager ),
        maClippingFunctor( rPolygon, 
                           rTransitionInfo, 
                           bDirectionForward, 
                           bModeIn ),
        mbSpriteActive(false)
{
    ENSURE_OR_THROW(
        rShapeManager,
        "ClippingAnimation::ClippingAnimation(): Invalid ShapeManager" );
}

ClippingAnimation::~ClippingAnimation()
{
    try
    {
        end_();
    }
    catch (uno::Exception &) 
    {
        OSL_ENSURE( false, rtl::OUStringToOString(
                        comphelper::anyToString(
                            cppu::getCaughtException() ),
                        RTL_TEXTENCODING_UTF8 ).getStr() );
    }
}

void ClippingAnimation::prefetch( const AnimatableShapeSharedPtr&,
                                  const ShapeAttributeLayerSharedPtr& )
{
}

void ClippingAnimation::start( const AnimatableShapeSharedPtr& 		rShape,
                               const ShapeAttributeLayerSharedPtr& 	rAttrLayer )
{
    OSL_ENSURE( !mpShape,
                "ClippingAnimation::start(): Shape already set" );
    OSL_ENSURE( !mpAttrLayer,
                "ClippingAnimation::start(): Attribute layer already set" );

    mpShape = rShape;
    mpAttrLayer = rAttrLayer;

    ENSURE_OR_THROW( rShape,
                      "ClippingAnimation::start(): Invalid shape" );
    ENSURE_OR_THROW( rAttrLayer,
                      "ClippingAnimation::start(): Invalid attribute layer" );

    mpShape = rShape;
    mpAttrLayer = rAttrLayer;

    if( !mbSpriteActive )
    {
        mpShapeManager->enterAnimationMode( mpShape );
        mbSpriteActive = true;
    }
}

void ClippingAnimation::end()
{
    end_();
}

void ClippingAnimation::end_()
{
    if( mbSpriteActive )
    {
        mbSpriteActive = false;
        mpShapeManager->leaveAnimationMode( mpShape );

        if( mpShape->isContentChanged() )
            mpShapeManager->notifyShapeUpdate( mpShape );
    }
}

bool ClippingAnimation::operator()( double nValue )
{
    ENSURE_OR_RETURN_FALSE(
        mpAttrLayer && mpShape,
        "ClippingAnimation::operator(): Invalid ShapeAttributeLayer" );
    
    // set new clip
    mpAttrLayer->setClip( maClippingFunctor( nValue,
                                             mpShape->getDomBounds().getRange() ) );
    
    if( mpShape->isContentChanged() )
        mpShapeManager->notifyShapeUpdate( mpShape );
    
    return true;
}

double ClippingAnimation::getUnderlyingValue() const
{
    ENSURE_OR_THROW(
        mpAttrLayer,
        "ClippingAnimation::getUnderlyingValue(): Invalid ShapeAttributeLayer" );
    
    return 0.0;     // though this should be used in concert with 
				    // ActivitiesFactory::createSimpleActivity, better
    				// explicitely name our start value.
				    // Permissible range for operator() above is [0,1]
}

} // anon namespace


AnimationActivitySharedPtr TransitionFactory::createShapeTransition(
    const ActivitiesFactory::CommonParameters&          rParms,
    const AnimatableShapeSharedPtr&                     rShape,
    const ShapeManagerSharedPtr&                        rShapeManager,
    const ::basegfx::B2DVector&                         rSlideSize,
    uno::Reference< animations::XTransitionFilter > const& xTransition )
{
    return createShapeTransition( rParms, 
                                  rShape, 
                                  rShapeManager, 
                                  rSlideSize,
                                  xTransition, 
                                  xTransition->getTransition(), 
                                  xTransition->getSubtype() );
}

AnimationActivitySharedPtr TransitionFactory::createShapeTransition(
    const ActivitiesFactory::CommonParameters& 				rParms,
    const AnimatableShapeSharedPtr& 						rShape,
    const ShapeManagerSharedPtr& 							rShapeManager,
    const ::basegfx::B2DVector&                             rSlideSize,
    ::com::sun::star::uno::Reference< 
    	::com::sun::star::animations::XTransitionFilter > const& xTransition,
    sal_Int16               								nType,
    sal_Int16               								nSubType )
{
    ENSURE_OR_THROW(
        xTransition.is(),
        "TransitionFactory::createShapeTransition(): Invalid XTransition" );
    
    const TransitionInfo* pTransitionInfo( 
        getTransitionInfo( nType, nSubType ) );

    AnimationActivitySharedPtr pGeneratedActivity;
    if( pTransitionInfo != NULL )
    {
        switch( pTransitionInfo->meTransitionClass )
        {
            default:
            case TransitionInfo::TRANSITION_INVALID:
                OSL_ENSURE( false, 
                            "TransitionFactory::createShapeTransition(): Invalid transition type. "
                            "Don't ask me for a 0 TransitionType, have no XTransitionFilter node instead!" );
                return AnimationActivitySharedPtr();

            
            case TransitionInfo::TRANSITION_CLIP_POLYPOLYGON:
            {
                // generate parametric poly-polygon
                ParametricPolyPolygonSharedPtr pPoly( 
                    ParametricPolyPolygonFactory::createClipPolyPolygon( 
                        nType, nSubType ) );
            
                // create a clip activity from that
                pGeneratedActivity = ActivitiesFactory::createSimpleActivity(
                    rParms,
                    NumberAnimationSharedPtr( 
                        new ClippingAnimation( 
                            pPoly,
                            rShapeManager,
                            *pTransitionInfo,
                            xTransition->getDirection(),
                            xTransition->getMode() ) ),
                    true );
            }
            break;
        
            case TransitionInfo::TRANSITION_SPECIAL:
            {
                switch( nType )
                {
                    case animations::TransitionType::RANDOM:
                    {
                        // select randomly one of the effects from the
                        // TransitionFactoryTable

                        const TransitionInfo* pRandomTransitionInfo( getRandomTransitionInfo() );
                        
                        ENSURE_OR_THROW( pRandomTransitionInfo != NULL,
                                          "TransitionFactory::createShapeTransition(): Got invalid random transition info" );

                        ENSURE_OR_THROW( pRandomTransitionInfo->mnTransitionType != animations::TransitionType::RANDOM,
                                          "TransitionFactory::createShapeTransition(): Got random again for random input!" );

                        // and recurse
                        pGeneratedActivity = createShapeTransition( rParms,
                                                                    rShape,
                                                                    rShapeManager,
                                                                    rSlideSize,
                                                                    xTransition,
                                                                    pRandomTransitionInfo->mnTransitionType,
                                                                    pRandomTransitionInfo->mnTransitionSubType );
                    }
                    break;
                
                    // TODO(F3): Implement slidewipe for shape
                    case animations::TransitionType::SLIDEWIPE:
                    {
                        sal_Int16 nBarWipeSubType(0);
                        bool	  bDirectionForward(true);

                        // map slidewipe to BARWIPE, for now
                        switch( nSubType )
                        {
                            case animations::TransitionSubType::FROMLEFT:
                                nBarWipeSubType = animations::TransitionSubType::LEFTTORIGHT;
                                bDirectionForward = true;
                                break;

                            case animations::TransitionSubType::FROMRIGHT:
                                nBarWipeSubType = animations::TransitionSubType::LEFTTORIGHT;
                                bDirectionForward = false;
                                break;

                            case animations::TransitionSubType::FROMTOP:
                                nBarWipeSubType = animations::TransitionSubType::TOPTOBOTTOM;
                                bDirectionForward = true;
                                break;

                            case animations::TransitionSubType::FROMBOTTOM:
                                nBarWipeSubType = animations::TransitionSubType::TOPTOBOTTOM;
                                bDirectionForward = false;
                                break;

                            default:
                                ENSURE_OR_THROW( false,
                                                  "TransitionFactory::createShapeTransition(): Unexpected subtype for SLIDEWIPE" );
                                break;
                        }

                        // generate parametric poly-polygon
                        ParametricPolyPolygonSharedPtr pPoly( 
                            ParametricPolyPolygonFactory::createClipPolyPolygon( 
                                animations::TransitionType::BARWIPE, 
                                nBarWipeSubType ) );
                    
                        // create a clip activity from that
                        pGeneratedActivity = ActivitiesFactory::createSimpleActivity(
                            rParms,
                            NumberAnimationSharedPtr( 
                                new ClippingAnimation( 
                                    pPoly,
                                    rShapeManager,
                                    *getTransitionInfo( animations::TransitionType::BARWIPE, 
                                                        nBarWipeSubType ),
                                    bDirectionForward,
                                    xTransition->getMode() ) ),
                            true );
                    }
                    break;

                    default:
                    {
                        // TODO(F1): Check whether there's anything left, anyway,
                        // for _shape_ transitions. AFAIK, there are no special
                        // effects for shapes...

                        // for now, map all to fade effect
                        pGeneratedActivity = ActivitiesFactory::createSimpleActivity(
                            rParms,
                            AnimationFactory::createNumberPropertyAnimation( 
                                ::rtl::OUString( 
                                    RTL_CONSTASCII_USTRINGPARAM("Opacity") ),
                                rShape, 
                                rShapeManager,
                                rSlideSize ),
                            xTransition->getMode() );
                    }
                    break;
                }
            }
            break;
        }
    }

    if( !pGeneratedActivity )
    {
        // No animation generated, maybe no table entry for given 
        // transition?
        OSL_TRACE(
            "TransitionFactory::createShapeTransition(): Unknown type/subtype (%d/%d) "
            "combination encountered",
            xTransition->getTransition(),
            xTransition->getSubtype() );
        OSL_ENSURE(
            false,
            "TransitionFactory::createShapeTransition(): Unknown type/subtype "
            "combination encountered" );
    }
    
    return pGeneratedActivity;
}

}
}
