
var enumerables = [
    //'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
    'valueOf',
    'toLocaleString',
    'toString',
    'constructor'
];
var objectPrototype = Object.prototype;
var toString = objectPrototype.toString;
var arrayPrototype = Array.prototype;
var slice = arrayPrototype.slice;
var supportsIndexOf = 'indexOf' in arrayPrototype;
var supportsSliceOnNodeList = true;

var Ext = Ext || {
    enumerables: enumerables,
    doDecode: function(json) {
        return eval("(" + json + ')');
    },
    USE_NATIVE_JSON: false,
    clone: function(item, cloneDom) {
        if (item == null) {
            return item;
        }
        // DOM nodes
        // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
        // recursively
        if (cloneDom !== false && item.nodeType && item.cloneNode) {
            return item.cloneNode(true);
        }
        var type = toString.call(item),
            i, j, k, clone, key;
        // Date
        if (type === '[object Date]') {
            return new Date(item.getTime());
        }
        // Array
        if (type === '[object Array]') {
            i = item.length;
            clone = [];
            while (i--) {
                clone[i] = Ext.clone(item[i], cloneDom);
            }
        }
        // Object
        else if (type === '[object Object]' && item.constructor === Object) {
            clone = {};
            for (key in item) {
                clone[key] = Ext.clone(item[key], cloneDom);
            }
            if (enumerables) {
                for (j = enumerables.length; j--; ) {
                    k = enumerables[j];
                    if (item.hasOwnProperty(k)) {
                        clone[k] = item[k];
                    }
                }
            }
        }
        return clone || item;
    },
    decode: function(json, safe) {
        try {
            // check USE_NATIVE_JSON here so it can be changed if needed
            if (Ext.USE_NATIVE_JSON) {
                return JSON.parse(json);
            }
            return this.doDecode(json);
        } catch (e) {
            if (safe) {
                return null;
            }
            Ext.raise({
                sourceClass: "Ext.JSON",
                sourceMethod: "decode",
                msg: "You're trying to decode an invalid JSON String: " + json
            });
        }
    },
    Object: {
        merge: function(destination) {
            var i = 1,
                args = arguments,
                ln = args.length,
                mergeFn = Ext.Object.merge,
                cloneFn = Ext.clone,
                object, key, value, sourceKey;
            for (; i < ln; i++) {
                object = args[i];
                for (key in object) {
                    value = object[key];
                    if (value && value.constructor === Object) {
                        sourceKey = destination[key];
                        if (sourceKey && sourceKey.constructor === Object) {
                            mergeFn(sourceKey, value);
                        } else {
                            destination[key] = cloneFn(value);
                        }
                    } else {
                        destination[key] = value;
                    }
                }
            }
            return destination;
        },
        mergeIf: function(destination) {
            var i = 1,
                ln = arguments.length,
                cloneFn = Ext.clone,
                object, key, value;
            for (; i < ln; i++) {
                object = arguments[i];
                for (key in object) {
                    if (!(key in destination)) {
                        value = object[key];
                        if (value && value.constructor === Object) {
                            destination[key] = cloneFn(value);
                        } else {
                            destination[key] = value;
                        }
                    }
                }
            }
            return destination;
        },
        each: function(object, fn, scope) {
            var enumerables = Ext.enumerables,
                i, property;
            if (object) {
                scope = scope || object;
                for (property in object) {
                    if (object.hasOwnProperty(property)) {
                        if (fn.call(scope, property, object[property], object) === false) {
                            return;
                        }
                    }
                }
                if (enumerables) {
                    for (i = enumerables.length; i--; ) {
                        if (object.hasOwnProperty(property = enumerables[i])) {
                            if (fn.call(scope, property, object[property], object) === false) {
                                return;
                            }
                        }
                    }
                }
            }
        }
    },
    Assert: {
        isFunction: // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
        // Object.prototype.toString (slower)
        (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
            return !!value && toString.call(value) === '[object Function]';
        } : function(value) {
            return !!value && typeof value === 'function';
        }
    },
    Array: {
        filter: ('filter' in arrayPrototype) ? function(array, fn, scope) {
            Ext.Assert.isFunction(fn, 'Ext.Array.filter must have a filter function passed as second argument.');
            return array.filter(fn, scope);
        } : function(array, fn, scope) {
            Ext.Assert.isFunction(fn, 'Ext.Array.filter must have a filter function passed as second argument.');
            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;
        },
        each: function(array, fn, scope, reverse) {
            array = Ext.Array.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;
        },
        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;
        },
        findBy: function(array, fn, scope) {
            var i = 0,
                len = array.length;
            for (; i < len; i++) {
                if (fn.call(scope || array, array[i], i)) {
                    return array[i];
                }
            }
            return null;
        },
        from: function(value, newReference) {
            if (value === undefined || value === null) {
                return [];
            }
            if (Ext.isArray(value)) {
                return (newReference) ? slice.call(value) : value;
            }
            var type = typeof value;
            // Both strings and functions will have a length property. In phantomJS, NodeList
            // instances report typeof=='function' but don't have an apply method...
            if (value && value.length !== undefined && type !== 'string' && (type !== 'function' || !value.apply)) {
                return Ext.Array.toArray(value);
            }
            return [
                value
            ];
        },
        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 Ext.Array.unique(array);
        },
        unique: function(array) {
            var clone = [],
                i = 0,
                ln = array.length,
                item;
            for (; i < ln; i++) {
                item = array[i];
                if (Ext.Array.indexOf(clone, item) === -1) {
                    clone.push(item);
                }
            }
            return clone;
        },
        indexOf: supportsIndexOf ? function(array, item, from) {
            // May be called with no array which causes an error.
            return array ? arrayPrototype.indexOf.call(array, item, from) : -1;
        } : function(array, item, from) {
            var i,
                length = array ? array.length : 0;
            for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
                if (array[i] === item) {
                    return i;
                }
            }
            return -1;
        },
        clone: function(array) {
            return slice.call(array);
        }
        
    },
    isArray: ('isArray' in Array) ? Array.isArray : function(value) {
        return toString.call(value) === '[object Array]';
    },
    isObject: (toString.call(null) === '[object Object]') ? function(value) {
    // check ownerDocument here as well to exclude DOM nodes
        return value != null && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
    } : function(value) {
        return toString.call(value) === '[object Object]';
    },
    isPrimitive: function(value) {
        var type = typeof value;
        return type === 'string' || type === 'number' || type === 'boolean';
    },
    isString: function(value) {
        return typeof value === 'string';
    },
    apply: function(object, config, defaults) {
        if (object) {
            if (defaults) {
                Ext.apply(object, defaults);
            }
            if (config && typeof config === 'object') {
                var i, j, k;
                for (i in config) {
                    object[i] = config[i];
                }
                if (enumerables) {
                    for (j = enumerables.length; j--; ) {
                        k = enumerables[j];
                        if (config.hasOwnProperty(k)) {
                            object[k] = config[k];
                        }
                    }
                }
            }
        }
        return object;
    }
};