package flare.animate
{
    import flare.animate.interpolate.Interpolator;
    import flare.util.Property;
    
    import flash.display.DisplayObject;
    
    /**
     * Transition that interpolates (in-be<em>tweens</em>) properties
     * of a target object over a time interval. The <tt>values</tt> property
     * represents the set of properties to tween and their final values. Any
     * arbitrary property (not just visual properties) can be tweened. The
     * Tween class handles tweening of Numbers, colors, Dates, Points,
     * Rectangles, and numeric Arrays. Properties of other types are simply
     * swapped when then Transition half-completes. Tweening for custom types
     * is possible, see the <tt>flare.animate.interpolate.Interpolator</tt>
     * class for more.
     * 
     * <p>Starting values are automatically determined from the tweened object.
     * Once determined, these starting values are stored to allow both forward
     * and backward playback. Use the <tt>reset</tt> method to force a tween to
     * redetermine the starting values the next time it is played. Tweens also
     * provide a <code>remove</code> flag for DisplayObjects. When set to true,
     * a display object will be removed from the display list at the end of the
     * tween. Note that playing the tween is reverse will not revert this
     * removal.</p>
     * 
     * <p>Internally, a Tween creates a set of Interpolator objects to compute
     * intermediate values for each property included in <tt>values</tt>. Note
     * that property names can involve nested properties. For example,
     * <tt>{"filters[0].blurX":5}</tt> is a valid tweening property, as both
     * array access (<tt>[]</tt>) and property access (<tt>.</tt>) syntax are
     * supported.</p>
     * 
     * <p>To manage a collection of objects being tweened simultaneously, use a
     * <tt>Transitioner</tt> object.</p>
     */
    public class Tween extends Transition
    {
        // -- Properties ------------------------------------------------------
        
        private var _interps:Array = new Array();
        private var _target:Object;
        private var _from:Object;
        private var _remove:Boolean = false;
        private var _visible:Object = null;
        private var _values:Object;
        
        /** The target object whose properties are tweened. */
        public function get target():Object { return _target; }
        public function set target(t:Object):void { _target = t; }
        
        /** Flag indicating if the target object should be removed from the
         *  display list at the end of the tween. Only applies when the target
         *  is a <code>DisplayObject</code>. */
        public function get remove():Boolean { return _remove; }
        public function set remove(b:Boolean):void { _remove = b; }
        
        /** The properties to tween and their target values. */
        public function get values():Object { return _values; }
        public function set values(o:Object):void { _values = o; }
        
        /** Optional starting values for tweened properties. */
        public function get from():Object { return _from; }
        public function set from(s:Object):void { _from = s; }
        
        
        // - Methods ----------------------------------------------------------
        
        /**
         * Creates a new Tween with the specified parameters.
         * @param target the target object
         * @param duration the duration of the tween, in seconds
         * @param values the properties to tween and their target values
         * @param remove a display list removal flag (for
         *  <code>DisplayObject</code> target objects
         * @param easing the easing function to use
         */
        public function Tween(target:Object, duration:Number=1,
            values:Object=null, remove:Boolean=false, easing:Function=null)
        {
            super(duration, 0, easing);
            
            _target = target;
            _remove = remove;
            _values = values==null ? {} : values;
            _from = {};
        }
        
        /** @inheritDoc */
        public override function dispose():void
        {
            // reclaim any old interpolators
            while (_interps.length > 0) {
                Interpolator.reclaim(_interps.pop());
            }
            // remove all target values
            for (var name:String in _values) {
                delete _values[name];
            }
            _visible = null;
            _remove = false;
            _target = null;
        }
        
        /**
         * Sets up this tween by creating interpolators for each tweened
         * property.
         */
        protected override function setup():void
        {
            // reclaim any old interpolators
            while (_interps.length > 0) {
                Interpolator.reclaim(_interps.pop());
            }
            
            // build interpolators
            var vc:Object, v0:Object, v1:Object;
            for (var name:String in _values) {
                // create interpolator only if start/cur/end values don't match
                vc = Property.$(name).getValue(_target);
                v0 = _from.hasOwnProperty(name) ? _from[name] : vc;
                v1 = _values[name];
                
                if (vc != v1 || vc != v0) {
                    if (name == "visible") {
                        // special handling for visibility
                        _visible = Boolean(v1);
                    } else {
                        _interps.push(Interpolator.create(_target, name, v0, v1));
                    }
                }
            }
        }
        
        /**
         * Updates target object visibility, if appropriate.
         */
        protected override function start():void
        {
            // set visibility
            var item:DisplayObject = _target as DisplayObject;
            if (item != null && Boolean(_visible))
                item.visible = true;
        }
        
        /**
         * Steps the tween, updating the tweened properties.
         */
        internal override function step(ef:Number):void
        {
            // run the interpolators
            for each (var i:Interpolator in _interps) {
                i.interpolate(ef);
            }
        }
        
        /**
         * Ends the tween, updating target object visibility and display
         * list membership, if appropriate.
         */
        protected override function end():void
        {
            // set visibility, remove from display list if requested
            var item:DisplayObject = _target as DisplayObject;
            if (item != null) {
                if (_remove && item.parent != null)
                    item.parent.removeChild(item);
                if (_visible != null)
                    item.visible = Boolean(_visible);
            }
        }
        
    } // end of class Tween
}