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

        /**
         * Transition that runs multiple transitions simultaneously (in parallel).
         * The duration of this parallel transition is computed as the maximum
         * total duration (duration + delay) among the sub-transitions. If the
         * duration is explicitly set, the sub-transition lengths will be
         * uniformly scaled to fit within the new time span.
         */
        public class Parallel extends Transition
        {
            // -- Properties ------------------------------------------------------

            /** Array of parallel transitions */
            protected var _trans:/*Transition*/Array = [];
            /** @private */
            protected var _equidur:Boolean;
            /** @private */
            protected var _dirty:Boolean = false;
            /** @private */
            protected var _autodur:Boolean = true;

            /**
             * If true, the duration of this sequence is automatically determined
             * by the longest 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 Parallel transition.
             * @param transitions a list of sub-transitions
             */
            public function Parallel(...transitions) {
                easing = Easing.none;
                for each (var t:Transition in transitions) {
                    _trans.push(t);
                }
                _dirty = true;
            }

            /**
             * Adds a new sub-transition to this parallel transition.
             * @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 parallel transition.
             * @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;
            }

            /**
             * Staggers the start of each sub-transition by a given interval. This
             * method will overwrite the delay settings for each sub-transition.
             * @param delay the delay between the start of each sub-transition.
             * @param reverse if true, staggering will be applied in the reverse
             *  order in which sub-transitions were added.
             * @return returns this parallel transition
             */
            public function stagger(delay:Number=0.05, reverse:Boolean=false):Parallel
            {
                var d:Number = 0, i:uint = 0;
                if (reverse) {
                    for (i=_trans.length; --i>=0;) {
                        _trans[i].delay = d;
                        d += delay;
                    }
                } else {
                    for each (var t:Transition in _trans) {
                        t.delay = d;
                        d += delay;
                    }
                }
                _dirty = true;
                return this;
            }

            /**
             * Computes the duration of this parallel transition.
             */
            protected function computeDuration():void {
                var d:Number=0, td:Number;
                if (_trans.length > 0) d = _trans[0].totalDuration;
                _equidur = true;
                for each (var t:Transition in _trans) {
                    td = t.totalDuration;
                    if (_equidur && td != d) _equidur = false;
                    d = Math.max(d, t.totalDuration);
                }
                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
            {
                for each (var t:Transition in _trans) { t.doSetup(); }
            }

            /**
             * Starts each sub-transition.
             */
            protected override function start():void
            {
                for each (var t:Transition in _trans) { t.doStart(_reverse); }
            }

            /**
             * Steps each sub-transition.
             * @param ef the current progress fraction.
             */
            internal override function step(ef:Number):void
            {
                var t:Transition;
                if (_equidur) {
                    // if all durations are the same, we can skip some calculations
                    for each (t in _trans) { t.doStep(ef); }
                } else {
                    // otherwise, make sure we respect the different lengths
                    var d:Number = duration;
                    for each (t in _trans) {
                        var td:Number = t.totalDuration;
                        var f:Number = d==0 || td==d ? 1 : td/d;
                        t.doStep(ef>f ? 1 : f==1 ? ef : ef/f);
                    }
                }
            }

            /**
             * Ends each sub-transition.
             */
            protected override function end():void
            {
                for each (var t:Transition in _trans) { t.doEnd(); }
            }

        } // end of class Parallel
    }