Object.extend(Position,
{
    /**
     * Creates an object representing the given element's box co-ordinates, with
     * <code>top</code>, <code>right</code>, <code>bottom</code> and
     * <code>left</code> properties.
     *
     * @param element {Element} an element.
     *
     * @return an object representing the given element's box co-ordinates.
     * @type Object
     */
    getBox: function(element)
    {
        var offset = Position.cumulativeOffset(element);
        return {
            top: offset[1],
            right: offset[0] + element.offsetWidth,
            bottom: offset[1] + element.offsetHeight,
            left: offset[0]
        };
    },

    /**
     * Determines if given co-ordinates are within a given box.
     *
     * @param box {Object} an object defining box co-ordinates.
     * @param x   {Number} an x co-ordinate.
     * @param y   {Number} a y co-ordinate.
     *
     * @return {Boolean} <code>true</code> if the co-ordinates are within the
     *                      box, <code>false</code> otherwise.
     */
    withinBox: function(box, x, y)
    {
        return (y >= box.top &&
                y <= box.bottom &&
                x >= box.left &&
                x <= box.right);
    }
});

// Ensure LIT namespace is available
if (typeof(LIT) == "undefined")
{
    var LIT = {};
}

/**
 * Handles setup of dropdown menus.
 */
LIT.Menu =
{
    /** Cache object for document mousemove event handlers. */
    handlerCache: {},

    /**
     * Initialises dropdown menus.
     *
     * @param menuIds {Array} a list of menu section ids - each menu section
     *                        must contain a second level header for activiation
     *                        of the menus and an unordered list for the menu's
     *                        content.
     */
    init: function(menuIds)
    {
        for (var i = 0; i < menuIds.length; i++)
        {
            var menuContainer = $(menuIds[i]);
            var header = menuContainer.getElementsByTagName("h2")[0];
            var menu = menuContainer.getElementsByTagName("ul")[0];
            Element.hide(menu);
            this.prepareMenu(header, menu);
        }
    },

    /**
     * Prepares a menu for use.
     *
     * @param header {Element} a menu's header.
     * @param menu   {Element} a menu.
     */
    prepareMenu: function(header, menu)
    {
        menu.style.position = "absolute";

        // Ensure the last menu item has a bottom border to close off the menu
        var items = menu.getElementsByTagName("li");
        for (var i = 0, item; item = items[i]; i++)
        {
            if (Element.hasClassName(item, "last"))
            {
                item.style.borderBottom = "1px solid #fff";
            }
        }

        Event.observe(header, "mouseover",
            this.showHandler(header, menu).bindAsEventListener(this));
    },

    /**
     * Creates an event handling function which determines when the user has
     * moused over a given menu's header and shows the menu if so.
     *
     * @param header {Element} a menu's header.
     * @param menu   {Element} a menu.
     *
     * @return an event handling function.
     * @type Function
     */
    showHandler: function(header, menu)
    {
        return function(e)
        {
            if (typeof(this.handlerCache[header.id]) != "function")
            {
                var menus = $("menus");
                var menuTop =
                    Position.cumulativeOffset(menus)[1] + menus.offsetHeight - 1;
                menu.style.top = menuTop + "px";

                Element.show(menu);
                Event.observe(document, "mousemove",
                    this.hideHandler(header, menu), true);
            }
        };
    },

    /**
     * Creates an event handling function which determines when the user has
     * moused out of a given menu or its header and hides the menu if so.
     *
     * @param header {Element} a menu's header.
     * @param menu   {Element} a menu.
     *
     * @return an event handling function.
     * @type Function
     */
    hideHandler: function(header, menu)
    {
        this.handlerCache[header.id] = function(e)
        {
            var x = Event.pointerX(e);
            var y = Event.pointerY(e);

            var headerBox = Position.getBox(header);
            // Allow some tolerance for top co-ordinates
            headerBox.top -= 1;
            // Headers may be shorter than the root menu container
            headerBox.bottom = headerBox.top + $("menus").offsetHeight;

            var menuBox = Position.getBox(menu);
            // Allow tolerance for going off the sides or bottom
            menuBox.left -= 5;
            menuBox.right += 5;
            menuBox.bottom +=5;

            if (!Position.withinBox(headerBox, x, y) &&
                !Position.withinBox(menuBox, x, y))
            {
                Element.hide(menu);
                Event.stopObserving(document, "mousemove",
                    this.handlerCache[header.id], true);
                this.handlerCache[header.id] = null;
            }
        }.bindAsEventListener(this); // Pre-bound "this" object, as we must
                                     // reference the exact same function when
                                     // removing the event listener.

        return this.handlerCache[header.id];
    }
};

// Always initialise on page load
Event.observe(window, "load", function()
{
    LIT.Menu.init(["menu-about", "menu-work", "menu-news"]);
});