/**
 * wendigo.utils.js
 * 10.22.10
 * 
 * Collection of common tools for use internally within the Wendigo framework.
 * 
 * @author Ryan Hardy 
 * @version 1.0
 */
(function() {
    var ns = jwyre.module("wendigo");
    // debug setting for log statements
    var debug = true;
        
    /*
    ============================================================================
                                    Internals
    ============================================================================
    */
    // time in millis before page times out
    //TODO: set to appropriate time when done testing
    //TODO: externalize to config options
    var timeout = 1000 * 60 * 100; 
    // the ids returned by call to setTimeout for page timeout functions
    var timeoutId, timeoutId2;
    // the function to call when the page has timed out
    var timeoutCb;
    // the function used to close the timeout prompt message
    var timeoutMsgCloser;
    // reference to Help window, if open
    var helpWin;
    // currently dynamically-loaded scripts (must be removed each time a new batch is loaded)
    var currScripts = jwyre.array();
    
    // global settings for common alert boxes
    var alertInit = {
        closeOnEnter : false,
        disableScroll : false,
        backgroundColor : "rgb(0, 0, 0)",
        backgroundZ: "100000",
        backgroundOpacity : ".20",
        font : "Trebuchet MS",
        fontSize : "11px",
        fontColor : "#ffffff",
        boxBorderSize : "3px",
        boxBorderColor : "#3f3f3f",
        boxBackgroundColor : "#333333",
        boxOpacity : "1.0",
        okButtonHtml : "<button class='btnAlert'>OK</button>",
        cancelButtonHtml : "<button class='btnAlert'>Cancel</button>",
        //okText : "Ok",
        //cancelText : "Cancel",
        buttonFont : "Trebuchet MS",
        buttonPadding : "5px",
        buttonMargin : "10px 0px 0px 0px",
        buttonFontSize : "11px",
        buttonFontColor : "rgb(0, 0, 0)",
        buttonBorderSize : "2px",
        buttonBorderColor : "rgb(100, 100, 100)",
        buttonBackgroundColor : "rgb(240, 240, 250)",
        topOverride : 75
    };
    var cAlert = jwyre.alert(alertInit);

    // default initializer object for jwydget.waiter() objects
    var waiterInit = {
        "showAnimation" : true,
        "numberOfBars" : 6,
        "barHeight" : 15,
        "barWidth" : 10,
        "barSpacing" : 10,
        "jumpDistance" : 20,
        "duration" : 500,
        "barStyles" : {
            "background-color" : "rgb(200, 200, 200)",
            "border" : "1px rgb(150, 150, 150) solid"
        },
        "activeStyles" : {
            "background-color" : "rgb(240, 240, 240)",
            "border" : "1px rgb(150, 150, 220) solid"
        },
        "labelText" : "Please wait...",
        "labelStyles" : {},
        "alertProps" : alertInit,
        "stagger" : false,
        "staggerInterval" : 100
    };

    function _doWait(callback, customs) {
        var waiter = ns.getWaiter(customs);
        var cancel = waiter.cancel;
        // add a delay on waiter cancel
        waiter.cancel = function() { window.setTimeout(cancel, 2000); };
        waiter.start(function() { callback(waiter); });
    }
    
    // common parsing function used to turn string into name/value pair map    
    function parseMap(str) {
        if (!str) {
            return null;
        }
        str = str.replace(/[\[\]]/g, "");
        var comps = jwyre.array(str.split(/ *; */));
        try {
            var map = {};
            for (var i = comps.iterator(); i.hasNext();) {
                var pr = i.next().split(/ *: */);
                if (pr[0] && pr[1]) {
                    map[pr[0]] = pr[1];                 
                }
            }
            return map;
        } catch (e) {
            return null;
        }
    }
     
    /*
    ============================================================================
                            Populate Wendigo object
    ============================================================================
    */
    ns = {  
        //TODO: (rmh - 4.19.11) 
        // flag indicates if this is being run on XP 
        isWinXP: (function() {
            var ua = navigator.userAgent;
            return new RegExp("NT 5.1").test(ua);
        })(),        
        /**
         * The mapping for the InternalWendigoPlugin as configured for this 
         * deployment.
         */
        _pluginMapping : "",
        /**
         * The mapping for the WendigoServlet as configured for this deployment.
         */
        _servletMapping : "",
        /**
         * Sets the mappings and initializes constants used throughout 
         * framework; needs to be called whenever a page has been loaded.
         * 
         * @param {string} pluginMapping
         * @param {string} servletMapping
         */
        loadMappings : function(pluginMapping, servletMapping) {
            ns._pluginMapping = pluginMapping;
            ns._servletMapping = servletMapping;
            
            /**
             * The HTML id of the container element for edit page content.
             */
            ns.ELEMENT_CONTENT = "#content";
            
            ns.REQUEST_CALL_CONTROL = ns._createRequest("callControl");
            ns.REQUEST_GET_SETTINGS = ns._createRequest("getSettings");
            ns.REQUEST_SAVE_PAGE = ns._createRequest("savePage");
            ns.REQUEST_ADD_PAGE = ns._createRequest("addPage");
            ns.REQUEST_GET_PAGE = ns._createRequest("getPage");
            ns.REQUEST_GET_PREVIEW = ns._createRequest("getPreview");
            ns.REQUEST_GET_PAGE_CONTENT = ns._createRequest("getPageContent");
            ns.REQUEST_EDIT_PAGE = ns._createRequest("editPage");
            ns.REQUEST_SHOW_INTERNAL_PG = ns._createRequest("showInternalPage");
            ns.REQUEST_ADD_WIDGET = ns._createRequest("addPage");
            ns.REQUEST_LOGOUT = ns._createRequest("doLogout");
            
            ns.REQUEST_POPULATE_MENU = ns._createRequest("populateMenu");

            ns.REQUEST_UPLOAD_FILE = ns._createRequest("uploadFile");
            ns.REQUEST_GET_FILES = ns._createRequest("getFiles");
            ns.REQUEST_SAVE_FILE = ns._createRequest("saveFile");
            ns.REQUEST_DELETE_FILE = ns._createRequest("deleteFile");
            ns.REQUEST_UPDATE_FILE = ns._createRequest("updateFile");

            ns.REQUEST_CREATE_CATEGORY = ns._createRequest("createCategory");
            ns.REQUEST_DELETE_CATEGORY = ns._createRequest("deleteCategory");
            ns.REQUEST_GET_CATEGORIES = ns._createRequest("getCategories");

            
            ns.REQUEST_GET_MESSAGES = ns._createRequest("getMessages");
            
            ns.REQUEST_CREATE_MENU = ns._createRequest("createMenu");
            ns.REQUEST_GET_MENU = ns._createRequest("getMenu");
            ns.REQUEST_SAVE_MENU = ns._createRequest("saveMenu");
            ns.REQUEST_DELETE_MENU = ns._createRequest("deleteMenu");

            ns.REQUEST_CREATE_EMAIL = ns._createRequest("createEmail");
            ns.REQUEST_GET_EMAIL = ns._createRequest("getEmail");
            ns.REQUEST_SAVE_EMAIL = ns._createRequest("saveEmail");
            ns.REQUEST_DELETE_EMAIL = ns._createRequest("deleteEmail");
            ns.REQUEST_SEND_EMAIL = ns._createRequest("sendEmail");
            
            ns.REQUEST_CREATE_GALLERY = ns._createRequest("createGallery");
            ns.REQUEST_CREATE_GALLERY_ITEM = ns._createRequest("createGalleryItem");
            ns.REQUEST_GET_GALLERY = ns._createRequest("getGallery");
            ns.REQUEST_SAVE_GALLERY = ns._createRequest("saveGallery");
            ns.REQUEST_DELETE_GALLERY = ns._createRequest("deleteGallery");

            ns.REQUEST_CREATE_VIDEO = ns._createRequest("createVideo");
            ns.REQUEST_GET_VIDEO = ns._createRequest("getVideo");
            ns.REQUEST_SAVE_VIDEO = ns._createRequest("saveVideo");
            ns.REQUEST_DELETE_VIDEO = ns._createRequest("deleteVideo");

            //TODO: these are unused as of right now; am not currently propagating
            // individual edit events to server
            ns.REQUEST_ADD_CONTENT = "";    
            ns.REQUEST_REMOVE_CONTENT = "";    
            ns.REQUEST_UPDATE_CONTENT = "";    
            ns.REQUEST_ADD_WIDGET = "";
            ns.REQUEST_REMOVE_WIDGET = "";
        },
        /**
         * Starts the session timeout.
         * 
         * @param {function} callback
         */
        startTimeout : function(callback) {
            ns.log("Calling startTimeout, callback=" + callback, debug);
            if (!timeoutCb) {
                timeoutCb = callback;
            }
            timeoutId = window.setTimeout(function() {
                // message will display for limited time before doing timeout automatically
                timeoutId2 = window.setTimeout(function() { timeoutCb(); }, 15 * 1000);
                var target;
                if (ns.editWindow && !ns.editWindow.closed) {
                    target = ns.editWindow.wendigo;
                } else {
                    target = ns;
                }
                timeoutMsgCloser = target.prompt(
                    "Your session is about to timeout. Press OK to continue or Cancel to logout.", 
                    // ok callback
                    function() {
                        ns.resetTimeout();
                    }, 
                    // cancel callback
                    timeoutCb,
                    {});
            }, timeout);
        },
        /**
         * Is used to reset the session timeout timer.
         */
        resetTimeout : function() {
            ns.log("Calling resetTimeout", debug);
            if (window.opener) {
                // for edit window, call dashboard's resetTimeout
                window.opener.wendigo.resetTimeout();
            } else {
                if (timeoutMsgCloser) {
                    timeoutMsgCloser();
                }
                window.clearTimeout(timeoutId);
                window.clearTimeout(timeoutId2);
                ns.startTimeout(timeoutCb);
            }
        },
        
        /**
         * Opens up a window with the help document for Wendigo.
         * 
         * @param {string} position (optional) anchor name to scroll window to
         */
        openHelpWindow : function(position) {
            ns.resetTimeout();
            if (helpWin) {
                helpWin.close();
            }
            var qry = jwyre.queryString();
            if (position) {
                qry.add("position", position);
            }
            helpWin = window.open(
                qry.getQueryString("http://www.wendigoweb.com/support/docs/manual.htm", false),
                "WendigoHelp",
                "height=600px, width=850px, location=no, menubar=no, resizable=no, scrollbars=yes, status=no, toolbar=no",
                true
            );
        },
        
        /**
         * Used to add items to wendigo object.
         * 
         * @param {Object} obj
         */
        _add : function(obj) {
            for (var i in obj) {
                ns[i] = obj[i];
            }
        },
        /**
         * Creates a complete request for the InternalWendigoPlugin by appending
         * the configured plugin mapping and servlet mapping to the given
         * command string.   
         * @param {string} request
         */
        _createRequest : function(request) {
            return request + "." + ns._pluginMapping + "." + ns._servletMapping;
        },        
        /**
         * 
         */
        isDebug : function() {
            return debug;
        },
        /**
         * 
         * @param {Object} obj
         * @param {boolean} override (optional)
         */
        log : function(obj, override) {
            override = jwyre.parseBoolean(override, true);
            try {
                if (console && console.log) {
                    if (override && debug) {
                        var msg;
                        // check to see if obj is an Error object by ducktype
                        if (obj.message && obj.filename) {
                            msg = "ERROR: " + obj.filename + ":" + obj.lineNumber;
                            msg += "\n" + obj.message;
                        } else {
                            msg = obj.toString();
                        }
                        console.log(msg);
                    }
                }               
            } catch (e) {}
        },
        /**
         * Obtains the custom jwydget.Alert object used within the Wendigo framework.
         * 
         * @param {Object} customs (optional)
         */
        getAlert : function(customs) {
            if (customs) {
                customs = jwyre.combine(alertInit, customs);
                return jwyre.alert(customs);
            }
            return cAlert;
        },
        /**
         * Obtains the custom jwydget.waiter object used within the Wendigo framework.
         * 
         * @param {Object} customs (optional)
         */
        getWaiter : function(customs) {
            if (!customs) {
                customs = {};
            } 
            var alertProps = customs.alertProps;
            alertProps = jwyre.combine(waiterInit.alertProps, alertProps);
            customs.alertProps = alertProps;        
            customs = jwyre.combine(waiterInit, customs);
            return jwyre.waiter(customs);
        },
        /**
         * @param {function} callback
         */
        doWait : _doWait,       
        /**
         * Performs a standard Ajax call, optionally wrapping call in a Waiter
         * (i.e. display 'Please Wait' animated graphic until completion of call).
         * 
         * Required init props:
         *    request: The server request to make.
         * 
         * Optional init props:
         *    doWait:           Boolean flag used to indicate whether to use Waiter (default is true)   
         *    doPrompt:         Boolean flag used to indicate if the user should be prompted
         *                      for confirmation prior to call
         *    promptMessage:     The message to display in the prompt window (only used if 
         *                      doPrompt is true; optional, will display default message if
         *                      not provided)
         *    cancelCallback:   Function to call if user cancels call (only used if
         *                      doPrompt is true)            
         *    parseJson:        Boolean flag used to indicate whether Ajax JSON parsing should occur (default is true)
         *    paramAdder:       Function used to add params to queryString object
         *    message:          A string used to display to user upon successful completion of call.
         *    callback:         Function to call upon successful completion of call 
         *                      (returned JSON passed as param, unless parseJson is false, 
         *                      in which case ajax object is passed).
         *    onError:          Function to call upon non-successful completion of call 
         *                      (returned JSON passed as param).
         *    waiterInit:       Initializer object for the Waiter (optional).
         *    
         * @param {Object} init
         */
        doAjax : function(init) {
            var doPrompt = jwyre.parseBoolean(init.doPrompt, false);
            var doWait = jwyre.parseBoolean(init.doWait, true);
            var prsJson = jwyre.parseBoolean(init.parseJson, true);
            function _(waiter) {
                var qry = jwyre.queryString();
                qry.add("isAjax", "true");
                if (init.paramAdder) {
                    init.paramAdder(qry);
                }
                jwyre.ajax(
                   function(ajax) {
                       try {
                           waiter.cancel();
                           if (!prsJson && init.callback) {
                               init.callback(ajax);  
                               return;
                           }
                           var json = ajax.getJSON(true, true);
                           if (json.result != "success") {
                                 if (init.onError) {
                                    init.onError(json)
                                    return;
                                 } 
                                 throw new Error(json.reason);
                           }
                           if (init.message != null) {
                               ns.message(init.message);
                           }
                           if (init.callback) {
                               init.callback(json);                         
                           }
                       } catch (e) {
                           ns.error(e.message);
                       }
                   },
                   function(ajax) {
                       waiter.cancel();
					   if (init.onError) {
					       init.onError({})
					   } else {
                           ns.error("Call to server has failed."); 					   	
					   }
                   }
                ).send(qry.getQueryString(init.request));
            }
            
            function _2() {
                if (doWait) {
                    _doWait(function(waiter) { _(waiter); }, init.waiterInit);
                } else {
                    // send in w/ Null waiter
                    _({ "cancel" : function() {} });
                }               
            }
            if (doPrompt) {
                var msg = init.promptMessage || "Are you sure you want to continue?";
                ns.prompt(msg, _2, init.cancelCallback);
            } else {
                _2();
            }
        },
        /**
         * 
         * @param {string || HTMLElement} contentArea
         * @param {string} rawText
         */
        doDynamicLoad : function(contentArea, rawText) {
            currScripts.each(function() { jwyre.remove(this); });
            currScripts = jwyre.array();
            jwyre.empty(contentArea);
            var html = jwyre.create(rawText, true);
            var temp = document.createDocumentFragment();
            html.each(function() {
                if (jwyre.tag(this) == "script") {
                    var scr = document.createElement("script");
                    scr.type = "text/javascript";
                    if (this.src != "") {
                        scr.src = this.src;                     
                    } else {
                        scr.text = this.text;                       
                    }
                    jwyre.append(temp, this);
//                    currScripts.push(scr);
                } else {
                    jwyre.append(temp, this);
                }
            })
            jwyre.append(contentArea, temp);
//            currScripts.each(function() { 
//                jwyre.append("head", this); 
//            });           
        },
        /**
         * 
         * @param {string} msg
         * @param {Object} customs (optional)
         */
        message : function(msg, customs) {
            this.getAlert(customs).message({"message" : msg });
        },
        /**
         * 
         * @param {string} msg
         * @param {Object} customs (optional)
         * @param {function} callback (optional) an optional function to call
         * when error message is closed
         */
        error : function(msg, customs, callback) {
            callback = (!callback) ? function() {} : callback;
            this.getAlert(customs).error({"message" : msg, "closerCallback" : callback });
        },
        /**
         * 
         * @param {Object} msg
         * @param {Object} okCallback
         * @param {Object} cancelCallback (optional)
         * @param {Object} customs (optional)
         */
        prompt : function(msg, okCallback, cancelCallback, customs) {
            return this.getAlert(customs).confirm({"message" : msg, "okCallback" : okCallback, "cancelCallback" : cancelCallback });
        },
        /**
         * Converts the provided HTMLElement to a string (it's tag, attributes and
         * HTML content all included).
         * 
         * @param {string | HTMLElement} element
         */
        getContent : function(element) {
            element = jwyre.element(element);
            if (!element) {
                return "";
            }
            // attempt to build innerContent first (may be a DocumentFragment)
            var innerContent = "";
            jwyre.children(element, false).each(function() {
                innerContent += ns.getContent(this);
            });
            var tag = jwyre.tag(element);
            if (!tag || tag == "") {
                if (element.nodeType == jwyre.Node.TEXT) {
                    // clean newlines/excess space
                    var str = element.nodeValue || "";
                    str = str.replace(/[\n\r]+/g, "").replace(/\s+/g, " "); 
                    return str;
                }
                return innerContent;
            }
            var content = "<" + tag;
            var atts = element.attributes || [];
            for (var i = 0; i < atts.length; i++) {
                var att = atts[i];
                var nm = att.nodeName;
                var val = att.nodeValue;
                content += " " + nm + "='" + val + "'";
            }
            if ("" == innerContent) {
                content += " />";
                return content;
            }
            content += ">";
            content += innerContent;
            content += "</" + tag + ">";
            return content;
        },
        /**
         * Rounds to the nearest multiple of the num given.
         */
        roundTo : function(number, snap) {
            var mod = number % snap;
            if (mod == 0) {
                return number;
            } else if (mod < (snap / 2)) {
                return number - mod;
            } else {
                return number + (snap - mod);
            }
        },        
        /*
        ============================================================================
                                   Styles-related code
        ============================================================================
        */
        Styles : (function() {
            /**
             * Adds a toString() method to a style map appropriate for sending to server.
             * 
             * @param {Object} styleMap
             */
            function Styles(styleMap) {
                // used as flag for parse method
                this._isStyleObj = true;                
                for (var i in styleMap) {
                    this[i] = styleMap[i];
                }
            }
            Styles.prototype = {
                /**
                 * Applies this object's styles to the given HTMLElement, overwriting
                 * any current common styles.
                 * 
                 * @param {string || HTMLElement} element
                 */
                apply : function(element) {
                    element = jwyre.element(element);
                    var map = this.getMap();
                    jwyre.style(element, map);
                },
                /**
                 * Returns a style map-object, suitable for using as argument 
                 * to a style-settter function.
                 */
                getMap : function() {
                    var map = {};
                    for (var style in this) {
                        if (typeof this[style] == "function") {
                            continue;
                        }
                        map[style] = this[style];
                    }
                    return map;
                },
                /**
                 * Returns a JSON string representation of this object.
                 */
                json : function() {
                    var obj = ns.JSON.create();
                    for (var key in this) {
                        if (typeof this[key] == "function") {
                            continue;
                        }
                        obj.add(key, this[key]);
                    }
                    return obj;
                },
                /**
                 * Returns a string representation of this object, suitable for using
                 * as the value for the 'style' attribute of an HTMLElement.
                 */
                toString : function() {
                    var str = "";
                    for (var i in this) {
                        if (typeof this[i] == "function") {
                            continue;
                        }
                        str += i + ":" + this[i] + "; ";
                    }
                    // trim trailing comma
                    str = str.substr(0, str.length - 1);
                    return str;
                }           
            };
            return {
                /**
                 * 
                 * @param {string | HTMLElement} obj
                 */
                parse : function(obj) {
                    // return null for null input (allow caller to detect error)
                    if (!obj) {
                        return null;
                    }
                    // if already a Styles object, return
                    if (obj._isStyleObj) {
                        return obj;
                    }
                    var str = obj;
                    if (typeof obj != "string") {
                        str = jwyre.attribute(obj, "style");
                    }
                    var map = parseMap(str);
                    if (map) {
                        return new Styles(map);         
                    } else {
                        throw new Error("Can not parse object into Style object (" + str + ").");
                    }
                },              
                /**
                 * 
                 * @param {Object} styleMap
                 */
                create : function(styleMap) {
                    return new Styles(styleMap);
                }               
            };
        })(),
        /**
         * Object used to encapsulate the height and width of an HTML element.
         */
        Size : (function() {
            function Size(width, height) {
                this._width = width;
                this._height = height;              
            }
            Size.prototype = {
                /**
                 * Applies the height and width of this object to the HTML element.
                 * 
                 * @param {string || HTMLElement} element
                 */ 
                apply : function(element) {
                    element = jwyre.element(element);
                    jwyre.style(element, {
                        "height" : this._height,
                        "width" : this._width
                    });
                },
                toString : function() {
                    return "Size [" + this._width + " x " + this._height + "]";
                }   
            };
            return {
                "create" : function(w, h) {
                    return new Size(w, h);
                },
                /**
                 * Obtains a Size object corresponding to the given
                 * element's current dimensions.
                 * 
                 * @param {string || HTMLElement} element
                 */
                "getSize" : function(element) {
                    element = jwyre.element(element);
                    var dims = jwyre.dimensions(element);
                    
                    return new Size(dims.width, dims.height);
                }
            };
        })(),
        /**
         * Object used to encapsulate the x- and y- position of an HTML element.
         */
        Position : (function() {
            function Position(x, y) {
                this._x = x;
                this._y = y;                
            }
            Position.prototype = {
                /**
                 * Applies the x- and y- position of this object to the HTML element.
                 * 
                 * @param {string || HTMLElement} element
                 */ 
                apply : function(element) {
                    element = jwyre.element(element);
                    jwyre.style(element, {
                        "top" : this._y,
                        "left" : this._x
                    });
                },
                toString : function() {
                    return "Position [" + this._x + "," + this._y + "]";
                }   
            };
            return {
                "create" : function(x, y) {
                    return new Position(x, y);
                },
                /**
                 * Obtains a Position object corresponding to the given
                 * element's current (absolute) position.
                 * 
                 * @param {string || HTMLElement} element
                 */
                "getPosition" : function(element) {
                    element = jwyre.element(element);
                    var pos = jwyre.position(element);
                    
                    return new Position(pos.left, pos.top);
                }
            };
        })(),
        /*
        ============================================================================
                                   JSON-related code
        ============================================================================
        */
        JSON : (function() {
            /**
             * Object that will return JSON from the toString() method.
             * May be provided an object with which to initialize the instance.
             * 
             * @param {Object} obj (optional)
             */
            function JSON(obj) {
                this._isJSON = true;
                var ary = jwyre.array();
                
                this.add = function(key, value) {
                    if (arguments.length == 1 && arguments[0]._isJSON) {
                        var json = arguments[0];
                        for (var jIt = json.iterator(); jIt.hasNext();) {
                            var k = jIt.next();
                            ary.push(k);
                            var v = json[k];
                            this[k] = v;
                        }
                        return;
                    }
                    ary.push(key);
                    if (value == undefined) {
                        value = "null";
                    } else if (value.json) {
                        //TODO: IE is doing something stupid, trying to work around
                        try {
                            value = value.json();       
                        } catch (e) { }
                    } else if (typeof value == "object" && !value._isJSON) {
                        value = new JSON(value);
                    }
                    if (typeof value == "string") {
                        //TODO: big effin' hack here....
                        value = (value.indexOf("'") == 0) ? value : "'" + value + "'";
                        this[key] = escape(value);
                    } else if (value._isJSON) {
                        this[key] = value;
                    } else {
                        this[key] = value;
                    }
                };
                
                // initialize instance
                if (obj != null) {
                    for (var i in obj) {
                        var val = obj[i];
                        if (obj.hasOwnProperty(i) && typeof val != "function") {
                            this.add(i, val);
                        }
                    }
                }
                
                this.iterator = function() {
                    return ary.iterator();
                };
                        
                this.toString = function() {
                    var str = "{";
                    for (var it = ary.iterator(); it.hasNext();) {
                        var key = it.next();
                        var val = this[key];
                        str += key + ":" + val;
                        if (it.hasNext()) {
                            str += ",";
                        }
                    }
                    str += "}";
                    return str;
                };
            }
            
            return {
                /**
                 * 
                 * @param {Object} obj
                 */
                create : function(obj) {
                    return new JSON(obj);
                }                               
            };
        })()
    };
    
    // add to global namespace
    window.wendigo = ns;    
})();
