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

        /**
         * Transition that runs multiple transitions one after the other in
         * sequence. By default, the total duration of the sequence is the sum of
         * the durations and delays of the sub-transitions. If the duration
         * of the sequence is set explicitly, the duration and delay for
         * sub-transitions will be uniformly scaled to fit within in the new
         * time span.
         */
        public class Sequence extends Transition
        {
            // -- Properties ------------------------------------------------------

            /** Array of sequential transitions */
            protected var _trans:/*Transition*/Array = [];
            /** @private */
            protected var _fracs:/*Number*/Array = [];
            /** @private */
            protected var _autodur:Boolean = true;
            /** @private */
            protected var _dirty:Boolean = false;
            /** @private */
            protected var _idx:int = 0;

            /**
             * If true, the duration of this sequence is automatically determined
             * by the durations of each sub-transition. This is the default behavior.
             */
            public function get autoDuration():Boolean { return _autodur; }
            public function set autoDuration(b:Boolean):void {
                _autodur = b;
                computeDuration();
            }

            /** @inheritDoc */
            public override function get duration():Number {
                if (_dirty) computeDuration();
                return super.duration;
            }
            public override function set duration(dur:Number):void {
                _autodur = false;
                super.duration = dur;
                _dirty = true;
            }

            // -- Methods ---------------------------------------------------------

            /**
             * Creates a new Sequence transition.
             * @param transitions an ordered list of sub-transitions
             */
            public function Sequence(...transitions) {
                easing = Easing.none;
                for each (var t:Transition in transitions) {
                    _trans.push(t);
                }
                _dirty = true;
            }

            /**
             * Adds a new transition to the end of this sequence.
             * @param t the transition to add
             */
            public function add(t:Transition):void
            {
                if (running) throw new Error("Transition is running!");
                _trans.push(t);
                _dirty = true;
            }

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

            /**
             * Computes the duration of this sequence transition.
             */
            protected function computeDuration():void
            {
                var d:Number = 0; _fracs = [0];
                // collect durations and compute sum
                for each (var t:Transition in _trans)
                    _fracs.push(d += t.totalDuration);
                // normalize durations to create progress fractions
                for (var i:int=1; i<=_trans.length; ++i)
                    _fracs[i] = (d==0 ? 0 : _fracs[i] / d);
                // set duration and scale
                if (_autodur) super.duration = d;
                _dirty = false;
            }

            /** @inheritDoc */
            public override function dispose():void {
                while (_trans.length > 0) { _trans.pop().dispose(); }
            }

            // -- Transition Handlers ---------------------------------------------

            /** @inheritDoc */
            public override function play(reverse:Boolean=false):void
            {
                if (_dirty) computeDuration();
                super.play(reverse);
            }

            /**
             * Sets up each sub-transition.
             */
            protected override function setup():void
            {
                // initialize each transition in proper sequence
                for each (var t:Transition in _trans) {
                    t.doSetup(); t.step(1.0);
                }
            }

            /**
             * Starts this sequence transition, starting the first sub-transition
             * to be played.
             */
            protected override function start():void
            {
                if (_reverse) {
                    // init for reverse playback
                    for (_idx=0; _idx<_trans.length; ++_idx) _trans[_idx].step(1);
                    _idx -= 1;
                } else {
                    // init for forward playback
                    for (_idx=_trans.length; --_idx>=0;) _trans[_idx].step(0);
                    _idx += 1;
                }
                if (_trans.length > 0)
                    _trans[_idx].doStart(_reverse);
            }

            /**
             * 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) t.doStart(_reverse);
                    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));
                }
            }

            /**
             * Ends this sequence transition, ending the last transition to be
             * played in the sequence as necessary.
             */
            protected override function end():void
            {
                if (_idx >= 0 && _idx < _trans.length) {
                    _trans[_idx].doStep(_reverse ? 0 : 1);
                    _trans[_idx].doEnd();
                }
            }

        } // end of class Sequence
    }