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

        import flash.utils.getDefinitionByName;
        import flash.utils.getQualifiedClassName;

        /**
         * Base class for value interpolators. This class also provides factory
         * methods for creating concrete interpolator instances -- see the
         * <code>create</code> method for details about interpolator creation.
         */
        public class Interpolator
        {
            /** The target object whose property is being interpolated. */
            protected var _target:Object;
            /** The property to interpolate. */
            protected var _prop:Property;

            /**
             * Base constructor for Interpolator instances.
             * @param target the object whose property is being interpolated
             * @param property the property to interpolate
             * @param value the target value of the interpolation
             */
            public function Interpolator(target:Object, property:String,
                                         start:Object, end:Object)
            {
                reset(target, property, start, end);
            }

            /**
             * Re-initializes an exising interpolator instance.
             * @param target the object whose property is being interpolated
             * @param property the property to interpolate
             * @param value the target value of the interpolation
             */
            public function reset(target:Object, property:String,
                                  start:Object, end:Object):void
            {
                _target = target;
                _prop = Property.$(property);
                init(start, end);
            }

            /**
             * Performs initialization of an interpolator, typically by
             * initializing the start and ending values. Subclasses should
             * override this method for custom initialization.
             * @param value the target value of the interpolation
             */
            protected function init(start:Object, end:Object) : void
            {
                // for subclasses to override
            }

            /**
             * Calculate and set an interpolated property value. Subclasses should
             * override this method to implement custom interpolation routines.
             * @param f the interpolation fraction (typically between 0 and 1)
             */
            public function interpolate(f:Number) : void
            {
                throw new Error("This is an abstract method");
            }

            // -- Interpolator Factory --------------------------------------------

            private static var _maxPoolSize:int = 10000;
            private static var _pools:Object = [];
            private static var _lookup:Object = buildLookupTable();
            private static var _rules:Array = buildRules();

            private static function buildLookupTable() : Object
            {
                // add variables to ensure classes are included by compiler
                var ni:NumberInterpolator;
                var di:DateInterpolator;
                var pi:PointInterpolator;
                var ri:RectangleInterpolator;
                var mi:MatrixInterpolator;
                var ai:ArrayInterpolator;
                var ci:ColorInterpolator;
                var oi:ObjectInterpolator;

                // build the value->interpolator lookup table
                var lut:Object = new Object();
                lut["Number"] = "flare.animate.interpolate::NumberInterpolator";
                lut["int"] = "flare.animate.interpolate::NumberInterpolator";
                lut["Date"] = "flare.animate.interpolate::DateInterpolator";
                lut["Array"] = "flare.animate.interpolate::ArrayInterpolator";
                lut["flash.geom::Point"] = "flare.animate.interpolate::PointInterpolator";
                lut["flash.geom::Rectangle"] = "flare.animate.interpolate::RectangleInterpolator";
                lut["flash.geom::Matrix"] = "flare.animate.interpolate::MatrixInterpolator";
                return lut;
            }

            private static function buildRules() : Array
            {
                var rules:Array = new Array();
                rules.push(isColor);
                return rules;
            }

            private static function isColor(target:Object, property:String, s:Object, e:Object)
                : String
            {
                return property.indexOf("Color")>=0 || property.indexOf("color")>=0
                    ? "flare.animate.interpolate::ColorInterpolator"
                    : null;
            }

            /**
             * Extends the interpolator factory with a new interpolator type.
             * @param valueType the fully qualified class name for the object type
             *  to interpolate
             * @param interpType the fully qualified class name for the
             *  interpolator class type
             */
            public static function addInterpolatorType(valueType:String, interpType:String) : void
            {
                _lookup[valueType] = interpType;
            }

            /**
             * Clears the lookup table of interpolator types, removing all
             * type to interpolator mappings.
             */
            public static function clearInterpolatorTypes():void
            {
                _lookup = new Object();
            }

            /**
             * Adds a rule to the interpolator factory. The input function should
             * take 4 arguments -- a target object, property name string, a
             * starting value, and a target value -- and either return a fully
             * qualified class name for the type of interpolator to use, or null if
             * this rule does not apply.
             * @param f the rule function for supplying custom interpolator types
             *  based on contextual conditions
             */
            public static function addInterpolatorRule(f:Function):void
            {
                _rules.push(f);
            }

            /**
             * Clears all interpolator rule functions from the interpolator
             * factory.
             */
            public static function clearInterpolatorRules():void
            {
                _rules = new Array();
            }

            /**
             * Returns a new interpolator instance for the given target object,
             * property name, and interpolation target value. This factory method
             * follows these steps to provide an interpolator instance:
             * <ol>
             *  <li>The list of installed interpolator rules is consulted, and if a
             *      rule returns a non-null class name string, an interpolator of
             *      that type will be returned.</li>
             *  <li>If all rules return null values, then the class type of the
             *      interpolation value is used to look up the appropriate
             *      interpolator type for that value. If a matching interpolator
             *      type is found, an interpolator is initialized and returned.
             *      </li>
             *  <li>If no matching type is found, a default ObjectInterpolator
             *      instance is initialized and returned.</li>
             * </ol>
             *
             * <p>By default, the interpolator factory contains two rules. The
             * first rule returns the class name of ColorInterpolator for any
             * property names containing the string "color" or "Color". The second
             * rule returns the class name of ObjectInterpolator for the property
             * name "shape".</p>
             *
             * <p>The default value type to interpolator type mappings are:
             * <ul>
             *  <li><code>Number -> NumberInterpolator</code></li>
             *  <li><code>int -> NumberInterpolator</code></li>
             *  <li><code>Date -> DateInterpolator</code></li>
             *  <li><code>Array -> ArrayInterpolator</code></li>
             *  <li><code>flash.geom.Point -> PointInterpolator</code></li>
             *  <li><code>flash.geom.Rectangle -> RectangleInterpolator</code></li>
             * </ul>
             * </p>
             *
             * <p>The interpolator factory can be extended either by adding new
             * interpolation rule functions or by adding new mappings from
             * interpolation value types to custom interpolator classes.</p>
             */
            public static function create(target:Object, property:String,
                                          start:Object, end:Object): Interpolator
            {
                // first, check the rules list for an interpolator
                var name:String = null;
                for (var i:uint=0; name==null && i<_rules.length; ++i) {
                    name = _rules[i](target, property, start, end);
                }
                // if no matching rule, use the type lookup table
                if (name == null) {
                    name = _lookup[getQualifiedClassName(end)];
                }
                // if that fails, use ObjectInterpolator as default
                if (name == null) {
                    name = "flare.animate.interpolate::ObjectInterpolator";
                }

                // now create the interpolator, recycling from the pool if possible
                var pool:Array = _pools[name] as Array;
                if (pool == null || pool.length == 0) {
                    // nothing in the pool, create a new instance
                    var Ref:Class = getDefinitionByName(name) as Class;
                    return new Ref(target, property, start, end) as Interpolator;
                } else {
                    // reuse an interpolator from the object pool
                    var interp:Interpolator = pool.pop() as Interpolator;
                    interp.reset(target, property, start, end);
                    return interp;
                }
            }

            /**
             * Reclaims an interpolator for later recycling. The reclaimed
             * interpolator should not be in active use by any other classes.
             * @param interp the Interpolator to reclaim
             */
            public static function reclaim(interp:Interpolator):void
            {
                var type:String = getQualifiedClassName(interp);
                var pool:Array = _pools[type] as Array;
                if (pool == null) {
                    _pools[type] = [interp];
                } else if (pool.length < _maxPoolSize) {
                    pool.push(interp);
                }
            }

        } // end of class Interpolator
    }