//@tag foundation,core
//@define Ext.JSON
//@require Ext.Function

/**
 * @class Ext.JSON
 * ダグラス・クロックフォードによるjson.jsの修正版。Objectプロトタイプには変更なし。[http://www.json.org/js.html](http://www.json.org/js.html)
 * @singleton
 */
Ext.JSON = new(function() {
    var useHasOwn = !! {}.hasOwnProperty,
    isNative = function() {
        var useNative = null;

        return function() {
            if (useNative === null) {
                useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
            }

            return useNative;
        };
    }(),
    pad = function(n) {
        return n < 10 ? "0" + n : n;
    },
    doDecode = function(json) {
        return eval("(" + json + ')');
    },
    doEncode = function(o) {
        if (!Ext.isDefined(o) || o === null) {
            return "null";
        } else if (Ext.isArray(o)) {
            return encodeArray(o);
        } else if (Ext.isDate(o)) {
            return Ext.JSON.encodeDate(o);
        } else if (Ext.isString(o)) {
            if (Ext.isMSDate(o)) {
               return encodeMSDate(o);
            } else {
                return 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);
        } else if (Ext.isObject(o)) {
            return encodeObject(o);
        } 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
    },
    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);
        }) + '"';
    },
    encodeArray = function(o) {
        var a = ["[", ""],
        // Note empty string in case there are no serializable members.
        len = o.length,
        i;
        for (i = 0; i < len; i += 1) {
            a.push(doEncode(o[i]), ',');
        }
        // Overwrite trailing comma (or empty string)
        a[a.length - 1] = ']';
        return a.join("");
    },
    encodeObject = function(o) {
        var a = ["{", ""],
        // Note empty string in case there are no serializable members.
        i;
        for (i in o) {
            if (!useHasOwn || o.hasOwnProperty(i)) {
                a.push(doEncode(i), ":", doEncode(o[i]), ',');
            }
        }
        // Overwrite trailing comma (or empty string)
        a[a.length - 1] = '}';
        return a.join("");
    },
    encodeMSDate = function(o) {
        return '"' + o + '"';
    };

    /**
     * 日付をエンコードします。これはリテラル表現としてJSON文字列に挿入される実際の文字列を返します。返される値はダブルクォーテーションで囲まれます。
     *
     * デフォルトの戻り値のフォーマットは"yyyy-mm-ddThh:mm:ss"です。
     * 
     * これをオーバライドするには:
     *
     *     Ext.JSON.encodeDate = function(d) {
     *         return Ext.Date.format(d, '"Y-m-d"');
     *     };
     *
     * @param {Date} d エンコードされる日付。
     * @return {String} JSON文字列で使用する文字列リテラル。
     */
    this.encodeDate = function(o) {
        return '"' + o.getFullYear() + "-" 
        + pad(o.getMonth() + 1) + "-"
        + pad(o.getDate()) + "T"
        + pad(o.getHours()) + ":"
        + pad(o.getMinutes()) + ":"
        + pad(o.getSeconds()) + '"';
    };

    /**
     * オブジェクトや配列、その他の値をエンコードします。
     * @param {Object} o エンコードする変数
     * @return {String} JSON文字列
     * @method
     */
    this.encode = function() {
        var ec;
        return function(o) {
            if (!ec) {
                // setup encoding function on first access
                ec = isNative() ? JSON.stringify : doEncode;
            }
            return ec(o);
        };
    }();


    /**
     * JSON文字列をオブジェクトにデコード(パース)します。JSONが無効なものならば、safeオプションを設定していない場合、関数はErrorをスローします。
     * @param {String} json JSON文字列
     * @param {Boolean} safe (optional) JSONが有効でない場合に、`null`を返すか、それとも、例外をスローするか。
     * @return {Object/null} 結果のオブジェクト
     * @method
     */
    this.decode = function() {
        var dc;
        return function(json, safe) {
            if (!dc) {
                // setup decoding function on first access
                dc = isNative() ? JSON.parse : doDecode;
            }
            try {
                return dc(json);
            } catch (e) {
                if (safe === true) {
                    return null;
                }
                Ext.Error.raise({
                    sourceClass: "Ext.JSON",
                    sourceMethod: "decode",
                    msg: "You're trying to decode an invalid JSON String: " + json
                });
            }
        };
    }();

})();
/**
 * {@link Ext.JSON#encode}の短縮形です。
 * @member Ext
 * @method encode
 * @alias Ext.JSON#encode
 */
Ext.encode = Ext.JSON.encode;
/**
 * {@link Ext.JSON#decode}の短縮形です。
 * @member Ext
 * @method decode
 * @alias Ext.JSON#decode
 */
Ext.decode = Ext.JSON.decode;