/*

  An experiment in JSON
  
  Copyright (c) 2007 Borgar Thorsteinsson (http://borgar.undraland.com/)

  You are free to use this under the terms of the MIT License
  http://en.wikipedia.org/wiki/MIT_License

  What this is:
  
    This is written to test the idea that given that Javascript is single threaded, 
    it might be possible to extend native objects (such as Object.prototype) temporarily
    while processing goes on. Then we simply remove the added prototype members and move on.
    
    While it seems to work fine, it hasn't been tested under stress conditions. I also have no 
    idea what sort of performance it gives.

  Tested:
    
    - Firefox 2.0.0.9 (mac)
    - Opera 9.23 (mac)
    - Safari 3.0.4 (mac)
    - MSIE 6 (win)

  Further info on why you shouldn't touch Object.prototype:
    
    http://erik.eae.net/archives/2005/06/06/22.13.54
    http://dean.edwards.name/weblog/2007/03/rules/
  
  Further info on JSON:

    http://www.json.org/
    http://www.json.org/js.html
    http://www.json.org/json2.js

*/
var JSON = (function () {

  //* private *//
  
  var _escapeChars = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' },

  // handles native strings or those passed in as arguments
  _string = function (str) 
  {
    str = (arguments.length > 0) ? ((typeof(str)==='undefined' && 'null') || str) : this;
    var n, s = [];
    while ((n = str.search(/[\b\t\n\r\f\\"]/)) > -1) {
      s.push(str.substring(0, n));
      s.push(_escapeChars[str[n]]);
      str = str.substring(n + 1);
    }
    s.push(str);
    return '"' + s.join('') + '"';
  },
  
  _number = function () {
    return isFinite(this) ? this+'' : null;
  },
  
  _object = function () {
    var _items = [], _itm;
    for (var _key in this) {
      if (this.hasOwnProperty(_key)) {
        _itm = this[_key];
        if (_itm) {
          _id = (_key ? _string(_key) + ' : ' : '');
          _items.push( _id + (_itm.toJSON && _itm.toJSON()) || _string(_itm) );
        }
      }
    }
    return '{\n'+_items.join(',\n')+'\n}';
  },
  
  _array = function () {
    var _items = [], _itm;
    for (var i=0,l=this.length; i<l; i++) {
      _itm = this[i];
      if (_itm) {
        _items.push( (_itm.toJSON && _itm.toJSON()) || _string(_itm) );
      }
    }
    return '['+_items.join(',')+']';
  },
  
  _date = function ()
  {
    var f = function (n,a) { return (n < 10 ? '0' + n : n)+(a||'-'); };
    return _string(
       f(this.getUTCFullYear())    + f(this.getUTCMonth()+1) +
       f(this.getUTCDate(),'T')    + f(this.getUTCHours(),':') +
       f(this.getUTCMinutes(),':') + f(this.getUTCSeconds(),'Z') );
  },
  
  _function = function ()
  {
    return _string('');
  };
  
  //* public *//

  return {
    to : function (obj) 
    {
      var _jsonString = '',
          no = [Array, Function, Number, String, Date, Object],  // Object goes last so we can skip hasOwnProp test on add
          jm = [_array, _function, _number, _string, _date, _object],
          pt = [];
      
      // add tokenizers to element prototypes (if needed)
      for (var i=0,l=no.length; i<l; i++) 
      {
        if (!no[i].prototype.toJSON) 
        {
          no[i].prototype.toJSON = jm[i];
          pt.push(no[i].prototype);
        }
      }
      // convert object to string using our method
      try 
      {
        _jsonString = obj.toJSON(); 
      }
      catch (ex) {}
      // clean up any prototype assignments
      // even if something went wrong we MUST clear them off
      while ((i = pt.pop())) {
        delete i.toJSON;
      }
      // done
      return _jsonString;
    },
    
    // From: http://www.json.org/json2.js
    // ...but I have removed the walker.
    from : function (str)
    {
      if (/^[\],:{}\s]*$/.test(str.replace(/\\./g, '@').
          replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, ']').
          replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) 
      {
        return eval('(' + str + ')');
      }
      throw new SyntaxError('parseJSON');
    }
  };
  
})();
