/**
 * Introducing Menu.js
 *
 * Menu.js is a free, small JavaScript (drop-down) menu for developers. It's
 * unobtrusive and only requires a HTML unordered list and your own CSS styles.
 * http://www.menujs.net/
 *
 * Requires Prototype JS version 1.5.0 or greater.
 * (Also supports version 1.6.0.*, avoiding all deprecated methods)
 * http://www.prototypejs.org/
 * _____________________________________________________________________________
 *
 * As of version 1.2, released under the MIT License:
 *
 * Copyright (c) 2007-2009 Charming Design, Niek Kouwenberg
 * http://www.charmingdesign.net/
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * _____________________________________________________________________________
 *
 * Special thanks to CARE Internet Services B.V.
 * http://www.care.nl/
 */
Menu = {

    /* Version of the Menu class */
    Version: "1.3.1",


    /*
     * CONSTANTS
     */

    /* Constant for a horizontal menu */
    HORIZONTAL: 1,

    /* Constant for a vertical menu */
    VERTICAL: 2,


    /*
     * MENU
     */

    /* Hold the ID of the menu */
    _menuId: null,

    /* Hold the menu UL node */
    _menuNode: null,


    /*
     * TEMPORARY VARIABLES
     */

    /* Hold the show timer */
    _showTimeout: null,

    /* Hold the hide timer */
    _hideTimeout: null,

    /* Will hold the link, submenu and level of the active menu node */
    _activeNodes: new Array(),


    /*
     * OPTIONS
     */

    /* Orientation of the menu (horizontal or vertical) */
    _orientation: 1,

    /* Time in milliseconds before showing the sub menu */
    _showPause: 0,

    /* Time in milliseconds before hiding the sub menu */
    _hidePause: 1000,

    /* Opacity (0 = transparent, 1 = opaque) */
    _opacity: 1,


    /*
     * METHODS
     */

    /**
     * Sets the time to wait before hiding the sub menu.
     *
     * @param    int        secs
     *
     * @deprecated Please use the options argument for Menu.init() instead.
     */
    setHidePause: function(secs)
    {
        Menu._hidePause = secs * 1000;

        /* deprecation warning */
        alert("Deprecated method Menu.setHidePause() used: Please use the options argument for Menu.init().");
    }, // function setHidePause

    /**
     * Initializes the dynamic menu.
     *
     * @param    string    menuId
     * @param    object    options
     *
     * Available options:
     * - orientation (int; one of: Menu.HORIZONTAL, Menu.VERTICAL)
     * - showPause (float; in seconds; default: 0.0)
     * - hidePause (float; in seconds; default: 1.0)
     * - opacity (float; 0 = transparent, 1 = opaque; default: 1; transparency of the sub menu)
     *
     * Example usage:
     * <script type="text/javascript">
     *     Menu.init("menu", {"hidePause": 0.5});
     * </script>
     *
     * This method can be called after document load, but it is preferred to be
     * called directly from your page (HTML <head>, before document load). This
     * way the menu loads faster and can be interacted with much sooner.
     */
    init: function(menuId, options)
    {
        /* Save menu ID (fall back to the default ID "menu") */
        Menu._menuId = (typeof menuId == "string") ? menuId : "menu";

        /* Save options */
        if (options)
        {
            /* Orientation */
            if (options.orientation != undefined)
            {
                Menu._orientation = options.orientation;
            }
            /* Show timeout in seconds */
            if (options.showPause != undefined)
            {
                Menu._showPause = options.showPause * 1000;
            }
            /* Hide timeout in seconds */
            if (options.hidePause != undefined)
            {
                Menu._hidePause = options.hidePause * 1000;
            }
            /* Sub menu opacity */
            if (options.opacity != undefined)
            {
                Menu._opacity = options.opacity;
            }
        }

        /* Check if the document is already loaded. Prototype 1.6.0 introduces
         * the document.loaded boolean, for 1.5*, check if we can retrieve an
         * element from the DOM (fails when document is not loaded).
         */
        if (document.loaded === true || $(Menu._menuId))
        {
            Menu._doInit();
        }
        /* This is how it should work (init called before document load) */
        else
        {
            /* Do the actual initialization on document load. The "dom:loaded"
             * event is preffered, but only available since 1.6.0 (with
             * document.observe construction). Fall back to the window onload
             * when not available.
             */
            if (document.observe)
            {
                document.observe("dom:loaded", Menu._doInit);
            }
            else
            {
                Element.observe(window, "load", Menu._doInit);
            }
        }
    }, // function init

    /**
     * Initializes the drop down menu.
     *
     * Should be called on page load.
     */
    _doInit: function()
    {
        /* After the DOM is loaded, save the menu node */
        Menu._menuNode = $(Menu._menuId);

        /* Add events to each first level menu node (if any with a submenu). The
         * Menu._addEvents() method will add events recursively.
         */
        Menu._addEvents(Menu._menuNode);
    }, // function _doInit

    /**
     * Recursively attaches events to the menu UL.
     *
     * @param    HTMLUListElement    ulElement
     * @param    int                    level
     */
    _addEvents: function(ulElement, level)
    {
        /* If level argument is not given, */
        if (isNaN(level))
        {
            level = 1;
        }

        /* Find all menu nodes */
        var elements = (Element.select) ? ulElement.select("li") : ulElement.getElementsBySelector("li")
        for (var i = 0; i < elements.length; i++)
        {
            /* Only use the direct descendants (we're using recursion) */
            if (elements[i].parentNode == ulElement)
            {
                /* Check if it has a sub menu (should return 1 or zero nodes) */
                var subMenus = (Element.select) ? elements[i].select("ul") : elements[i].getElementsBySelector("ul");
                if (subMenus.length > 0)
                {
                    /* Add expand listener to the node */
                    elements[i].observe("mouseover", Menu._showSubMenu.bindAsEventListener(elements[i], level));

                    /* Add collapse listener to the node if it's the first level
                     * (because the LI contains all other submenu's, therefore
                     * any other listeners are over kill).
                     */
                    if (level == 1)
                    {
                        elements[i].observe("mouseout", Menu._hideSubMenu.bindAsEventListener(elements[i], level));
                    }

                    /* Add "submenu" class for this node link */
                    var a = (Element.select) ? elements[i].select("a")[0] : elements[i].getElementsBySelector("a")[0];
                    a.addClassName("submenu");

                    /* Recursion: check if the sub menu has nodes as well */
                    Menu._addEvents(subMenus[0], (level + 1));
                }
                /* No sub menu */
                else
                {
                    /* Only hide any expanded sub menu when hovering this node */
                    elements[i].observe("mouseover", Menu._quickHideSubMenu.bindAsEventListener(elements[i], level));
                }
            }
        }
    }, // function _addEvents

    /**
     * Shows the sub menu.
     *
     * @param    Event    e
     * @param    int        level
     */
    _showSubMenu: function(e, level)
    {
        /* Don't bubble up */
        Event.stop(e);

        /* is this the first menu node to be opened (need to check if we should apply the show timeout ) */
        var isFirst = (Menu._activeNodes.length == 0);

        /* Hide previous opened sub menu */
        Menu._quickHideSubMenu(e, level);

        /* Is this the first menu node and do we have a show delay? */
        if (isFirst && Menu._showPause > 0)
        {
            /* Show in x (mili)seconds */
            Menu._showTimeout = window.setTimeout(function() { Menu._doShowSubMenu(this, level); }.bind(this), Menu._showPause);
        }
        else
        {
            Menu._doShowSubMenu(this, level);
        }
    }, // function _showSubMenu

    /**
     * Shows the sub menu.
     *
     * @param    Event    e
     * @param    int        level
     */
    _doShowSubMenu: function(node, level)
    {
        /* Clear possible timeout */
        if (Menu._showTimeout)
        {
            window.clearTimeout(Menu._showTimeout);
        }

        /* Get node link and sub menu */
        var a = (Element.select) ? node.select("a")[0] : node.getElementsBySelector("a")[0];
        var subMenu = (Element.select) ? node.select("ul")[0] : node.getElementsBySelector("ul")[0];

        /* Keep hover style as long as opened */
        a.addClassName("menu_open");
        /* Since 1.3, the class is also applied to the parent LI */
        a.parentNode.addClassName("menu_open");

        /* Show sub menu */
        subMenu.style.visibility = "visible";
        subMenu.style.position = "absolute";

        /* Set correct position. (Note: the horizontal/vertical properties only
         * apply to the first level nodes. All other levels are fixed vertical.
         */
        var pos = (Element.positionedOffset) ? node.positionedOffset() : Position.positionedOffset(node);
        if (level == 1 && Menu._orientation == Menu.HORIZONTAL)
        {
            subMenu.style.left = pos[0] + "px";
            subMenu.style.top = (pos[1] + node.getHeight()) + "px";
        }
        else
        {
            subMenu.style.left = (pos[0] + node.getWidth()) + "px";
            subMenu.style.top = pos[1] + "px";
        }

        /* Apply opacity if not fully opaque. (Apply for sub menu of first level
         * only, because otherwise opacity would be doubled.)
         */
        if (level == 1 && Menu._opacity > 0 && Menu._opacity < 1)
        {
            subMenu.setOpacity(Menu._opacity);
        }

        /* Save submenu */
        Menu._activeNodes.push({"level": level, "link": a, "subMenu": subMenu});
    }, // function _doShowSubMenu

    /**
     * Immediately hides the active menu.
     *
     * @param    Event    e
     * @param    int        level
     */
    _quickHideSubMenu: function(e, level)
    {
        /* Don't bubble up */
        Event.stop(e);

        /* Clear possible timeout */
        if (Menu._hideTimeout)
        {
            window.clearTimeout(Menu._hideTimeout);
        }

        /* And hide the open menus from the given level up */
        Menu._doHideSubMenusFromLevel(level);
    }, // function _quickHideSubMenu

    /**
     * Method for hiding all sub menus.
     *
     * Triggered onmouseout of first sub menu (level 2).
     *
     * @param    Event    e
     * @param    int        level
     */
    _hideSubMenu: function(e, level)
    {
        /* Don't bubble up */
        Event.stop(e);

        /* if hiding (lost focus on first level), do not show! */
        if (Menu._showTimeout)
        {
            window.clearTimeout(Menu._showTimeout);
        }

        /* No pause? Don't use the timeout */
        if (Menu._hidePause <= 0)
        {
            /* Hide all sub menus */
            Menu._doHideSubMenusFromLevel(1);
        }
        else
        {
            /* Hide in x (mili)seconds */
            Menu._hideTimeout = window.setTimeout(function() { Menu._doHideSubMenusFromLevel(1); }, Menu._hidePause);
        }
    }, // function _hideSubMenu

    /**
     * Hides all active sub menus from the given level.
     *
     * @param    int        level    (Default: 1)
     *
     * @return    boolean
     */
    _doHideSubMenusFromLevel: function(level)
    {
        /* If level argument is not given, default to 1 */
        if (isNaN(level))
        {
            level = 1;
        }

        /* Remove these sub menus from the array */
        Menu._activeNodes = Menu._activeNodes.findAll(function(node)
        {
            /* Hide all sub menus with a level equal or higher than the given
             * level, and return false to remove these from the array.
             */
            if (node.level >= level)
            {
                /* Remove hover style */
                if (node.link)
                {
                    node.link.removeClassName("menu_open");
                    node.link.parentNode.removeClassName("menu_open");
                }
                /* Hide sub menu */
                if (node.subMenu)
                {
                    node.subMenu.style.visibility = "hidden";
                }
                /* Return false to remove node from the array */
                return false;
            }
            /* Return true for the other nodes, keeping them in the array */
            return true;
        });
    } // function _doHideSubMenusFromLevel

}; // class Menu
