    package flare.animate
    {
        import flare.util.Property;
        import flare.util.IValueProxy;

        import flash.display.DisplayObject;
        import flash.geom.Rectangle;
        import flash.utils.Dictionary;

        /**
         * Parallel transition with convenience methods for adding new
         * object tweens, helping to incrementally construct a group of transitions.
         * A Transitioner will automatically generate tweens for any number of
         * items and properties, simplifying the task of creating animated
         * transitions.
         *
         * <p>For example, the following code creates a 1 second
         * animation for two items. The first item is translated to the point
         * (50,50) and the second item is scaled along the x dimension to twice the
         * normal size.</p>
         * <pre>
         * var item1:Sprite, item2:Sprite; // assume these are two drawn sprites
         * var t:Transitioner = new Transitioner(1); // create 1-second transition
         * t.$(item1).x = 50;
         * t.$(item1).y = 50;
         * t.$(item2).scaleX = 2;
         * t.play();
         * </pre>
         *
         * <p>In the code above, the <code>$</code> method takes an item (this
         * can be any ActionScript object, but is often a <code>DisplayObject</code>
         * instance) and returns an <code>Object</code> which stores the names of
         * the properties to animate and their target values. Behind the scenes,
         * the <code>Transitioner</code> automatically creates <code>Tween</code>
         * objects as needed.</p>
         *
         * <p>The object returned by the <code>$</code> method is a proxy object
         * that passes the values to underlying tweens as needed. This same proxy
         * object is reused across calls to the <code>$</code> method so do
         * <strong>not</strong> attempt to use multiple return values from the
         * <code>$</code> method simultaneously. <em>The following example shows
         * what you should not do!</em></p>
         * <pre>
         * var o1:Object = t.$(item1);
         * var o2:Object = t.$(item2); // o2==o1, now configured for item2
         * o1.x = 5; // actually sets the value 5 to item2, NOT item1
         * </pre>
         *
         * <p>
         * A transitioner can also be set to "immediate" mode, either by setting
         * the <code>immediate</code> property to true, or by passing in
         * <code>NaN</code> as the duration value to the constructor. When in
         * immediate mode, a transitioner will <strong>NOT</strong> generate
         * <code>Tween</code> instances to animate the properties. Instead, the
         * transitioner will set the values of the target objects immediately.
         * For example, when in immediate mode, the <code>$</code> operator is
         * equivalent to directly setting the property:
         * <code>t.$(item1).x = 50</code> has exactly the same result at
         * <code>t.x = 50</code>. The static property
         * <code>Transitioner.DEFAULT</code> provides a default instance of an
         * immediate-mode transitioner.
         * </p>
         *
         * <p>
         * With these features, transitioners provide a highly flexible way to
         * update values in your application. You can write layout and other
         * methods once, using a transitioner to update all the property values.
         * When animation is desired, a standard transitioner can be passed in
         * to your routines. When immediate updates are desired, you can reuse
         * the same code, but just pass in a transitioner in immediate mode
         * instead. Whether or not value updates are animated or immediate then
         * becomes easy to control.
         * </p>
         *
         * <p>
         * Transitioners also provide optimizations to improve animation
         * performance. However, they are not enabled by default, as the
         * optimizations make some assumptions about how the transitioner will
         * be used. See the <code>optimize</code> property and
         * <code>dispose</code> method for more information.
         * </p>
         */
        public class Transitioner extends Parallel implements IValueProxy
        {
            /** The default, immediate-mode transitioner instance. */
            public static const DEFAULT:Transitioner = new Transitioner(NaN);

            /**
             * Gets a transitioner instance depending upon the input value.
             * @param t input determining the transitioner instance to return. If
             *  the input is a transitioner, it is simply returned. If the input is
             *  a number, a new Transitioner with duration set to the input value
             *  is returned, unless the number is less than zero, in which case the
             *  default immediate-mode transitioner is returned. If the input is
             *  null, <code>Transitioner.DEFAULT</code> is returned.
             * @return a Transitioner instance determined by the input
             */
            public static function instance(t:*):Transitioner {
                if (t is Number) {
                    var dur:Number = Number(t);
                    return dur<0 ? Transitioner.DEFAULT : new Transitioner(dur);
                } else if (t == null) {
                    return Transitioner.DEFAULT;
                } else {
                    return t as Transitioner;
                }
            }

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

            private var _immediate:Boolean;
            private var _lookup:/*Object->Tween*/Dictionary = new Dictionary();

            private var _proxy:ValueProxy;
            private var _optimize:Boolean = false;
            private var _subdur:Number;

            /** @private */
            public override function get duration():Number {
                return _trans.length==0 ? _subdur : super.duration;
            }

            /** Immediate mode flag, used to bypass tween generation and perform
             *  immediate updates of target object values. */
            public function get immediate():Boolean { return _immediate; }
            public function set immediate(b:Boolean):void {
                _immediate = b;
                if (!immediate && _proxy == null) _proxy = new ValueProxy(this);
            }

            /**
             * Flag indicating if aggressive optimization should be applied.
             * This can significantly decrease processing time when large numbers
             * of elements are involved. However, the optimization process makes a
             * few assumptions about how the transitioner will be used. If these
             * assumptions are not met, the animations may exhibit unexpected
             * behaviors.
             *
             * <p>The assumptions made for optimized transitioners are:
             * <ol>
             * <li>The property values of tweened objects will not change between
             *     the time their target values are set and the transition is
             *     played. This allows the transitioner to avoid creating tweens
             *     when properties have the same starting and ending values, and
             *     immediately set values for DisplayObjects that are not visible.
             *     However, this means the starting value must stay the same. In
             *     particular, this means that optimized transitioners are often
             *     inappropriate for use within a <code>Sequence</code>.</li>
             * <li>The transitioner will only be played once, then discarded.
             *     This allows the transitioner to automatically recycle all
             *     generated <code>Tween</code> and <code>Interpolator</code>
             *     instances, reducing initialization time across transitioners by
             *     reusing objects.</li>
             * </ol>
             * </p>
             */
            public function get optimize():Boolean { return _optimize; }
            public function set optimize(b:Boolean):void { _optimize = b; }

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

            /**
             * Creates a new Transitioner with specified duration.
             * @param duration the length of the transition. If this value is NaN,
             *  the transitioner will be in immediate mode, in which all changes
             *  are immediately applied and no tweens are generated.
             * @param easing the easing function to use for this transition. If
             *  null, the function Easing.none will be used.
             * @param optimize boolean flag indicating if the transitioner should
             *  attempt to optimize tween construction. See the documentation
             *  for the <code>optimize</code> property for mode details.
             * @param id an optional id. If non-null, any other running transition
             *  with the same id will be canceled when this transitioner is played.
             */
            public function Transitioner(duration:Number=1, easing:Function=null,
                                         optimize:Boolean=false, id:String=null)
            {
                super.easing = easing==null ? DEFAULT_EASING : easing;
                _subdur = duration;
                _optimize = optimize;
                _immediate = isNaN(duration);
                if (id!=null) this.id = id;
                if (!_immediate) _proxy = new ValueProxy(this);
            }

            /**
             * Indicates if the Transitioner contains a Tween for the given object.
             * @param o the object to test for
             * @return true if there is a Tween for the object, false otherwise
             */
            public function hasTweenFor(o:Object):Boolean
            {
                return _immediate ? false : (_lookup[o] != undefined);
            }

            /**
             * Returns the Tween for the given object, creating a new tween if no
             * tween is yet associated with the object. This method returns null if
             * the transitioner is in immediate mode.
             * @param o the target object
             * @return a tween for the input target object, or null if this
             *  transitioner is in immediate mode.
             */
            public function _(o:Object):Tween
            {
                if (_immediate) return null;

                var tw:Tween = _lookup[o];
                if (tw == null) {
                    add(tw = getTween(o, _subdur));
                    tw.easing = Easing.none;
                    _lookup[o] = tw;
                }
                return tw;
            }

            /**
             * Returns the values object of the Tween for the given object. If no
             * tween is associated with the object, a new one is created.
             *
             * If the transitioner is in immediate mode, then no Tween will be
             * created. Instead, the input object will be returned. This allows
             * value updates to be set immediately, rather than animated by a
             * tween.
             * @param o the target object
             * @return the <code>values</code> object for the target object's
             *  tween, or the target object itself if this transitioner is in
             *  immediate mode.
             */
            public function $(o:Object):Object
            {
                return _immediate ? o : _proxy.init(o);
            }

            /**
             * Sets property values for a target object. This method has the same
             * effect as setting a property on the object returned by the
             * <code>$</code> method.
             *
             * <p>If the transitioner is in immediate mode, the property name will
             * be parsed and the value set at the end of the property chain. If
             * the transitioner is not in immediate mode, the property name and
             * values will simply be added to a Tween. If no Tween is associated
             * with the input object, a new one will be created.</p>
             *
             * @param o the target object
             * @param name the property name string
             * @param value the property value to set
             */
            public function setValue(o:Object, name:String, value:*):void
            {
                if (_immediate) {
                    // set the object property
                    Property.$(name).setValue(o, value);
                } else if (optimize && getValue(o, name) == value) {
                    // do nothing, optimize the call away...
                } else if (optimize && o is DisplayObject && !getValue(o, "visible")) {
                    Property.$(name).setValue(o, value);
                } else {
                    // add to a tween
                    _(o).values[name] = value;
                }
            }

            /**
             * Retrieves property values for a target object. This method has the
             * same effect as accessing a property using the object returned by the
             * <code>$</code> method.
             *
             * <p>If the transitioner is in immediate mode, the property name will
             * be parsed and the value retrieved diretly from the target object. If
             * the transitioner is not in immediate mode, this method will first
             * try to lookup the value in the tween <code>values</code> for the
             * target object. If this does not succeed, the property value will be
             * retrieved directly from the target object itself as in the immediate
             * mode case.</p>
             *
             * @param o the target object
             * @param name the property name string
             * @return the property value for the target object, either from the
             *  target object's tween values, or, failing that, the object itself.
             */
            public function getValue(o:Object, name:String):*
            {
                if (!_immediate) {
                    var tw:Tween = _lookup[o];
                    if (tw != null && tw.values[name] != undefined) {
                        return tw.values[name];
                    }
                }
                return Property.$(name).getValue(o);
            }

            /**
             * Sets the delay of the tween for the given object. If the
             * transitioner is in immediate mode, this method has no effect.
             * @param o the object to set the delay for
             * @param delay the delay, in seconds
             */
            public function setDelay(o:Object, delay:Number):void
            {
                if (!_immediate) {
                    _(o).delay = delay;
                }
            }

            /**
             * Gets the delay of the tween for the given object. If the
             * transitioner is in immediate mode or no tween has been created for
             * the input object, this method returns zero.
             * @param o the object to get the delay for
             * @return the delay of the tween, or zero if there is no tween
             */
            public function getDelay(o:Object):Number
            {
                if (_immediate) return 0;
                var tw:Tween = _lookup[o];
                return tw==null ? 0 : tw.delay;
            }

            /**
             * Sets the removal status of a display object with this transition.
             * If true, the display object will be removed from its parent in the
             * display list when the transition completes. If this transitioner is
             * in immediate mode, any removals are performed immediately.
             * @param dobj a display object
             * @param b true to remove the display object from the display list
             *  at the end of the transition (the default). If false, the removal
             *  status will be updated so that the display object will not be
             *  removed (not applicable in immediate mode).
             */
            public function removeChild(dobj:DisplayObject, b:Boolean=true):void
            {
                if (_immediate && b) {
                    if (dobj.parent) dobj.parent.removeChild(dobj);
                } else if (!_immediate) {
                    _(dobj).remove = b;
                }
            }

            /**
             * Indicates if a display object is scheduled to be removed from the
             * display list when this transition completes. This method always
             * returns false if this transitioner is in immediate mode.
             * @param d a display object
             * @return true if the display object will be removed from the display
             *  list at the end of this transition, false otherwise. This method
             *  always returns false if the transitioner is in immediate mode.
             */
            public function willRemove(d:DisplayObject):Boolean
            {
                if (_immediate) return false;
                var tw:Tween = _lookup[d];
                return (tw != null && tw.remove);
            }

            /** @inheritDoc */
            protected override function end():void
            {
                super.end();
                if (_optimize) dispose();
            }

            /**
             * Disposes of the internal state of this transitioner.
             * Contained tweens and their interpolators will be collected and
             * recycled for future reuse, improving initialization times for
             * subsequent transitioners. This method is automatically called at the
             * end of the transition if the <code>optimize</code> flag is true.
             * Otherwise, this method can be invoked manually when a transitioner
             * is no longer needed.
             */
            public override function dispose():void
            {
                while (_trans.length > 0) {
                    var t:Transition = _trans.pop();
                    t.dispose();
                    if (t is Tween) reclaimTween(t as Tween);
                }
            }

            /**
             * Computes the approximate size of the given object after this
             * transitioner has been run. This calculation is performed by
             * applying the final <code>scaleX</code>, <code>scaleY</code>, and
             * <code>size</code> values of the object.
             * @param d the display object to compute the size for
             * @param r a rectangle for storing the results
             * @return a rectangle whose <code>width</code> and <code>height</code>
             *  properties contain the end size values.
             */
            public function endSize(d:DisplayObject, r:Rectangle=null):Rectangle
            {
                if (r==null) r = new Rectangle();
                var t:Tween, v:Object, o:Object = Object(d);
                var scaleX:Number, scaleY:Number, size:Number;

                if (_immediate || (t=_lookup[d])==null) {
                    r.width = d.width;
                    r.height = d.height;
                } else {
                    v = t.values;
                    if (v.hasOwnProperty("scaleX")) {
                        scaleX = d.scaleX;
                        d.scaleX = v.scaleX;
                    }
                    if (v.hasOwnProperty("scaleY")) {
                        scaleY = d.scaleY;
                        d.scaleY = v.scaleY;
                    }
                    if (v.hasOwnProperty("size")) {
                        size = o.size;
                        o.size = v.size;
                    }
                    r.width = d.width;
                    r.height = d.height;
                    if (v.hasOwnProperty("scaleX")) d.scaleX = scaleX;
                    if (v.hasOwnProperty("scaleY")) d.scaleY = scaleY;
                    if (v.hasOwnProperty("size"))   o.size = size;
                }
                return r;
            }

            /**
             * Computes the approximate bounds of the given object after this
             * transitioner has been run. This calculation is performed by
             * applying the final <code>scaleX</code>, <code>scaleY</code>,
             * <code>size</code>, <code>x</code>, and <code>y</code> values of
             * the object.
             * @param d the display object to compute the size for
             * @param coords the target coordinate space for the bounds
             * @return a rectangle whose <code>width</code> and <code>height</code>
             *  properties contain the end size values.
             */
            public function endBounds(d:DisplayObject,
                coords:DisplayObject):Rectangle
            {
                var r:Rectangle = new Rectangle();
                var t:Tween, v:Object, o:Object = Object(d);
                var scaleX:Number, scaleY:Number, size:Number, x:Number, y:Number;

                if (_immediate || (t=_lookup[d])==null) {
                    r = d.getBounds(coords);
                } else {
                    v = t.values;
                    if (v.hasOwnProperty("scaleX")) {
                        scaleX = d.scaleX;
                        d.scaleX = v.scaleX;
                    }
                    if (v.hasOwnProperty("scaleY")) {
                        scaleY = d.scaleY;
                        d.scaleY = v.scaleY;
                    }
                    if (v.hasOwnProperty("size")) {
                        size = o.size;
                        o.size = v.size;
                    }
                    if (v.hasOwnProperty("x")) {
                        x = d.x;
                        d.x = v.x;
                    }
                    if (v.hasOwnProperty("y")) {
                        y = d.y;
                        d.y = v.y;
                    }
                    r = d.getBounds(coords);
                    if (v.hasOwnProperty("scaleX")) d.scaleX = scaleX;
                    if (v.hasOwnProperty("scaleY")) d.scaleY = scaleY;
                    if (v.hasOwnProperty("size"))   o.size = size;
                    if (v.hasOwnProperty("x"))      d.x = x;
                    if (v.hasOwnProperty("y"))      d.y = y;
                }
                return r;
            }

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

            private static var _maxPoolSize:int = 10000;
            private static var _tweenPool:Array = [];
            private static var _count:int = 0;

            private static function getTween(o:Object, duration:Number):Tween
            {
                var tw:Tween;
                if (_tweenPool.length > 0) {
                    tw = _tweenPool.pop();
                    tw.target = o;
                    tw.duration = duration;
                    tw.enabled = true;
                } else {
                    tw = new Tween(o, duration);
                }
                return tw;
            }

            private static function reclaimTween(tw:Tween):void
            {
                if (_tweenPool.length < _maxPoolSize) {
                    _tweenPool.push(tw);
                }
            }

        } // end of class Transitioner
    }

    import flare.animate.Transitioner;
    import flare.util.Property;

    import flash.utils.flash_proxy;
    import flash.utils.Proxy;

    /**
     * Helper class that gets/sets values for a Transitioner.
     * This layer of indirection allows us to perform "behind-the-scenes"
     * handling while maintaining simple property assignment syntax.
     */
    dynamic class ValueProxy extends Proxy
    {
        private var _trans:Transitioner;
        private var _object:Object;

        public function ValueProxy(trans:Transitioner) {
            _trans = trans;
        }

        public function init(obj:Object):Object
        {
            _object = obj;
            return this;
        }

        override flash_proxy function callProperty(methodName:*, ... args):* {
            return null;
        }

        override flash_proxy function getProperty(name:*):* {
            return _trans.getValue(_object, name);
        }

        override flash_proxy function setProperty(name:*, value:*):void {
            _trans.setValue(_object, name, value);
        }

    } // end of class ValueProxy