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

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

#include <discreteactivitybase.hxx>


namespace slideshow
{
    namespace internal
    {
        DiscreteActivityBase::DiscreteActivityBase( const ActivityParameters& rParms ) :
            ActivityBase( rParms ),
            mpWakeupEvent( rParms.mpWakeupEvent ),
            maDiscreteTimes( rParms.maDiscreteTimes ),
            mnSimpleDuration( rParms.mnMinDuration ),
            mnCurrPerformCalls( 0 )
        {
            ENSURE_OR_THROW( mpWakeupEvent,
                              "DiscreteActivityBase::DiscreteActivityBase(): Invalid wakeup event" );

            ENSURE_OR_THROW( !maDiscreteTimes.empty(), 
                              "DiscreteActivityBase::DiscreteActivityBase(): time vector is empty, why do you create me?" );

#ifdef DBG_UTIL
            // check parameters: rDiscreteTimes must be sorted in
            // ascending order, and contain values only from the range
            // [0,1]
            for( ::std::size_t i=1, len=maDiscreteTimes.size(); i<len; ++i )
            {
                if( maDiscreteTimes[i] < 0.0 ||
                    maDiscreteTimes[i] > 1.0 || 
                    maDiscreteTimes[i-1] < 0.0 ||
                    maDiscreteTimes[i-1] > 1.0 )
                {
                    ENSURE_OR_THROW( false, "DiscreteActivityBase::DiscreteActivityBase(): time values not within [0,1] range!" );
                }

                if( maDiscreteTimes[i-1] > maDiscreteTimes[i] )
                    ENSURE_OR_THROW( false, "DiscreteActivityBase::DiscreteActivityBase(): time vector is not sorted in ascending order!" );
            }

            // TODO(E2): check this also in production code?
#endif
        }

        void DiscreteActivityBase::startAnimation()
        {
            // start timer on wakeup event
            mpWakeupEvent->start();
        }

        sal_uInt32 DiscreteActivityBase::calcFrameIndex( sal_uInt32 	nCurrCalls,
                                                         ::std::size_t 	nVectorSize ) const
        {
            if( isAutoReverse() )
            {
                // every full repeat run consists of one
                // forward and one backward traversal.
                sal_uInt32 nFrameIndex( nCurrCalls % (2*nVectorSize) );

                // nFrameIndex values >= nVectorSize belong to
                // the backward traversal
                if( nFrameIndex >= nVectorSize )
                    nFrameIndex = 2*nVectorSize - nFrameIndex; // invert sweep

                return nFrameIndex;
            }
            else
            {
                return nCurrCalls % nVectorSize ;
            }
        }

        sal_uInt32 DiscreteActivityBase::calcRepeatCount( sal_uInt32 	nCurrCalls,
                                                          ::std::size_t	nVectorSize ) const
        {
            if( isAutoReverse() )
                return nCurrCalls / (2*nVectorSize); // we've got 2 cycles per repeat
            else
                return nCurrCalls / nVectorSize;
        }

        bool DiscreteActivityBase::perform()
        {
            // call base class, for start() calls and end handling
            if( !ActivityBase::perform() )
                return false; // done, we're ended

            const ::std::size_t nVectorSize( maDiscreteTimes.size() );

            // actually perform something
            // ==========================

            // TODO(Q3): Refactor this mess

            // call derived class with current frame index (modulo 
            // vector size, to cope with repeats)
            perform( calcFrameIndex( mnCurrPerformCalls, nVectorSize ), 
                     calcRepeatCount( mnCurrPerformCalls, nVectorSize ) );
                    
            // calc next index
            ++mnCurrPerformCalls;

            // calc currently reached repeat count
            double nCurrRepeat( double(mnCurrPerformCalls) / nVectorSize );

            // if auto-reverse is specified, halve the
            // effective repeat count, since we pass every
            // repeat run twice: once forward, once backward.
            if( isAutoReverse() )
                nCurrRepeat /= 2.0;

            // schedule next frame, if either repeat is indefinite 
            // (repeat forever), or we've not yet reached the requested
            // repeat count
            if( !isRepeatCountValid() ||
                nCurrRepeat < getRepeatCount() )
            {
                // add wake-up event to queue (modulo 
                // vector size, to cope with repeats).

                // repeat is handled locally, only apply acceleration/deceleration.
                // Scale time vector with simple duration, offset with full repeat 
                // times.
                //
                // Somewhat condensed, the argument for setNextTimeout below could
                // be written as 
                //
                // mnSimpleDuration*(nFullRepeats + calcAcceleratedTime( currentRepeatTime )),
                //
                // with currentRepeatTime = maDiscreteTimes[ currentRepeatIndex ]
                //
                // Note that calcAcceleratedTime() is only applied to the current repeat's value,
                // not to the total resulting time. This is in accordance with the SMIL spec.
                //
                mpWakeupEvent->setNextTimeout( 
                    mnSimpleDuration*(
                        calcRepeatCount( 
                            mnCurrPerformCalls, 
                            nVectorSize ) + 
                        calcAcceleratedTime( 
                            maDiscreteTimes[
                                calcFrameIndex( 
                                    mnCurrPerformCalls, 
                                    nVectorSize ) ] ) ) );

                getEventQueue().addEvent( mpWakeupEvent );
            }
            else
            {
                // release event reference (relation to wakeup event
                // is circular!)
                mpWakeupEvent.reset();

                // done with this activity
                endActivity();
            }

            return false; // remove from queue, will be added back by the wakeup event.
        }

        void DiscreteActivityBase::dispose()
        {
            // dispose event
            if( mpWakeupEvent )
                mpWakeupEvent->dispose();
                    
            // release references
            mpWakeupEvent.reset();

            ActivityBase::dispose();
        }
    }
}
