function global_loaded() {
    //set up the page event bus if it's not already set up
    //wtf is this - oh its used for mochiwindow. its gross up in here.
    if(typeof(window.EVENT_BUS) == 'undefined'){
        window.EVENT_BUS = {};
    }

    //Run a few automagic utility functions
    MochiToggle._init();
    MochiTip._init();
    MochiInputList._init();
    MochiMessage._init();
    MochiStripes._init();
    MochiFlyout._init();
    MochiUtil.backLinks();
    MochiUtil.auto_swfobject();
    MochiUtil.selectOnFocus();
    get_message_count();

    //Setup feedback component
    if(getElement("feedback_link"))
       MochiFeedback._init("feedback_link");

    //Setup help panel
    MochiHelp._init();
}
addLoadEvent(global_loaded);


/********  Feedback component ********/
MochiFeedback = {};
MochiFeedback.AJAX_URL = "/pub/feedback";
MochiFeedback._init = function(){
    var self = this;

    self.FEEDBACK_PANEL = getElement("feedback_panel");
    self.FORM = getElement("feedback_form");
    self.TEXT = getElement("feedback_text");
    self.SUBMIT = getElement("feedback_submit");
    self.THANKYOU = getElement("feedback_thank");
    self.ERROR = getElement("feedback_error");

    self.window = new MochiDropDown(
        self.FEEDBACK_PANEL,
        [getElement('feedback_link')], //toggle
        [], //show only
        [getElement('feedback_close'), getElement('feedback_cancel')] //hide only
    );
    //events
    connect(self.SUBMIT, "onclick", function(e) { e.stop(); self.sendForm(); })
}

MochiFeedback.sendForm = function() {
    var self = MochiFeedback;
    var d = doXHR(self.AJAX_URL, {
        method: 'POST',
        sendContent: queryString(
            {url: window.location, comment: self.TEXT.value}),
        headers: [
            ['Content-type', 'application/x-www-form-urlencoded'],
            ['Accept', 'application/json']
        ]
    }).addCallback(evalJSONRequest);
    d.addBoth(method(self, 'postResponse'));
}

MochiFeedback.postResponse = function(req) {
    var self = MochiFeedback;
    fade(self.FORM, {duration: .25});
    if(req.success) {
        appear(self.THANKYOU, {
            afterFinish: function() {
                callLater(1.2, method(self.window, 'hide'));
            }
        });
    } else {
 removeElementClass(self.ERROR, 'hide');
        appear(self.ERROR);
        log(req.message);
    }
}

/********  Help component ********/
MochiHelp = {};
MochiHelp.DATA_URL = "/support/faq_lookup_json"; // url for the json data
MochiHelp.DATA = {};
MochiHelp.HELP_PANEL_LINK = "help_panel_link" //id or element for the link that activates the help panel
MochiHelp.HELP_PANEL = "help_panel"; //id or element for the help panel

MochiHelp._init = function() {
    var self = MochiHelp;

    self.HELP_PANEL_LINK = getElement(self.HELP_PANEL_LINK);
    self.HELP_PANEL = getElement(self.HELP_PANEL);

    //Look for page help and initialize the topics
    if( getElement('page_help') && getElement('topic_data')) {

        c = eval( '(' + getElement('topic_data').value + ')' );
        self.FAQ_TOPICS = c.topics;
        if (c.category) {
          self.FAQ_CATEGORY = c.category;
        } else {
          self.FAQ_CATEGORY = ""
        }
        //clearing search bar
        connect('s_help', 'onfocus', function(e) { e.stop(); if(hasElementClass(e.src(), 'default')) { e.src().value = ''; removeElementClass(e.src(), 'default'); } });
        connect('s_help', 'onblur', function(e) { e.stop(); if(e.src().value == '') { e.src().value = 'Search help\u2026'; addElementClass(e.src(), 'default'); }; });
        connect('f_help', 'onsubmit', function(e) { e.stop(); document.location = '/support/faq?q='+escape(getElement('s_help').value); });

        //Get data and process it
        self.DATA_URL_CATEGORY = self.DATA_URL + "/" + self.FAQ_CATEGORY;
        var d = loadJSONDoc(self.DATA_URL_CATEGORY);
        d.addCallback(self._initializeData);
        d.addCallback(self._createHelpPanel);

    } else {
        if(getElement('page_help')) { addElementClass('page_help', 'hide'); }
    }
}

MochiHelp._initializeData = function(req) {
    var self = MochiHelp;
    self.DATA.categories = {};
    forEach(self.FAQ_TOPICS, function(id){
        if(req.categories[id]) { self.DATA.categories[id] = req.categories[id]; }
    });
}

MochiHelp._createHelpPanel = function() {
    var self = MochiHelp;

    //hide the content panel off to the right
    self.HELP_PANEL.style.overflow = 'hidden';

    //load topic list and set first topic
    var cats = sorted(values(self.DATA.categories), reverseKeyComparator("priority"));
    replaceChildNodes('help_topics',
        UL(null,
            map(function(topic) {
                var a = A({'href':'#', 'rel': topic.id} );
                a.innerHTML = topic.name;
                connect(a, "onclick", function(e) { e.stop(); self.selectTopic(topic); });
                return LI(null, a);
            }, cats)
        )
    );
    self.selectTopic(self.DATA.categories[keys(self.DATA.categories)[0]]);

    //Set events
    self.window = new MochiDropDown(
        self.HELP_PANEL,
        [self.HELP_PANEL_LINK, getElementsByTagAndClassName("a", "b-close")[0]]
    );

    connect(self.window, "onHide", self.showIndex);
    connect(self.window, "onShow", function() { MochiUtil.trackEvent('page_help'); });    //Google analytics call
    connect("help_back", "onclick", function(e) { e.stop(); self.showIndex(); });
}

MochiHelp._helpPanelAppendFaqs = function(category, data){
    var self = MochiHelp;
    //load FAQ's
    if(data){
        var faq_element = getElement('help_faq');
        appendChildNodes(faq_element,
            createDOM("h5", null, category),
            UL(null,
                map(function(faq) {
                    var a = A({'href':'#', 'rel': faq.id}, faq.title);
                    connect(a, "onclick", function(e) { e.stop(); self.showDetail(faq); });
                    return LI(null, a);
                }, values(data.entries))
            )
        );
    }
    return data;
}

MochiHelp._buildQuestions = function(topic) {
    var self = MochiHelp;
    var faqs = sorted(values(topic.faqs), reverseKeyComparator("priority"));
    replaceChildNodes('help_faq',
        UL(null,
            map(function(faq) {
                var a = A({'href':'#', 'rel': faq.id});
                a.innerHTML = faq.question;
                connect(a, "onclick", function(e) { e.stop(); self.showDetail(faq); });
                return LI(null, a);
            }, faqs)
        )
    );
}

MochiHelp.selectTopic = function(topic) {
    var self = MochiHelp;
    forEach(getElement('help_topics').getElementsByTagName('li'), function(e) {
        if(getNodeAttribute(e.getElementsByTagName('a')[0], 'rel') == topic.id) {
            addElementClass(e, 'active');
        } else {
            removeElementClass(e, 'active');
        }
    });
    self._buildQuestions(topic);
}

MochiHelp.showDetail = function(detail) {
    var self = MochiHelp;
    getElement('help_title').innerHTML = detail.question;
    getElement('help_text').innerHTML = detail.answer;
    Move("help_slider", { x: -550, duration: .25, mode: 'relative' } );
}

MochiHelp.showIndex = function() {
    Move("help_slider", { x: 0, duration: .25, mode: 'absolute', afterFinish: function() { getElement('help_text').innerHTML = ''; } } );
}


/********  General components ********/

//Abstract: a generic pop-up window. Copy prototype to custom obj
function MochiWindow(element, toggleLinks, showLinks, hideLinks) {
    this.init(element, toggleLinks, showLinks, hideLinks);
    return this;
}

MochiWindow.prototype.init = function(element, toggleLinks, showLinks, hideLinks){
    if(arguments.length) { //check in case it's inherited
        var self = this;
        self.element = getElement(element);

        hideElement(self.element);
        removeElementClass(self.element, 'hide');

        //wtf is this? don't know...
        if(!window.windowKeyHandler){
            window.windowKeyHandler = connect(window, "onkeyup", function(e){
                if(e.key().string == 'KEY_ESCAPE'){
                    signal(window.EVENT_BUS,'hideAllWindows');
                };
            });
        }

        //set events for links
        if(toggleLinks) { if(!isArrayLike(toggleLinks)) { toggleLinks = [toggleLinks]; }; forEach(toggleLinks, function(l) { connect(l, "onclick", function(e){ e.stop(); self.toggle(e); }) }) }
        if(showLinks) { if(!isArrayLike(showLinks)) { showLinks = [showLinks]; }; forEach(showLinks, function(l) { connect(l, "onclick", function(e){ e.stop(); self.show(e); }) }) }
        if(hideLinks) { if(!isArrayLike(hideLinks)) { hideLinks = [hideLinks]; }; forEach(hideLinks, function(l) { connect(l, "onclick", function(e){ e.stop(); self.hide(e); }) }) }

        self.bodyEvent = connect(document.body, "onclick", function(e) { if(!self.isOver && self.isVisible) { self.hide(e); } });
        connect(self.element, "onmouseover", function(e) { e.stop(); self.isOver = true; });
        connect(self.element, "onmouseout", function(e) { e.stop(); self.isOver = false; });
        connect(window.EVENT_BUS, 'hideAllWindows', self, "hide");

        //check for parent?
        appendChildNodes(document.body, self.element);
    }
}

MochiWindow.prototype.toggle = function(e) {
    var self = this;
    signal(self, "onToggle");
    if(self.isVisible) { self.hide(e); } else { self.show(e); }
}

MochiWindow.prototype.show = function(e) {
    var self = this;
    if(!self.isVisible){
        if(e){
            self.lastsrc = e.src();
            addElementClass(self.lastsrc,'opened');
        }
        signal(window.EVENT_BUS,'hideAllWindows');
        signal(self, "onShow");
        appear(self.element, {duration: .25});
        self.isVisible = true;
    }
}

MochiWindow.prototype.hide = function(e) {
    var self = this;
    if(self.isVisible){
        if(self.lastsrc){
            removeElementClass(self.lastsrc,'opened');
        }
        signal(self, "onHide");
        fade(self.element, {duration: .25});
        self.isVisible = false;
    }
}

//Abstract: a generic pop-down-from-top window. Copy prototype to custom obj
function MochiDropDown(element, toggleLinks, showLinks, hideLinks) {
    this.init(element, toggleLinks, showLinks, hideLinks);
    return this;
}

update(MochiDropDown.prototype, MochiWindow.prototype);
MochiDropDown.prototype.show = function(e){
    var self=this;
    if(!self.isVisible){
        if(e){
            var srcElem = e.src();
            self.lastsrc = srcElem;
            var spos = getElementPosition(srcElem);
            var sdims = getElementDimensions(srcElem)
            var vdims = getViewportDimensions();
            var dims = elementDimensions(self.element);
            var sbot = spos.y + sdims.h;
            self.yoffset = dims.h + sbot + 50;
            if(spos.x + dims.w > vdims.w){
                var sright = spos.x + sdims.w;
                var pos = {x:(sright-dims.w), y:(sbot+10)-self.yoffset};
            }else{
                var pos = {x:spos.x, y:(sbot+10)-self.yoffset};
            }
            setElementPosition(self.element,pos);
            removeElementClass(self.element,'hide');
            addElementClass(self.lastsrc,'opened');
            self.element.style.display='block';
        }
        signal(window.EVENT_BUS,'hideAllWindows');
        signal(self,'onShow');
        Move(self.element, {duration: .25, y: self.yoffset, afterFinish: function() { self.element.isAnimating = false; }, beforeStart: function() { self.element.isAnimating = true; }});
        self.isVisible = true;
    }
}
MochiDropDown.prototype.hide = function(e) {
    var self = this;
    if(self.isVisible){
        if(self.lastsrc){
            removeElementClass(self.lastsrc,'opened');
        }
        signal(self, "onHide");
        Move(self.element, {duration: .25, y: -self.yoffset, afterFinish: function() { self.element.isAnimating = false; }, beforeStart: function() { self.element.isAnimating = true; }});
        self.isVisible = false;
    }
}

//Pop up help tips
//Uses MochiWindow
MochiTip = {};
MochiTip._init = function() {
    var self = MochiTip;
    self.TIP_LINK_CLASS = 'tooltip'
    self.TIP_LINKS = getElementsByTagAndClassName('a', self.TIP_LINK_CLASS)
    self.TIP_OFFSET = {x: 10, y:-10};
    self.TIP_PANELS = [];

    //build it all
    self._buildToolTips();
};

MochiTip._buildToolTips = function() {
    var self = MochiTip;

    //create the DOM object and MochiTooltip
    forEach(self.TIP_LINKS, function(link){
        var id = getNodeAttribute(link, "rel");
        if(id) {
            var tip = getElement(id);
            if(tip) {
                removeElementClass(tip, 'hide');
                var w = new MochiWindow(tip, link);
                connect(w, 'onShow', function(e) {
                   var t = getElementPosition(link);
                   var d = getElementDimensions(link);
                   setElementPosition(tip, { x: (t.x + d.w + self.TIP_OFFSET.x), y: (t.y + self.TIP_OFFSET.y) } );
                });
                self.TIP_PANELS.push(w);
            }
        }
    });
}

/* Admitedly this is a lot like tooltips, but whatever. Cleanup on isle 12 */
MochiFlyout = {};
MochiFlyout._init = function() {
    var self = MochiFlyout;
    self.TIP_LINK_CLASS = 'flyout'
    self.TIP_LINKS = getElementsByTagAndClassName('a', self.TIP_LINK_CLASS)
    self.TIP_OFFSET = {x: -5, y:-15};
    self.TIP_PANELS = [];

    //build it all
    self._buildFlyouts();
};

MochiFlyout._buildFlyouts = function() {
    var self = MochiFlyout;

    //create the DOM object and MochiTooltip
    forEach(self.TIP_LINKS, function(link){

        var id = getNodeAttribute(link, "rel");
        if(id) {
            var tip = getElement(id);
            if(tip) {
                removeElementClass(tip, 'hide');
                var win = new MochiWindow(tip);
                connect(win, 'onShow', function(e) {
                   var t = getElementPosition(link);
                   var d = getElementDimensions(link);
                   setElementPosition(tip, { x: (t.x + d.w + self.TIP_OFFSET.x), y: (t.y + self.TIP_OFFSET.y) } );
                });
                self.TIP_PANELS.push(win);
                //oer st tim, over again, cancel tim.
                var timer = null;
                connect(link, 'onmouseover', function(ev) {
                    if(timer) { clearTimeout(timer); }
                    var s = function() { win.show(); };
                    timer = setTimeout(s, 500);
                });
                connect(link, 'onmouseout', function(ev) {
                    if(timer) { clearTimeout(timer); }
                    var s = function() { win.hide(); };
                    timer = setTimeout(s, 500);
                });

            }
        }
    });
}

/* Used to show a message for a period of time then remove itself...used for form messages */
MochiMessage = {};
MochiMessage._init = function() {
    forEach($$('.fade'), function(elm) {
        setTimeout(function() {
            fade(elm);
        }, 2000);
    });
}

//Auto selected items in a list of radiobuttons / checkboxes
MochiInputList = {};
MochiInputList._init = function() {
    var lists = $$('ul.form ul.list');

    forEach(lists, function(el) {
        var inputs = el.getElementsByTagName('input');
        var sel = function(item) {
            if (item.type == 'radio') {
                forEach(inputs, function(i){
                    removeElementClass(getFirstParentByTagAndClassName(i, 'li'),
                                       'active'); });
            }
            addElementClass(getFirstParentByTagAndClassName(item, 'li'), 'active');
        }
        var unsel = function(item) {
            removeElementClass(getFirstParentByTagAndClassName(item, 'li'),
                               'active');
        }
        forEach(inputs, function(input) {
            connect(input, 'onclick', function(ev) {
                if(ev.src().checked) { sel(ev.src()); }
                else if(ev.src().type == 'checkbox') { unsel(ev.src()); }
            });
            if(input.checked) { sel(input); }
        });
    });
}

//Show/hides
MochiToggle = {};
MochiToggle._init = function() {
    var links = getElementsByTagAndClassName('a', 'toggle');
    forEach(links, function(el) {
        var id = getNodeAttribute(el, 'rel');
        if(id) { connect(el, 'onclick', function(ev){
            ev.stop();
            if(hasElementClass(el, 'slide')) {
                if(hasElementClass(id, 'hide')) {
                    hideElement(id);
                    removeElementClass(id, 'hide');
                }
                toggle(id, 'slide', { duration : .2 } );
            } else {
                toggleElementClass('hide', id);
            }
            toggleElementClass('active', ev.src()); })
        }
    })
}

// Connects on-page tab behavior to a tab group. Each tab will show a panel on-screen
TabManager = {};
TabManager.initiate = function() {
    var self = TabManager;
    self.tabs = $$(".tabs li");
    forEach(self.tabs, function(tab) {
        connect(tab, "onclick", function(e) { e.stop(); self.selectTab(e.src()); });
    });
    if(self.tabs.length > 0)
        self.selectTab(self.findTabInQueryString()); //check query for a tab to highlight, else highlight first
}

TabManager.selectTab = function(tab) {
    var self = TabManager;
    forEach(self.tabs, function(element) {  //clear out the tab classes and hide content
        removeElementClass(element, "active");
        addElementClass(getElement(getNodeAttribute(element.getElementsByTagName("a")[0], "rel")), "hide");
    });
    addElementClass(tab, "active");
    removeElementClass(getElement(getNodeAttribute(tab.getElementsByTagName("a")[0], "rel")), "hide");
}

TabManager.findTabInQueryString = function() {
    var self = TabManager;
    var q = document.location.href.split('?')[1];
    if(!q) {
        return self.tabs[0];
    } else {
        var parm = parseQueryString(document.location.href.split('?')[1]).tab;
        var r = null;
        forEach(self.tabs, function(tab){
           getNodeAttribute(tab.getElementsByTagName('a')[0], "rel") == parm ? r = tab :0;
        });
        return r;
    }
}

MochiStripes = {};
MochiStripes._init = function() {
    var tables = $$('table.data');
    forEach(tables, function(t){
        MochiStripes.add(t)
    });
}
MochiStripes.add = function(table) {
    var rows = table.getElementsByTagName('tr');
    var i = false;
    forEach(rows, function(r){
        removeElementClass(r, 'alt')
        if(i) { addElementClass(r, 'alt'); i = false; } else { i = true; }
    });
}




/******************
Utility namespace for you know...utility functions
******************/

MochiUtil = {};
//shouldn't mochikit have this...or the DOM
MochiUtil.getElementsByAttributeValue = function(parentNode, attribute, value) {
    if(parentNode && attribute && value) {
        var found = new Array();
        nodeWalk(parentNode, function(thisNode){
            if(getNodeAttribute(thisNode, attribute) == value) { found.push(thisNode); }
            return thisNode.childNodes;
        });
        return found;
    }
    return null;
}

MochiUtil.auto_swfobject = function() {
    var elems = getElementsByTagAndClassName("div", "swfobject");
    for (var i = 0; i < elems.length; i++) {
        var elem = elems[i];
        var params = strip(scrapeText(elem)).split(/\s+/);
        var url = params[0];
        var width = params[1];
        var height = params[2];
        if (typeof(swfobject) != 'undefined') {
            var subdiv = DIV({id: "auto_swfobject_swf_" + i});
            replaceChildNodes(elem, subdiv);
            var params = {wmode: 'transparent'};
            var flashvars = {};
            var attributes = {};
            var express = "/static/global/js/swfobject/expressInstall.swf";
            swfobject.embedSWF(url, subdiv.id, width, height, '8.0.0', express, flashvars, params, attributes);
            removeElementClass(elem, "swfobject");
        } else if (typeof(SWFObject) != 'undefined') {
            /* TODO: This code is deprecated support for pre-#5685 SWFObject
                     please remove me! */
            var so = new SWFObject(url, "swf_" + i, width, height, "7");
            so.addParam("wmode", "transparent");
            so.write(elem);
            removeElementClass(elem, "swfobject");
        }
    }
}

MochiUtil.getAnchor = function() {
    var s = document.location.toString();
    a = "";
    if(s.indexOf('#') > 0) {
        a = s.slice(s.indexOf('#')+1, s.length);
    }
    return a;
}

MochiUtil.selectOnFocus = function() {
    forEach(getElementsByTagAndClassName('input', 'select_on_focus'),function (el){
        connect(el, 'onfocus', function (e) {
            e.src().select();
        });
    });
}

MochiUtil.backLinks = function() {
    forEach($$('a.back'), function (el){
        connect(el, 'onclick', function (e) {
            e.stop()
            history.back();
        });
    });
}

MochiUtil.trackEvent = function(evt) {
    if(pageTracker)
        pageTracker._trackPageview('/event/'+evt);
}

MochiUtil.connectFormElements = function(form, ev, handler) {
    form = getElement(form);
    var i = concat(form.getElementsByTagName('input'), form.getElementsByTagName('select'), form.getElementsByTagName('textarea'));
    forEach(i, function(e){ connect(e, ev, handler); });
}

MochiUtil.makeWindowIeSafe = function(w) {
    //place frame in window
}

//Very ugly I know, but necessary for a hack, this can return a cooler object if we need it later
MochiUtil.isIE6 = function() {
   if( (navigator.appName.indexOf('Microsoft Internet Explorer') >= 0) && (navigator.appVersion.indexOf('6.') > 0) ) {
       return true;
   }
   return false;
}

//
//
//Creates random colors
function MochiColorCycle(clamp) {
    if(!clamp)
        clamp = parseInt('ffffff', 16);
    else
        clamp = parseInt(clamp, 16);
    this.init(clamp);
    return this;
}

MochiColorCycle.prototype.init = function(clamp) {
    var self = this;
    self.colors = [];
    self.named_colors = Color.namedColors();
    for(var c in self.named_colors) {
        color = self.named_colors[c].substr(1, self.named_colors[c].length);
        if(parseInt(color, 16) <= clamp)
            self.colors.push( self.named_colors[c].substr(1, self.named_colors[c].length ));
    }
}

MochiColorCycle.prototype.getColor = function() {
    var self = this;
    rnd = Math.floor(Math.random() * self.colors.length);
    return self.colors.splice(rnd, 1)[0];
}

MochiColorCycle.prototype.releaseColor = function(color) {
    var self = this;
    self.colors.push( color );
}

function get_alias() {
    if (!getElement("mochi_alias")) return "";
    return scrapeText("mochi_alias");
}

function get_message_count() {
    if (!getElement("mp_messages_count")) return;
    var url = "/community/message/get_unread_messages";
    replaceChildNodes("mp_messages_count", "\u2026");
    loadJSONDoc(url, {user_alias: get_alias()}).addBoth(
        function (rval) {
            var count = rval.messages;
            if (count) {
                addElementClass("mp_messages", "unread");
            } else {
                if (typeof(count) == 'undefined') {
                    count = '\u2026';
                }
                removeElementClass("mp_messages", "unread");
            }
            replaceChildNodes("mp_messages_count", count);
        });
}
