package flare.util
{        
    /**
     * Utility class for accessing arbitrary property chains, allowing
     * nested property expressions (e.g., <code>x.a.b.c</code> or 
     * <code>x.a[1]</code>). To reduce initialization times, this class also
     * maintains a static cache of all Property instances created using the
     * static <code>$()</code> method.
     */
    public class Property implements IEvaluable, IPredicate
    {
        private static const DELIMITER:* = /[\.|\[(.*)\]]/;
        
        private static var __cache:Object = {};
        private static var __stack:Array = [];
        private static var __proxy:IValueProxy;

        /**
         * Requests a Property instance for the given property name. This is a
         * factory method that caches and reuses property instances, saving
         * memory and construction time. This method is the preferred way of
         * getting a property and should be used instead of the constructor.
         * @param name the name of the property
         * @return the requested property instance
         */
        public static function $(name:String):Property
        {
            if (name == null) return null;
            var p:Property = __cache[name];
            if (p == null) {
                p = new Property(name);
                __cache[name] = p;
            }
            return p;
        }
        
        /**
         * Clears the cache of created Property instances
         */
        public static function clearCache():void
        {
            __cache = {};
        }

        /** A minimal <code>IValueProxy</code> instance that gets and sets
         *  property values through <code>Property</code> instances. */
        public static function get proxy():IValueProxy {
            if (__proxy == null) __proxy = new PropertyProxy();
            return __proxy;
        }
        
        // --------------------------------------------------------------------
        
        private var _field:String;
        private var _chain:Array;
        
        /** The property name string. */
        public function get name():String { return _field; }
        
        // --------------------------------------------------------------------
        
        /**
         * Creates a new Property, in most cases the static <code>$</code>
         * method should be used instead of this constructor.
         * @param name the property name string
         */
        public function Property(name:String) {
            if (name == null) {
                throw new ArgumentError("Not a valid property name: "+name);
            }
            
            _field = name;
            _chain = null;
            
            if (_field != null) {
                var parts:Array = _field.split(DELIMITER);
                if (parts.length > 1) {
                    _chain = [];
                    for (var i:int=0; i<parts.length; ++i) {
                        if (parts[i].length > 0)
                            _chain.push(parts[i]);
                    }
                }
            }
        }
        
        /**
         * Gets the value of this property for the input object.
         * @param x the object to retrieve the property value for
         * @return the property value
         */
        public function getValue(x:Object):*
        {
            if (x == null) {
                return null;
            } else if (_chain == null) {
                return x[_field];
            } else {
                for (var i:uint=0; i<_chain.length; ++i) {
                    x = x[_chain[i]];
                }
                return x;
            }
        }
        
        /**
         * Gets the value of this property for the input object; this
         * is the same as <code>getValue</code>, but provided in order to
         * implement the <code>IEvaluable</code> interface.
         * @param x the object to retrieve the property value for
         * @return the property value
         */
        public function eval(x:Object=null):*
        {
            if (x == null) {
                return null;
            } else if (_chain == null) {
                return x[_field];
            } else {
                for (var i:uint=0; i<_chain.length; ++i) {
                    x = x[_chain[i]];
                }
                return x;
            }
        }
        
        /**
         * Gets the value of this property and casts the result to a
         * Boolean value.
         * @param x the object to retrieve the property value for
         * @return the property value as a Boolean
         */
        public function predicate(x:Object):Boolean
        {
            return Boolean(eval(x));
        }
        
        /**
         * Sets the value of this property for the input object. If the reset
         * flag is true, all properties along a property chain will be updated.
         * Otherwise, only the last property in the chain is updated.
         * @param x the object to set the property value for
         * @param val the value to set
         */
        public function setValue(x:Object, val:*):void
        {
            if (_chain == null) {
                x[_field] = val;
            } else {
                __stack.push(x);
                for (var i:uint=0; i<_chain.length-1; ++i) {
                    __stack.push(x = x[_chain[i]]);    
                }
                
                var p:Object = __stack.pop();
                p[_chain[i]] = val;
                
                for (i=_chain.length-1; --i >= 0; ) {
                    x = p;
                    p = __stack.pop();
                    try {
                        p[_chain[i]] = x;
                    } catch (err:Error) {}
                }
            }
        }
        
        /**
         * Deletes a dynamically-bound property from an object.
         * @param x the object from which to delete the property
         */
        public function deleteValue(x:Object):void
        {
            if (_chain == null) {
                delete x[_field];
            } else {
                for (var i:uint=0; i<_chain.length-1; ++i) {
                    x = x[_chain[i]];
                }
                delete x[_chain[i]];
            }
        }
        
    } // end of class Property
}

import flare.util.IValueProxy;
import flare.util.Property;

/** A simple value proxy that uses Property instances to set and
 *  get values for input objects. */
class PropertyProxy implements IValueProxy
{
    public function setValue(o:Object, name:String, value:*):void
    {
        Property.$(name).setValue(o, value);
    }
    public function getValue(o:Object, name:String):*
    {
        return Property.$(name).getValue(o);
    }
    public function $(o:Object):Object
    {
        return o;
    }
}