    package flare.animate
    {
        import flare.util.Arrays;
        import flare.util.Maths;

        /**
         * Transition that runs sub-transitions in sequential order while also
         * invoking a function before each sub-transition is run. Each function
         * must take a Transition instance (often a Transitioner) as input.
         * Function sequences can only be played forwards; any attempt to play
         * one in reverse will result in an error.
         *
         * <p>Function sequences are useful for ensuring a particular function
         * is run before a sub-transition begins. For example, the function may
         * populate the values of a Transitioner on the fly, or may be used to
         * control other variables or side-effects that affect the subsequent
         * sub-transition(s).</p>
         */
        public class FunctionSequence extends Sequence
        {
            /** @private */
            protected var _funcs:/*Function*/Array = [];
            /** @private */
            protected var _offsetStart:Boolean = true;

            /** Flag indicating if extra time should be added to the transition to
             *  offset the running time of invoked functions. True by default. */
            public function get offsetStartTime():Boolean { return _offsetStart; }
            public function set offsetStartTime(b:Boolean):void { _offsetStart = b; }

            // --------------------------------------------------------------------

            /**
             * Creates a new FunctionSequence transition.
             */
            public function FunctionSequence()
            {
                super();
            }

            /**
             * Adds a function call and corresponding transition to this sequence.
             * As the sequence plays, functions will be called at the beginning of
             * their subsequence, with the provided transition passed as the
             * function's sole argument.
             * @param f a function to call at the beginning of a sub-sequence
             * @param t the transition to run after the function call. This
             *  transition will be passed in as an input to the function. This
             *  value should be either a <code>Transition</code> instance or
             *  a number indicating the duration for a new
             *  <code>Transitioner</code>.
             */
            public function push(f:Function, t:*):void
            {
                var tr:Transition = t is Transition ? Transition(t)
                                                    : Transitioner.instance(t);
                super.add(tr);
                _funcs.push(f);
            }

            /**
             * Removes a sub-transition function from this sequence. The
             * corresponding transition instance will also be removed.
             * @param t the transition to remove
             * @return true if the transition was found and removed, false
             *  otherwise
             */
            public function removeFunction(f:Function):Boolean
            {
                if (running) throw new Error("Transition is running!");
                var idx:int = Arrays.remove(_funcs, f);
                var rem:Boolean = idx > 0;
                if (rem) {
                    _trans.splice(idx, 1);
                    _dirty = true;
                }
                return rem;
            }

            /** @inheritDoc */
            public override function add(t:Transition):void
            {
                super.add(t);
                _funcs.push(null);
            }

            /**
             * Removes a sub-transition from this sequence. Any corresponding
             * function will also be removed.
             * @param t the transition to remove
             * @return true if the transition was found and removed, false
             *  otherwise
             */
            public override function remove(t:Transition):Boolean
            {
                if (running) throw new Error("Transition is running!");
                var idx:int = Arrays.remove(_trans, t);
                var rem:Boolean = idx > 0;
                if (rem) {
                    _funcs.splice(idx, 1);
                    _dirty = true;
                }
                return rem;
            }

            /** @inheritDoc */
            public override function dispose():void {
                super.dispose();
                Arrays.clear(_funcs);
            }

            /**
             * Plays this function sequence. Function sequences can not be played
             * in reverse.
             * @param reverse If true, an error will be thrown and the sequence
             *  will not play.
             */
            public override function play(reverse:Boolean=false):void {
                if (reverse) throw new Error(
                    "Function sequences can't be played in reverse.");
                super.play(false);
            }

            private function invoke(idx:int, t:Transition):void {
                var f:Function = _funcs[idx];
                if (f != null) {
                    if (_offsetStart) {
                        var d:Number = new Date().time;
                        f(t);
                        d = new Date().time - d;
                        _start += d;
                    } else {
                        f(t);
                    }
                }
            }

            // --------------------------------------------------------------------

            /**
             * Sets up each sub-transition.
             */
            protected override function setup():void
            {
            }

            /**
             * Starts this sequence transition, starting the first sub-transition
             * to be played.
             */
            protected override function start():void
            {
                if (_trans.length > 0) {
                    var t:Transition = _trans[_idx];
                    invoke(_idx, t); t.doSetup(); t.doStart(false);
                }
            }

            /**
             * Steps this sequence transition, ensuring that any sub-transitions
             * between the previous and current progress fraction are properly
             * invoked.
             * @param ef the current progress fraction.
             */
            internal override function step(ef:Number):void
            {
                // find the right sub-transition
                var t:Transition, f0:Number, f1:Number, i:int, inc:int;
                f0 = _fracs[_idx]; f1 = _fracs[_idx+1]; inc = (ef<=f0 ? -1 : 1);

                for (i = _idx; i>=0 && i<_trans.length; i+=inc) {
                    // get transition and progress fractions
                    t = _trans[i]; f0 = _fracs[i]; f1 = _fracs[i+1];
                    // hand-off to new transition
                    if (i != _idx) {
                        invoke(i, t); t.doSetup(); t.doStart(false);
                    }
                    if ((inc<0 && ef >= f0) || (inc>0 && ef <= f1)) break;
                    t.doStep(inc<0 ? 0 : 1);
                    t.doEnd();
                }
                _idx = i; // set the transition index

                if (_idx >= 0 && _idx < _trans.length) {
                    // run transition with mapped fraction
                    t.doStep(Maths.invLinearInterp(ef, f0, f1));
                }
            }
        } // end of class FunctionSequence
    }