/**
 * @author Jacky Nguyen <jacky@sencha.com>
 * @docauthor Jacky Nguyen <jacky@sencha.com>
 * @class Ext.Object
 *
 * A collection of useful static methods to deal with objects
 *
 * @singleton
 */


(function() {

var ExtObject = Ext.Object = {

    /**
     * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
     * query strings. For example:

    var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);

    // objects then equals:
    [
        { name: 'hobbies', value: 'reading' },
        { name: 'hobbies', value: 'cooking' },
        { name: 'hobbies', value: 'swimming' },
    ];

    var objects = Ext.Object.toQueryObjects('dateOfBirth', {
        day: 3,
        month: 8,
        year: 1987,
        extra: {
            hour: 4
            minute: 30
        }
    }, true); // Recursive

    // objects then equals:
    [
        { name: 'dateOfBirth[day]', value: 3 },
        { name: 'dateOfBirth[month]', value: 8 },
        { name: 'dateOfBirth[year]', value: 1987 },
        { name: 'dateOfBirth[extra][hour]', value: 4 },
        { name: 'dateOfBirth[extra][minute]', value: 30 },
    ];

     * @param {String} name
     * @param {Mixed} value
     * @param {Boolean} recursive
     * @markdown
     */

    toQueryObjects
: function(name, value, recursive) {
       
var self = ExtObject.toQueryObjects,
            objects
= [],
            i
, ln;

       
if (Ext.isArray(value)) {
           
for (i = 0, ln = value.length; i < ln; i++) {
               
if (recursive) {
                    objects
= objects.concat(self(name + '[' + i + ']', value[i], true));
               
}
               
else {
                    objects
.push({
                        name
: name,
                        value
: value[i]
                   
});
               
}
           
}
       
}
       
else if (Ext.isObject(value)) {
           
for (i in value) {
               
if (value.hasOwnProperty(i)) {
                   
if (recursive) {
                        objects
= objects.concat(self(name + '[' + i + ']', value[i], true));
                   
}
                   
else {
                        objects
.push({
                            name
: name,
                            value
: value[i]
                       
});
                   
}
               
}
           
}
       
}
       
else {
            objects
.push({
                name
: name,
                value
: value
           
});
       
}

       
return objects;
   
},

    /**
     * Takes an object and converts it to an encoded query string

- Non-recursive:

    Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
    Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
    Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
    Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
    Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"

- Recursive:

    Ext.Object.toQueryString({
        username: 'Jacky',
        dateOfBirth: {
            day: 1,
            month: 2,
            year: 1911
        },
        hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
    }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
              // username=Jacky
              //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
              //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff

     *
     * @param {Object} object The object to encode
     * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
     * (PHP / Ruby on Rails servers and similar). Defaults to false
     * @return {String} queryString
     * @markdown
     */

    toQueryString
: function(object, recursive) {
       
var paramObjects = [],
           
params = [],
            i
, j, ln, paramObject, value;

       
for (i in object) {
           
if (object.hasOwnProperty(i)) {
                paramObjects
= paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
           
}
       
}

       
for (j = 0, ln = paramObjects.length; j < ln; j++) {
            paramObject
= paramObjects[j];
            value
= paramObject.value;

           
if (Ext.isEmpty(value)) {
                value
= '';
           
}
           
else if (Ext.isDate(value)) {
                value
= Ext.Date.toString(value);
           
}

           
params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
       
}

       
return params.join('&');
   
},

    /**
     * Converts a query string back into an object.
     *
- Non-recursive:

    Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
    Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
    Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
    Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}

- Recursive:

    Ext.Object.fromQueryString("username=Jacky&dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff", true);

    // returns
    {
        username: 'Jacky',
        dateOfBirth: {
            day: '1',
            month: '2',
            year: '1911'
        },
        hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
    }

     * @param {String} queryString The query string to decode
     * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
     * PHP / Ruby on Rails servers and similar. Defaults to false
     * @return {Object}
     */

    fromQueryString
: function(queryString, recursive) {
       
var parts = queryString.replace(/^\?/, '').split('&'),
           
object = {},
            temp
, components, name, value, i, ln,
            part
, j, subLn, matchedKeys, matchedName,
            keys
, key, nextKey;

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

           
if (part.length > 0) {
                components
= part.split('=');
                name
= decodeURIComponent(components[0]);
                value
= (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';

               
if (!recursive) {
                   
if (object.hasOwnProperty(name)) {
                       
if (!Ext.isArray(object[name])) {
                           
object[name] = [object[name]];
                       
}

                       
object[name].push(value);
                   
}
                   
else {
                       
object[name] = value;
                   
}
               
}
               
else {
                    matchedKeys
= name.match(/(\[):?([^\]]*)\]/g);
                    matchedName
= name.match(/^([^\[]+)/);

                   
//<debug error>
                   
if (!matchedName) {
                       
Ext.Error.raise({
                            sourceClass
: "Ext.Object",
                            sourceMethod
: "fromQueryString",
                            queryString
: queryString,
                            recursive
: recursive,
                            msg
: 'Malformed query string given, failed parsing name from "' + part + '"'
                       
});
                   
}
                   
//</debug>

                    name
= matchedName[0];
                    keys
= [];

                   
if (matchedKeys === null) {
                       
object[name] = value;
                       
continue;
                   
}

                   
for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
                        key
= matchedKeys[j];
                        key
= (key.length === 2) ? '' : key.substring(1, key.length - 1);
                        keys
.push(key);
                   
}

                    keys
.unshift(name);

                    temp
= object;

                   
for (j = 0, subLn = keys.length; j < subLn; j++) {
                        key
= keys[j];

                       
if (j === subLn - 1) {
                           
if (Ext.isArray(temp) && key === '') {
                                temp
.push(value);
                           
}
                           
else {
                                temp
[key] = value;
                           
}
                       
}
                       
else {
                           
if (temp[key] === undefined || typeof temp[key] === 'string') {
                                nextKey
= keys[j+1];

                                temp
[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
                           
}

                            temp
= temp[key];
                       
}
                   
}
               
}
           
}
       
}

       
return object;
   
},

    /**
     * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
     * by returning `false` in the callback function. For example:

    var person = {
        name: 'Jacky'
        hairColor: 'black'
        loves: ['food', 'sleeping', 'wife']
    };

    Ext.Object.each(person, function(key, value, myself) {
        console.log(key + ":" + value);

        if (key === 'hairColor') {
            return false; // stop the iteration
        }
    });

     * @param {Object} object The object to iterate
     * @param {Function} fn The callback function. Passed arguments for each iteration are:

- {String} `key`
- {Mixed} `value`
- {Object} `object` The object itself

     * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
     * @markdown
     */

    each
: function(object, fn, scope) {
       
for (var property in object) {
           
if (object.hasOwnProperty(property)) {
               
if (fn.call(scope || object, property, object[property], object) === false) {
                   
return;
               
}
           
}
       
}
   
},

    /**
     * Merges any number of objects recursively without referencing them or their children.

    var extjs = {
        companyName: 'Ext JS',
        products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
        isSuperCool: true
        office: {
            size: 2000,
            location: 'Palo Alto',
            isFun: true
        }
    };

    var newStuff = {
        companyName: 'Sencha Inc.',
        products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
        office: {
            size: 40000,
            location: 'Redwood City'
        }
    };

    var sencha = Ext.Object.merge(extjs, newStuff);

    // extjs and sencha then equals to
    {
        companyName: 'Sencha Inc.',
        products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
        isSuperCool: true
        office: {
            size: 30000,
            location: 'Redwood City'
            isFun: true
        }
    }

     * @param {Object} object,...
     * @return {Object} merged The object that is created as a result of merging all the objects passed in.
     * @markdown
     */

    merge
: function(source, key, value) {
       
if (typeof key === 'string') {
           
if (value && value.constructor === Object) {
               
if (source[key] && source[key].constructor === Object) {
                   
ExtObject.merge(source[key], value);
               
}
               
else {
                    source
[key] = Ext.clone(value);
               
}
           
}
           
else {
                source
[key] = value;
           
}

           
return source;
       
}

       
var i = 1,
            ln
= arguments.length,
           
object, property;

       
for (; i < ln; i++) {
           
object = arguments[i];

           
for (property in object) {
               
if (object.hasOwnProperty(property)) {
                   
ExtObject.merge(source, property, object[property]);
               
}
           
}
       
}

       
return source;
   
},

    /**
     * Returns the first matching key corresponding to the given value.
     * If no matching value is found, null is returned.

    var person = {
        name: 'Jacky',
        loves: 'food'
    };

    alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'

     * @param {Object} object
     * @param {Object} value The value to find
     * @markdown
     */

    getKey
: function(object, value) {
       
for (var property in object) {
           
if (object.hasOwnProperty(property) && object[property] === value) {
               
return property;
           
}
       
}

       
return null;
   
},

    /**
     * Gets all values of the given object as an array.

    var values = Ext.Object.getValues({
        name: 'Jacky',
        loves: 'food'
    }); // ['Jacky', 'food']

     * @param {Object} object
     * @return {Array} An array of values from the object
     * @markdown
     */

    getValues
: function(object) {
       
var values = [],
            property
;

       
for (property in object) {
           
if (object.hasOwnProperty(property)) {
                values
.push(object[property]);
           
}
       
}

       
return values;
   
},

    /**
     * Gets all keys of the given object as an array.

    var values = Ext.Object.getKeys({
        name: 'Jacky',
        loves: 'food'
    }); // ['name', 'loves']

     * @param {Object} object
     * @return {Array} An array of keys from the object
     */

    getKeys
: ('keys' in Object.prototype) ? Object.keys : function(object) {
       
var keys = [],
            property
;

       
for (property in object) {
           
if (object.hasOwnProperty(property)) {
                keys
.push(property);
           
}
       
}

       
return keys;
   
},

    /**
     * Gets the total number of this object's own properties

    var size = Ext.Object.getSize({
        name: 'Jacky',
        loves: 'food'
    }); // size equals 2

     * @param {Object} object
     * @return {Number} size
     * @markdown
     */

    getSize
: function(object) {
       
var size = 0,
            property
;

       
for (property in object) {
           
if (object.hasOwnProperty(property)) {
                size
++;
           
}
       
}

       
return size;
   
}
};


/**
 * A convenient alias method for {@link Ext.Object#merge}
 *
 * @member Ext
 * @method merge
 */

Ext.merge = Ext.Object.merge;

/**
 * A convenient alias method for {@link Ext.Object#toQueryString}
 *
 * @member Ext
 * @method urlEncode
 * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
 */

Ext.urlEncode = function() {
   
var args = Ext.Array.from(arguments),
        prefix
= '';

   
// Support for the old `pre` argument
   
if ((typeof args[1] === 'string')) {
        prefix
= args[1] + '&';
        args
[1] = false;
   
}

   
return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
};

/**
 * A convenient alias method for {@link Ext.Object#fromQueryString}
 *
 * @member Ext
 * @method urlDecode
 * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
 */

Ext.urlDecode = function() {
   
return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
};

})();