/*XWebMenu v3 Alpha by Jeremy McPeak @ http://www.wdonline.com (c) 2009

Current Features
- API for creating and populating menu on a site (albeit different from the past APIs)
- Ability to create and populate menu with JSON
- Ability to create the populate menu with XML
- Closures galore; There may be some questionable statements that may cause leaks. I haven't
     put much work into that, but I will before the final.
- Enable/disable items.
- Icons for menu items (not menu strip items)

Need to Add Features
- Image based menus?
- Context menu
- Create menu by parsing HTML in the document?

This code is released under the Creative Commons Attribution-Share Alike 3.0 license. You can copy, distribute, and 
display the provided code, and all derivative works based upon it, as long as I am given credit. Please 
keep this disclaimer at the top of the file.
*/

var xwebMenu = {};

(function() {
    // private stuff
    var menuNum = 0;
    var objNum = 0;

    function getMenuName() {
        return "xwebMenu-" + menuNum++;
    }

    function getObjId() {
        return "xwebMenu-object-" + objNum++;
    }

    // CSS class names here
    var menuBarClassName = "cls-xwebmenu-menubar";
    var menuBarItemClassName = "cls-xwebmenu-menubaritem";
    var menuClassName = "cls-xwebmenu-menu";
    var menuItemContainerClassName = "cls-xwebmenu-menu-itemcontainer";
    var menuItemClassName = "cls-xwebmenu-menu-itemcontainer-item";
    var menuItemArrowClassName = "cls-xwebmenu-menu-itemcontainer-item-arrow";

    // Browser sniffing
    var client = function() {
        var ua = navigator.userAgent.toLowerCase();
        var client = {
            isOpera: false,
            isSafari: false,
            isGecko: false,
            isIe: false,
            isSupported: false
        };

        if (window.opera) {
            client.isOpera = (parseFloat(window.opera.version()) > 9.5);
        } else if (/applewebkit\/(\S+)/.test(ua)) {
            client.isSafari = (parseInt(RegExp["$1"], 10) > 522);
        } else if (/gecko\/(\d{8})/.test(ua)) {
            client.isGecko = (parseInt(RegExp["$1"], 10) > 20020512);
        } else if (/msie ([^;]+)/.test(ua)) {
            client.isIe = (parseInt(RegExp.$1, 10) >= 6);
        }

        client.isSupported = (client.isGecko || client.isOpera || client.isIe || client.isSafari)

        return client;
    } ();

    // this is zero-based, as opposed to other "enums"
    // it's only because works out better
    var itemState = {
        normal: 0,
        hover: 1,
        active: 2,
        disabled: 3
    };

    /** XML Functions **/
    function createXHR() {
        if (window.XMLHttpRequest) {
            var oHttp = new XMLHttpRequest();
            return oHttp;
        } else if (window.ActiveXObject) {
            var versions = [
            "MSXML2.XmlHttp.6.0",
            "MSXML2.XmlHttp.3.0"
        ];

            for (var i = 0; i < versions.length; i++) {
                try {
                    oHttp = new ActiveXObject(versions[i]);
                    return oHttp;
                } catch (error) {
                    //do nothing here
                }
            }
        }

        throw new Error("XMLHttpRequest is not supported by the browser.");
    }

    function parseXml(options, doc) {
        var docEl = doc.documentElement;

        options.id = docEl.getAttribute("id") || options.id || null;
        options.behavior = x.behavior[docEl.getAttribute("behavior").toLowerCase()] || options.behavior || null;

        options.items = parseItems(docEl.selectNodes("items/item"));

        this.__load(options);
    }

    function parseItems(itemNodes) {
        var items = [];

        for (var i = 0; i < itemNodes.length; i++) {
            var itemNode = itemNodes[i];
            var item = {};

            var text = itemNode.getAttribute("text");
            var imagesNode = itemNode.selectSingleNode("images");
            var enabled = itemNode.getAttribute("enabled");

            // if text isn't null, or if it is null and imageNode isn't
            // then it's not a separator
            if (text != null || (text == null && imagesNode != null)) {
                item.text = itemNode.getAttribute("text");
                item.href = itemNode.getAttribute("href");
                item.icon = itemNode.getAttribute("icon");

                if (typeof enabled === "string") {
                    if (enabled.toLowerCase() == "false") {
                        item.enabled = false;
                    }
                } else {
                    item.enabled = true;
                }

                if (imagesNode) {
                    var defaultImage = imagesNode.getAttribute("normal");
                    var hoverImage = imagesNode.getAttribute("hover");
                    var activeImage = imagesNode.getAttribute("active");

                    item.images = {
                        normal: defaultImage,
                        hover: hoverImage,
                        active: activeImage
                    };
                }

                var menu = itemNode.selectSingleNode("menu");

                if (menu) {
                    item.menu = {};
                    item.menu.id = menu.getAttribute("id") || null;

                    item.menu.items = parseItems(menu.selectNodes("items/item"));
                }

                // check for events
                var eventNodes = itemNode.selectNodes("clickEvents/event");
                if (eventNodes && eventNodes.length > 0) {
                    item.clickEvents = [];

                    for (var j = 0; j < eventNodes.length; j++) {
                        var eventNode = eventNodes[j];

                        var context = eventNode.getAttribute("context");
                        var fp;

                        if (context) {
                            fp = [new Function(eventNode.text), window[context]];
                        } else {
                            fp = new Function(eventNode.text);
                        }

                        item.clickEvents.push(fp);
                    }

                }
            }

            items.push(item);
        }

        return items;
    }

    function getXmlFile(options, context) {
        var req = createXHR();
        var obj;

        req.onreadystatechange = function() {
            if (req.readyState == 4) {
                if (req.status == 200) {
                    parseXml.apply(context, [options, req.responseXML]);
                }
            }
        };

        req.open("get", options.xmlFile);

        req.send(null);
    }

    // begin augmenting xwebMenu
    var x = xwebMenu;

    //behavior enum
    x.behavior = {
        normal: 1,
        hover: 2
    };

    // might implement this more later...
    var orientation = {
        dropDown: 1,
        sideShow: 2
    };

    x.create = function(options) {
        if (client.isSupported) {
            options = options || {};

            return new x.XWebMenu(options);

        } else {
            throw new Error("XWebMenu Error: Unsupported browser.");
        }
    };

    x.XWebMenu = function(options) {
        options = options || {};

        if (options.xmlFile) {
            getXmlFile(options, this);
        } else {
            this.__load(options);
        }
    };

    x.XWebMenu.prototype = {
        constructor: x.XWebMenu,
        __load: function(options) {
            var parentEl = options.parentElement;

            this._id = options.id || getMenuName();
            this.__name = this._id;
            this.__orientation = orientation.dropDown;
            this.items = [];
            this.behavior = options.behavior || x.behavior.normal;
            this._activeMenu = null;
            this._queueTimeout = null;

            this._draw(parentEl);

            // handle the document's click event
            document.addEventListener("click", function(that) {
                return function(e) {
                    var eSrc = (window.event) ? event.srcElement : e.target;
                    that._handleDocumentClick(eSrc);
                };
            } (this), false);

            if (options.items) {
                for (var i = 0; i < options.items.length; i++) {
                    this.add(options.items[i]);
                }
            }
        },
        _draw: function(parentElement) {
            parentElement = parentElement || document.body;

            var el = document.createElement("div");
            el.id = this._id;
            el.className = menuBarClassName;
            parentElement.appendChild(el);

            el.onmouseout = function(that) {
                return function(e) {
                    if (that.behavior === x.behavior.hover) {
                        that._queueForClose();
                    }
                };
            } (this);

            el.onmouseover = function(that) {
                return function(e) {
                    if (that.behavior === x.behavior.hover) {
                        if (that._queueTimeout != null) {
                            that._clearQueue();
                        }
                    }
                };
            } (this);

            el = null;
        },
        _queueForClose: function() {
            this._queueTimeout = window.setTimeout(function(that) {
                return function() {
                    if (that._activeMenu) {
                        that._activeMenu._menuItem.deactivate();
                    }
                };
            } (this), 1000);
        },
        _clearQueue: function() {
            window.clearTimeout(this._queueTimeout);
            this._queueTimeout = null;
        },
        _handleDocumentClick: function(eSrc) {
            var el = document.getElementById(this._id);

            if (!el.contains(eSrc)) {
                if (this._activeMenu) {
                    this._activeMenu._menuItem.deactivate();
                }
            }

            el = null;
        },
        add: function(options) {
            options.parent = this;

            var item = new x.MenuBarItem(options);

            this.items.push(item);
        },
        getItemById: function(id) {
            for (var i = 0; i < this.items.length; i++) {
                if (this.items[i]._id === id) {
                    return this.items[i];
                }
            }

            return null;
        },
        getIndexOfItem: function(item) {
            for (var i = 0; i < this.items.length; i++) {
                if (this.items[i] === item) {
                    return i;
                }
            }

            return -1;
        }
    };

    x.Menu = function(options) {
        options = options || {};
        this._menuItem = options.item;
        this._activeMenu = null;
        this._parent = options.parent;
        this.__name = (this._parent && this._parent.__name) || null;
        this._id = options.id || getObjId();
        this.items = [];

        if (this._menuItem) {
            this._draw();
        }

        if (options.items) {
            for (var i = 0; i < options.items.length; i++) {
                this.add(options.items[i]);
            }
        }
    };

    x.Menu.prototype = {
        constructor: x.Menu,
        _draw: function() {
            var divMain = document.getElementById(this.__name);

            var divOuter = document.createElement("div");
            divOuter.id = this._id;
            divOuter.className = menuClassName;

            var divItemContainer = document.createElement("div");
            divItemContainer.id = this._id + "-itemContainer";
            divItemContainer.className = menuItemContainerClassName;

            divMain.appendChild(divOuter);
            divOuter.appendChild(divItemContainer);

            divMain = null;
            divOuter = null;
            divItemContainer = null;
        },
        add: function(options) {
            options.parent = this;

            // if text isn't undefined, or if it is and images isn't
            if (typeof options.text !== "undefined" || (typeof options.text === "undefined" && typeof options.images !== "undefined")) {
                var item = new x.MenuItem(options); // it's a normal item
                this.items.push(item);
            } else { // it's a separator
                // not adding this to the items array yet
                // doing so may cause some issues with the positioning
                (new x.ItemSeparator(options));
            }
        },
        hide: function() {
            var el = document.getElementById(this._id);

            el.style.display = "none";

            if (this._activeMenu) {
                this._activeMenu._menuItem.deactivate();
            }

            el = null;
        },
        isShown: function() {
            var el = document.getElementById(this._id);
            var value = el.style.display === "block";

            el = null;
            return (value);
        },
        // coords is an object with x and y properties
        show: function(coords) {
            if (typeof coords === "undefined" || typeof coords.x === "undefined") {
                this._menuItem.activate();
            } else {
                var el = document.getElementById(this._id);

                el.style.left = coords.x + "px";
                el.style.top = coords.y + "px";

                el.style.display = "block";

                el = null;
            }
        },
        getItemById: function(id) {
            for (var i = 0; i < this.items.length; i++) {
                if (this.items[i]._id === id) {
                    return this.items[i];
                }
            }

            return null;
        },
        getIndexOfItem: function(item) {
            for (var i = 0; i < this.items.length; i++) {
                if (this.items[i] === item) {
                    return i;
                }
            }

            return -1;
        },
        removeItem: function(obj) {
            var item;
            if (typeof obj === "string") {
                item = this.getItemById(obj);
            } else if (obj.constructor === x.MenuItem) {
                item = obj;
            }
            else {
                throw new Error("XWebMenu MenuID: " + this._id + "; Method: removeItem(); Invalid Argument");
            }

            if (item != null) {
                this.items.pop(item);

                item.remove();
            }
        }
    };

    x.ItemSeparator = function(options) {
        this._id = getObjId();
        this._parent = options.parent;

        this._draw();
    };

    x.ItemSeparator.prototype = {
        constructor: x.ItemSeparator,
        _draw: function() {
            var el = document.createElement("div");
            var divItemContainer = document.getElementById(this._parent._id + "-itemContainer");

            el.className = "cls-xwebmenu-menu-itemseparator";
            divItemContainer.appendChild(el);

            el = null;
            divItemContainer = null;
        }
    };

    x.Item = function(options) {
        options = options || {};
        this._id = options.id || getObjId();
        this._parent = options.parent;
        this.__name = (this._parent && this._parent.__name) || null;
        this.text = options.text || "";
        this.href = options.href || "";
        this.icon = options.icon;
        this._clickEvents = [];
        this.__className = options._className;
        this.images = options.images;
        this._enabled = (typeof options.enabled !== "undefined") ? options.enabled : true;

        if (options.clickEvents && options.clickEvents.constructor === Array) {
            for (var i = 0; i < options.clickEvents.length; i++) {
                var obj = options.clickEvents[i];

                if (obj.constructor === Array) {
                    this.addClickHandler(obj[0], obj[1]);
                } else if (typeof obj === "function") {
                    this.addClickHandler(obj, this);
                }
            }
        }

        if (options.menu) {
            if (options.menu.constructor === x.Menu) {
                this.menu = options.menu;
                this.menu._parent = this._parent;
                this.menu._menuItem = this;
                this.menu.__name = this.__name;

                this.menu._draw();

            } else if (options.menu.constructor === String) {
                this.menu = new x.Menu({
                    id: options.menu,
                    parent: this._parent,
                    item: this
                });
            } else if (typeof options.menu === "object") {
                var menuOptions = options.menu;

                menuOptions.item = this;
                menuOptions.parent = this._parent;

                this.menu = new x.Menu(menuOptions);
            }
        } else {
            this.menu = null;
        }

        this._draw();
    };

    x.Item.prototype = {
        constructor: x.Item,
        _draw: function() {

        },
        _click: function(e) {
            if (this._enabled) {
                return this.__click(e);
            } else {
                return false;
            }
        },
        __click: function(e) {
            var eSrc = (window.event) ? event.srcElement : e.target;

            if (this._enabled) {
                if (this._clickEvents.length > 0) {
                    for (var i = 0; i < this._clickEvents.length; i++) {
                        var fp = this._clickEvents[i][0] || this._clickEvents[i];
                        var obj = this._clickEvents[i][1] || this;

                        // we need to close menus
                        var menuSys = this;

                        // find the menu strip
                        while (menuSys._id != this.__name) {
                            menuSys = menuSys._parent;
                        }

                        // close menus if it exists
                        if (menuSys) {
                            if (menuSys._activeMenu) {
                                menuSys._activeMenu._menuItem.deactivate();
                            }
                        }

                        fp.apply(obj, []);
                    }

                    return false;
                }

                if (!this.menu) {
                    return true;
                }
                else {
                    return false;
                }
            } else {
                return false;
            }
        },
        _calculateLocation: function() {
            var parentEl = document.getElementById(this._parent._id);
            var el = document.getElementById(this._id);

            var elX = el.offsetLeft;
            var elY = el.offsetTop;

            var direction = null;

            // the return object
            var coords = {
                x: 0,
                y: 0
            };

            // trying to do some smart positioning here
            if (this._parent.items.length > 1) {
                var index = this._parent.getIndexOfItem(this);

                // we grab the items around this item to check their orientation
                var itemsToCheck = [
                    this._parent.items[index - 1] || null,
                    this._parent.items[index + 1] || null
                ];

                for (var i = 0; i < itemsToCheck.length; i++) {
                    if (itemsToCheck[i]) {
                        var itemEl = document.getElementById(itemsToCheck[i]._id);

                        // check the orientation of the menu
                        // vertical first
                        if (itemEl.offsetLeft === elX) {
                            direction = orientation.sideShow;
                        }

                        // horizontal
                        if (itemEl.offsetTop === elY) {
                            direction = orientation.dropDown;
                        }

                        itemEl = null;
                        break;
                    }
                }
            } else {
                // only one item, so let's go with the defaults
                direction = (this._parent.constructor === x.XWebMenu) ? orientation.dropDown : orientation.sideShow;
            }

            if (direction === orientation.dropDown) {
                coords.x = elX;
                coords.y = elY + el.offsetHeight;
            } else {
                coords.x = parentEl.offsetLeft + parentEl.offsetWidth;
                coords.y = (this._parent.constructor === x.XWebMenu) ? elY : elY + parentEl.offsetTop;
            }

            el = null;

            return coords;
        },
        _changeState: function(state) {
            var item = document.getElementById(this._id);
            var suffix = "";

            var suffixes = [
                "",
                "-hover",
                "-active",
                "-disabled"
            ];

            item.className = this.__className + suffixes[state];

            if (this.images) {
                var img = document.getElementById(this._id + "-img");

                if (state === itemState.hover) {
                    img.src = this.images.hover;
                } else if (state === itemState.active) {
                    img.src = this.images.active;
                } else {
                    img.src = this.images.normal;
                }

                img = null;
            }

            item = null;
        },
        _mouseover: function(e) {
            if (this._enabled) {
                this.__mouseover(e);
            }
        },
        _mouseout: function(e) {
            if (this._enabled) {
                if (!this.menu || (this.menu && !this.menu.isShown())) {
                    this._changeState(itemState.normal);
                }
            }
        },
        createMenu: function(options) {
            this.menu = new x.Menu(options);
        },
        setHref: function(href) {
            var a = document.getElementById(this._id + "-a");
            a.href = href;

            a = null;
        },
        setText: function(text) {
            this.text = text;
            document.getElementById(this._id + "-a").innerHTML = text;
        },
        addClickHandler: function(fp, context) {
            if (typeof context === "undefined") {
                this._clickEvents.push(fp);
            } else {
                this._clickEvents.push([
                fp,
                context
            ]);
            }
        },
        deactivate: function() {
            this._changeState(itemState.normal);

            if (this.menu && this.menu.isShown()) {
                this.menu.hide();
                this._parent._activeMenu = null;
            }
        },
        _focus: function(e) {

        },
        disable: function() {
            this._enabled = false;
            this._changeState(itemState.disabled);
        },
        enable: function() {
            this._enabled = true;
            this._changeState(itemState.normal);
        },
        isEnabled: function() {
            return this._enabled;
        }
    };

    x.MenuBarItem = function(options) {
        options._className = menuBarItemClassName;
        x.Item.apply(this, [options]);
    };

    x.MenuBarItem.prototype = new x.Item();
    x.MenuBarItem.prototype.contructor = x.MenuBarItem;
    x.MenuBarItem.prototype._draw = function() {
        var a = document.createElement("a");
        a.className = menuBarItemClassName;
        a.id = this._id;

        if (!this.images) {
            a.innerHTML = this.text;
        } else {
            var img = document.createElement("img");
            img.id = this._id + "-img";
            img.src = this.images.normal;
        }

        a.href = this.href;
        a.onclick = function(that) {
            return function(e) {
                return that._click(e);
            };
        } (this);

        a.onmouseover = function(that) {
            return function(e) {
                return that._mouseover(e);
            };
        } (this);

        a.onmouseout = function(that) {
            return function(e) {
                return that._mouseout(e);
            };
        } (this);

        a.onfocus = function(that) {
            return function(e) {
                that._focus(e);
            };
        } (this);

        document.getElementById(this._parent._id).appendChild(a);

        if (img) {
            a.appendChild(img);
            img = null;
        }

        a = null;

        if (!this._enabled) {
            this._changeState(itemState.disabled);
        }
    };

    x.MenuBarItem.prototype.activate = function() {
        this._changeState(itemState.active);

        if (this.menu) {
            this.menu.show(this._calculateLocation());
            this._parent._activeMenu = this.menu;
        }
    };

    x.MenuBarItem.prototype.__mouseover = function(e) {
        if (this._parent._activeMenu && this._parent._activeMenu !== this.menu) {
            this._parent._activeMenu._menuItem.deactivate();

            if (this.menu) {
                this.activate();
            } else {
                this._changeState(itemState.hover);
            }
        } else if (this._parent.behavior === x.behavior.hover && this.menu) {
            if (this.menu) {
                if (!this.menu.isShown()) {
                    this.activate();
                }
            }
        } else {
            this._changeState(itemState.hover);
        }
    };

    x.MenuBarItem.prototype.__click = function(e) {
        if (this.menu) {
            if (!this.menu.isShown()) {
                this.activate();
            } else {
                this.deactivate();
                this._mouseover();
            }
        }

        return x.Item.prototype.__click.apply(this, [e]);
    };

    x.MenuItem = function(options) {
        options._className = menuItemClassName;
        x.Item.apply(this, [options]);
    };

    x.MenuItem.prototype = new x.Item();
    x.MenuItem.prototype.constructor = x.MenuItem;
    x.MenuItem.prototype._draw = function() {
        var divItemContainer = document.getElementById(this._parent._id + "-itemContainer");

        var divItem = document.createElement("div");
        divItem.id = this._id;
        divItem.className = menuItemClassName;

        var divArrow = document.createElement("div");
        divArrow.id = this._id + "-arrow";

        if (this.menu) {
            divArrow.className = menuItemArrowClassName;
        }

        var aLink = document.createElement("a");
        aLink.id = this._id + "-a";

        if (!this.images) {
            aLink.innerHTML = this.text;
        } else {
            var img = document.createElement("img");
            img.id = this._id + "-img";
            img.src = this.images.normal;
        }

        aLink.href = this.href;

        if (this.icon) {
            aLink.style.backgroundImage = "url(" + this.icon + ")"; //"transparent url(" + this.icon + ") no-repeat 2px 0px";
        }

        divItemContainer.appendChild(divItem);
        divItem.appendChild(divArrow);
        divArrow.appendChild(aLink);
        if (img) {
            aLink.appendChild(img);
            img = null;
        }

        divItem.onclick = function(that) {
            return function(e) {
                return that._click(e);
            };
        } (this);

        divItem.onmouseover = function(that) {
            return function(e) {
                return that._mouseover(e);
            };
        } (this);

        divItem.onmouseout = function(that) {
            return function(e) {
                return that._mouseout(e);
            };
        } (this);

        divItemContainer = null;
        divItem = null;
        divArrow = null;
        aLink = null;

        if (!this._enabled) {
            this._changeState(itemState.disabled);
        }
    };

    x.MenuItem.prototype.activate = function() {
        this._changeState(itemState.hover);

        if (this.menu) {
            this.menu.show(this._calculateLocation());
            this._parent._activeMenu = this.menu;
        }
    };

    x.MenuItem.prototype.__mouseover = function(e) {
        if (this._parent._activeMenu && this._parent._activeMenu !== this.menu) {
            this._parent._activeMenu._menuItem.deactivate();
        }

        if (this.menu) {
            this.activate();
        } else {
            this._changeState(itemState.hover);
        }
    };

    x.MenuItem.prototype.__click = function(e) {
        if (this.menu) {
            if (!this.menu.isShown()) {
                this.activate();
            } else {
                this.deactivate();
                this._mouseover();
            }
        }

        return x.Item.prototype.__click.apply(this, [e]);
    };


    // create addEventListener for non-supporting browsers
    if (typeof addEventListener === "undefined") {
        document.addEventListener = function(eventType, listener) {
            eventType = "on" + eventType;

            document.attachEvent(eventType, listener);
        };
    }

    // extending non-IE browsers
    if (typeof __defineGetter__ === "function") {

        if (typeof Element.prototype.contains !== "function") {
            Element.prototype.contains = function(el) {
                if (el === this) {
                    return true;
                }

                if (el === null) {
                    return false;
                }

                return this.contains(el.parentNode);
            };
        }

        if (typeof Element.prototype.selectSingleNode !== "function") {
            Element.prototype.selectSingleNode = function(xpath) {
                var result = this.ownerDocument.evaluate(xpath, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);

                if (result != null) {
                    return result.singleNodeValue;
                } else {
                    return null;
                }
            };
        }

        if (typeof Element.prototype.selectNodes !== "function") {
            Element.prototype.selectNodes = function(xpath) {
                var result = this.ownerDocument.evaluate(xpath, this, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);

                var nodes = new Array;

                if (result != null) {
                    var el = result.iterateNext();
                    while (el) {
                        nodes.push(el);
                        el = result.iterateNext();
                    }
                }

                return nodes;
            };
        }

        if (typeof Node.prototype.text == "undefined") {
            Node.prototype.__defineGetter__("text", function() {
                var children = this.childNodes;
                var length = children.length;
                var text = [];

                for (var i = 0; i < length; i++) {
                    text[i] = children[i].nodeValue;
                }

                return text.join("");
            });
        }
    }
})();