/**
 * @property {Boolean} [USE_NATIVE_JSON=true]
 * @member Ext
 * Indicates whether to use native browser parsing for JSON methods.
 * This option is ignored if the browser does not support native JSON methods.
 *
 * **Note:** Native JSON methods will not work with objects that have functions.
 * Also, property names must be quoted, otherwise the data will not parse.
 */
Ext.USE_NATIVE_JSON = true;
 
/**
 * Modified version of [Douglas Crockford's JSON.js][dc] that doesn't
 * mess with the Object prototype.
 *
 * [dc]: http://www.json.org/js.html
 *
 * @class Ext.JSON
 * @singleton
 */
Ext.JSON = (new(function() {
// @define Ext.JSON
// @require Ext
// @require Ext.Error
 
/* eslint-disable indent */
var me = this,
    hasNative = window.JSON && JSON.toString() === '[object JSON]',
    useHasOwn = !! {}.hasOwnProperty,
    pad = function(n) {
        return n < 10 ? "0" + n : n;
    },
    doDecode = function(json) {
        return eval("(" + json + ')');
    },
    doEncode = function(o, newline) {
        // http://jsperf.com/is-undefined
        if (=== null || o === undefined) {
            return "null";
        }
        else if (Ext.isDate(o)) {
            return me.encodeDate(o);
        }
        else if (Ext.isString(o)) {
            if (Ext.isMSDate(o)) {
               return me.encodeMSDate(o);
            }
            else {
                return me.encodeString(o);
            }
        }
        else if (typeof o === "number") {
            // don't use isNumber here, since finite checks happen inside isNumber
            return isFinite(o) ? String(o) : "null";
        }
        else if (Ext.isBoolean(o)) {
            return String(o);
        }
        // Allow custom serialization by adding a toJSON method to any object type.
        // Date/String have a toJSON in some environments, so check these first.
        else if (typeof o.toJSON === 'function') {
            return doEncode(o.toJSON());
        }
        else if (Ext.isArray(o)) {
            return encodeArray(o, newline);
        }
        else if (Ext.isObject(o)) {
            return encodeObject(o, newline);
        }
        else if (typeof o === "function") {
            return "null";
        }
        
        return 'undefined';
    },
    m = {
        "\b": '\\b',
        "\t": '\\t',
        "\n": '\\n',
        "\f": '\\f',
        "\r": '\\r',
        '"': '\\"',
        "\\": '\\\\',
        '\x0b': '\\u000b' // ie doesn't handle \v
    },
    /* eslint-disable-next-line no-control-regex, no-useless-escape */
    charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
    encodeString = function(s) {
        return '"' + s.replace(charToReplace, function(a) {
            var c = m[a];
            
            return typeof c === 'string'
                ? c
                : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"';
    },
    encodeMSDate = function(o) {
        return '"' + o + '"';
    },
 
    encodeArrayPretty = function(o, newline) {
        var len = o.length,
            cnewline = newline + '   ',
            sep = ',' + cnewline,
            a = ["[", cnewline], // Note newline in case there are no members
            i;
 
        for (= 0; i < len; i += 1) {
            a.push(me.encodeValue(o[i], cnewline), sep);
        }
 
        // Overwrite trailing comma (or empty string)
        a[a.length - 1] = newline + ']';
 
        return a.join('');
    },
 
    encodeObjectPretty = function(o, newline) {
        var cnewline = newline + '   ',
            sep = ',' + cnewline,
            a = ["{", cnewline], // Note newline in case there are no members
            i, val;
 
        for (in o) {
            val = o[i];
            
            if (!useHasOwn || o.hasOwnProperty(i)) {
                // To match JSON.stringify, we shouldn't encode functions or undefined
                if (typeof val === 'function' || val === undefined || val.isInstance) {
                    continue;
                }
                
                a.push(me.encodeValue(i) + '' + me.encodeValue(val, cnewline), sep);
            }
        }
 
        // Overwrite trailing comma (or empty string)
        a[a.length - 1] = newline + '}';
 
        return a.join('');
    },
 
    encodeArray = function(o, newline) {
        if (newline) {
            return encodeArrayPretty(o, newline);
        }
 
        /* eslint-disable-next-line vars-on-top */
        var a = ["[", ""], // Note empty string in case there are no serializable members.
            len = o.length,
            i;
        
        for (= 0; i < len; i += 1) {
            a.push(me.encodeValue(o[i]), ',');
        }
        
        // Overwrite trailing comma (or empty string)
        a[a.length - 1] = ']';
        
        return a.join("");
    },
 
    encodeObject = function(o, newline) {
        if (newline) {
            return encodeObjectPretty(o, newline);
        }
 
        /* eslint-disable-next-line vars-on-top */
        var a = ["{", ""], // Note empty string in case there are no serializable members.
            i, val;
        
        for (in o) {
            val = o[i];
            
            if (!useHasOwn || o.hasOwnProperty(i)) {
                // To match JSON.stringify, we shouldn't encode functions or undefined
                if (typeof val === 'function' || val === undefined) {
                    continue;
                }
                
                a.push(me.encodeValue(i), ":", me.encodeValue(val), ',');
            }
        }
        
        // Overwrite trailing comma (or empty string)
        a[a.length - 1] = '}';
        
        return a.join("");
    };
    
    /**
     * Encodes a String. This returns the actual string which is inserted into the JSON string
     * as the literal expression. **The returned value includes enclosing double quotation marks.**
     *
     * To override this:
     *
     *     Ext.JSON.encodeString = function(s) {
     *         return 'Foo' + s;
     *     };
     *
     * @param {String} s The String to encode
     * @return {String} The string literal to use in a JSON string.
     * @method encodeString
     */
    me.encodeString = encodeString;
 
    /**
     * The function which {@link #encode} uses to encode all javascript values to their JSON
     * representations when {@link Ext#USE_NATIVE_JSON} is `false`.
     * 
     * This is made public so that it can be replaced with a custom implementation.
     *
     * @param {Object} o Any javascript value to be converted to its JSON representation
     * @return {String} The JSON representation of the passed value.
     * @method encodeValue
     */
    me.encodeValue = doEncode;
 
    /**
     * Encodes a Date. This returns the actual string which is inserted into the JSON string
     * as the literal expression. **The returned value includes enclosing double quotation marks.**
     *
     * The default return format is `"yyyy-mm-ddThh:mm:ss"`.
     *
     * To override this:
     *
     *     Ext.JSON.encodeDate = function(d) {
     *         return Ext.Date.format(d, '"Y-m-d"');
     *     };
     *
     * @param {Date} o The Date to encode
     * @return {String} The string literal to use in a JSON string.
     */
    me.encodeDate = function(o) {
        return '"' + o.getFullYear() + "-" +
            pad(o.getMonth() + 1) + "-" +
            pad(o.getDate()) + "T" +
            pad(o.getHours()) + ":" +
            pad(o.getMinutes()) + ":" +
            pad(o.getSeconds()) + '"';
    };
 
    /**
     * Encodes an Object, Array or other value.
     * 
     * If the environment's native JSON encoding is not being used ({@link Ext#USE_NATIVE_JSON}
     * is not set, or the environment does not support it), then ExtJS's encoding will be used.
     * This allows the developer to add a `toJSON` method to their classes which need serializing
     * to return a valid JSON representation of the object.
     * 
     * @param {Object} o The variable to encode.
     * @return {String} The JSON string.
     */
    me.encode = function(o) {
        // check USE_NATIVE_JSON here so it can be changed if needed
        if (hasNative && Ext.USE_NATIVE_JSON) {
            return JSON.stringify(o);
        }
        
        return me.encodeValue(o);
    };
 
    /**
     * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws
     * a SyntaxError unless the safe option is set.
     *
     * @param {String} json The JSON string.
     * @param {Boolean} [safe=false] `true` to return null, otherwise throw an exception
     * if the JSON is invalid.
     * @return {Object} The resulting object.
     */
    me.decode = function(json, safe) {
        try {
            // check USE_NATIVE_JSON here so it can be changed if needed
            if (hasNative && Ext.USE_NATIVE_JSON) {
                return JSON.parse(json);
            }
            
            return 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
            });
        }
    };
 
    me.encodeMSDate = encodeMSDate;
 
    // Alias for backwards compatibility
    if (!Ext.util) {
        Ext.util = {};
    }
    
    Ext.util.JSON = me;
 
    /**
     * Shorthand for {@link Ext.JSON#encode}
     * @member Ext
     * @method encode
     * @inheritdoc Ext.JSON#encode
     */
    Ext.encode = me.encode;
 
    /**
     * Shorthand for {@link Ext.JSON#decode}
     * @member Ext
     * @method decode
     * @inheritdoc Ext.JSON#decode
     */
    Ext.decode = me.decode;
})());
nbsp;isLeft = c === '[';
                    isRight = c === ']';
 
                    special = isDot || isLeft || isRight || !c;
                    
                    // If either:
                    // a) Not a special char
                    // b) We're nested more than 1 deep, no single char can bring us out
                    // c) We are in an expr & it's not an ending brace
                    // Then just push the character on
                    if (!special || inExpr > 1 || (inExpr && !isRight)) {
                        part += c;
                    }
                    else if (special) {
                        bracketed = false;
                        
                        if (isLeft) {
                            ++inExpr;
                        }
                        else if (isRight) {
                            --inExpr;
                            bracketed = true;
                        }
 
                        if (part) {
                            if (bracketed) {
                                part = '[' + part + ']';
                            }
                            else {
                                part = '.' + part;
                            }
                            
                            current += part;
                            
                            // Concatting the empty string to the start fixes a very odd
                            // intermittent bug with IE9/10.
                            // On some occasions, without it, it will end up generating
                            // raw.foo.bar.baz && raw.foo.bar.baz && raw.foo.bar.baz
                            // At this point, not really sure why forcibly casting it to a string
                            // makes a difference
                            parts.push('' + current);
                            part = '';
                        }
                    }
                }
                
                result = parts.join(' && ');
                result = Ext.functionFactory('raw', 'return ' + result);
            }
            
            return result;
        };
    }()),
 
    /**
     * @private
     * @method
     * Returns an accessor function for the passed Field. Gives support for properties such as
     * the following:
     * 
     * - 'someProperty'
     * - 'some.property'
     * - '["someProperty"]'
     * - 'values[0]'
     * 
     * This is used by {@link #buildExtractors} to create optimized extractor expressions when
     * converting raw data into model instances. This method is used at the field level to
     * dynamically map values to model fields.
     */
    createFieldAccessor: function(field) {
        // Need to capture me for the extractor
        var me = this,
            mapping = field.mapping,
            hasMap = mapping || mapping === 0,
            map = hasMap ? mapping : field.name;
            
        if (hasMap) {
            if (typeof map === 'function') {
                return function(raw, self) {
                    return field.mapping(raw, self);
                };
            }
            else {
                return me.createAccessor(map);
            }
        }
    },
 
    getAccessorKey: function(prop) {
        var simple = this.getUseSimpleAccessors() ? 'simple' : '';
        
        return this.callParent([simple + prop]);
    },
 
    privates: {
        copyFrom: function(reader) {
            this.callParent([reader]);
            this.getRoot = reader.getRoot;
        },
 
        setupExtractor: function(prop, defaultFn) {
            return prop ? this.getAccessor(prop) : defaultFn;
        }
    }
});