//adds an event listener to an event
function addEventListener( element, event_name, observer, capturing ) {
    if ( element.addEventListener ) // the DOM2, W3C way  
        element.addEventListener( event_name, observer, capturing );
    else if ( element.attachEvent ) // the IE way  
        element.attachEvent( "on" + event_name, observer );
}

//compares 2 nodes
function nodeEquals(node1, node2) {
    if (node1.offsetLeft == node2.offsetLeft
        && node1.offsetTop == node2.offsetTop
        && node1.offsetHeight == node2.offsetHeight
        && node1.offsetWidth == node2.offsetWidth
        && node1.innerHTML == node2.innerHTML) {
        return true;
    }
    return false;
}

/*
PURPOSE: shows all the given element in a recursive manner.
PARAMETER: element - element's children to show.
PARAMETER: elementTypeToHide - Element type to show (only if specified).
PARAMETER: timeout - runs on a timer with the specified timeout (only if specified).
EXAMPLE: showElements(["hello","hi"], timeout);
*/
function showElements(elements, timeout) {
    var arr = [];
    var displayBlockInDueTime = function(element, timeout) {
        return setTimeout(function(){element.style.display = 'block';}, timeout);
    }
    for (i = elements.length - 1; i >= 0; --i) {
        arr.push(displayBlockInDueTime(elements[i], timeout*((elements.length-i)+1)));
    }
    return arr;
}

/*
PURPOSE: calculates easing In
PARAMETER: r - the value to ease.
PARAMETER: ease - the easing value.
*/
function easeIn(r, ease) {
    return Math.pow(r, ease);
}

/*
PURPOSE: calculates easing out
PARAMETER: r - the value to ease.
PARAMETER: ease - the easing value.
*/
function easeOut(r, ease) {
    return Math.pow(r, 1-ease/100);
}

/*
PURPOSE: hides all the given elements in a recursive manner.
PARAMETER: elements - array of elements to hide.
PARAMETER: elementType - Element type to hide (only if specified).
PARAMETER: timeout - runs on a timer with the specified timeout (only if specified).
EXAMPLE: hideElements(["hello","hi"], timeout);
*/
function hideElements(elements, timeout) {
    var arr = [];
    var displayNoneInDueTime = function(element, timeout) {
        return setTimeout(function(){element.style.display = 'none';}, timeout);
    }
    for (i = elements.length - 1; i >= 0; --i) {
        arr.push(displayNoneInDueTime(elements[i], timeout*((elements.length-i)+1)));
    }
    return arr;
}

/*
PARAM: root - root node.
NOTES: Used only for list menus structured accordingly: 
<ul>
    <li>
        root
        <ul>
            <li>
                leaf
                <ul>....</ul>
            </li>
        </ul>
    </li>
    <li>root</li>
    <li>root</li>
</ul>.
*/
function Menu(root, timeout) {
    var timers = null;
    
    function clearTimers() {
        if (timers) {
            for (var i = 0; i < timers.length; ++i) {
                clearTimeout(timers[i]);
            }
        }
    }
    
    function isChild(parent, child) {
        if (parent.hasChildNodes()) {
            for (var i = 0; i < parent.childNodes.length; ++i) {
                if (nodeEquals(parent.childNodes[i], child)) {
                    return true;
                } else if (isChild(parent.childNodes[i], child)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    function close_menu(e) {
        clearTimers();
        e = (e) ? e : window.event;
        var newSource = (e.relatedTarget) ? e.relatedTarget : e.toElement;
        var ul = null;
        
        if (isChild(root, newSource)) {
            while(newSource.tagName != "LI") {
                newSource = newSource.parentNode;
            }
            if (ul = newSource.getElementsByTagName("UL")[0]) {
                if (ul = ul.getElementsByTagName("UL")) {
                    for (var i = 0; i < ul.length; ++i) {
                        timers = hideElements(ul[i].getElementsByTagName("LI"), timeout);
                    }
                }
            }  
        } else if (nodeEquals(root, newSource)) {
            if (ul = root.getElementsByTagName("UL")[0]) {
                if (ul = ul.getElementsByTagName("UL")) {
                    for (var i = 0; i < ul.length; ++i) {
                        timers = hideElements(ul[i].getElementsByTagName("LI"), timeout);
                    }
                }
            }
        } else {
            timers = hideElements(root.getElementsByTagName("LI"), timeout);
        }
    }
    
    function open_menu(e) {
        clearTimers();
        e = (e) ? e : window.event;
        var source = (e.srcElement) ? e.srcElement : this;
        var ul = null;
        var usableChildren = [];
        while(source.tagName != "LI") {
            source = source.parentNode;
        }
        if (source && source.hasChildNodes()) {
            if (ul = source.getElementsByTagName("UL")[0])
            {
                if (ul.hasChildNodes()) {
                    ul.style.display = "block";
                    for (var i = 0; i < ul.childNodes.length; ++i) {
                        if (ul.childNodes[i]
                            && ul.childNodes[i].nodeType == 1 
                            && ul.childNodes[i].tagName == "LI") {
                            usableChildren.push(ul.childNodes[i]);
                        }
                    }
                    timers = showElements(usableChildren.reverse(), timeout);
                }
            }
        }
    }
    
    this.setup = function(root) {
        var li = root.getElementsByTagName("LI");
        addEventListener(root, "mouseover", open_menu, false);
        addEventListener(root, "mouseout", close_menu, false);
        for (i = 0; i < li.length; ++i) {
            addEventListener(li[i], "mouseover", open_menu, false);
        }
    };
    
    this.setup(root);
}