/**
 * @author Jacky Nguyen <jacky@sencha.com>
 * @docauthor Jacky Nguyen <jacky@sencha.com>
 * @class Ext.Array
 *
 * A set of useful static methods to deal with arrays; provide missing methods for older browsers.

 * @singleton
 * @markdown
 */

(function() {

   
var arrayPrototype = Array.prototype,
        slice
= arrayPrototype.slice,
        supportsForEach
= 'forEach' in arrayPrototype,
        supportsMap
= 'map' in arrayPrototype,
        supportsIndexOf
= 'indexOf' in arrayPrototype,
        supportsEvery
= 'every' in arrayPrototype,
        supportsSome
= 'some' in arrayPrototype,
        supportsFilter
= 'filter' in arrayPrototype,
        supportsSort
= function() {
           
var a = [1,2,3,4,5].sort(function(){ return 0; });
           
return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
       
}(),
        supportsSliceOnNodeList
= true,
       
ExtArray;
   
try {
       
// IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
       
if (typeof document !== 'undefined') {
            slice
.call(document.getElementsByTagName('body'));
       
}
   
} catch (e) {
        supportsSliceOnNodeList
= false;
   
}

   
ExtArray = Ext.Array = {
       
/*
         * Iterates an array or an iterable value and invoke the given callback function for each item.

    var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];

    Ext.Array.each(countries, function(name, index, countriesItSelf) {
        console.log(name);
    });

    var sum = function() {
        var sum = 0;

        Ext.Array.each(arguments, function(value) {
            sum += value;
        });

        return sum;
    };

    sum(1, 2, 3); // returns 6

         * The iteration can be stopped by returning false in the function callback.

    Ext.Array.each(countries, function(name, index, countriesItSelf) {
        if (name === 'Singapore') {
            return false; // break here
        }
    });

         * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
         * argument is not iterable, the callback function is called once.
         * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
         * the current `index`. Arguments passed to this callback function are:

- `item`: {Mixed} The item at the current `index` in the passed `array`
- `index`: {Number} The current `index` within the `array`
- `allItems`: {Array/NodeList/Mixed} The `array` passed as the first argument to `Ext.Array.each`

         * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
         * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
         * Defaults false
         * @return {Boolean} See description for the `fn` parameter.
         * @markdown
         */

        each
: function(array, fn, scope, reverse) {
            array
= ExtArray.from(array);

           
var i,
                ln
= array.length;

           
if (reverse !== true) {
               
for (i = 0; i < ln; i++) {
                   
if (fn.call(scope || array[i], array[i], i, array) === false) {
                       
return i;
                   
}
               
}
           
}
           
else {
               
for (i = ln - 1; i > -1; i--) {
                   
if (fn.call(scope || array[i], array[i], i, array) === false) {
                       
return i;
                   
}
               
}
           
}

           
return true;
       
},

        /**
         * Iterates an array and invoke the given callback function for each item. Note that this will simply
         * delegate to the native Array.prototype.forEach method if supported.
         * It doesn't support stopping the iteration by returning false in the callback function like
         * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
         * {@link Ext.Array#each}
         *
         * @param {Array} array The array to iterate
         * @param {Function} fn The function callback, to be invoked these arguments:
         *
- `item`: {Mixed} The item at the current `index` in the passed `array`
- `index`: {Number} The current `index` within the `array`
- `allItems`: {Array} The `array` itself which was passed as the first argument

         * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
         * @markdown
         */

        forEach
: function(array, fn, scope) {
           
if (supportsForEach) {
               
return array.forEach(fn, scope);
           
}

           
var i = 0,
                ln
= array.length;

           
for (; i < ln; i++) {
                fn
.call(scope, array[i], i, array);
           
}
       
},

        /**
         * Get the index of the provided `item` in the given `array`, a supplement for the
         * missing arrayPrototype.indexOf in Internet Explorer.
         *
         * @param {Array} array The array to check
         * @param {Mixed} item The item to look for
         * @param {Number} from (Optional) The index at which to begin the search
         * @return {Number} The index of item in the array (or -1 if it is not found)
         * @markdown
         */

        indexOf
: function(array, item, from) {
           
if (supportsIndexOf) {
               
return array.indexOf(item, from);
           
}

           
var i, length = array.length;

           
for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
               
if (array[i] === item) {
                   
return i;
               
}
           
}

           
return -1;
       
},

        /**
         * Checks whether or not the given `array` contains the specified `item`
         *
         * @param {Array} array The array to check
         * @param {Mixed} item The item to look for
         * @return {Boolean} True if the array contains the item, false otherwise
         * @markdown
         */

        contains
: function(array, item) {
           
if (supportsIndexOf) {
               
return array.indexOf(item) !== -1;
           
}

           
var i, ln;

           
for (i = 0, ln = array.length; i < ln; i++) {
               
if (array[i] === item) {
                   
return true;
               
}
           
}

           
return false;
       
},

        /**
         * Converts any iterable (numeric indices and a length property) into a true array.

function test() {
    var args = Ext.Array.toArray(arguments),
        fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);

    alert(args.join(' '));
    alert(fromSecondToLastArgs.join(' '));
}

test('just', 'testing', 'here'); // alerts 'just testing here';
                                 // alerts 'testing here';

Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']

         * @param {Mixed} iterable the iterable object to be turned into a true Array.
         * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
         * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
         * index of the iterable value
         * @return {Array} array
         * @markdown
         */

        toArray
: function(iterable, start, end){
           
if (!iterable || !iterable.length) {
               
return [];
           
}

           
if (typeof iterable === 'string') {
                iterable
= iterable.split('');
           
}

           
if (supportsSliceOnNodeList) {
               
return slice.call(iterable, start || 0, end || iterable.length);
           
}

           
var array = [],
                i
;

            start
= start || 0;
           
end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;

           
for (i = start; i < end; i++) {
                array
.push(iterable[i]);
           
}

           
return array;
       
},

        /**
         * Plucks the value of a property from each item in the Array. Example:
         *
    Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]

         * @param {Array|NodeList} array The Array of items to pluck the value from.
         * @param {String} propertyName The property name to pluck from each element.
         * @return {Array} The value from each item in the Array.
         */

        pluck
: function(array, propertyName) {
           
var ret = [],
                i
, ln, item;

           
for (i = 0, ln = array.length; i < ln; i++) {
                item
= array[i];

                ret
.push(item[propertyName]);
           
}

           
return ret;
       
},

        /**
         * Creates a new array with the results of calling a provided function on every element in this array.
         * @param {Array} array
         * @param {Function} fn Callback function for each item
         * @param {Object} scope Callback function scope
         * @return {Array} results
         */

        map
: function(array, fn, scope) {
           
if (supportsMap) {
               
return array.map(fn, scope);
           
}

           
var results = [],
                i
= 0,
                len
= array.length;

           
for (; i < len; i++) {
                results
[i] = fn.call(scope, array[i], i, array);
           
}

           
return results;
       
},

        /**
         * Executes the specified function for each array element until the function returns a falsy value.
         * If such an item is found, the function will return false immediately.
         * Otherwise, it will return true.
         *
         * @param {Array} array
         * @param {Function} fn Callback function for each item
         * @param {Object} scope Callback function scope
         * @return {Boolean} True if no false value is returned by the callback function.
         */

        every
: function(array, fn, scope) {
           
//<debug>
           
if (!fn) {
               
Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
           
}
           
//</debug>
           
if (supportsEvery) {
               
return array.every(fn, scope);
           
}

           
var i = 0,
                ln
= array.length;

           
for (; i < ln; ++i) {
               
if (!fn.call(scope, array[i], i, array)) {
                   
return false;
               
}
           
}

           
return true;
       
},

        /**
         * Executes the specified function for each array element until the function returns a truthy value.
         * If such an item is found, the function will return true immediately. Otherwise, it will return false.
         *
         * @param {Array} array
         * @param {Function} fn Callback function for each item
         * @param {Object} scope Callback function scope
         * @return {Boolean} True if the callback function returns a truthy value.
         */

        some
: function(array, fn, scope) {
           
//<debug>
           
if (!fn) {
               
Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
           
}
           
//</debug>
           
if (supportsSome) {
               
return array.some(fn, scope);
           
}

           
var i = 0,
                ln
= array.length;

           
for (; i < ln; ++i) {
               
if (fn.call(scope, array[i], i, array)) {
                   
return true;
               
}
           
}

           
return false;
       
},

        /**
         * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
         *
         * @see Ext.Array.filter
         * @param {Array} array
         * @return {Array} results
         */

        clean
: function(array) {
           
var results = [],
                i
= 0,
                ln
= array.length,
                item
;

           
for (; i < ln; i++) {
                item
= array[i];

               
if (!Ext.isEmpty(item)) {
                    results
.push(item);
               
}
           
}

           
return results;
       
},

        /**
         * Returns a new array with unique items
         *
         * @param {Array} array
         * @return {Array} results
         */

        unique
: function(array) {
           
var clone = [],
                i
= 0,
                ln
= array.length,
                item
;

           
for (; i < ln; i++) {
                item
= array[i];

               
if (ExtArray.indexOf(clone, item) === -1) {
                    clone
.push(item);
               
}
           
}

           
return clone;
       
},

        /**
         * Creates a new array with all of the elements of this array for which
         * the provided filtering function returns true.
         * @param {Array} array
         * @param {Function} fn Callback function for each item
         * @param {Object} scope Callback function scope
         * @return {Array} results
         */

        filter
: function(array, fn, scope) {
           
if (supportsFilter) {
               
return array.filter(fn, scope);
           
}

           
var results = [],
                i
= 0,
                ln
= array.length;

           
for (; i < ln; i++) {
               
if (fn.call(scope, array[i], i, array)) {
                    results
.push(array[i]);
               
}
           
}

           
return results;
       
},

        /**
         * Converts a value to an array if it's not already an array; returns:
         *
         * - An empty array if given value is `undefined` or `null`
         * - Itself if given value is already an array
         * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
         * - An array with one item which is the given value, otherwise
         *
         * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
         * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
         * defaults to false
         * @return {Array} array
         * @markdown
         */

       
from: function(value, newReference) {
           
if (value === undefined || value === null) {
               
return [];
           
}

           
if (Ext.isArray(value)) {
               
return (newReference) ? slice.call(value) : value;
           
}

           
if (value && value.length !== undefined && typeof value !== 'string') {
               
return Ext.toArray(value);
           
}

           
return [value];
       
},

        /**
         * Removes the specified item from the array if it exists
         *
         * @param {Array} array The array
         * @param {Mixed} item The item to remove
         * @return {Array} The passed array itself
         */

        remove
: function(array, item) {
           
var index = ExtArray.indexOf(array, item);

           
if (index !== -1) {
                array
.splice(index, 1);
           
}

           
return array;
       
},

        /**
         * Push an item into the array only if the array doesn't contain it yet
         *
         * @param {Array} array The array
         * @param {Mixed} item The item to include
         * @return {Array} The passed array itself
         */

        include
: function(array, item) {
           
if (!ExtArray.contains(array, item)) {
                array
.push(item);
           
}
       
},

        /**
         * Clone a flat array without referencing the previous one. Note that this is different
         * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
         * for Array.prototype.slice.call(array)
         *
         * @param {Array} array The array
         * @return {Array} The clone array
         */

        clone
: function(array) {
           
return slice.call(array);
       
},

        /**
         * Merge multiple arrays into one with unique items. Alias to {@link Ext.Array#union}.
         *
         * @param {Array} array,...
         * @return {Array} merged
         */

        merge
: function() {
           
var args = slice.call(arguments),
                array
= [],
                i
, ln;

           
for (i = 0, ln = args.length; i < ln; i++) {
                array
= array.concat(args[i]);
           
}

           
return ExtArray.unique(array);
       
},

        /**
         * Merge multiple arrays into one with unique items that exist in all of the arrays.
         *
         * @param {Array} array,...
         * @return {Array} intersect
         */

        intersect
: function() {
           
var intersect = [],
                arrays
= slice.call(arguments),
                i
, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;

           
if (!arrays.length) {
               
return intersect;
           
}

           
// Find the smallest array
           
for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
               
if (!minArray || array.length < minArray.length) {
                    minArray
= array;
                    x
= i;
               
}
           
}

            minArray
= Ext.Array.unique(minArray);
            arrays
.splice(x, 1);

           
// Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
           
// an item in the small array, we're likely to find it before reaching the end
           
// of the inner loop and can terminate the search early.
           
for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
               
var count = 0;

               
for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
                   
for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
                       
if (x === y) {
                            count
++;
                           
break;
                       
}
                   
}
               
}

               
if (count === arraysLn) {
                    intersect
.push(x);
               
}
           
}

           
return intersect;
       
},

        /**
         * Perform a set difference A-B by subtracting all items in array B from array A.
         *
         * @param {Array} array A
         * @param {Array} array B
         * @return {Array} difference
         */

        difference
: function(arrayA, arrayB) {
           
var clone = slice.call(arrayA),
                ln
= clone.length,
                i
, j, lnB;

           
for (i = 0,lnB = arrayB.length; i < lnB; i++) {
               
for (j = 0; j < ln; j++) {
                   
if (clone[j] === arrayB[i]) {
                        clone
.splice(j, 1);
                        j
--;
                        ln
--;
                   
}
               
}
           
}

           
return clone;
       
},

        /**
         * Sorts the elements of an Array.
         * By default, this method sorts the elements alphabetically and ascending.
         *
         * @param {Array} array The array to sort.
         * @param {Function} sortFn (optional) The comparison function.
         * @return {Array} The sorted array.
         */

        sort
: function(array, sortFn) {
           
if (supportsSort) {
               
if (sortFn) {
                   
return array.sort(sortFn);
               
} else {
                   
return array.sort();
               
}
           
}

           
var length = array.length,
                i
= 0,
                comparison
,
                j
, min, tmp;

           
for (; i < length; i++) {
                min
= i;
               
for (j = i + 1; j < length; j++) {
                   
if (sortFn) {
                        comparison
= sortFn(array[j], array[min]);
                       
if (comparison < 0) {
                            min
= j;
                       
}
                   
} else if (array[j] < array[min]) {
                        min
= j;
                   
}
               
}
               
if (min !== i) {
                    tmp
= array[i];
                    array
[i] = array[min];
                    array
[min] = tmp;
               
}
           
}

           
return array;
       
},

        /**
         * Recursively flattens into 1-d Array. Injects Arrays inline.
         * @param {Array} array The array to flatten
         * @return {Array} The new, flattened array.
         */

        flatten
: function(array) {
           
var worker = [];

           
function rFlatten(a) {
               
var i, ln, v;

               
for (i = 0, ln = a.length; i < ln; i++) {
                    v
= a[i];

                   
if (Ext.isArray(v)) {
                        rFlatten
(v);
                   
} else {
                        worker
.push(v);
                   
}
               
}

               
return worker;
           
}

           
return rFlatten(array);
       
},

        /**
         * Returns the minimum value in the Array.
         * @param {Array|NodeList} array The Array from which to select the minimum value.
         * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
         *                   If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
         * @return {Mixed} minValue The minimum value
         */

        min
: function(array, comparisonFn) {
           
var min = array[0],
                i
, ln, item;

           
for (i = 0, ln = array.length; i < ln; i++) {
                item
= array[i];

               
if (comparisonFn) {
                   
if (comparisonFn(min, item) === 1) {
                        min
= item;
                   
}
               
}
               
else {
                   
if (item < min) {
                        min
= item;
                   
}
               
}
           
}

           
return min;
       
},

        /**
         * Returns the maximum value in the Array
         * @param {Array|NodeList} array The Array from which to select the maximum value.
         * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
         *                   If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
         * @return {Mixed} maxValue The maximum value
         */

        max
: function(array, comparisonFn) {
           
var max = array[0],
                i
, ln, item;

           
for (i = 0, ln = array.length; i < ln; i++) {
                item
= array[i];

               
if (comparisonFn) {
                   
if (comparisonFn(max, item) === -1) {
                        max
= item;
                   
}
               
}
               
else {
                   
if (item > max) {
                        max
= item;
                   
}
               
}
           
}

           
return max;
       
},

        /**
         * Calculates the mean of all items in the array
         * @param {Array} array The Array to calculate the mean value of.
         * @return {Number} The mean.
         */

        mean
: function(array) {
           
return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
       
},

        /**
         * Calculates the sum of all items in the given array
         * @param {Array} array The Array to calculate the sum value of.
         * @return {Number} The sum.
         */

        sum
: function(array) {
           
var sum = 0,
                i
, ln, item;

           
for (i = 0,ln = array.length; i < ln; i++) {
                item
= array[i];

                sum
+= item;
           
}

           
return sum;
       
}

   
};

    /**
     * Convenient alias to {@link Ext.Array#each}
     * @member Ext
     * @method each
     */

   
Ext.each = Ext.Array.each;

    /**
     * Alias to {@link Ext.Array#merge}.
     * @member Ext.Array
     * @method union
     */

   
Ext.Array.union = Ext.Array.merge;

    /**
     * Old alias to {@link Ext.Array#min}
     * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
     * @member Ext
     * @method min
     */

   
Ext.min = Ext.Array.min;

    /**
     * Old alias to {@link Ext.Array#max}
     * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
     * @member Ext
     * @method max
     */

   
Ext.max = Ext.Array.max;

    /**
     * Old alias to {@link Ext.Array#sum}
     * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
     * @member Ext
     * @method sum
     */

   
Ext.sum = Ext.Array.sum;

    /**
     * Old alias to {@link Ext.Array#mean}
     * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
     * @member Ext
     * @method mean
     */

   
Ext.mean = Ext.Array.mean;

    /**
     * Old alias to {@link Ext.Array#flatten}
     * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
     * @member Ext
     * @method flatten
     */

   
Ext.flatten = Ext.Array.flatten;

    /**
     * Old alias to {@link Ext.Array#clean Ext.Array.clean}
     * @deprecated 4.0.0 Use {@link Ext.Array.clean} instead
     * @member Ext
     * @method clean
     */

   
Ext.clean = Ext.Array.clean;

    /**
     * Old alias to {@link Ext.Array#unique Ext.Array.unique}
     * @deprecated 4.0.0 Use {@link Ext.Array.unique} instead
     * @member Ext
     * @method unique
     */

   
Ext.unique = Ext.Array.unique;

    /**
     * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
     * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
     * @member Ext
     * @method pluck
     */

   
Ext.pluck = Ext.Array.pluck;

    /**
     * Convenient alias to {@link Ext.Array#toArray Ext.Array.toArray}
     * @param {Iterable} the iterable object to be turned into a true Array.
     * @member Ext
     * @method toArray
     * @return {Array} array
     */

   
Ext.toArray = function() {
       
return ExtArray.toArray.apply(ExtArray, arguments);
   
}
})();