// setColor for debug
var setColor = function (sColor)
{
    var oMenu = document.getElementById('categories');
    dhtml.unsetClassName(oMenu, 'red');
    dhtml.unsetClassName(oMenu, 'orange');
    dhtml.unsetClassName(oMenu, 'blue');
    dhtml.unsetClassName(oMenu, 'pink');
    dhtml.unsetClassName(oMenu, 'yellow');
    dhtml.unsetClassName(oMenu, 'green');
    dhtml.setClassName(oMenu, sColor);
};


/**
 * Menu class
 *
 * Menu life cycle :
 * 1) Instanciation of the Menu object and its MenuItem objects (__construct).
 * 2) Loading of the menu, generally on window load (build).
 * 3) Interaction with user actions (other methods).
 *
 * @access     public
 */
var Menu = function (sNodeID,
                     aChildren)
{

    /**
     * Menu._this member
     *
     * Self reference to allow private methods to access the object.
     *
     * @access     private
     * @var        object
     */
    var _this = this;


    /**
     * Menu._bBuilt member
     *
     * Determines whether the Menu's object has already been rendered.
     *
     * @access     private
     * @var        bool
     */
    var _bBuilt;


    /**
     * Menu._sID member
     *
     * Identifier (id) of the HTML <ul> node that will have the menu built
     * into. If the node is not found, menu won't load actually.
     *
     * @access     private
     * @var        string
     */
    var _sID;


    /**
     * Menu._aChildren member
     *
     * Array of main MenuItem childrens. A Menu object must have at least one
     * MenuItem in this array.
     *
     * @access     private
     * @var        array
     */
    var _aChildren;


    /**
     * Menu._construct() method
     *
     * Constructor of the Menu object.
     *
     * @constructor
     * @access     private
     * @param      string sNodeID
     * @param      array aChildren
     */
    var _construct = function (sNodeID,
                               aChildren)
    {
        // register this instance in the static array and get unique identifier
        _sID = sNodeID;
        Menu.aInstances[_sID] = _this;
        var sInstance = 'Menu.aInstances["'+_sID+'"]';

        // set up the children
        _aChildren = aChildren;

        // menus not built yet
        _bBuilt = false;

        // Load the menus BEFORE window loaded.
        // Currently, only Gecko browsers (Firefox, Mozilla, Netscape) and
        // Opera support this very interesting feature. IE also supports this
        // feature but in a such messy and proprietary way (it involves two
        // additionnal files, a .js and a .htc file, just to make things work,
        // but I'm sorry, I don't eat this kind of food, and you shouldn't
        // too) that it is almost impossible to write something clean,
        // functionnal and cross-browser. So the menu's will only load after
        // every JS, CSS and image file is downloaded and parsed with IE.
        // See: http://dean.edwards.name/weblog/2005/09/busted/
        dhtml.addEvent(document,
                       'DOMContentLoaded',
                       new Function('oEvent',
                                    sInstance+'.build(oEvent);'));

        // load the menus AFTER window loaded
        dhtml.addEvent(window,
                       'load',
                       new Function('oEvent',
                                    sInstance+'.build(oEvent);'));

        // close the menus when user click somewhere else on the page
        dhtml.addEvent(document,
                       'mousedown',
                       new Function('oEvent',
                                    sInstance+'.close(oEvent);'));

    }


    /**
     * Menu.build() method
     *
     * Loads the Menu into its final HTML format using W3C DOM scripting.
     *
     * @access     private
     * @return     void
     */
    this.build = function ()
    {
        // menus already built
        if (_bBuilt)
        {
            return false;
        };
        _bBuilt = true;

        // get the root <ul> element
        var oUl = document.getElementById(_sID);

        // <root> ul element not found, abort Menu building
        if (!oUl)
        {
            return false;
        };

        // a click in a Menu object won't reach the document node in the
        // bubbling phase, this way any click in the document that is not in
        // the menu can run a function to immediately close the opened menus
        // without waiting for the timeout
        oUl.onmousedown = function (oEvent)
        {
            // get event [IE]
            if (!oEvent)
            {
                var oEvent = window.event;
            };

            // cancel event propagation [IE]
            oEvent.cancelBubble = true;

            // cancel event propagation [others]
            if (oEvent.stopPropagation)
            {
                oEvent.stopPropagation();
            };

            return false;
        }

        // clean the root, as the menu will dynamically re-create <li> items in
        // it : it's a cleaner way to (re)build the menu, in case of format or
        // markup error
        while (oUl.firstChild)
        {
            oUl.removeChild(oUl.firstChild);
        };

        // no MenuItem object is currently opened
        oUl.opened = -1;

        // build each child and add it to the root MenuItem node
        for (var i = 0; i < _aChildren.length; i++)
        {
            _aChildren[i].build(oUl,
                                1);
        };
    }


    /**
     * Menu.close() method
     *
     * Close any opened <li> item from this menu.
     *
     * @access     public
     * @return     void
     */
    this.close = function ()
    {
        // get the root <ul> associated with this menu
        var oUl = document.getElementById(_sID);

        // no item currently opened, abort
        if (!oUl.opened)
        {
            return;
        };

        // get any opened <li> item
        var oLi = MenuItem.aInstances[oUl.opened];

        // unable to get that <li> item
        if (!oLi)
        {
            return;
        };

        // close that item
        oLi.hide();
    };


    /**
     * Menu.toString() method
     *
     * Returns a string representation of this object.
     *
     * @access     public
     * @return     string
     */
    this.toString = function ()
    {
        return '[object Menu]';
    }

    // build the object
    _construct(sNodeID,
               aChildren);
}


/**
 * Menu.aInstances member
 *
 * This static array holds references to each Menu object created on this page.
 *
 * @static
 * @access     public
 * @var        array
 */
Menu.aInstances = new Array();


/**
 * MenuItem class
 *
 * @access     public
 */
var MenuItem = function (sLabel,
                         sURL,
                         sTarget,
                         sIcon,
                         aChildren)
{

    /**
     * MenuItem._this member
     *
     * Self reference to allow private methods to access the object.
     *
     * @access     private
     * @var        object
     */
    var _this = this;


    /**
     * MenuItem._iID member
     *
     * Identifier (id) of the MenuItem instance.
     *
     * @access     private
     * @var        int
     */
    var _iID;


    /**
     * MenuItem._iLevel member
     *
     * Nesting level of this MenuItem.
     *
     * @access     private
     * @var        int
     */
    var _iLevel;


    /**
     * MenuItem._sIcon member
     *
     * Icon picture attached to this MenuItem, if any.
     *
     * @access     private
     * @var        string
     */
    var _sIcon;


    /**
     * MenuItem._sLabel member
     *
     * Rendered text of the item.
     *
     * @access     private
     * @var        string
     */
    var _sLabel;


    /**
     * MenuItem._sTarget member
     *
     * Target window of the MenuItem's link, if any.
     *
     * @access     private
     * @var        string
     */
    var _sTarget;


    /**
     * MenuItem._sURL member
     *
     * URL of the menu link
     *
     * @access     private
     * @var        string
     */
    var _sURL;


    /**
     * MenuItem._aChildren member
     *
     * Array of children MenuItem objects.
     *
     * @access     private
     * @var        array
     */
    var _aChildren;


    /**
     * MenuItem._aNodes member
     *
     * Array of HTML nodes used to render this MenuItem. These datas are filled
     * during the second phase of the Menu lifecycle (loading).
     *
     * @access     private
     * @var        array
     */
    var _aNodes;


    /**
     * MenuItem._oOpenStateTimeout member
     *
     * Timeout reference. When the item is closing, a short delays allows the
     * nested <ul> to remain displayed during a few milliseconds. This is the
     * reference to the timeout so that delay may be cancelled.
     *
     * @access     private
     * @var        object
     */
    var _oOpenStateTimeout;


    /**
     * MenuItem._construct() method
     *
     * Constructor of the MenuItem object.
     *
     * @constructor
     * @access     private
     * @param      string sLabel
     * @param      string sURL
     * @param      string sTarget
     * @param      string sIcon
     * @param      array aChildren
     */
    var _construct = function (sLabel,
                               sURL,
                               sTarget,
                               sIcon,
                               aChildren)
    {
        // register this object in the static instances array
        _iID = MenuItem.aInstances.length;
        MenuItem.aInstances.push(_this);

        // set up the object members
        _sLabel = sLabel;
        _sURL = sURL;
        _sTarget = sTarget;
        _sIcon = sIcon;
        _aChildren = aChildren;
        _iLevel = -1;
        _aNodes = new Array();
        _oOpenStateTimeout = null;
    }


    /**
     * MenuItem.build() method
     *
     * Loads the MenuItem into its final HTML format using W3C DOM scripting.
     *
     * @access     public
     * @param      object oParent
     * @param      int iLevel
     * @return     object
     */
    this.build = function (oParent,
                           iLevel)
    {
        // save the MenuItem nesting level
        _iLevel = iLevel;

        // create the <li>
        var oLi = _aNodes['li'] = document.createElement('li');
        oParent.appendChild(oLi);
        oLi.iID = _iID;
        oLi.title = _sLabel;

        // action to perform when mouse rolls over the item
        oLi.onmouseover =
        oLi.onfocus = function (oEvent)
        {
            // get event [IE]
            if (!oEvent)
            {
                var oEvent = window.event;
            };

            // call back the associated MenuItem
            return MenuItem.aInstances[_iID].onMouseOver(oEvent);
        }

        // action to perform when mouse rolls out of the item
        oLi.onmouseout =
        oLi.onblur = function (oEvent)
        {
            // get event [IE]
            if (!oEvent)
            {
                var oEvent = window.event;
            };

            // call back the associated MenuItem
            return MenuItem.aInstances[_iID].onMouseOut(oEvent);
        }

        // create the <img>, if any
        if (_sIcon)
        {
            var oImg = _aNodes['img'] = document.createElement('img');
            oLi.appendChild(oImg);
            oImg.src = _sIcon;
            oImg.alt = '';
        };

        // create the <a>
        var oA = _aNodes['a'] = document.createElement('a');
        oLi.appendChild(oA);
        oA.href = _sURL;
        oA.appendChild(document.createTextNode(_sLabel));

        oA.target = _sTarget;

        // icon is given, so the link must be 'iconic'
        if (_sIcon)
        {
            dhtml.setClassName(oA,
                               'iconic');
        };

        // only if children items available
        if (_aChildren &&
            _aChildren.length)
        {
            // make this MenuItem a container
            dhtml.setClassName(oLi,
                               'container');

            // create the nested <ul>
            var oUl = _aNodes['ul'] = document.createElement('ul');
            oLi.appendChild(oUl);
            oUl.opened = -1;
            oUl.style.zIndex = 1000 + _iLevel;

            // create nested MenuItem's
            var iLevel = _iLevel + 1;
            for (var i = 0; i < _aChildren.length; i++)
            {
                _aChildren[i].build(oUl,
                                    iLevel);
            };
        };
    }


    /**
     * MenuItem.getID() method
     *
     * @access     public
     * @return     int
     */
    this.getID = function ()
    {
        return _iID;
    }


    /**
     * MenuItem.getWidth() method
     *
     * @access     public
     * @return     string
     */
    this.getWidth = function ()
    {
        return _aNodes['a'].offsetWidth;
    }


    /**
     * MenuItem.setWidth() method
     *
     * @access     public
     * @param      string sWidth
     * @return     void
     */
    this.setWidth = function (sWidth)
    {
        _aNodes['li'].style.width = sWidth;
    }


    /**
     * MenuItem.hide() method
     *
     * @access     public
     * @return     void
     */
    this.hide = function ()
    {
        // if item was waiting to open/close, reset the timeout, and close it now
        clearTimeout(_oOpenStateTimeout);

        // get the nodes
        var oLi = _aNodes['li'];
        var oA = _aNodes['a'];
        var oUl = _aNodes['ul'];

        // this item is now closed (in fact, mouse is out of this item)
        dhtml.unsetClassName(oLi, 'open');
        dhtml.unsetClassName(oA, 'open');

        // no children to hide, just abort
        if (!_aChildren.length)
        {
            return false;
        };

        // hide children
        if (-1 != oUl.opened &&
            _iID != oUl.opened)
        {
            var oItem = MenuItem.aInstances[oUl.opened];
            oItem.hide();
        };

        // hide it
//        oUl.style.display = 'none';
        oUl.style.left = '-1024px';

        // tell the parent <ul> that it has no opened item anymore
        oLi.parentNode.opened = -1;
    }


    /**
     * MenuItem.onMouseOut() method
     *
     * When mouse goes out of an <li>, this method is called to close the <li>
     * with a little timeout
     *
     * @access     public
     * @return     void
     */
    this.onMouseOut = function (oEvent)
    {
        // get the nodes
        var oLi = _aNodes['li'];
        var oA = _aNodes['a'];

        // if this item doesn't have any child, the mouse out effect applies
        // immediately
        if (!_aChildren.length)
        {
            // this item is now closed (in fact, mouse is out of this item)
            dhtml.unsetClassName(oLi, 'open');
            dhtml.unsetClassName(oA, 'open');
            return false;
        };

        // wait for this delay before closing item
        if ('blur' != oEvent.type)
        {
            _oOpenStateTimeout = setTimeout('MenuItem.aInstances['+_iID+'].hide()',
                                            1000);
        }
        else
        {
            MenuItem.aInstances[_iID].hide();
        };

        return false;
    }


    /**
     * MenuItem.onMouseOver() method
     *
     * When mouse goes over an <li>, this method is called to open the <li>
     * nested menu.
     *
     * @access     public
     * @return     void
     */
    this.onMouseOver = function (oEvent)
    {
        // show the current item
        this.show();

        return false;
    }


    /**
     * MenuItem.show() method
     *
     * @access     public
     * @return     void
     */
    this.show = function ()
    {
        // if item was waiting to open/close, reset the timeout, so it won't close
        clearTimeout(_oOpenStateTimeout);

        // get the nodes
        var oLi = _aNodes['li'];
        var oA = _aNodes['a'];
        var oUl = _aNodes['ul'];

        // this item is now closed (in fact, mouse is out of this item)
        dhtml.setClassName(oLi, 'open');
        dhtml.setClassName(oA, 'open');

        // check in the parent <ul> for an opened <li>
        // will close opened <li> that is a sibling of the current <li>
        var iOpened = oLi.parentNode.opened;
        if (-1 != iOpened)
        {
            // the current <li> was already opened, abort
            if (iOpened == _iID)
            {
                return;
            };

            // immediately hide the opened sibling <li>
            MenuItem.aInstances[iOpened].hide();
        };

        // as long as this <li> is nested in another one, the parent <li> must
        // be opened before, otherwhise the computation of the nested <li>
        // position will fail
        if ('li' == oLi.parentNode.parentNode.nodeName.toLowerCase())
        {
            var iParentID = oLi.parentNode.parentNode.iID;
            var oParent = MenuItem.aInstances[iParentID];
            oParent.show(true);
        };

        // no children, nothing to show, abort
        if (!_aChildren ||
            !_aChildren.length)
        {
            return false;
        };

        // render the nested <ul> in the viewport (display: block) but make it
        // invisible so it won't display during the size computation
        oUl.style.width = 'auto';
//        oUl.style.display = 'block';
//        oUl.style.visibility = 'hidden';
        oUl.style.margin = '0px';

        // to correctly draw nested <ul> and <li>'s, the <li>'s must adjust to
        // the size of the bigger one
        var aChidlren = oUl.childNodes;
        var aWidths = new Array();

        // get the width of each <li>
        for (var i = 0; i < aChildren.length; i++)
        {
            var oChild = aChildren[i];
            oChild.setWidth('auto');;
            aWidths.push(parseInt(oChild.getWidth()));
        };

        // here is the biggest <li> width found
        var iMax = arrayMax(aWidths) + 12;

        // now apply this width to all the <li>'s
        for (var i = 0; i < aChildren.length; i++)
        {
            var oChild = aChildren[i];
            oChild.setWidth(iMax+'px');
        };

        // size and scrolling of the document (viewport)
        var aDocScroll = document.getScroll();
        var aDocSize = document.getSize();
        var aDocDX = aDocScroll[0] + aDocSize[0];
        var aDocDY = aDocScroll[1] + aDocSize[1];

        // size and position of the nested <ul>
        var aMenuPos = dhtml.findPos(oUl);
        var aMenuSize = [oUl.offsetWidth,
                         oUl.offsetHeight];
        var aMenuDX = aMenuPos[0] + aMenuSize[0];
        var aMenuDY = aMenuPos[1] + aMenuSize[1];

        // if the nested <ul> doesn't appear completely, adjust the position so
        // the whole nested <ul> does completely appear
        var iOffset = 1;
        if (_iLevel > 1)
        {
            iOffset = 3;
        };
        var iX = Math.max(0, aMenuDX - aDocDX);
        var iY = Math.max(0, aMenuDY - aDocDY + iOffset);

        // adjust nested menu position
        oUl.style.marginLeft = (- iX)+'px';
        oUl.style.marginTop = (- iY)+'px';

        // make the menu visible
        oUl.style.width = (iMax + 2)+'px';
        oUl.style.left = '100%';
//        oUl.style.visibility = 'visible';

        // and tell the parent <ul> that the current <li> is now opened
        oLi.parentNode.opened = _iID;
    }


    /**
     * MenuItem.toString() method
     *
     * Returns a string representation of this object.
     *
     * @access     public
     * @return     string
     */
    this.toString = function ()
    {
        return '[object MenuItem]';
    }

    // build the object
    _construct(sLabel,
               sURL,
               sTarget,
               sIcon,
               aChildren);
}


/**
 * MenuItem.aInstances member
 *
 * This static array holds references to each MenuItem object created on this
 * page.
 *
 * @static
 * @access     private
 * @var        array
 */
MenuItem.aInstances = new Array();


