/**************************************************************
 * 
 * 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/canvastools.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <cppcanvas/basegfxfactory.hxx>

#include "slidechangebase.hxx"
#include "tools.hxx"

#include <boost/bind.hpp>
#include <algorithm>

using namespace com::sun::star;

namespace slideshow {
namespace internal {

SlideChangeBase::SlideChangeBase( boost::optional<SlideSharedPtr> const & leavingSlide,
                                  const SlideSharedPtr&                   pEnteringSlide,
                                  const SoundPlayerSharedPtr&             pSoundPlayer,
                                  const UnoViewContainer&                 rViewContainer,
                                  ScreenUpdater&                          rScreenUpdater,
                                  EventMultiplexer&                       rEventMultiplexer,
                                  bool                                    bCreateLeavingSprites, 
                                  bool                                    bCreateEnteringSprites ) :
      mpSoundPlayer( pSoundPlayer ),
      mrEventMultiplexer(rEventMultiplexer),
      mrScreenUpdater(rScreenUpdater),
      maLeavingSlide( leavingSlide ),
      mpEnteringSlide( pEnteringSlide ),
      maViewData(),
      mrViewContainer(rViewContainer),
      mbCreateLeavingSprites(bCreateLeavingSprites),
      mbCreateEnteringSprites(bCreateEnteringSprites),
      mbSpritesVisible(false),
      mbFinished(false),
      mbPrefetched(false)
{
    ENSURE_OR_THROW(
        pEnteringSlide,
        "SlideChangeBase::SlideChangeBase(): Invalid entering slide!" );
}

SlideBitmapSharedPtr SlideChangeBase::getLeavingBitmap( const ViewEntry& rViewEntry ) const
{
    if( !rViewEntry.mpLeavingBitmap )
        rViewEntry.mpLeavingBitmap = createBitmap(rViewEntry.mpView, 
                                                  maLeavingSlide);

    return rViewEntry.mpLeavingBitmap;
}

SlideBitmapSharedPtr SlideChangeBase::getEnteringBitmap( const ViewEntry& rViewEntry ) const
{
    if( !rViewEntry.mpEnteringBitmap )
        rViewEntry.mpEnteringBitmap = createBitmap( rViewEntry.mpView,
                                                    boost::optional<SlideSharedPtr>(mpEnteringSlide) );

    return rViewEntry.mpEnteringBitmap;
}

SlideBitmapSharedPtr SlideChangeBase::createBitmap( const UnoViewSharedPtr&                rView,
                                                    const boost::optional<SlideSharedPtr>& rSlide ) const
{
    SlideBitmapSharedPtr pRet;
    if( !rSlide )
        return pRet;

    SlideSharedPtr const & pSlide = *rSlide;
    if( !pSlide )
    {
        // TODO(P3): No need to generate a bitmap here. This only made
        // the code more uniform. Faster would be to simply clear the
        // sprite to black.

        // create empty, black-filled bitmap
        const basegfx::B2ISize slideSizePixel( 
            getSlideSizePixel( mpEnteringSlide->getSlideSize(),
                               rView )); 

        cppcanvas::CanvasSharedPtr pCanvas( rView->getCanvas() );

        // create a bitmap of appropriate size
        cppcanvas::BitmapSharedPtr pBitmap( 
            cppcanvas::BaseGfxFactory::getInstance().createBitmap( 
                pCanvas, 
                slideSizePixel ) );
                
        ENSURE_OR_THROW(
            pBitmap,
            "SlideChangeBase::createBitmap(): Cannot create page bitmap" );
                
        cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas(
            pBitmap->getBitmapCanvas() );
                
        ENSURE_OR_THROW( pBitmapCanvas,
                          "SlideChangeBase::createBitmap(): "
                          "Cannot create page bitmap canvas" );

        // set transformation to identitiy (->device pixel)
        pBitmapCanvas->setTransformation( ::basegfx::B2DHomMatrix() );

        // clear bitmap to black
        fillRect( pBitmapCanvas,
                  ::basegfx::B2DRectangle( 0.0, 0.0,
                                           slideSizePixel.getX(),
                                           slideSizePixel.getY() ),
                  0x000000FFU );
                
        pRet.reset( new SlideBitmap( pBitmap ));
    }
    else
    {
        pRet = pSlide->getCurrentSlideBitmap( rView );
    }

    return pRet;
}

::basegfx::B2ISize SlideChangeBase::getEnteringSlideSizePixel( const UnoViewSharedPtr& pView ) const
{
    return getSlideSizePixel( mpEnteringSlide->getSlideSize(), 
                              pView );
}

::basegfx::B2ISize SlideChangeBase::getLeavingSlideSizePixel( const UnoViewSharedPtr& pView ) const
{
    return getSlideSizePixel( (*maLeavingSlide)->getSlideSize(), 
                              pView );
}


void SlideChangeBase::renderBitmap(
    SlideBitmapSharedPtr const & pSlideBitmap,
    cppcanvas::CanvasSharedPtr const & pCanvas )
{
    if( pSlideBitmap && pCanvas )
    {
        // need to render without any transformation (we
        // assume device units):
        const basegfx::B2DHomMatrix viewTransform(
            pCanvas->getTransformation() );
        const basegfx::B2DPoint pageOrigin(
            viewTransform * basegfx::B2DPoint() );
        const cppcanvas::CanvasSharedPtr pDevicePixelCanvas(
            pCanvas->clone() );

        // render at output position, don't modify bitmap object (no move!):
        const basegfx::B2DHomMatrix transform(basegfx::tools::createTranslateB2DHomMatrix(
            pageOrigin.getX(), pageOrigin.getY()));
        
        pDevicePixelCanvas->setTransformation( transform );
        pSlideBitmap->draw( pDevicePixelCanvas );
    }
}

void SlideChangeBase::prefetch( const AnimatableShapeSharedPtr&,
                                const ShapeAttributeLayerSharedPtr& )
{
    // we're a one-shot activity, and already finished
    if( mbFinished || mbPrefetched )
        return;

    // register ourselves for view change events
    mrEventMultiplexer.addViewHandler( shared_from_this() );

    // init views and create slide bitmaps
    std::for_each( mrViewContainer.begin(),
                   mrViewContainer.end(),
                   boost::bind( &SlideChangeBase::viewAdded,
                                this,
                                _1 ));

    mbPrefetched = true;
}

void SlideChangeBase::start( const AnimatableShapeSharedPtr&     rShape,
                             const ShapeAttributeLayerSharedPtr& rLayer )
{
    // we're a one-shot activity, and already finished
    if( mbFinished )
        return;

    prefetch(rShape,rLayer); // no-op, if already done

    // start accompanying sound effect, if any
    if( mpSoundPlayer )
    {
        mpSoundPlayer->startPlayback();
        // xxx todo: for now, presentation.cxx takes care about the slide
        // #i50492#  transition sound object, so just release it here
        mpSoundPlayer.reset();
    }
}

void SlideChangeBase::end()
{
    // we're a one-shot activity, and already finished
    if( mbFinished )
        return;

    try
    {
        // draw fully entered bitmap:
        ViewsVecT::const_iterator aCurr( beginViews() );
        const ViewsVecT::const_iterator aEnd( endViews() );
        while( aCurr != aEnd )
        {
            // fully clear view content to background color
            aCurr->mpView->clearAll();

            const SlideBitmapSharedPtr pSlideBitmap( getEnteringBitmap( *aCurr ));
            pSlideBitmap->clip( basegfx::B2DPolyPolygon() /* no clipping */ );
            renderBitmap( pSlideBitmap,
                          aCurr->mpView->getCanvas() );

            ++aCurr;
        }
    }
    catch( uno::Exception& )
    {
        // make sure releasing below happens
    }

    // swap changes to screen
    mrScreenUpdater.notifyUpdate();

    // make object dysfunctional
    mbFinished = true;
    ViewsVecT().swap(maViewData);
    maLeavingSlide.reset();
    mpEnteringSlide.reset();

    // sprites have been binned above
    mbSpritesVisible = false;

    // remove also from event multiplexer, we're dead anyway
    mrEventMultiplexer.removeViewHandler( shared_from_this() );
}

bool SlideChangeBase::operator()( double nValue )
{
    if( mbFinished )
        return false;
    
    const std::size_t nEntries( maViewData.size() );    
    bool bSpritesVisible( mbSpritesVisible );
    
    for( ::std::size_t i=0; i<nEntries; ++i )
    {
        // calc sprite offsets. The enter/leaving bitmaps are only
        // as large as the actual slides. For scaled-down
        // presentations, we have to move the left, top edge of
        // those bitmaps to the actual position, governed by the
        // given view transform. The aSpritePosPixel local
        // variable is already in device coordinate space
        // (i.e. pixel).

        ViewEntry& rViewEntry( maViewData[i] );
        const ::cppcanvas::CanvasSharedPtr& rCanvas( rViewEntry.mpView->getCanvas() );
        ::cppcanvas::CustomSpriteSharedPtr& rInSprite( rViewEntry.mpInSprite );
        ::cppcanvas::CustomSpriteSharedPtr& rOutSprite( rViewEntry.mpOutSprite );
        
        // TODO(F2): Properly respect clip here.

        // Might have to be transformed, too.
        const ::basegfx::B2DHomMatrix aViewTransform(
            rViewEntry.mpView->getTransformation() );
        const ::basegfx::B2DPoint aSpritePosPixel(
            aViewTransform * ::basegfx::B2DPoint() );
        
        // move sprite to final output position, in 
        // device coordinates
        if( rOutSprite )
            rOutSprite->movePixel( aSpritePosPixel );
        if( rInSprite )
            rInSprite->movePixel( aSpritePosPixel );
        
        if( !mbSpritesVisible )
        {
            if( rOutSprite )
            {
                // only render once: clipping is done
                // exclusively with the sprite
                const ::cppcanvas::CanvasSharedPtr pOutContentCanvas(
                    rOutSprite->getContentCanvas() );
                if( pOutContentCanvas)
                {
                    // TODO(Q2): Use basegfx bitmaps here

                    // TODO(F1): SlideBitmap is not fully portable
                    // between different canvases!
                    
                    // render the content
                    OSL_ASSERT( getLeavingBitmap( rViewEntry ) );
                    if( getLeavingBitmap( rViewEntry ) )
                        getLeavingBitmap( rViewEntry )->draw( pOutContentCanvas );
                }
            }
            
            if( rInSprite )
            {
                // only render once: clipping is done
                // exclusively with the sprite
                const ::cppcanvas::CanvasSharedPtr pInContentCanvas(
                    rInSprite->getContentCanvas() );
                if( pInContentCanvas )
                {
                    // TODO(Q2): Use basegfx bitmaps here

                    // TODO(F1): SlideBitmap is not fully portable
                    // between different canvases!
                    
                    // render the content
                    getEnteringBitmap( rViewEntry )->draw( pInContentCanvas );
                }
            }
        }
        
        if( rOutSprite )
            performOut( rOutSprite, rViewEntry, rCanvas, nValue );
        if( rInSprite )
            performIn( rInSprite, rViewEntry, rCanvas, nValue );
        
        // finishing deeds for first run.
        if( !mbSpritesVisible) 
        {
            // enable sprites:
            if( rOutSprite )
                rOutSprite->show();
            if( rInSprite )
                rInSprite->show();
            bSpritesVisible = true;
        }
    } // for_each( sprite )
    
    mbSpritesVisible = bSpritesVisible;
    mrScreenUpdater.notifyUpdate();
    
    return true;
}

void SlideChangeBase::performIn(
    const cppcanvas::CustomSpriteSharedPtr&   /*rSprite*/,
    const ViewEntry&                          /*rViewEntry*/,
    const cppcanvas::CanvasSharedPtr&         /*rDestinationCanvas*/,
    double                                    /*t*/ )
{
}

void SlideChangeBase::performOut(
    const cppcanvas::CustomSpriteSharedPtr&  /*rSprite*/,
    const ViewEntry&                         /*rViewEntry*/,
    const cppcanvas::CanvasSharedPtr&        /*rDestinationCanvas*/,
    double                                   /*t*/ )
{
}

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

void SlideChangeBase::viewAdded( const UnoViewSharedPtr& rView )
{
    // we're a one-shot activity, and already finished
    if( mbFinished )
        return;

    maViewData.push_back( ViewEntry(rView) );

    ViewEntry& rEntry( maViewData.back() );
    getEnteringBitmap( rEntry );
    getLeavingBitmap( rEntry );
    addSprites( rEntry );
}

void SlideChangeBase::viewRemoved( const UnoViewSharedPtr& rView )
{
    // we're a one-shot activity, and already finished
    if( mbFinished )
        return;

    // erase corresponding entry from maViewData
    maViewData.erase(
        std::remove_if(
            maViewData.begin(), 
            maViewData.end(),
            boost::bind(
                std::equal_to<UnoViewSharedPtr>(),
                rView,
                // select view:
                boost::bind( &ViewEntry::getView, _1 ))),
        maViewData.end() );
}

void SlideChangeBase::viewChanged( const UnoViewSharedPtr& rView )
{
    // we're a one-shot activity, and already finished
    if( mbFinished )
        return;

    // find entry corresponding to modified view
    ViewsVecT::iterator aModifiedEntry(
        std::find_if(
            maViewData.begin(), 
            maViewData.end(),
            boost::bind(
                std::equal_to<UnoViewSharedPtr>(),
                rView,
                // select view:
                boost::bind( &ViewEntry::getView, _1 ) )));
        
    OSL_ASSERT( aModifiedEntry != maViewData.end() );
    if( aModifiedEntry == maViewData.end() )
        return;

    // clear stale info (both bitmaps and sprites prolly need a
    // resize)
    clearViewEntry( *aModifiedEntry );
    addSprites( *aModifiedEntry );
}

void SlideChangeBase::viewsChanged()
{
    // we're a one-shot activity, and already finished
    if( mbFinished )
        return;

    ViewsVecT::iterator       aIter( maViewData.begin() );
    ViewsVecT::iterator const aEnd ( maViewData.end() );
    while( aIter != aEnd )
    {
        // clear stale info (both bitmaps and sprites prolly need a
        // resize)
        clearViewEntry( *aIter );
        addSprites( *aIter );
        
        ++aIter;
    }
}

cppcanvas::CustomSpriteSharedPtr SlideChangeBase::createSprite(
    UnoViewSharedPtr const & pView, 
    basegfx::B2DSize const & rSpriteSize,
    double                   nPrio ) const
{
    // TODO(P2): change to bitmapsprite once that's working
    const cppcanvas::CustomSpriteSharedPtr pSprite(
        pView->createSprite( rSpriteSize,
                             nPrio ));

    // alpha default is 0.0, which seems to be 
    // a bad idea when viewing content...
    pSprite->setAlpha( 1.0 );
    if (mbSpritesVisible)
        pSprite->show();

    return pSprite;
}

void SlideChangeBase::addSprites( ViewEntry& rEntry )
{
    if( mbCreateLeavingSprites && maLeavingSlide )
    {
        // create leaving sprite:
        const basegfx::B2ISize leavingSlideSizePixel(
            getLeavingBitmap( rEntry )->getSize() );

        rEntry.mpOutSprite = createSprite( rEntry.mpView, 
                                           leavingSlideSizePixel, 
                                           100 );
    }
    
    if( mbCreateEnteringSprites ) 
    {
        // create entering sprite:
        const basegfx::B2ISize enteringSlideSizePixel(
            getSlideSizePixel( mpEnteringSlide->getSlideSize(),
                               rEntry.mpView ));

        rEntry.mpInSprite = createSprite( rEntry.mpView, 
                                          enteringSlideSizePixel, 
                                          101 );
    }
}

void SlideChangeBase::clearViewEntry( ViewEntry& rEntry )
{
    // clear stale info (both bitmaps and sprites prolly need a 
    // resize)  
    rEntry.mpEnteringBitmap.reset();
    rEntry.mpLeavingBitmap.reset();
    rEntry.mpInSprite.reset();
    rEntry.mpOutSprite.reset();
}

} // namespace internal
} // namespace presentation