Menu = Class.create();

Menu.prototype = {
    initialize: function(elem) {
        // Returns menu object from menu link
        this.source = elem.getElementsByTagName('A')[0];
        this.body = elem.getElementsByTagName('DIV')[0];
        this.width = this.body.getWidth();
        this.level = this.getLevel();
        this.scheduler = new Object();
        this.shown = 0;
        this.postStyling();
        this.body.style.display = 'none';
        this.body.style.visibility = 'hidden'; // otherwise IE shows
                                               // box's outline
        // Hover events                                       
        Event.observe(
            this.source, "mouseover", 
            this.handleSourceOver.bindAsEventListener(this), true
        );
        Event.observe(
            this.source, "mouseout", 
            this.handleSourceOut.bindAsEventListener(this), false
        );
        Event.observe(
            this.body, "mouseover", 
            this.handleBodyOver.bindAsEventListener(this), false
        );
        Event.observe(
            this.body, "mouseout", 
            this.handleBodyOut.bindAsEventListener(this), false
        );
        // Click events                                       
        Event.observe(
            this.source, "click", 
            this.handleSourceClick.bindAsEventListener(this), true
        );
    },
    getLevel: function() {
        var a = this.source.parentNode.recursivelyCollect('parentNode');
        for(var i = 0; i < a.length; ++i) {
            if (a[i].id == 'mainnav') {
                var l = i % 2 + 1;
                break;
            }
        }
        if (!(/^[1-2]$/.test(l))) {
                throw new Error('Error: Level not in 1-2');
                return false;
            }
        return l; 
    },
    handleSourceOver: function(e) {
        Event.stop(e);
        if (typeof(Menu.scheduler.menusOut) != 'undefined') {
            this.cancelSchedule({type: 'menusOut'});
        }
        if (this.shown && typeof(this.scheduler.bodyOut) != 'undefined')
            this.cancelSchedule({type: 'bodyOut'});
        var lag = this.level > 1 ? 100 : 100
        this.scheduleLag({type: 'sourceOver', ms: lag});
    },
    handleSourceOut: function(e) {
        var r = Event.relatedElement(e);
        this.scheduleLag({type: 'sourceOut', ms: 100});
        Event.stop(e);
        if (!this.shown && typeof(this.scheduler.sourceOver) != 'undefined')
            this.cancelSchedule({type: 'sourceOver'});
    },
    handleBodyOver: function(e) {
        Event.stop(e);
        // first prevent all menus from closing if
        // user had hovered out of menus div
        if (typeof(Menu.scheduler.menusOut) != 'undefined') {
            this.cancelSchedule({type: 'menusOut'});
        }
        if (this.shown && typeof(this.scheduler.sourceOut) != 'undefined') {
            var r = Event.relatedElement(e);
            this.cancelSchedule({type: 'sourceOut'});
        }
    },
    handleBodyOut: function(e) {
        Event.stop(e);
        var t = Event.element(e);
        var r = Event.relatedElement(e);
        if ( !Element.descendantOf(r, this.body)
              && r != this.source) 
        { 
              if (!Element.descendantOf(r, $('mainnav'))) {
                  this.scheduleLag({type: 'menusOut', ms: 500});
              } else {
                  this.scheduleLag({type: 'bodyOut', ms: 300});
              }
        }
    },
    handleSourceClick: function(e) {
        Event.stop(e);
        this.cancelSchedule({type: 'sourceOver'});
        this.hideBody();
        this.showBody('slidedown');
    },
    showBody: function(effect) {
        if (this.shown) return true;
        // need this?
        if (this.level == 1) {
            Menu.hide({level: this.level});
            Menu.hide({level: this.level + 1});
        }
        this.position();
        this.source.style.color = '#31BEF2';
        this.body.style.height = this.body.height;
        this.body.style.visibility = 'visible'; // needed for IE 
        typeof effect === 'string' ? eval(this.setEffect(effect)) 
                                   : eval(this.setEffect('show'));
        this.shown = 1;
    },
    hideBody: function(effect) {
        if (this.level > 1) {
            Menu.hide({level: this.level});
        } else {
            Menu.hide({level: this.level, except: this});
            Menu.hide({level: this.level + 1});
        }
    },
    _hideBody: function(effect) {
        if (!this.shown) return true;
        this.source.style.color = '';
        effect ? eval(this.setEffect(effect)) : eval(this.setEffect('hide'));
        this.shown = 0;
        // clean up
        delete this.scheduler.sourceOver;
        delete this.scheduler.sourceOut;
        delete this.scheduler.bodyOver;
    },
    position: function() {
        var x, y;
        if (this.level == 1) {
            x = Menu.barWidth - this.width -
            Position.positionedOffset(this.source)[0];
            if (x < 0) x = 0; // remember - right aligned
        } else {
            x = - this.width - 2;
            y =  47;
        }
        if (typeof y != 'undefined') this.body.style.top = y + 'px';
        if (typeof x != 'undefined') this.body.style.right = x + 'px';
    },
    postStyling: function() {
        // completes remaining not done via CSS
        // 1. remove first entry's divider border
        var t = Element.down(this.body);
        Element.immediateDescendants(t)[0].style.backgroundImage = 'none';
        // 2. set fixed height to this.body DIV (needed for proper slide effect)
        var h = Element.getDimensions(this.body).height;
        this.body.height = this.level > 1 ? 50 + 'px' : h + 'px';
        this.body.style.height = this.body.height;
        // 3. set abs position to level 1 this.source (issue w/ webkit)
        if (this.level == 1) {
            var l = Element.up(this.source);
            var w = Element.getDimensions(l).width;
           l.style.width = w + 'px';
           //this.source.up.style.width = w + 'px';
           //this.source.style.top = '0';
           //this.source.style.position = 'absolute';
           //this.source.style.top = '0';
           //this.source.style.left = Position.cumulativeOffset(this.source)[0] +
           //'px';
        }
        // 4. make 
        this.source.style.cursor = 'default';
    },
    setEffect: function(effect) {
        if (!effect) return false;
        switch (effect) {
        case 'show':
            e = "Element.show(this.body)";
            break;
        case 'hide':
            e = "Element.hide(this.body)";
            break;
        case 'fade':
            e = "new Effect.Fade(this.body, {duration: 0.15, fps: 60})";
            break;
        case 'slideup':
            e = "new Effect.SlideUp(this.body, {duration: 0.15, fps: 60, queue:'front'})";
            break;
        case 'slidedown':
            e = "new Effect.SlideDown(this.body, {duration: 0.15, fps: 60, queue:'end'})";
            break;
        default:
            throw new Error('setEffect: Unrecognized effect: ' + effect +
            'called by: ' + this.setEffect.caller);
            return false;
        }
        return e;
    },
    scheduleLag: function(opts) {
        // type: sourceOut or bodyOut
        // ms: milliseconds
        opts.ms = opts.ms || 1000;
        if (!opts.type) throw new Error('scheduleLag: must pass type'); 
        th = this;
        var t = this.scheduler.sourceOut 
        if (opts.type == 'sourceOut') {
            this.scheduler.sourceOut = new Object();
            this.scheduler.sourceOut.fx = setTimeout("th.hideBody('slideup')", opts.ms);
            //console.log('scheduled sourceOut');
        } else if (opts.type == 'bodyOut') {
            if (typeof(this.scheduler.bodyOut) != 'undefined') return true;
            this.scheduler.bodyOut = new Object();
            this.scheduler.bodyOut.fx = setTimeout("th.hideBody('slideup')", opts.ms);
            //console.log('scheduled bodyOut');
        } else if (opts.type == 'sourceOver') {
            this.scheduler.sourceOver = new Object();
            this.scheduler.sourceOver.fx = setTimeout("th.showBody('slidedown')", opts.ms);
            //console.log('scheduled sourceOver');
        } else if (opts.type == 'menusOut') {
            if (typeof(Menu.scheduler.menusOut) != 'undefined') return true;
            Menu.scheduler.menusOut = new Object();
            Menu.scheduler.menusOut.fx = setTimeout("Menu.hide({all: true})", opts.ms);
            //console.log('scheduled menusOut');
        }
    },
    cancelSchedule: function(opts) {
        try {
            // type: sourceOut or bodyOut
            if (opts.type == 'sourceOut') {
                clearTimeout(this.scheduler.sourceOut.fx);
                delete this.scheduler.sourceOut;
                //console.log('canceled sourceOut');
            } else if (opts.type == 'bodyOut') {
                clearTimeout(this.scheduler.bodyOut.fx);
                delete this.scheduler.bodyOut;
                //console.log('canceled bodyOut');
            } else if (opts.type == 'sourceOver') {
                clearTimeout(this.scheduler.sourceOver.fx);
                delete this.scheduler.sourceOver;
                //console.log('canceled sourceOver');
            } else if (opts.type == 'menusOut') {
                clearTimeout(Menu.scheduler.menusOut.fx);
                delete Menu.scheduler.menusOut;
                //console.log('canceled menusOut');
            }
        } catch(e) {
            // place something here for debugging
        }
    }
}

Object.extend(Menu, {
    init: function() { 
        Menu.allMenus = Menu.getAllMenus();
        Menu.barWidth = $('mainnav').getWidth();
        Menu.scheduler = new Object();
        Event.observe(
            document, "click", 
            Menu.handleClickOut.bindAsEventListener(this), false
        );
        // Menus start off as inivisble to spare the user
        // from viewing the setup steps
        $('mainnav').setStyle({ visibility: 'visible' });
        return true;
    },
    getAllMenus: function() {
       // This class function generated the allMenus property
       // it should be generated only once
       if (this.allMenus) return true;
       var p = $$('#mainnav LI');
       var elems = p.findAll(function(e) {
            return Menu.whetherMenu(e);
       });
       return elems.collect(function(elem) {
           return new Menu(elem);
       });
    },
    whetherMenu: function(elem) {
        // Menu definition: LI elements
        // with a DIV.dropdown child
        return Element.immediateDescendants(elem).length == 2 &&
        Element.immediateDescendants(elem)[1].tagName == 'DIV' &&
        Element.immediateDescendants(elem)[1].className == 'dropdown';
    },
    hide: function(opts) {
        // opts is a hash
        // except: body -- hide all except this one
        // all: true -- hide all level menus 
        // level: int -- hide all menus at this level
        if (opts.level) {
            if (/[0-9]/.test(opts.level)) {
                Menu.allMenus.select(function(m) { 
                    return m.level == opts.level;
                }).each(function(m) {
                   if (opts.except) {
                       if (m.body != opts.except.body
                           && !Element.descendantOf(m.body, opts.except.body)
                       ) return m._hideBody('slideup');
                   } else {
                       return m._hideBody('slideup');
                   }
                });
            }
        } else if (opts.all) {
            Menu.allMenus.each(function(m) {
               if (
                      m.body != opts.except
                      && !Element.descendantOf(m.body, opts.except)
                  ) 
                return m._hideBody('slideup');
            });
        } else if (opts.one) {
            var m = opts.one;
            return m._hideBody('slideup');
        }
    },
    handleClickOut: function(e) {
        var el = Event.element(e);
       // if (Event.findElement(e, 'form').nodeName == 'FORM')
       //     return true;
        if (Element.descendantOf(el, $('mainnav'))) return true;
        //Event.stop(e);
        Menu.hide({all: true});
    }
});

