/**
 * General utility functions.
 */
OOMBLE.util.WebUtils = function() {
    
    return {
   
        errorDialog: null, 
        
        /**
         * Creates a dialog to display.
         */
        createFormDialog: function(form, title, width) {
            var dialog = new OOMBLE.widget.Window({
                title: title,
                frame: true,
                width: width,
                height: 'auto',
                shim: true,
                resizable: false,
                collapsible: false,
                hideBorders: true,
                layout: 'fit',
                html: form
            });
            return dialog;
        },
    
        /**
         * Adds the submit and cancel buttons to a form.
         */
        addFormButtons: function(dialog, dismiss, submit, success, failure, scope, actionUrl) {
            var okButton = {
                text: 'Ok',
                handler: function() {
                    var root = dialog.getEl().dom;
                    var form = Ext.DomQuery.selectNode('form', root);
                    if (form == null) {
                        return;
                    }

                    // Check if ok to submit.
                    if (submit != null && !submit.call(scope, dialog, form)) {
                        return;
                    }
                    
                    // Make sure there is a url to post to.
                    if (actionUrl == null) {
                        return;
                    }

                    var fs = Ext.lib.Ajax.serializeForm(form);
                    var data = Ext.urlDecode(fs);

                    // Setup the ajax submission.
                    OOMBLE.util.WebUtils.sendAjaxRequest(
                        actionUrl, 
                        data, 
                        dialog, 
                        true,
                        scope, 
                        success,
                        failure
                    );
                }
            }
            
            var cancelButton = {
                text: 'Cancel',
                handler: function() {
                    // Check if ok to dismiss.
                    if (dismiss != null) {
                        var root = dialog.getEl().dom;
                        var form = OOMBLE.util.WebUtils.getElementByTagName(root, 'form');
                        if (form == null) {
                            return;
                        }
                        if (!dismiss.call(scope, dialog, form)) {
                            return;
                        }
                    }
                    dialog.hide();
                    dialog.destroy();
                }
            }
            
            dialog.addButton(okButton);
            dialog.addButton(cancelButton);
        },

        /**
         * Sends an AJAX request to the server for processing.
         */
        sendAjaxRequest: function(url, params, data, interp, scope, success, exception, failure) {

            var args = [ data, scope, success, exception, failure, interp ];
            
            var responseSuccess = function(o) {
                var cdata      = o.argument[0];
                var cscope     = o.argument[1];
                var cexception = o.argument[3];
                var csuccess   = o.argument[2];
                var cinterp    = o.argument[5];
                var response   = o.responseText;
                
                // If the response is not to be converted into an
                // object, just pass the response back as is.
                if (cinterp != null && !cinterp) {
                    if (csuccess != null) {
                        csuccess.call(cscope, response, cdata);
                    }
                    return;
                }
                
                // Unpack the response.
                var obj = OOMBLE.util.WebUtils.decodeJSON(response);
                if (obj.isException) {
                    if (cexception != null) {
                        cexception.call(cscope, obj.status.message, cdata);
                    }
                } else {
                    if (csuccess != null) {
                        // TODO: remove isPrimitive - send back new response structure
                        if (obj.isPrimitive) {
                            csuccess.call(cscope, obj.value, cdata);
                        } else {
                            csuccess.call(cscope, obj.data, cdata);
                        }
                    }
                }
            };
            
            // Pop a dialog on error or call a custom function.
            var responseFailure = function(o) {
//                var cdata      = o.argument[0];
//                var cscope     = o.argument[1];
//                var cexception = o.argument[3];
//                var response   = o.responseText;

                OOMBLE.util.WebUtils.messageBox("Error", o.statusText);
                /*
                var obj = OOMBLE.util.WebUtils.decodeJSON(response);
                if (obj != null && obj.isException) {
                    if (cexception != null) {
                        cexception.call(cscope, obj.status.message, cdata);
                    }
                } else {
                    OOMBLE.util.WebUtils.messageBox("Error", o.statusText);
                }
                */
            };
            
            // Define the functions.
            var callback = {
                success: responseSuccess,
                failure: responseFailure,
                argument: args,
                scope: scope,
                cache: false
            };
            
            // Pack native types.  These match the primitive
            // value bean objects on the server.
            if (params != null) {
                if (typeof(params) !== 'object' || params instanceof Array) {
                    // Escape quotes.
                    if (typeof(params) == 'string') {
                        params = params.replace(/'/g, "\\'");
                        params = params.replace(/"/g, '\\"');
                    }
                    var obj = new Object();
                    obj.value = params;
                    params = obj;
                }
                params = OOMBLE.util.WebUtils.encodeJSON(params);
            }
            
            // Send the request.
            YAHOO.util.Connect.asyncRequest('POST', url, callback, params);
        },

        /**
         * Tells if a bit is set in a set of bits.
         */
        isBitSet: function(bits, bit) {
            return ((bits & bit) == 0) ? false : true;
        },
    
        /**
         * Sets a bit in a set of bits.
         */
        setBit: function(bits, bit) {
            return bits | bit;
        },
    
        /**
         * Unsets a bit in a set of bits.
         */
        unsetBit: function(bits, bit) {
            return bits & (~bit);
        },
        
        /**
         * Gets the current seconds from 1970.
         */
        now: function() {
            var date = new Date();
            return date.getTime();
        },
        
        /**
         * Gets the current hostname of the server.
         */
        getServerAddr: function() {
            return document.location.hostname;
        },

        /**
         * Gets the current port of the server.
         */
        getServerPort: function() {
            var port = document.location.port;
            if (OOMBLE.util.WebUtils.isEmpty(port)) {
                return 80;
            } else {
                return parseInt(port, 10);
            }
        },

        /**
         * Gets the file name of a file path.
         */
        getBasename: function(filePath) {
            filePath = filePath.replace(/.*\//, "");
            filePath = filePath.replace(/.*\\/, "");
            return filePath;
        },

        /**
         * Parses the domain from the url.
         */
        parseDomain: function(url) {
            var pattern = /^(\w+):\/\/([\w.]+)\/?(.*)/;
            var parts = url.match(pattern);
            if (parts == null || parts.length < 3) {
                return null;
            }
            return parts[2];
        },

        /**
         * Parses XML code and returns the root of the document.
         */
        parseXML: function(xml) {
            var doc = null;
            // Internet explorer.
            if (window.ActiveXObject) {
                doc = new ActiveXObject("Microsoft.XMLDOM");
                doc.async="false";
                doc.loadXML(xml);
            } else {
                // Mozilla, Firefox, Opera, etc.
                var parser = new DOMParser();
                doc = parser.parseFromString(text,"text/xml");
            }
            if (doc == null)
                return null;  // XXX throw
            
            return doc.documentElement;                
        },
        
        /** 
         * Enocdes text into a JSON string.
         */        
        encodeJSON: function(obj) {
            return Ext.util.JSON.encode(obj);
        },
        
        /**
         * Decodes a JSON string into an object.
         */
        decodeJSON: function(text) {
            if (OOMBLE.util.WebUtils.isEmpty(text)) {
                return text;
            } 
            var obj = Ext.util.JSON.decode(text);
            OOMBLE.util.WebUtils.decodeDates(obj);
            return obj;
        },

        /**
         * Filter for handling dates in JSON strings.  This matches
         * with the maven JSON parser used in the Java code.
         */
        decodeDates: function(obj) {
            for (i in obj) {
                var value = obj[i];
                if (value == null) {
                    continue;
                }
                if (value.time && value.date) {
                    obj[i] = new Date(value.time);
                } else if (typeof value == "object") {
                    OOMBLE.util.WebUtils.decodeDates(value);
                }
            }
        },
        
        handleDialogOk: function() { 
	    this.hide(); 
	},

        /*
        showErrorDialog: function(message) {
            if (this.errorDialog == null) {
                this.errorDialog = new YAHOO.widget.SimpleDialog("errordialog", 
                    { width: "300px",
                      fixedcenter: true,
                      modal: true,
                      visible: false,
                      draggable: false,
                      constraintoviewport: true
                    } 
                );  
                var buttons = [ { text:"Ok", handler:OOMBLE.util.WebUtils.handleDialogOk, isDefault:true } ];
                this.errorDialog.setHeader("Error!"); 
                this.errorDialog.setBody(message); 
                this.errorDialog.cfg.queueProperty("buttons", buttons);                     
                this.errorDialog.cfg.setProperty("icon", YAHOO.widget.SimpleDialog.ICON_WARN);                     
                this.errorDialog.render(document.body);
            } else {
                this.errorDialog.setBody(message); 
            }
            this.errorDialog.show();
        },
        */
        
        /**
         * Gets a short version of text.
         */
        getAbridged: function(text) {
            if (text == null)
                return null;
            // XXX fix
            return text;
        },
        
        /**
         * Gets a frame element given its name.
         */
        getFrameById: function(id) {
            var docFrames = parent.frames;
            if (docFrames == null)
                return null;
            for (var i = 0; i < docFrames.length; i++) {
                var frame = docFrames[i];
                if (frame == null)
                    continue;
                try {
                    // Some frames are not accessible.
                    var el = frame.frameElement;
                    if (el == null)
                        continue;
                } catch (e) {
                    continue;
                }
                if (el.id == id)
                    return frame;
            }
            return null;
        },
        
        /**
         * Gets the document in the frame by id.
         */
        getFrameDocumentById: function(id) {
            var frame = this.getFrameById(id);
            if (frame == null)
                return null;
            return frame.document;
        },

        /**
         * Gets the document in the frame by frame.
         */
        getFrameDocument: function(frame) {
            if (frame.contentDocument)
                return frame.contentDocument;
            else if (frame.document)
                return frame.document;
            return null;
        },
        
        /**
         * Gets the document body in the frame by id.
         */
        getFrameBodyById: function(id) {
            var doc = this.getFrameDocumentById(id);
            if (doc == null)
                return null;
            return doc.body;
        },

        /**
         * Set the page loaded into a frame.
         */
        setFrameSrc: function(id, url) {
            var frame = OOMBLE.util.WebUtils.getFrameById(id);
            if (frame == null) 
                return;
            frame.location.href = url;
        },
        
        /**
         * Removes a child element from a parent node.  Makes
         * sure that the element is a child before it is removed.
         */
        removeChild: function(parent, target) {
            if (parent == null || target == null)
                return null;
            var children = parent.childNodes;
            if (children == null || children.length == 0)
                return null;
            for (var i = 0; i < children.length; i++) {
                var child = children[i];
                if (child == target) {
                    parent.removeChild(child);
                    return child;
                }
            }
            return null;
        },

        /**
         * Detaches a child node from a parent node.
         */
        detachChild: function(child) {
            if (child == null) {
                return;
            }
            var parent = child.parentNode;
            if (parent != null)
                parent.removeChild(child);
        },

        /**
         * Displays a dialog message to the user.
         */
        messageBox: function(title, message) {
            var box = Ext.MessageBox.alert(title, message);
            var dlg = box.getDialog();
            var shimEl = document.createElement('iframe');
            var el = dlg.getEl();
            el.insertSibling(shimEl);
            
            dlg.on("hide", function() {
                shimEl.style.display = "none";
            }, this);
            
            var size              = el.getSize();
            shimEl.style.top      = el.getTop();
            shimEl.style.left     = el.getLeft();
            shimEl.style.width    = size.width;
            shimEl.style.height   = size.height;
            shimEl.style.position = "absolute";
            shimEl.style.display  = "block";
        },
            
        /**
         * Gets the name of the media type given its value.
         */
        getMediaName: function(mediaType) {
            switch (mediaType) {
                case 2:
                    return "Image";
                case 3:
                    return "Audio";
                case 4:
                    return "Video";
                default:
                    return "Unknown";
            }
        },

        /**
         * Formats a msec value to a hr:min:sec format.
         */
        formatTime: function(msec) {
            if (msec == null)
                return "0:00";
            var sec = Math.floor(msec / 1000);
            var min = Math.floor(sec / 60);
            sec = sec % 60;
            var hr = Math.floor(min / 60);
            min = min % 60;
            if (sec < 10)
                sec = "0" + sec;
            if (hr < 0)
                return hr + ":" + min + ":" + sec;
            else
                return min + ":" + sec;
        },
            
        /**
         * Formats a cents value into $dollars.cents.
         */
        formatPrice: function(total) {
            if (total == 0)
                return "$0.00";
            var dollars = Math.floor(total / 100);
            var cents = total % 100;
            return "$" + dollars + "." + cents;
        },
        
        /**
         * Formats a number into byte value.
         */
        formatBytes: function(numBytes) {
            var whole = 0;
            var frac = 0;
            var byteLabel = null;
            if (numBytes < 1024) {
                byteLabel = numBytes + " B";
            } else if (numBytes >= 1024 && numBytes < 1048576) {
                whole = Math.floor(numBytes / 1024);
                frac = OOMBLE.util.WebUtils.getFraction(numBytes % 1024);
                byteLabel = whole + "." + frac + " KB";
            } else if (numBytes > 1048576 && numBytes < 1073741824) {
                whole = Math.floor(numBytes / 1048576);
                frac = OOMBLE.util.WebUtils.getFraction(numBytes % 1048576);
                byteLabel = whole + "." + frac + " MB";
            } else {
                whole = Math.floor(numBytes / 1073741824);
                frac = OOMBLE.util.WebUtils.getFraction(numBytes % 1073741824);
                byteLabel = whole + "." + frac + " GB";
            }
            return byteLabel;
        },  
        
        /*
         * Gets the fractional part of a number.
         */
        getFraction: function(frac) {
            var digits = 0;
            var left = frac;
            while (left > 1) {
                left /= 10;
                digits++;
            }
            if (digits == 1)
                return frac * 10;
            else if (digits == 2)
                return frac;
            else {
                return Math.floor(frac / (Math.pow(10, (digits-2))));
            }
        },
        
        /**
         * Gets the element that is a decendant of a root node.
         */
        getElementById: function(root, id) {
            if (root == null)
                return null;
            // Check if this is an element.
            if (root.tagName == null)
                root = document.getElementById(root);
            if (root == null || root.id == id || id == null)
                return root;
            var children = root.childNodes;
            if (children == null || children.length == 0)
                return null;
            for (var i = 0; i < children.length; i++) {
                var child = children[i];
                var el = OOMBLE.util.WebUtils.getElementById(child, id);
                if (el != null)
                    return el;
            }
            return null;
        },

        /**
         * Gets the first element that is a decendant of a root node.
         */
        getElementByTagName: function(root, name) {
            if (root == null)
                return null;
            if (this.isEqual(root.tagName, name))
                return root;
            // Check if this is an element.
            var children = root.childNodes;
            if (children == null || children.length == 0)
                return null;
            for (var i = 0; i < children.length; i++) {
                var child = children[i];
                var el = OOMBLE.util.WebUtils.getElementByTagName(child, name);
                if (el != null)
                    return el;
            }
            return null;
        },

        /**
         * Add a clone method to the object class.
         */
        clone: function(deep) {
            var objectClone = new this.constructor();
            for (var property in this)
                if (!deep)
                    objectClone[property] = this[property];
                else if (typeof this[property] == 'object')
                    objectClone[property] = this[property].clone(deep);
                else
                    objectClone[property] = this[property];
            return objectClone;
        },
        
        /**
         * Fills in a template DOM element from 
         * the properties in a data variable.
         */
        fillTemplate: function(template, data) {
            for (name in data) {
                var obj = OOMBLE.util.WebUtils.getElementById(template, name);
                if (obj == null)
                    continue;
                var tagName = obj.tagName;
                if (tagName == "IMG")
                    obj.src = data[name];
                else if (tagName == "A")
                    obj.href = data[name];
                else if (data[name] != null)
                    obj.innerText = data[name];
                else if (data[name] == null)
                    obj.innerText = "";
            }
        },
            
        /**
         * Shortens a string and addes ellipses if too long.
         */
        truncateString: function(str, len) {
            if (str == null || str.length <= len)
                return str;
            return str.substring(0, len) + "...";
        },
            
        /**
         * Sets up an image to be resized once loaded.
         */
        resizeImage: function(img, url, maxHeight, maxWidth, resizeFunc, resizeScope, userData) {
            
            // Hide the image until it is loaded and resized.
            img.style.visibility = "hidden";
            
            // Set the handler to resize the image when loaded
            // and the height and width of the image are known.
            img.onload = function() {

                var height = this.height;
                var width = this.width;
                
                // Do nothing if the image is smaller than the bounding rectangle.
                if (height <= maxHeight && width <= maxWidth) {
                    this.style.height = height;
                    this.style.width = width;
                    this.style.visibility = 'visible';
                    
                    // Notify that resize is finished.
                    if (resizeFunc) {
                        resizeFunc.call(resizeScope, this, userData);
                    }
                    return;
                }
                
                // Calculate the image horizontal and vertical aspect ratios.
                var horzAspect = width / maxWidth;
                var vertAspect = height / maxHeight;
                
                // Adjust the image size.
                var h = 0;
                var w = 0;
                if (horzAspect < vertAspect) {
                    w = (width * maxHeight) / height;
                    h = maxHeight;
                } else {
                    w = maxWidth;
                    h = (height * maxWidth) / width;
                }
                
                this.style.width = w + "px";
                this.style.height = h + "px";
                
                // Now that the image is resized, display it.
                this.style.visibility = 'visible';
                
                // Notify that resize is finished.
                if (resizeFunc) {
                    resizeFunc.call(resizeScope, this, userData);
                }
            };
            
            // Set the source after the onload handler so that
            // we make sure the handler is called.  IE will send
            // the event immediately if the image is cached.
            img.src = url;
        },
        
        /**
         * Tells if a string starts with the given prefix.
         */
        startsWith: function(target, prefix) {
            if (prefix == null || target == null)
                return false;
            if (prefix.length > target.length)
                return false;
            var part = target.substring(0, prefix.length);
            if (prefix.toLowerCase() == part.toLowerCase())
                return true;
            return false;
        },
        
        /**
         * Tells if a string ends with the given postfix.
         */
        endsWith: function(target, postfix) {
            if (postfix == null || target == null)
                return false;
            if (postfix.length > target.length)
                return false;
            var part = target.substring(target.length - postfix.length);
            if (postfix.toLowerCase() == part.toLowerCase())
                return true;
            return false;
        },
        
        /**
         * Gets the folder part of a url pathname.
         */
        getUrlPath: function(path) {
            if (path == null)
                return null;
            var index = path.lastIndexOf("/");
            if (index == -1)
                return path;
            return path.substring(0, index);
        },
            
        /**
         * Attaches an element to a new target location.
         */
        moveElement: function(sId, tId) {
            var sEl = YAHOO.util.Dom.get(sId);
            var tEl = YAHOO.util.Dom.get(tId);
            var parent = sEl.parentNode;
            if (parent != null)
                parent.removeChild(sEl);
            tEl.appendChild(sEl);
            return sEl;
        },
        
        /**
         * Tells if two strings are equal ignoring case.
         */
        isEqual: function(str1, str2) {
            if (str1 == null && str2 == null)
                return true;
            if (str1 == null || str2 == null)
                return false;
            return (str1.toLowerCase() == str2.toLowerCase());
        },
        
        /**
         * Tells if the string is empty or null.
         */
        isEmpty: function(str) {
            return (str == null || str == "") ? true : false;
        },

        /**
         * Tells if a variable is undefined or null.
         */
        isNull: function(value) {
            return (value === undefined || value == null) ? true : false;
        },

        /**
         * Removes an element from the DOM.  
         * Returns a handle to the removed element.
         */
        removeElement: function(id) {
            var wEl = YAHOO.util.Dom.get(id);
            var parent = wEl.parentNode;
            if (parent != null)
                parent.removeChild(wEl);
            return wEl;
        },

        /**
         * Attaches an element to a parent node.
         */
        attachElement: function(pId, sId) {
            var pEl = YAHOO.util.Dom.get(pId);
            var sEl = YAHOO.util.Dom.get(sId);
            pEl.appendChild(sEl);
        },
            
        /**
         * Creates a new GUID.
         */
        newGuid: function() {
            var result, i, j;
            result = '';
            for (j = 0; j < 32; j++) {
                if (j == 8 || j == 12|| j == 16|| j == 20)
                    result = result + '-';
                i = Math.floor(Math.random()*16).toString(16).toLowerCase();
                result = result + i;
            }
            return result;
        },
        
        /**
         * Creates a random string of given char radix.
         */
        newRandString: function(numChars, radix) {
            if (numChars <= 0)
                return null;
            var result = '';
            for (var i = 0; i < numChars; i++) {
                var value = Math.floor(Math.random() * radix).toString(radix).toLowerCase();
                result += value;
            }
            return result;
        },
        
        /**
         * Creates a random number.
         */
        newRandNumber: function() {
            return OOMBLE.util.WebUtils .newRandString(16, 10);
        },

        /**
         * Replaces the host with the exact server host.  The host
         * in the url may be different i.e., www.oomble.com v. oomble.com.
         */
        replaceWithServerHost: function(url) {

            // Replace the target server with the proxy server.
            var pattern = /^(\w+):\/\/([\w.]+)\/?(.*)/;
            var parts = url.match(pattern);
            
            // If in a bad format, just use as-is.
            if (parts == null)
                return url;
    
            if (parts.length < 4)
                return url;
            
            var protocol = parts[1];
            var path = parts[3];
            var hostname = document.location.hostname;
            return protocol + "://" + hostname + "/" + path;
        }
    };
}();
