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
}