/*
 * Copyright (c) 2007 Collactive. All rights reserved.
 */
if (typeof(Collactive) == 'undefined') {
	Collactive = {};
}

// DOM Utilities (cross-browser functionality, etc.)
Collactive.DomUtils = {
	// getElementById ($ function from Prototype)
	gEBI: function() {
	    var results = [], element;
	    for (var i = 0; i < arguments.length; i++) {
	        element = arguments[i];
	        if (typeof element == 'string')
	            element = this.getDocument().getElementById(element);
	        results.push(element);
	    }
	    return results.length < 2 ? results[0] : results;
	},

	// Gets one element, by name and index
	gEBN: function(name, index) {
	    if (index == null) {
	        index = 0;
	    }

	    var el = this.getDocument().getElementsByName(name);
	    if (el != null && el.length > 0) {
	        return el[index];
	    }
	    return null;
	},

	// Gets one element, by tag and index
	gEBT: function(name, index) {
	    if (index == null) {
	        index = 0;
	    }

	    var el = this.getDocument().getElementsByTagName(name);
	    if (el != null && el.length > 0) {
	        return el[index];
	    }
	    return null;
	},

    // We save a list of listeners so that we can clean up
    listeners: [],

    // Registers an event listener
    registerListener: function(element, eventType, functionToCall, keepDefault) {
        var newFunc = function(event) {
        	var target;
        	if (!event) event = window.event;
        	if (event.target) target = event.target;
        	else if (event.srcElement) target = event.srcElement;
        	if (target && target.nodeType == 3) target = target.parentNode; // defeat Safari bug

            var rc;
            try {
                rc = functionToCall(event, target);
            } catch (e) {
                if (typeof(console) != 'undefined' && console.error) {
                    console.error("Exception in listener for " + eventType + ": " + e);
                } else if (typeof(GM_log) != 'undefined') {
					GM_log("ERROR - Exception in listener for " + eventType + ": " + e);
				}
            }
            if (keepDefault == false || (keepDefault == null && rc == false)) {
                if (event.preventDefault) {
                    event.preventDefault();
                }
                return false;
            }
        };
        var item = element;

		if (item == window && eventType == 'unload') {
			this.unloadListeners.push(newFunc);
		} else {
	        if (item.addEventListener) {
	            item.addEventListener(eventType, newFunc, true);
	        } else if (item.attachEvent) {
	            item.attachEvent('on' + eventType, newFunc);
	        } else {
	            item['on' + eventType] = newFunc;
	        }

	        this.listeners.push({
	            element: item,
	            name: eventType,
	            listener: newFunc
	        });
		}

        return newFunc;

    },


    // Removes a listener
    removeListener: function(element, name, listener) {
		if (element == window && name == 'unload') {
	        for (var i = 0; i < this.unloadListeners.length; i++) {
	            var l = this.unloadListeners[i];
	            if (l == listener) {
	                this.unloadListeners.splice(i, 1);
	                return true;
	            }
	        }
		} else {
	        if (element.removeEventListener) {
	            element.removeEventListener(name, listener, true);
	        } else if (element.detachEvent) {
	            element.detachEvent('on' + name, listener);
	        }

	        for (var i = 0; i < this.listeners.length; i++) {
	            var l = this.listeners[i];
	            if (l.element == element && l.name == name && l.listener == listener) {
	                this.listeners.splice(i, 1);
	                return true;
	            }
	        }
		}

        return false;
    },

    // Removes all listeners, optionally only of a given type
    removeAllListeners: function(eventType) {
        for (var i = 0; i < this.unloadListeners.length; i++) {
            var wasRemoved = this.removeListener(window, 'unload',  this.unloadListeners[i]);
            if (wasRemoved) {
				i--;
			}
		}
        for (var i = 0; i < this.listeners.length; i++) {
            var l = this.listeners[i];
            if (eventType != null && eventType != l.name) {
                continue;
            }
            var wasRemoved = this.removeListener(l.element, l.name, l.listener);
            if (wasRemoved) {
                i--;
            }
        }
    },


    // Whether we've registered the unload handler yet
    unloadRegistered: false,
	unloadListeners: [],

    // Registers the window.unload event so that we clear up event handlers
    registerUnload: function() {
        if (this.unloadRegistered) {
            return;
        }

        var newFunc = Collactive.bind(this, function(event) {
            for (var i = 0; i < this.unloadListeners.length; i++) {
                this.unloadListeners[i](event);
            }
            this.removeAllListeners();
        });

        if (window.addEventListener) {
            window.addEventListener("unload", newFunc, true);
        } else if (window.attachEvent) {
            window.attachEvent('onunload', newFunc);
        } else {
            window['onunload'] = newFunc;
        }

        this.unloadRegistered = true;
    },


    // Add something to our body
    addToBody: function(element) {
        var bodies = this.getDocument().getElementsByTagName("BODY");
        var body = bodies[0];
        if (body == null) {
            body = this.getDocument().createElement("body");
            this.getDocument().appendChild(body);
        }
        body.appendChild(element);
    },


    // Adds a stylesheet to the document
	addStyleSheet: function(cssText) {
		var heads = this.getDocument().getElementsByTagName('head');
		var head = heads[0];
		var style;
		style = this.getDocument().createElement('style');
		style.type = 'text/css';
		if (navigator.appName.indexOf("Microsoft") != -1) {
			style.styleSheet.cssText = cssText;
		} else {
			style.innerHTML = cssText;
		}

		head.appendChild(style);
	},


    // Gets the dimension of the current window
    getWindowDimensions: function() {
        if (parseInt(navigator.appVersion)>3) {
            if (navigator.appName == "Netscape") {
                return { width: window.innerWidth, height: window.innerHeight,
						 xOffset: window.pageXOffset, yOffset: window.pageYOffset };
            }
            if (navigator.appName.indexOf("Microsoft") != -1) {
                return { width: (this.getDocument().documentElement.clientWidth ? this.getDocument().documentElement.clientWidth : this.getDocument().body.clientWidth),
						 height: (this.getDocument().documentElement.clientHeight ? this.getDocument().documentElement.clientHeight : this.getDocument().body.clientHeight),
						 xOffset: (this.getDocument().documentElement.scrollLeft ? this.getDocument().documentElement.scrollLeft : this.getDocument().body.scrollLeft),
						 yOffset: (this.getDocument().documentElement.scrollTop ? this.getDocument().documentElement.scrollTop : this.getDocument().body.scrollTop) };
            }
        }

    },


    // Gets the dimensions of the current document
    getDocumentDimensions: function() {
        if (parseInt(navigator.appVersion) > 3) {
            if (navigator.appName == "Netscape") {
                return { width: this.getDocument().documentElement.scrollWidth,
                         height: this.getDocument().documentElement.scrollHeight };
            }
            if (navigator.appName.indexOf("Microsoft") != -1) {
                return { width: this.getDocument().body.scrollWidth, height: this.getDocument().body.scrollHeight };
            }
        }
    },


    // Finds the absolute position of an element
    findPosition: function(elem) {
        var leftPos = 0;
        var topPos = 0;
        while (elem != null) {
            leftPos += elem.offsetLeft;
            topPos += elem.offsetTop;
            elem = elem.offsetParent;
        }
        return { left: leftPos, top: topPos };
    },


    // Scrolls to an element
    scrollToElement: function(elem) {
        var pos = this.findPosition(elem);
        window.scrollTo(pos.left, pos.top);
    },


    // Gets top, left, width, height of an element
    getElementInfo: function(elem) {
        if (elem instanceof Array) {
            // Union of several elements
            var elems = elem;
            var top = null;
            var left = null;
            var right = null;
            var bottom = null;
            for (var i = 0; i < elems.length; i++) {
                elem = elems[i];
                var info = Collactive.DomUtils.getElementInfo(elem);
                if (top == null || info.top < top) {
                    top = info.top;
                }
                if (left == null | info.left < left) {
                    left = info.left;
                }
                if (right == null || (info.left + info.width) > right) {
                    right = info.left + info.width;
                }
                if (bottom == null || (info.top + info.height) > bottom) {
                    bottom = info.top + info.height;
                }
            }
            return { left: left, top: top, width: right - left, height: bottom - top };

        } else {
            // Just one element
            var pos =  Collactive.DomUtils.findPosition(elem);
            return { left: pos.left, top: pos.top, width: elem.offsetWidth, height: elem.offsetHeight };
        }
    },


    // Finds a link with the given address as a string, regexp or by giving a comparator function
    findLink: function(spec) {
        var matcher = null;

        if (typeof(spec) == "string") {
            matcher = function(a) { return (a.href != null && a.href == spec) };
        } else if (spec instanceof RegExp) {
            matcher = function(a) { return (a.href != null && (a.href.match(spec) != null)); };
        } else if (typeof(spec) == "function") {
            matcher = spec;
        } else {
            throw "Invalid argument";
        }

        for (var i in this.getDocument().links) {
            var link = this.getDocument().links[i];

            try {
              if (matcher(link)) {
                  return link;
                }
            } catch (e) {
                // Ignore exceptions in matcher, treat as no match (useful for null comparisons)
            }
        }

        return null;
    },


    // Finds a form with the given address
    findForm: function(url) {
        for (var i in this.getDocument().forms) {
            var form = this.getDocument().forms[i];
            if (form.action == null) {
                continue;
            }
            if ((typeof(url) == "string" && form.action == url) || form.action.match(url)) {
                return form;
            }
        }
        return null;
    },
	findElementByName: function(name, spec) {
        var matcher = null;

        if (typeof(spec) == "string") {
            matcher = function(a) { return (a.src != null && a.src == spec) };
        } else if (spec instanceof RegExp) {
            matcher = function(a) { return (a.src != null && (a.src.match(spec) != null)); };
        } else if (typeof(spec) == "function") {
            matcher = spec;
        } else {
            throw "Invalid argument";
        }
		elements = this.getDocument().getElementsByName(name);
		var l = elements.length;
        for (var i = 0; i < l; ++i) {
            var elem = elements[i];
            try {
                if (matcher(elem)) {
                    return elem;
                }
            } catch (e) {
                // Ignore exceptions in matcher, treat as no match (useful for null comparisons)
            }
        }

        return null;
	},
	findElementsByTagName: function(name, spec) {
        var matcher = null;

        if (typeof(spec) == "string") {
            matcher = function(a) { return (a.innerHTML != null && a.innerHTML == spec) };
        } else if (spec instanceof RegExp) {
            matcher = function(a) { return (a.innerHTML != null && (a.innerHTML.match(spec) != null)); };
        } else if (typeof(spec) == "function") {
            matcher = spec;
        } else {
            throw "Invalid argument";
        }
		elements = this.getDocument().getElementsByTagName(name);
		var l = elements.length;
		results = []
        for (var i = 0; i < l; ++i) {
            var elem = elements[i];
            try {
                if (matcher(elem)) {
					results[results.length] = elem;
                }
            } catch (e) {
                // Ignore exceptions in matcher, treat as no match (useful for null comparisons)
            }
        }

        return results;
	},
    // Finds an image with the given address as a string, regexp or by giving a comparator function
    findImage: function(spec) {
        var matcher = null;

        if (typeof(spec) == "string") {
            matcher = function(a) { return (a.src != null && a.src == spec) };
        } else if (spec instanceof RegExp) {
            matcher = function(a) { return (a.src != null && (a.src.match(spec) != null)); };
        } else if (typeof(spec) == "function") {
            matcher = spec;
        } else {
            throw "Invalid argument";
        }

        for (var i in this.getDocument().images) {
            var img = this.getDocument().images[i];

            try {
                if (matcher(img)) {
                    return img;
                }
            } catch (e) {
                // Ignore exceptions in matcher, treat as no match (useful for null comparisons)
            }
        }

        return null;
    },

    // Finds an input by its type and value
    findInput: function(type, value) {
        var inputs = this.getDocument().getElementsByTagName("input");
        for (var i = 0; i < inputs.length; i++) {
            if (inputs[i].type != null && inputs[i].type == type) {
                if (value == null) {
                    return inputs[i];
                } else if (inputs[i].value != null && inputs[i].value == value) {
                    return inputs[i];
                }
            }
        }

        return null;
    },

    findInputByTitle: function(title) {
        var inputs = this.getDocument().getElementsByTagName("input");
        for (var i = 0; i < inputs.length; i++) {
            if (inputs[i].getAttribute("title") != null &&
                inputs[i].getAttribute("title") == title) {
                    return inputs[i];
            }
        }
        return null;
    },

    // Calls an existing handler. Useful when trying to 'click' A elements, whose
    // behavior is not consistent (a.click() does not work in Firefox)
    callHandler: function(elem, eventName, callback, errorCallback) {
        // Firefox quirk: you can't do func=elem[eventName] and then call func(),
        // you must call elem[eventName]() directly!
        var mode = null;
        if (elem[eventName] != null) {
            // IE is here - elem.click() can be called directly
            mode = 1;
        } else if (elem["on" + eventName] != null) {
            // Firefox - you must call elem.onclick()
            mode = 2;
        } else {
            throw "No event handler for " + elem + " - " + eventName;
        }

        this.getWindow().setTimeout(function() {
            try {
                if (mode == 1) {
                    elem[eventName]();
                } else if (mode == 2) {
                    elem["on" + eventName]();
                }

                if (callback != null) {
                    callback();
                }
            } catch (e) {
                if (errorCallback != null) {
                    errorCallback(e);
                }
            }
        }, 100);
    },


    // Finds a form in the given elements ancestors
    findContainingForm: function(elem) {
        while (elem.parentNode != null) {
            elem = elem.parentNode;
            if (elem.tagName == "FORM") {
                return elem;
            }
        }

        return null;
    },


    // Returns whether the given element is contained in the given parent
    isContained: function(elem, parent) {
        if (elem.parentNode == parent) {
            return true;
        }

        var newElem = elem.parentNode;
        if (newElem == elem || newElem == null) {
            return false;
        }

        return this.isContained(newElem, parent);
    },


    // Removes all children of the given node
    removeAllChildren: function(node) {
        while (node.firstChild != null) {
            node.removeChild(node.firstChild);
        }
    },


    // Checks whether an image is loaded
    isImageLoaded: function(img) {
        if (!img.complete) {
            // IE only
            return false;
        }

        // Mozilla
        if (typeof img.naturalWidth != "undefined" && img.naturalWidth == 0) {
            return false;
        }

        // No other way of checking: assume it's ok.
        return true;
    },


    // Waits for a condition to become true by polling it and then calls a callback
    // All times are in millis
    waitForCondition: function(condition, callback, timeout, pollInterval) {
        this.runUntilSuccess(function() {
            if (condition() != true) {
                throw "Condition not true yet";
            }
        }, callback, timeout, pollInterval);
    },


    // Tries perform an action until it succeeds - i.e. does not throw an exception
    // All times are in millies
    runUntilSuccess: function(actionCallback, finishCallback, timeout, pollInterval) {
        var expireAt = null;
        if (timeout != null) {
            expireAt = new Date().getTime() + timeout;
        }

        if (pollInterval == null) {
            pollInterval = 100;
        }

        this._runOnce(actionCallback, finishCallback, pollInterval, expireAt);
    },

    getWindow: function() {
        if (typeof(unsafeWindow) != 'undefined') {
            return unsafeWindow;
        }
        return window;
    },

    getDocument: function() {
        return this.getWindow().document;
    },

    // Implementation for tryAction - tries run the action once
    _runOnce: function(actionCallback, finishCallback, pollInterval, expireAt) {
        if (expireAt != null && new Date().getTime() > expireAt) {
            finishCallback(false);
            return;
        }

        try {
            actionCallback();
        } catch (e) {
            setTimeout(function() {
                Collactive.DomUtils._runOnce(actionCallback, finishCallback, pollInterval, expireAt);
            }, pollInterval);
            return;
        }

        if (finishCallback != null) {
            finishCallback(true);
        }
    },

    isIE: function() {
        return (navigator.userAgent.toLowerCase().indexOf('msie') != -1);
    },

    isIE7: function() {
        return (navigator.userAgent.toLowerCase().indexOf('msie 7.0') != -1);
    },

    isFirefox3: function() {
        return (navigator.userAgent.toLowerCase().indexOf('firefox/3.0') != -1);
    }

};

Collactive.DomUtils.registerUnload();