/*
 * jQuery JSON Plugin
 * version: 1.0 (2008-04-17)
 *
 * This document is licensed as free software under the terms of the
 * MIT License: http://www.opensource.org/licenses/mit-license.php
 *
 * Brantley Harris technically wrote this plugin, but it is based somewhat
 * on the JSON.org website's http://www.json.org/json2.js, which proclaims:
 * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
 * I uphold.  I really just cleaned it up.
 *
 * It is also based heavily on MochiKit's serializeJSON, which is 
 * copywrited 2005 by Bob Ippolito.
 */
 
(function($) {   
    // Format integers to have at least two digits.
    function toIntegersAtLease(n) {
        return n < 10 ? '0' + n : n;
    }

    // Yes, it polutes the Date namespace, but we'll allow it here, as
    // it's damned usefull.
    Date.prototype.toJSON = function(date) {
        return date.getUTCFullYear()   + '-' +
             toIntegersAtLease(date.getUTCMonth() + 1) + '-' +
             toIntegersAtLease(date.getUTCDate());
    };

    var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g;
    
    // table of character substitutions
    var meta = {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"' : '\\"',
        '\\': '\\\\'
    };

    // Places quotes around a string, inteligently.
    // If the string contains no control characters, no quote characters, and no
    // backslash characters, then we can safely slap some quotes around it.
    // Otherwise we must also replace the offending characters with safe escape
    // sequences.
    $.quoteString = function(string) {
        if (escapeable.test(string)) {
            return '"' + string.replace(escapeable, function (a)  {
                var c = meta[a];
                if (typeof c === 'string') {
                    return c;
                }
                
                c = a.charCodeAt();
                return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
            }) + '"';
        }
        
        return '"' + string + '"';
    };
    
    $.toJSON = function(o, compact) {
        var type = typeof(o);
        
        if (type == "undefined") {
            return "undefined";
        } else if (type == "number" || type == "boolean") {
            return o + "";
        } else if (o === null) {
            return "null";
        }
        
        // Is it a string?
        if (type == "string") {
            return $.quoteString(o);
        }
        
        // Does it have a .toJSON function?
        if (type == "object" && typeof o.toJSON == "function") { 
            return o.toJSON(compact);
        }
        
        // Is it an array?
        if (type != "function" && typeof(o.length) == "number") {
            var ret = [];
            for (var i = 0; i < o.length; i++) {
                ret.push( $.toJSON(o[i], compact) );
            }
            
            if (compact) {
                return "[" + ret.join(",") + "]";
            } else {
                return "[" + ret.join(", ") + "]";
            }
        }
        
        // If it's a function, we have to warn somebody!
        if (type == "function") {
            throw new TypeError("Unable to convert object of type 'function' to json.");
        }
        
        // It's probably an object, then.
        ret = [];
        for (var k in o) {
            var name;
            var type = typeof(k);
            
            if (type == "number") {
                name = '"' + k + '"';
            } else if (type == "string") {
                name = $.quoteString(k);
            } else {
                continue;  //skip non-string or number keys
            }
            
            var val = $.toJSON(o[k], compact);
            if (typeof(val) != "string") {
                // skip non-serializable values
                continue;
            }
            
            if (compact) {
                ret.push(name + ":" + val);
            } else {
                ret.push(name + ": " + val);
            }
        }
        
        return "{" + ret.join(", ") + "}";
    };
    
    $.compactJSON = function(o) {
        return $.toJSON(o, true);
    };
    
    // Evals JSON that we know
    $.evalJSON = function(src) {
        return eval("(" + src + ")");
    };
    
    // Evals JSON in a way that is *more* secure.
    $.secureEvalJSON = function(src) {
        var filtered = src;
        filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@');
        filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
        filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
        
        if (/^[\],:{}\s]*$/.test(filtered)) {
            return eval("(" + src + ")");
        } else {
            throw new SyntaxError("Error parsing JSON, source is not valid.");
        }
    };
    
    // Validates a JSON string. VERY BASIC. USE AT YOUR OWN RISK.
    // Author: Jon Ursenbach <lorderunion@gmail.com>
    $.validJSON = function(jsonStr) {
        if (
            jsonStr != {} && 
            jsonStr != '' && 
            jsonStr != 'NULL' && 
            jsonStr != 'null' && 
            jsonStr != '"null"' &&
            jsonStr != '[ ]' &&
            jsonStr != '[]'
        ) {
            return true;
        } else {
            return false;
        }
    };
    
    // Adds JSON to an existing JSON object, or if it doesn't already exist it creates one.
    // Author: Jon Ursenbach <lorderunion@gmail.com>
    $.addToJSON = function(node, index, data) {
        var nodevalue;
        var jsonlist;
            
        nodevalue = node.val();
        if (nodevalue != '' && nodevalue != '[]' && nodevalue != null) {
            jsonlist = $.secureEvalJSON(nodevalue);
        }
            
        if (jsonlist == null) {
            jsonlist = {};
        }
            
        if (typeof jsonlist[index] == 'undefined') {
            jsonlist[index] = data;
        }
            
        node.val($.toJSON(jsonlist));
    };
    
    // Removes JSON from an existing JSON object.
    // Author: Jon Ursenbach <lorderunion@gmail.com>
    $.removeFromJSON = function(node, index){
        var nodevalue;
        var jsonlist;
            
        nodevalue = node.val();
        if (nodevalue != '' && nodevalue != '[]' && nodevalue != null) {
            jsonlist = $.secureEvalJSON(nodevalue);
        }
            
        if (jsonlist == null) {
            jsonlist = {};
        }
            
        if (typeof jsonlist[index] == 'object') {        
            delete jsonlist[index];
            node.val($.toJSON(jsonlist));
        }
    };
})(jQuery);