package flare.util
{
    /**
     * Utility methods for working with arrays.
     */
    public class Arrays
    {
        public static const EMPTY:Array = new Array(0);
        
        /**
         * Constructor, throws an error if called, as this is an abstract class.
         */
        public function Arrays() {
            throw new ArgumentError("This is an abstract class.");
        }
        
        /**
         * Returns the maximum value in an array. Comparison is determined
         * using the greater-than operator against arbitrary types.
         * @param a the array
         * @param p an optional property from which to extract the value.
         *  If this is null, the immediate contents of the array are compared.
         * @return the maximum value
         */
        public static function max(a:Array, p:Property=null):Number
        {
            var x:Number = Number.MIN_VALUE;
            if (p) {
                var v:Number;
                for (var i:uint=0; i<a.length; ++i) {
                    v = p.getValue(a[i]);
                    if (v > x) x = v;
                }
            } else {
                for (i=0; i<a.length; ++i) {
                    if (a[i] > x) x = a[i];
                }
            }
            return x;
        }
        
        /**
         * Returns the index of a maximum value in an array. Comparison is
         * determined using the greater-than operator against arbitrary types.
         * @param a the array
         * @param p an optional property from which to extract the value.
         *  If this is null, the immediate contents of the array are compared.
         * @return the index of a maximum value
         */
        public static function maxIndex(a:Array, p:Property=null):Number
        {
            var x:Number = Number.MIN_VALUE;
            var idx:int = -1;
            
            if (p) {
                var v:Number;
                for (var i:uint=0; i<a.length; ++i) {
                    v = p.getValue(a[i]);
                    if (v > x) { x = v; idx = i; }
                }
            } else {
                for (i=0; i<a.length; ++i) {
                    if (a[i] > x) { x = a[i]; idx = i; }
                }
            }
            return idx;
        }
        
        /**
         * Returns the minimum value in an array. Comparison is determined
         * using the less-than operator against arbitrary types.
         * @param a the array
         * @param p an optional property from which to extract the value.
         *  If this is null, the immediate contents of the array are compared.
         * @return the minimum value
         */
        public static function min(a:Array, p:Property=null):Number
        {
            var x:Number = Number.MAX_VALUE;
            if (p) {
                var v:Number;
                for (var i:uint=0; i<a.length; ++i) {
                    v = p.getValue(a[i]);
                    if (v < x) x = v;
                }
            } else {
                for (i=0; i<a.length; ++i) {
                    if (a[i] < x) x = a[i];
                }
            }
            return x;
        }
        
        /**
         * Returns the index of a minimum value in an array. Comparison is
         * determined using the less-than operator against arbitrary types.
         * @param a the array
         * @param p an optional property from which to extract the value.
         *  If this is null, the immediate contents of the array are compared.
         * @return the index of a minimum value
         */
        public static function minIndex(a:Array, p:Property=null):Number
        {
            var x:Number = Number.MAX_VALUE, idx:int = -1;
            if (p) {
                var v:Number;
                for (var i:uint=0; i<a.length; ++i) {
                    v = p.getValue(a[i]);
                    if (v < x) { x = v; idx = i; }
                }
            } else {
                for (i=0; i<a.length; ++i) {
                    if (a[i] < x) { x = a[i]; idx = i; }
                }
            }
            return idx;
        }
        
        /**
         * Fills an array with a given value.
         * @param a the array
         * @param o the value with which to fill the array
         */
        public static function fill(a:Array, o:*) : void
        {
            for (var i:uint = 0; i<a.length; ++i) {
                a[i] = o;
            }
        }
        
        /**
         * Makes a copy of an array or copies the contents of one array to
         * another.
         * @param a the array to copy
         * @param b the array to copy values to. If null, a new array is
         *  created.
         * @param a0 the starting index from which to copy values
         *  of the input array
         * @param b0 the starting index at which to write value into the
         *  output array
         * @param len the number of values to copy
         * @return the target array containing the copied values
         */
        public static function copy(a:Array, b:Array=null, a0:int=0, b0:int=0, len:int=-1) : Array {
            len = (len < 0 ? a.length : len);
            if (b==null) {
                b = new Array(b0+len);
            } else {
                while (b.length < b0+len) b.push(null);
            }

            for (var i:uint = 0; i<len; ++i) {
                b[b0+i] = a[a0+i];
            }
            return b;
        }
        
        /**
         * Clears an array instance, removing all values.
         * @param a the array to clear
         */
        public static function clear(a:Array):void
        {
            while (a.length > 0) a.pop();
        }
                
        /**
         * Removes an element from an array. Only the first instance of the
         * value is removed.
         * @param a the array
         * @param o the value to remove
         * @return the index location at which the removed element was found,
         * negative if the value was not found.
         */
        public static function remove(a:Array, o:Object) : int {
            var idx:int = a.indexOf(o);
            if (idx == a.length-1) {
                a.pop();
            } else if (idx >= 0) {
                a.splice(idx, 1);
            }
            return idx;
        }
        
        /**
         * Removes the array element at the given index.
         * @param a the array
         * @param idx the index at which to remove an element
         * @return the removed element
         */
        public static function removeAt(a:Array, idx:uint) : Object {
            if (idx == a.length-1) {
                return a.pop();
            } else {
                var x:Object = a[idx];
                a.splice(idx,1);
                return x;
            }
        }
        
        /**
         * Performs a binary search over the input array for the given key
         * value, optionally using a provided property to extract from array
         * items and a custom comparison function.
         * @param a the array to search over
         * @param key the key value to search for
         * @param prop the property to retrieve from objecs in the array. If null
         *  (the default) the array values will be used directly.
         * @param cmp an optional comparison function
         * @return the index of the given key if it exists in the array,
         *  otherwise -1 times the index value at the insertion point that
         *  would be used if the key were added to the array.
         */
        public static function binarySearch(a:Array, key:Object,
            prop:String=null, cmp:Function=null) : int
        {
            var p:Property = prop ? Property.$(prop) : null;
            if (cmp == null)
                cmp = function(a:*,b:*):int {return a>b ? 1 : a<b ? -1 : 0;}
            
            var x1:int = 0, x2:int = a.length, i:int = (x2>>1);
            while (x1 < x2) {
                var c:int = cmp(p ? p.getValue(a[i]) : a[i], key);
                if (c == 0) {
                    return i;
                } else if (c < 0) {
                    x1 = i + 1;
                } else {
                    x2 = i;
                }
                i = x1 + ((x2 - x1)>>1);
            }
            return -1*(i+1);
        }
        
        /**
         * Sets a named property value for items stored in an array.
         * The value can take a number of forms:
         * <ul>
         *  <li>If the value is a <code>Function</code>, it will be evaluated
         *      for each element and the result will be used as the property
         *      value for that element.</li>
         *  <li>If the value is an <code>IEvaluable</code> instance, it will be
         *      evaluated for each element and the result will be used as the
         *      property value for that element.</li>
         *  <li>In all other cases, the property value will be treated as a
         *      literal and assigned for all elements.</li>
         * </ul>
         * @param list the array to set property values for
         * @param name the name of the property to set
         * @param value the value of the property to set
         * @param filter a filter function determining which items
         *  in the array should be processed
         * @param p an optional <code>IValueProxy</code> for setting the values
         */
        public static function setProperty(a:Array,
            name:String, value:*, filter:Function, p:IValueProxy=null):void
        {
            if (p == null) p = Property.proxy;
            var v:Function = value is Function ? value as Function :
                value is IEvaluable ? IEvaluable(value).eval : null;
            for each (var o:Object in a) if (filter==null || filter(o))
                p.setValue(o, name, v!=null ? v(p.$(o)) : value);
        }

    } // end of class Arrays
}