/**
 * Simple JSON serializer.
 *
 * @requires javelin-install
 * @provides javelin-json
 * @javelin
 */

/**
 * JSON serializer and parser. This class uses the native JSON parser if it is
 * available; if not, it provides an eval-based parser and a simple serializer.
 *
 * NOTE: This class uses eval() on some systems, without sanitizing input. It is
 * not safe to use with untrusted data. Javelin does not provide a library
 * suitable for parsing untrusted JSON.
 *
 * Usage is straightforward:
 *
 *    JX.JSON.stringify({"bees":"knees"}); // Returns string: '{"bees":"knees"}'
 *    JX.JSON.parse('{"bees":"knees"}');   // Returns object: {"bees":"knees"}
 *
 * @task json      JSON Manipulation
 * @task internal  Internal
 * @group util
 */
JX.install('JSON', {
  statics : {


/* -(  JSON Manipulation  )-------------------------------------------------- */


    /**
     * Parse a **trusted** JSON string into an object. Accepts a valid JSON
     * string and returns the object it encodes.
     *
     * NOTE: This method does not sanitize input and uses an eval-based parser
     * on some systems. It is **NOT SAFE** to use with untrusted inputs.
     *
     * @param   string A valid, trusted JSON string.
     * @return  object The object encoded by the JSON string.
     * @task json
     */
    parse : function(data) {
      if (typeof data != 'string') {
        return null;
      }

      if (window.JSON && JSON.parse) {
        var obj;
        try {
          obj = JSON.parse(data);
        } catch (e) {}
        return obj || null;
      }

      return eval('(' + data + ')');
    },

    /**
     * Serialize an object into a JSON string. Accepts an object comprised of
     * maps, lists and scalars and transforms it into a JSON representation.
     * This method has undefined behavior if you pass in other complicated
     * things, e.g. object graphs containing cycles, document.body, or Date
     * objects.
     *
     * @param   object  An object comprised of maps, lists and scalars.
     * @return  string  JSON representation of the object.
     * @task json
     */
    stringify : function(val) {
      if (window.JSON && JSON.stringify) {
        return JSON.stringify(val);
      }

      var out = [];
      if (
        val === null || val === true || val === false || typeof val == 'number'
      ) {
        return '' + val;
      }

      if (val.push && val.pop) {
        var v;
        for (var ii = 0; ii < val.length; ii++) {

          // For consistency with JSON.stringify(), encode undefined array
          // indices as null.
          v = (typeof val[ii] == 'undefined') ? null : val[ii];

          out.push(JX.JSON.stringify(v));
        }
        return '[' + out.join(',') + ']';
      }

      if (typeof val == 'string') {
        return JX.JSON._esc(val);
      }

      for (var k in val) {
        out.push(JX.JSON._esc(k) + ':' + JX.JSON.stringify(val[k]));
      }
      return '{' + out.join(',') + '}';
    },


/* -(  Internal  )----------------------------------------------------------- */


    // Lifted more or less directly from Crockford's JSON2.
    _escexp : /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

    // List of control character escape codes.
    _meta : {
      '\b' : '\\b',
      '\t' : '\\t',
      '\n' : '\\n',
      '\f' : '\\f',
      '\r' : '\\r',
      '"'  : '\\"',
      '\\' : '\\\\'
    },

    /**
     * Quote and escape a string for inclusion in serialized JSON. Finds
     * characters in the string which need to be escaped and uses
     * @{method:_replace} to escape them.
     *
     * @param string Unescaped string.
     * @return string Escaped string.
     * @task internal
     */
    _esc : function(str) {
      JX.JSON._escexp.lastIndex = 0;
      return JX.JSON._escexp.test(str) ?
        '"' + str.replace(JX.JSON._escexp, JX.JSON._replace) + '"' :
        '"' + str + '"';
    },

    /**
     * Helper callback for @{method:_esc}, escapes characters which can't be
     * represented normally in serialized JSON.
     *
     * @param string Unescaped character.
     * @return string Escaped character.
     * @task internal
     */
    _replace : function(m) {
      if (m in JX.JSON._meta) {
        return JX.JSON._meta[m];
      }
      return '\\u' + (('0000' + m.charCodeAt(0).toString(16)).slice(-4));
    }
  }
});
