jQuery.namespace=function(F,B){var C,A=F.split("."),E=window,D=false;if(/[^a-zA-Z.]/.test(F)){return false}for(C=0;C<A.length;C++){if(!E[A[C]]){E[A[C]]={};D=true}E=E[A[C]]}if(!!B){return D}return true};
// Constants
var BAMBOO_DASH_DISPLAY_TOGGLES = "bamboo.dash.display.toggles";

//
// Display the given help page in a separate window
//
function openHelp(helpPage)
{
    window.open(helpPage, 'manualPopup', 'width=770,height=550,scrollbars=yes,status=yes,resizable=yes');
}

AJS.BambooDialog = function(options) {
    var dialog;
    options = options || {};
    options = jQuery.extend({}, options, {
        keypressListener: function(e) {
            if (e.keyCode === 27) {
                dialog.remove();
            }
        }
    });
    dialog = new AJS.Dialog(options);
    return dialog;
};

String.prototype.replaceAll = function(pcFrom, pcTo)
{
    var MARKER = "js___bmbo_mrk"
    var i = this.indexOf(pcFrom);
    var c = this;
    while (i > -1)
    {
        c = c.replace(pcFrom, MARKER);
        i = c.indexOf(pcFrom);
    }

    i = c.indexOf(MARKER);
    while (i > -1)
    {
        c = c.replace(MARKER, pcTo);
        i = c.indexOf(MARKER);
    }

    return c;
}


if (!jQuery.generateId) {
  jQuery.generateId = function() {
    return arguments.callee.prefix + arguments.callee.count++;
  };
  jQuery.generateId.prefix = 'jq-';
  jQuery.generateId.count = 0;

  jQuery.fn.generateId = function() {
    return this.each(function() {
      this.id = jQuery.generateId();
    });
  };
}


/*
 Runs on a change of a
*/
function handleOnSelectShowHide(sel)
{
    var hideClassName = "dependsOn" + sel.name;
    hideClassName = hideClassName.replaceAll(".", "\\.");
    
    var switchValue = getSwitchValue(sel);
    if (!switchValue && sel.type == 'radio') return;

    var selector = "#" + sel.form.id + " ." + hideClassName;

    var showPattern = "showOn" + switchValue;
    AJS.$(selector).each(function()
    {
        var deps = this;
        if (isContainsClass(deps, showPattern))
        {
            deps.style.display = '';
        }
        else
        {
            deps.style.display = 'none';
        }
    });
}

/**
 * JSOn listData looks like:
 *
 *  [data: [{value: 'key1', text: 'name for the screen', supportedValues: ['dependencyKey1', 'dependencyKey2']},
 *   {value: 'key2', text: "name 2", supportedValues: ['dependencyKey1']}]]
 *
 * If supportedValues is empty it will be shown for all selections in selParentJQ.
 *
 * @param selParentJQ - the select list that the your data depends on
 * @param selToMutateJQ - the select list that you want to mutate based on the contents of the selDependency
 * @param listDataJson - the json containing all information required to generate the selToMutate
 */
function mutateSelectListContent(selParentJQ, selToMutateJQ, listDataJson)
{
    var selParent = selParentJQ[0];
    var selToMutate = selToMutateJQ[0];
    var currentSelectedValue = selToMutateJQ.val();
    var switchValue = getSwitchValue(selParent);
    //wipe existing items
    selToMutate.options.length = 0;

    var listData = listDataJson.data;
    // got through selToMutate check if each item is in allowed Items, if not remove?
    for (var i = 0; i<listData.length; i++){
        var value = listData[i].value;
        var text = listData[i].text;
        var allowedOptions = listData[i].supportedValues;

        var show = allowedOptions === null || allowedOptions.length <= 0 || !switchValue;
        if (!show)
        {
            for (var x = 0; x < allowedOptions.length; x++)
            {
                var allowedOption = allowedOptions[x];
                if (switchValue === allowedOption)
                {
                    show = true;
                    break;
                }
            }
        }

        if (show)
        {
            selToMutate.options[selToMutate.options.length]=new Option(text, value, false, value === currentSelectedValue);
        }
    }

    // make sure we update the dependents of the mutated list.
    handleOnSelectShowHide(selToMutate)
}

function getSwitchValue(obj)
{
    if (obj.options && obj.selectedIndex > -1)
    {
        var opt = obj.options[obj.selectedIndex];
        //  Special uiSwitch* class prefix
        if (opt.className.indexOf('uiSwitch') != -1)
        {
            var uiSwitch = opt.className.substring(opt.className.lastIndexOf("uiSwitch") + 8);
            return uiSwitch;
        }
        else
        {
            return opt.value;
        }
    }
    else if (obj.type == 'radio')
    {
        if (obj.checked)
            return obj.value;
        else
            return null;
    }
    else if (obj.type == 'checkbox')
    {
        if (obj.checked)
            return obj.value;
        else
            return 'false';
    }
    else
    {
        return obj.value;
    }
}


/*
 Toggles hide / unhide an element. Also attemots to change the "elementId + header" element to have the headerOpened / headerClosed class.
 Also saves the state in a cookie
*/
function toggleElement(elementId, suffix)
{
    var elem = document.getElementById(elementId);
    if (elem)
    {
        if (readFromConglomerateCookie("bamboo.conglomerate.general.cookie", elementId, '1') == '1')
        {
            elem.style.display = "none";
            var toggler = document.getElementById(elementId + "Toggle");
            if (toggler)
            {
                toggler.innerHTML = "show" + (suffix ? " " + suffix : "");
            }
            //            removeClassName(elementId + 'header', 'headerOpened');
            //            addClassName(elementId + 'header', 'headerClosed');
            saveToConglomerateCookie("bamboo.conglomerate.general.cookie", elementId, '0');
        }
        else
        {
            elem.style.display = "";
            var toggler = document.getElementById(elementId + "Toggle");
            if (toggler)
            {
                toggler.innerHTML = "hide" + (suffix ? " " + suffix : "");
            }
            //            removeClassName(elementId + 'header', 'headerClosed');
            //            addClassName(elementId + 'header', 'headerOpened');
            eraseFromConglomerateCookie("bamboo.conglomerate.general.cookie", elementId);
        }
    }
}

function toggleDivsWithCookie(elementShowId, elementHideId)
{
    var elementShow = document.getElementById(elementShowId);
    var elementHide = document.getElementById(elementHideId);
    if (elementShow.style.display == 'none')
    {
        elementHide.style.display = 'none';
        elementShow.style.display = 'block';
        saveToConglomerateCookie("bamboo.viewissue.cong.general.cookie", elementShowId, null);
        saveToConglomerateCookie("bamboo.viewissue.cong.general.cookie", elementHideId, '0');
    }
    else
    {
        elementShow.style.display = 'none';
        elementHide.style.display = 'block';
        saveToConglomerateCookie("bamboo.viewissue.cong.general.cookie", elementHideId, null);
        saveToConglomerateCookie("bamboo.viewissue.cong.general.cookie", elementShowId, '0');
    }
}

/*
 Similar to toggle. Run this on page load.
*/
function restoreDivFromCookie(elementId, cookieName, defaultValue, suffix)
{
    if (defaultValue == null)
        defaultValue = '1';

    var elem = document.getElementById(elementId);
    if (elem)
    {
        if (readFromConglomerateCookie(cookieName, elementId, defaultValue) != '1')
        {
            elem.style.display = "none";
            var toggler = document.getElementById(elementId + "Toggle");
            if (toggler)
            {
                toggler.innerHTML = "show" + (suffix ? " " + suffix : "");
            }
            //            removeClassName(elementId + 'header', 'headerOpened');
            //            addClassName(elementId + 'header', 'headerClosed')
        }
        else
        {
            elem.style.display = "";
            var toggler = document.getElementById(elementId + "Toggle");
            if (toggler)
            {
                toggler.innerHTML = "hide" + (suffix ? " " + suffix : "");
            }
            //            removeClassName(elementId + 'header', 'headerClosed');
            //            addClassName(elementId + 'header', 'headerOpened')
        }
    }
}

/*
 Similar to toggle. Run this on page load.
*/
function restoreElement(elementId, suffix)
{
    restoreDivFromCookie(elementId, "bamboo.conglomerate.general.cookie", '1', suffix);
}

// Cookie handling functions

function saveToConglomerateCookie(cookieName, name, value)
{
    var cookieValue = getCookieValue(cookieName);
    cookieValue = addOrAppendToValue(name, value, cookieValue);

    saveCookie(cookieName, cookieValue, 365);
}

function readFromConglomerateCookie(cookieName, name, defaultValue)
{
    var cookieValue = getCookieValue(cookieName);
    var value = getValueFromCongolmerate(name, cookieValue);
    if (value != null)
    {
        return value;
    }

    return defaultValue;
}

function eraseFromConglomerateCookie(cookieName, name)
{
    saveToConglomerateCookie(cookieName, name, "");
}

function getValueFromCongolmerate(name, cookieValue)
{
    var newCookieValue = null;
    // a null cookieValue is just the first time through so create it
    if (cookieValue == null)
    {
        cookieValue = "";
    }
    var eq = name + "-";
    var cookieParts = cookieValue.split('|');
    for (var i = 0; i < cookieParts.length; i++)
    {
        var cp = cookieParts[i];
        while (cp.charAt(0) == ' ')
        {
            cp = cp.substring(1, cp.length);
        }
        // rebuild the value string exluding the named portion passed in
        if (cp.indexOf(name) == 0)
        {
            return cp.substring(eq.length, cp.length);
        }
    }
    return null;
}

//either append or replace the value in the cookie string
function addOrAppendToValue(name, value, cookieValue)
{
    var newCookieValue = "";
    // a null cookieValue is just the first time through so create it
    if (cookieValue == null)
    {
        cookieValue = "";
    }

    var cookieParts = cookieValue.split('|');
    for (var i = 0; i < cookieParts.length; i++)
    {
        var cp = cookieParts[i];

        // ignore any empty tokens
        if (cp != "")
        {
            while (cp.charAt(0) == ' ')
            {
                cp = cp.substring(1, cp.length);
            }
            // rebuild the value string exluding the named portion passed in
            if (cp.indexOf(name) != 0)
            {
                newCookieValue += cp + "|";
            }
        }
    }

    // always append the value passed in if it is not null or empty
    if (value != null && value != '')
    {
        var pair = name + "-" + value;
        if ((newCookieValue.length + pair.length) < 4020)
        {
            newCookieValue += pair;
        }
    }
    return newCookieValue;
}

function getCookieValue(name, defaultString)
{
    var eq = name + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++)
    {
        var c = ca[i];
        while (c.charAt(0) == ' ')
        {
            c = c.substring(1, c.length);
        }
        if (c.indexOf(eq) == 0)
        {
            return c.substring(eq.length, c.length);
        }
    }

    return defaultString;
}

function saveCookie(name, value, days)
{
    var ex;
    if (days)
    {
        var d = new Date();
        d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
        ex = "; expires=" + d.toGMTString();
    }
    else
    {
        ex = "";
    }
    document.cookie = name + "=" + value + ex + ";path=" + "/";
}

/*
Reads a cookie. If none exists, then it returns and
*/
function readCookie(name, defaultValue)
{
    var cookieVal = getCookieValue(name);
    if (cookieVal != null)
    {
        return cookieVal;
    }

    // No cookie found, then save a new one as on!
    if (defaultValue)
    {
        saveCookie(name, defaultValue, 365);
        return defaultValue;
    }
    else
    {
        return null;
    }
}

function eraseCookie(name)
{
    saveCookie(name, "", -1);
}

function isContainsClass(obj, strClass)
{
    return obj.className.indexOf(strClass) != -1;
}

function addUniversalOnload(myFunction)
{
    AJS.$(document).ready(myFunction);
}

function attachHandler(object, event, myFunction)
{
    AJS.$("#" + object).bind(event, myFunction);
}

function toggleOn(e, toggleGroup_id)
{

    var onToggle = document.getElementById(toggleGroup_id + "_toggler_on");
    var offToggle = document.getElementById(toggleGroup_id + "_toggler_off");
    var target = document.getElementById(toggleGroup_id + "_target");
    onToggle.style.display = 'block';
    offToggle.style.display = 'none';
    target.style.display = 'block';
    saveToConglomerateCookie(BAMBOO_DASH_DISPLAY_TOGGLES, toggleGroup_id, null);
}

function toggleOff(e, toggleGroup_id)
{
    var onToggle = document.getElementById(toggleGroup_id + "_toggler_on");
    var offToggle = document.getElementById(toggleGroup_id + "_toggler_off");
    var target = document.getElementById(toggleGroup_id + "_target");
    onToggle.style.display = 'none';
    offToggle.style.display = 'block';
    target.style.display = 'none';
    saveToConglomerateCookie(BAMBOO_DASH_DISPLAY_TOGGLES, toggleGroup_id, '0');
}

function restoreTogglesFromCookie(toggleGroup_id)
{
    var elem = document.getElementById(toggleGroup_id + "_target");
    if (elem)
    {
        if (readFromConglomerateCookie(BAMBOO_DASH_DISPLAY_TOGGLES, toggleGroup_id, null) == '0')
        {
            toggleOff(null, toggleGroup_id);
        }
        else
        {
            toggleOn(null, toggleGroup_id);
        }
    }
}

function collapseAll()
{
    AJS.$("#allPlansSection a.projectLabel").each(function(){
        toggleOff(null, this.id);
    });
}

function expandAll()
{
    AJS.$("#allPlansSection a.projectLabel").each(function(){
        toggleOn(null, this.id);
    });
}

// Forms JS
function toggleContainingCheckbox(e)
{
    if (e && e.target.tagName == "INPUT")
    {
        return true;
    }

    AJS.$(this).find("INPUT[type=checkbox]").each(function(){
        this.checked = !this.checked;
    });

    return true;
}



// ------------------------------------------------------------------------------------------------- Common Ajax Objects

function ajaxSubmitHandler(e, updateDivId)
{
    e.preventDefault();
    e.stopPropagation();

    var updater = getEl(updateDivId).getUpdateManager();
    var form = e.findTarget(null, 'form');

    updater.formUpdate(form);

    //alert('e: ' + e + ' ctx: ' + updater + ' this: ' + link.href);
}


function ajaxClickHandlerForDiv(e, updateDivId)
{

    var updater = getEl(updateDivId).getUpdateManager();
    var link = e.findTarget('internalLink', 'a');

    if (link != null)
    {
        e.preventDefault();
        updater.update(link.href, null, null, true);
    }

}

// This method will leak if used in a reloading panel. Use with care! 
function rewriteForms(el, oResponseObject)
{
    var updater = el.getUpdateManager();
    var forms = el.getChildrenByTagName('form');
    for (var i = 0; i < forms.length; i++)
    {
        forms[i].addManagedListener('submit', ajaxSubmitHandler, el.id);
    }
}

function clearHandlers(el)
{

    var yuiTooltips =  el.getChildrenByClassName('yuiTooltips', 'div');
    for (var i = 0; i < yuiTooltips.length; i++)
    {
        var currentTooltip = yuiTooltips[i];
        currentTooltip.removeAllListeners();
        currentTooltip.remove();
    }

    var forms = el.getChildrenByTagName('form');
    for (var i = 0; i < forms.length; i++)
    {
        forms[i].removeAllListeners();
    }

    var evented = el.getChildrenByClassName('evented');
    for (var i = 0; i < evented.length; i++)
    {
        evented[i].removeAllListeners();
    }

    return true;
}


function toggleFavourite(url)
{
    AJS.$.get(url);
    return false;
}

/*
  Toggles the image's icon between having the suffix of '_on' and '_off'. This also clears the title.
*/
function toggleIcon(e)
{
    AJS.$(this).find("img").each(function(){
        var dom = this;
        if (dom.src)
        {
        if (dom.src.indexOf('_on.') != -1)
        {
            toggleIconOff(dom);
        }
        else if (dom.src.indexOf('_off') != -1)
        {
            toggleIconOn(dom);
        }
        }
    });

    return true;
}

function toggleIconOff(dom)
{
    dom.src = dom.src.replace('_on.', '_off.');
}

function toggleIconOn(dom)
{
    dom.src = dom.src.replace('_off.', '_on.');
}

function checkFormChanged(form)
{
    var domForm;
    if (form.dom)
    {
        domForm = form.dom;
    }
    else
    {
        domForm = form;
    }

    var textFields = domForm.getElementsByTagName("INPUT");
    for (var i = 0; i < textFields.length; i++)
    {
        var textField = textFields[i];
        if (textField.type == "text"){
            if (textField.value != textField.defaultValue) return true;
        } else if (textField.type == "textarea") {
            if (textField.value != textField.defaultValue) return true;
        } else if (textField.type == "checkbox") {
            if (textField.checked != textField.defaultChecked) return true;
        } else if (textField.type == "radio") {
            if (textField.checked != textField.defaultChecked) return true;
        } else if (textField.type == "password") {
            if (textField.value != textField.defaultValue) return true;
        }
    }

   var selectFields = domForm.getElementsByTagName("select");
    for (var y = 0; y < selectFields.length; y++)
    {
       var mySelect = selectFields[y];
        for (var z=0; z < mySelect.options.length; z++)
        {
            if (mySelect.options[z].defaultSelected) {
               if (!mySelect.options[z].selected) {
                   return true;
               } else {
                   break;
               }
            }
        }
    }

    var textAreas = domForm.getElementsByTagName("textarea");
    for (var x = 0; x < textAreas.length; x++)
    {
       var textArea = textAreas[x];
       if (textArea.value != textArea.defaultValue) return true;
    }

    return false;
};

/*
*   For user picker - when you click "Check All' all the select boxes are changed
*/
    var selectedBoxes = [];

    function setCheckboxes()
    {
        var numelements = document.selectorform.elements.length;
        var item0 = document.selectorform.elements[0];
        var item1;

        for (var i=1 ; i < numelements ; i++)
        {
            item1 = document.selectorform.elements[i];
            item1.checked = item0.checked;
            if (!selectedBoxes[item1.name])
                selectedBoxes[item1.name] = [];
            selectedBoxes[item1.name][item1.value] = item1.checked;
        }
    }

/**
 * Checks if the form has changed. If it hasn't then returns to the URL. If it has, submits the form with the returnUrl set to the URL
 * @param elem         DOM element or jQuery selector
 * @param contextPath
 * @param url
 */
function submitFormIfChanged(elem, contextPath, url)
{
    var theForm = elem.form ? elem.form : AJS.$(elem).get(0);

    if (checkFormChanged(theForm))
    {
        theForm.action = theForm.action + (theForm.action.indexOf('?') == -1 ? '?' : '&') + 'returnUrl=' + url;
        theForm.submit();
    }
    else
    {
        location.href = contextPath + url;
    }
}

/*
*  For User Picker - checkboxes are named after users so by compiling a list of all selected checkboxes
*   we have a list of users (comma seperated)
*/
    function getEntityNames()
    {
        var numelements = document.selectorform.elements.length;
        var item;
        var checkedList = "";

        var sep = "";
        for (var i = 0 ; i < numelements ; i++)
        {
            item = document.selectorform.elements[i];
            if (item != null && item.type == "checkbox" && item.name != "all" && item.checked == true)
            {
                var itemValue = item.value;
                itemValue = itemValue.replace(/\\/g, "\\\\").replace(/,/g, "\\,");
                checkedList  = checkedList + sep + itemValue;
                sep = ", ";
            }
        }

        return checkedList;
    }

/*
*   For User Picker - takes comma seperated list of users and places them in specified field.
*/
   function addUsers(commaDelimitedUserNames, fieldID, multiSelect)
    {
        var element = document.getElementById(fieldID);
        var currentUsers = element.value;
        if (!multiSelect) {
            element.value = commaDelimitedUserNames;
        } else if (currentUsers != null && currentUsers != ""){
            element.value = currentUsers + ", " + commaDelimitedUserNames;
        } else {
            element.value = commaDelimitedUserNames;
        }
    }

var STATUS_PREFIX = 'statusSection';
var REASON_PREFIX = 'reasonSummary';
var DURATION_PREFIX = 'durationSummary';
var LAST_BUILT = 'lastBuiltSummary';
var TESTCOUNT_PREFIX = 'testSummary';
var LATEST_BUILD_PREFIX = 'latestBuild';
var PLAN_PROPS = [LATEST_BUILD_PREFIX, REASON_PREFIX, DURATION_PREFIX, TESTCOUNT_PREFIX, LAST_BUILT];

function updatePlan(plan)
{
    try
    {
        var planKey = plan.planKey;

        //the following for loops have len initialised as such for improved performance
        for (var i = 0, len = PLAN_PROPS.length; i < len; i++)
        {
            var propPrefix =  PLAN_PROPS[i];
            var elem = AJS.$("#" + propPrefix + planKey);
            if (LATEST_BUILD_PREFIX == propPrefix)
            {
                // Update the status when first item and clear the existing classes on this element

                elem.closest(".planKeySection").attr('class', 'planKeySection ' + plan.statusClass);
            }
            elem.html(plan[propPrefix]);
        }
        var imgs = AJS.$("#" + STATUS_PREFIX + planKey).find("img");
        for (var i = 0, len = imgs.length; i < len; i++)
        {
            var statusIcon = imgs.get(i);
            var newPath = BAMBOO.contextPath + plan.statusIconPath;
            if (newPath != statusIcon.src)
            {
                statusIcon.src = newPath;
                statusIcon.title = plan.statusText;
            }
        }

        // Update favourites
        var faves = AJS.$('#favouriteIconFor_' + planKey);
        for (var i = 0, len = faves.length; i < len; i++)
        {
            var fave = faves.get(i);
            if (plan.favourite)
            {
                toggleIconOn(fave);
            }
            else
            {
                toggleIconOff(fave);
            }
        }

        if (plan.allowStop)
        {
            AJS.$('#stopBuild_' + planKey).show();
            AJS.$('#manualBuild_' + planKey).hide();
        }
        else
        {
            AJS.$('#stopBuild_' + planKey).hide();
            AJS.$('#manualBuild_' + planKey).show();
        }
    }
    catch(ex)
    {
        //alert(ex);
        console.warn(ex);
    }
}

function updatePlans(sinceSystemTime)
{
        // Make the call to the server for JSON data
    if (!sinceSystemTime) sinceSystemTime = 0;
    // Define the callbacks for the asyncRequest
    var callbacks = {
        dataType: 'json',
        url:BAMBOO.contextPath + "/ajax/viewPlanUpdates.action?sinceSystemTime=" + sinceSystemTime,
        success : function (response) {
            if (!BAMBOO.reloadDashboard) return; // do nudda

            // Process the JSON data returned from the server
            try {
                if (response.plans)
                {
                    var plans = response.plans;
                    var plan;
                    for (var i = 0, len = plans.length; i < len; i++)
                    {
                        plan = plans[i];
                        updatePlan(plan);
                    }
                }
            }
            catch (x) {
                console.warn("JSON Parse failed! " + x + "\n", response);
            }

            var currentTime = 0;
            //var currentTime = holder.currentTime;
            setTimeout("updatePlans(" + currentTime +");", BAMBOO.reloadDashboardTimeout * 1000);
            AJS.$("#ajaxErrorHolder").hide();// hide error message jsut in case its visible
        },

        error : function () {
            AJS.$("#ajaxErrorHolder").show();
        },

        timeout : 60000
    };

    AJS.$.ajax(callbacks);
}

function reloadPanel(id, url, reloadEvery, loadScripts, previousText, callback)
{
    AJS.$.get(processReloadUrl(url), function(data)
    {
        var reponseText = data;

        if (!previousText || previousText != reponseText)
        {
            // only update if the previous response is different
            updateDomObject(reponseText, id, loadScripts, callback);
        }
        setTimeout(function() {reloadPanel(id, url, reloadEvery, loadScripts, reponseText, callback);}, reloadEvery * 1000);
    });

}

function processReloadUrl(url)
{
    if (BAMBOO.buildLastCurrentStatus)
    {
        var indexOfCurrentStatus = url.indexOf("&lastCurrentStatus");
        if (indexOfCurrentStatus != -1)
        {
            url = url.substring(0, indexOfCurrentStatus);
        }
        
        return url + "&lastCurrentStatus=" + BAMBOO.buildLastCurrentStatus;

    }
    else
    {
        return url;
    }
}

function updateDomObject(html, targetId, loadScripts, callback)
{
    if(typeof html == 'undefined'){
        html = '';
    }
    if(loadScripts !== true){
        AJS.$("#" + targetId).html(html);
        if (AJS.$.isFunction(callback))
        {
            AJS.$("#" + targetId).each(callback);
        }
        return;
    }
    var id = AJS.$.generateId();
    html += '<span id="' + id + '"></span>';

    AJS.$("#" + id).ready(function(){
        var hd = document.getElementsByTagName("head")[0];
        var re = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/img;
        var srcRe = /\ssrc=([\'\"])(.*?)\1/i;
        var match;
        while(match = re.exec(html)){
            var srcMatch = match[0].match(srcRe);
            if(srcMatch && srcMatch[2]){
                var s = document.createElement("script");
                s.src = srcMatch[2];
                hd.appendChild(s);
            }else if(match[1] && match[1].length > 0){
                eval(match[1]);
            }
        }
        var el = document.getElementById(id);
        if(el){el.parentNode.removeChild(el);}
    });
    var newHtml = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/img, '');
    AJS.$("#" + targetId).html(newHtml);

    if (AJS.$.isFunction(callback))
    {
        AJS.$("#" + targetId).each(callback);
    }
    return;
}


function updateInlineSection(url, sectionName)
{
     AJS.$.get(processReloadUrl(url), function(data)
      {
          updateDomObject(data, sectionName, "true", "null");
      });
}


function addConfirmationToLinks()
{
    AJS.$('a.requireConfirmation').bind("click", function(e)
    {
        if (!confirm('Please confirm that you are about to \n' + this.title))
        {
            return false;
        }
        else
        {
            return true;
        }
        });
}

function selectFirstFieldOfForm(formId)
{
     try
     {
        var form = AJS.$(formId);
        if (form && form.elements)
        {
            var errorFields = "#" + formId + " ." + "errorField";
            //var errorFields = getElementsByClassName('errorField', '*', form);
            if (errorFields && errorFields.length > 0)
            {
                errorFields[0].focus();
                return;
            }

            var elems = form.elements;
            for (var i = 0; i < elems.length; i++)
            {
                var elem = elems[i];
                if (elem.type != 'hidden')
                {
                        elem.focus();
                        break;
                }
            }
        }
    }
    catch (exception)
    {
        // if the form is inside a yui tab layout, it will try to do this for every tab on page load.
        // IE doesn't like this (can't focus on a control which is not visible i.e. hidden in the tabs)
    }
}

function reloadIfNoFormChanged()
{
    var forms = document.forms;
    for (var i = 0; i < forms.length; i++)
    {
        var form = forms[i];
        if (checkFormChanged(form))
        {
            return;
        }
    }
    window.location.reload();    
}

/**
 * Build queue related stuff
 */
var buildQueue = function()
{
    AJS.$(document).ready(function() {buildQueue.portletReloadCallback();});

    return {
        /**
         * Shows and hides Build Queue's action panels
         */
        displayActions : function(actionsId)
        {
            var prevActionsId = readFromConglomerateCookie(BAMBOO_DASH_DISPLAY_TOGGLES, "buildQueueActions", actionsId);
            AJS.$("#builders").removeClass(prevActionsId);
            AJS.$("#builders").addClass(actionsId);

            saveToConglomerateCookie(BAMBOO_DASH_DISPLAY_TOGGLES, "buildQueueActions", actionsId);
        },

        restoreDisplayActionsFromCookie : function()
        {
            this.displayActions(readFromConglomerateCookie(BAMBOO_DASH_DISPLAY_TOGGLES, "buildQueueActions", "actions-queueControl"));
        },

        portletReloadCallback : function()
        {
            buildQueue.restoreDisplayActionsFromCookie();
        }
    };
}();


function initCommitsTooltip(targetId, planKey, buildNumber)
{
    AJS.InlineDialog(AJS.$("#" + targetId),
                 targetId,
                 BAMBOO.contextPath + "/build/ajax/viewBuildCommits.action?buildKey=" + planKey + "&buildNumber=" + buildNumber,
                {onHover: true, fadeTime: 50, hideDelay: 0, showDelay: 100, width: 300, offsetX: 0,offsetY: 10});
}

function initCommentTooltip(targetId, planKey, buildNumber)
{
    var link = AJS.$("#" + targetId);
    AJS.InlineDialog(link,
             targetId,
             BAMBOO.contextPath + "/build/ajax/viewBuildComments.action?buildKey=" + planKey + "&buildNumber=" + buildNumber,
            {onHover: true, fadeTime: 50, hideDelay: 0, showDelay: 100, width: 300, offsetX: 0,offsetY: 10});
}

function bindMaven2DependencyConfirmation()
{
    var checkbox = AJS.$("#newDependencyValue");
    
    checkbox.click(function()
    {
        var oldValue = AJS.params.oldDependencyValue;
        var newValue = checkbox.is(':checked');

        if (isEdit) {
            if (!oldValue && newValue) {
                AJS.$("#mavenDependencyOnWarning").show();
                AJS.$("#mavenDependencyOffWarning").hide();
            }
            else if (!newValue && oldValue)
            {
                AJS.$("#mavenDependencyOnWarning").hide();
                AJS.$("#mavenDependencyOffWarning").show();
            }
            else
            {
                AJS.$("#mavenDependencyOnWarning").hide();
                AJS.$("#mavenDependencyOffWarning").hide();
            }
        } else {
            if (newValue) {
                AJS.$("#mavenDependencyOnWarning").show();
            } else {
                AJS.$("#mavenDependencyOnWarning").hide();
            }
        }
    });

    AJS.$(document).ready(function () {

        var checked = checkbox.is(":checked");
        if (checked && !isEdit) {
            AJS.$("#dependencyOnWarning").show();
            AJS.$("#mavenDependencyOffWarning").hide();
        } else {
            AJS.$("#mavenDependencyOnWarning").addClass("hidden");
            AJS.$("#mavenDependencyOffWarning").addClass("hidden");
        }
    });
}


/**
 * This function is not mentioned to be used directly but rather via @dj.simpleDialogForm FTL macro defined in dojo.ftl
 *
 * All the input params are also described in the dojo.ftl
 */
function simpleDialogForm(triggerSelector, getDialogBodyUrl, dialogWidth, dialogHeight, submitLabel, submitMode, submitCallback)
{
    function clearAllErrors(formJQ)
    {
        formJQ.find('.errorBox,.actionErrorList').remove();
    }

    function addActionError(formJQ, errors)
    {
        var list = AJS.$('<ul class="actionErrorList"/>');
        for (var i = 0; i < errors.length; i++) {
            list.append('<li><span class="errorMessage">' + errors[i] + '</span></li>');
        }
        formJQ.find("a").before(list);
    }

    function addFieldError(formJQ, fieldName, fieldErrors)
    {
        AJS.$('#fieldLabelArea_' + formJQ.attr('id') + '_' + fieldName).each(function()
        {
            var errorBox = AJS.$('<div class="errorBox"/>');
            AJS.$(this).before(errorBox);

            for (var i = 0; i < fieldErrors.length; i++) {
                var error = AJS.$('<div class="fieldError" errorfor="' + formJQ.attr('id') + '_' + fieldName +'"/>');
                error.html(fieldErrors[i]);
                errorBox.append(error);
            }
        });
    }

    function applyErrors(formJQ, result)
    {
        if (result.fieldErrors) {
            for (var fieldName in result.fieldErrors) {
                addFieldError(formJQ, fieldName, result.fieldErrors[fieldName]);
            }
        }

        if (result.errors) {
            addActionError(formJQ, result.errors);
        }
    }

    /**
     * This is called where callee requests non-ajax submit.
     * 1st phase is to call validation via AJAX using form's action attribute and then submit form in a classical way.
     *
     * @param formJQ  form to be submitted
     */
    function validateSubmitForm(formJQ)
    {
        clearAllErrors(formJQ);

        function validationCallback(result)
        {
            if (result.status.toUpperCase() == "OK")
            {
                formJQ.submit();
            }
            else
            {
               applyErrors(formJQ, result);
            }
        }

        AJS.$.post(formJQ.attr("action"), formJQ.serialize() + '&bamboo.enableJSONValidation=true', validationCallback, "json");
    }

    /**
     * For Ajax submit there's no separate validation phase.
     * Assumption is made that action will validate itself and will return proper JSON response in case of validation errors.
     *
     * @param formJQ          jQuery wrapper for form
     * @param submitCallback
     */
    function ajaxSubmitForm(formJQ, submitCallback)
    {
        clearAllErrors(formJQ);

        function successCallback(result)
        {
            if (result.status.toUpperCase()  == "OK")
            {
                submitCallback(result);
            }
            else
            {
               applyErrors(formJQ, result);
            }
        }

         function errorCallback(result)
        {
            if (result.status == 500)
            {
                addActionError(formJQ, Array("An internal server error has occurred. Please check the logs for more details."));
            }
            else
            {
                addActionError(formJQ, Array("An unknown error has occurred."));
            }
        }

        AJS.$.ajax({
            type: "POST",
            url: formJQ.attr("action"),
            data: formJQ.serialize(),
            success: successCallback,
            error: errorCallback,
            dataType: "json"
        });
    }

    function showDialog(linkElementJQ)
    {
        var popup = new AJS.BambooDialog({width: dialogWidth, height: dialogHeight});
        popup.addHeader(linkElementJQ.attr('title'));

        // @TODO Added by jdumay to workaround some bug... We should revisit this post AUI 2.0 upgrade
        popup.addPanel("I am invisible", "invisible!");

        popup.addButton("Cancel", function(dialog)
        {
            dialog.remove();
        });

        var panel = popup.getCurrentPanel();
        AJS.$.get(getDialogBodyUrl, function(data) {
            var dialogForm = "<div id='simpleDialogForm'>" + data + "</div>";
            panel.html(dialogForm);
            popup.addButton(submitLabel, function(dialog) {
                var formJQ = AJS.$("#simpleDialogForm form");

                if (submitMode == "ajax")
                {
                    ajaxSubmitForm(formJQ, function(result)
                    {
                        if (AJS.$.isFunction(submitCallback))
                        {
                            submitCallback(result);
                        }
                        dialog.remove();
                    });
                }
                else
                {
                    validateSubmitForm(formJQ);
                }
            }, 'simpleDialogFormSubmitButton');
            popup.getPage(0).button[0].moveRight();
        });

        popup.show();
    }

    AJS.$(triggerSelector).click(function()
    {
        showDialog(AJS.$(this));
    });
}

/**
 * General checkbox tree utilities.
 */
var checkboxTree = function()
{
    return {
        DONT_ENABLE_PARENT_ON_ALL_CHILDREN_ENABLED : 1 << 0,
        DISABLE_CHILDREN_ON_PARENT_CHECKED : 1 << 1,
        /**
         * Cascade state of a parent checkbox to collection of children checkboxes.
         * @param jqParent    parent checkbox (jQuery object)
         * @param jqChildren  children checkboxes (jQuery object)
         */
        cascadeToChildren : function(jqParent, jqChildren, options)
        {
            jqChildren.attr("checked", jqParent.attr("checked"));
            if (options & this.DISABLE_CHILDREN_ON_PARENT_CHECKED)
            {
                jqChildren.attr("disabled", jqParent.attr("checked"));
            }
        },

        /**
         * Propagate state of children checkboxes to parent checkbox.
         * @param jqParent    parent checkbox (jQuery object)
         * @param jqChildren  children checkboxes (jQuery object)
         * @param options     options that modify default behavior
         */
        propagateToParent : function(jqParent, jqChildren, options)
        {
            if (jqChildren.is(":not(:checked)"))
            {
                jqParent.removeAttr("checked");
            }
            else
            {
                if (!(options & this.DONT_ENABLE_PARENT_ON_ALL_CHILDREN_ENABLED))
                {
                    jqParent.attr("checked", "checked");
                }    
            }
        }
    };
}();


/**
 * Plan checkbox tree handling (top -> down and bottom -> up).
 *
 * Requirements:
 * - top level "Select all plans and projects" checkbox has name "tmpSelectedAll",
 * - project level checkboxes have value attribute equal to project key
 * - plan level checkboxes have id attribute equal to plan key
 */
var planCheckboxTree = function()
{
    var jqSelectedAll;
    var jqSelectedProjects;
    var jqSelectedProjectsByKey = {};
    var jqSelectedPlans = {};

    return {
        DONT_ENABLE_PROJECT_ON_ALL_PLANS_ENABLED : 1,
        /**
         * One-time preparation to avoid costly jQuery'ing in runtime.
         * @param nameSelectedAll       Name of top level "Select all plans and projects" checkbox
         * @param nameSelectedProjects  Name of project level checkboxes
         * @param nameSelectedBuilds    Name of plan level checkboxes
         */
        initialize : function(nameSelectedAll, nameSelectedProjects, nameSelectedBuilds)
        {
            jqSelectedAll = AJS.$("input:checkbox[name=" + nameSelectedAll + "]").eq(0);
            jqSelectedProjects = AJS.$("input:checkbox[name=" + nameSelectedProjects + "]");
            jqSelectedProjects.each(function()
            {
                var projectKey = this.value;
                jqSelectedProjectsByKey[projectKey] = AJS.$(this);
                jqSelectedPlans[projectKey] = AJS.$("input:checkbox[name=" + nameSelectedBuilds + "][id^=checkbox_" + projectKey + "-]");
            });
        },
        /**
         * Handler for toggling "select all plans and projects"
         */
        toggleAll : function()
        {
            checkboxTree.cascadeToChildren(jqSelectedAll, jqSelectedProjects);
            jqSelectedProjects.each(function()
            {
                var projectKey = this.value;
                checkboxTree.cascadeToChildren(jqSelectedProjectsByKey[projectKey], jqSelectedPlans[projectKey]);
            });
        },
        
        /**
         * Handler for toggling project checkbox
         * @param projectKey
         */
        toggleProject : function(projectKey)
        {
            checkboxTree.cascadeToChildren(jqSelectedProjectsByKey[projectKey], jqSelectedPlans[projectKey]);
            checkboxTree.propagateToParent(jqSelectedAll, jqSelectedProjects);
        },

        /**
         * Handler for toggling plan checkbox
         * @param projectKey
         * @param options     options that modify default behavior
         */
        togglePlan : function(projectKey, options)
        {
            if (options & this.DONT_ENABLE_PROJECT_ON_ALL_PLANS_ENABLED)
            {
                checkboxTree.propagateToParent(jqSelectedProjectsByKey[projectKey], jqSelectedPlans[projectKey], checkboxTree.DONT_ENABLE_PARENT_ON_ALL_CHILDREN_ENABLED);
            }
            else
            {
                checkboxTree.propagateToParent(jqSelectedProjectsByKey[projectKey], jqSelectedPlans[projectKey]);
            }
            checkboxTree.propagateToParent(jqSelectedAll, jqSelectedProjects);
        }
    };
}();


function enableShowSnapshotsForMaven2Dependencies(showAllSelector, showOnlySnapshotsSelector, tableSelector)
{
    AJS.$(document).ready(function() {
        var showAllLink = AJS.$(showAllSelector);
        var showOnlySnapshotsLink = AJS.$(showOnlySnapshotsSelector);
        var table = AJS.$(tableSelector);

        showAllLink.click(function() {
            showOnlySnapshotsLink.show();
            showAllLink.hide();
            AJS.$('.gav-tbody-releases').show();
        });

        showOnlySnapshotsLink.click(function() {
            showAllLink.show();
            showOnlySnapshotsLink.hide();
            AJS.$('.gav-tbody-releases').hide();
        });

        showOnlySnapshotsLink.click();
    });
}

function removeError(errorNumber)
{
    var removeErrorForm = window.opener.AJS.$('#removeErrorHiddenForm_' + errorNumber);
    removeErrorForm.submit();
    window.close();
}

function addAliasSubmitCallback(result)
{
    AJS.$("select.selectAlias")
            .append(AJS.$(document.createElement("option")).attr("value", result.aliasId).attr("selected", "selected").text(result.aliasName))
            .val(result.aliasId);
}

/*per plan action tooltip, originally for requirements tooltip on plan config page
 * @param args - some are compulsory for hitting the action to display content, the rest are for styling
 * targetId: (compulsory) the id of the content that the tooltip should appear over
 * actionName: (compulsory) the action to hit
 * planKey: (compulsory) the key of the plan
 * fadeTime: (optional)
 * hideDelay: (optional)
 * showDelay: (optional)
 * width: (optional)
 * offsetX: (optional)
 * offsetY: (optional)
 */
function attachActionToTooltip(args)
{
    AJS.InlineDialog(AJS.$("#" + args.targetId),
                 args.targetId,
                 BAMBOO.contextPath + "/ajax/" + args.actionName + ".action?planKey=" + args.planKey,
                {onHover: true,
                 fadeTime: (args.fadeTime || 200),
                 hideDelay: (args.hideDelay || 150),
                 showDelay: (args.showDelay || 150),
                 width: (args.width || 300),
                 offsetX: (args.offsetX || 0),
                 offSetY: (args.offsetY || 10)
                });
}

/**
 * Checks if any forms on the page need a submit on change hook.
 */
addUniversalOnload(function(){
    AJS.$(".submitOnChange").change(function(){ AJS.$(this).closest("form").submit(); });
});

/**
 * Current Activity screen
 */
var CurrentActivity = {
    // Default options
    options: {
        contextPath: null,
        getBuildsUrl: null,
        viewAgentUrl: null,
        reorderBuildUrl: null,
        stopBuildUrl: null,
        manageElasticInstancesUrl: null,
        emptyQueueText: null,
        emptyBuildingText: null,
        cancellingBuildText: null,
        cancelBuildText: null,
        queueOutOfDateText: null,
        canBuildElastically: null,
        canBuildElasticallyAdmin: null,
        fetchingBuildData: null,
        hasAdminPermission: false,
        caParent: null,
        buildingParent: null,
        queueParent: null,
        activityStream: null,
        agentSummary: null
    },
    updateTimeout: null, // ID of timeout which determines when the data will be next fetched from the server
    building: null, // jQuery object referring to the list holding the currently building builds (set on init)
    queue: null, // jQuery object referring to the list holding the queued builds (set on init)
    noBuilding: null, // jQuery object referring to the message shown when there are no currently building builds (set on init)
    noQueued: null, // jQuery object referring to the message shown when there are no queued builds (set on init)
    loadingBuilding: null, // jQuery object referring to the loading indicator shown while currently building data is being fetched the first time (set on init)
    loadingQueued: null, // jQuery object referring to the loading indicator shown while queue data is being fetched the first time (set on init)
    queueOutOfDate: null, // jQuery object referring to the message shown when the queue order has been updated remotely and is not reflected locally (set on init)
    updateTimestamp: null, // Timestamp (represented as milliseconds since the Unix epoch) that the builds were last updated
    isBeingSorted: false, // Is the queue currently being sorted?
    disabledStopHTML: '', // HTML to replace the stop button with if it's disabled (set on init)
    /**
     * Generates a nice, readable string of Agents that the Plan can build on
     * @param executableAgents - Array of Agent objects
     * @returns {String} A readable sentence containing the list of agents
     */
    generateBuildableAgentsText: function (executableAgents) {
        var agentNames = [];
        for (var i = 0; i < executableAgents.length; i++) {
            agentNames.push(executableAgents[i].name);
        }
        return "Can build on " + agentNames.join(", ");
    },
    /**
     * Generates the list item
     * @param build - Build object
     * @returns {Object} containing the list item or false if no list item is built (would require build.status to be something other than BUILDING or QUEUED)
     */
    generateListItem: function (build) {
        var ca = CurrentActivity,
            o = ca.options,
            el = "";
        if (build.hasReadPermission) {
            if (build.status == "BUILDING") {
                var progressCss = build.messageType == "PROGRESS" ? ' style="width:' + build.percentageComplete + '%;"' : '';
                var buildAgentInfo = build.agent ? ' building on <a href="' + o.viewAgentUrl + '?agentId=' + build.agent.id + '" class="' + build.agent.type.toLowerCase() + '">' + build.agent.name + '</a>' : '';
                el = '<li id="b' + build.buildResultKey + '" class="buildRow"><div class="buildInfo"><a href="' + o.contextPath + '/browse/' + build.planKey + '/log">' + build.planName + '</a>' + buildAgentInfo + '</div><span class="message ' + build.messageType.toLowerCase() + '"><span class="progress-bar"' + progressCss + '></span><span class="progress-text">' + build.messageText + '</span></span>';
                if (build.hasBuildPermission) {
                    var stopButtonHTML = (build.isBeingStopped) ? ca.disabledStopHTML : '<a href="' + o.stopBuildUrl + '?buildKey=' + build.planKey + '" title="' + o.cancelBuildText + '" class="build-stop">' + o.cancelBuildText + '</a>';
                    el += '<ul class="buildActions"><li>' + stopButtonHTML + '</li></ul>';
                }
                el += '</li>';
            } else if (build.status == "QUEUED") {
                var title = build.executableAgents ? ' title="' + ca.generateBuildableAgentsText(build.executableAgents) + '"' : '',
                    handle = o.hasAdminPermission ? '<span class="handle"></span>' : '';
                el = '<li id="b' + build.buildResultKey + '" class="buildRow"><div class="buildInfo">' + handle + '<a href="' + o.contextPath + '/browse/' + build.planKey + '/log">' + build.planName + '</a></div><span class="message ' + build.messageType.toLowerCase() + '"' + title + '>' + build.messageText + '</span>';
                if (build.executableElasticImages || build.hasBuildPermission) {
                    el += '<ul class="buildActions">';
                    if (build.executableElasticImages) {
                        if (o.hasAdminPermission) {
                            el += '<li><a href="' + o.manageElasticInstancesUrl + '" title="' + o.canBuildElasticallyAdmin + '" class="build-elastic">' + o.canBuildElasticallyAdmin + '</a></li>';
                        } else {
                            el += '<li><span title="' + o.canBuildElastically + '" class="build-elastic-disabled">' + o.canBuildElastically + '</span></li>';
                        }
                    }
                    if (build.hasBuildPermission) {
                        el += '<li><a href="' + o.stopBuildUrl + '?buildKey=' + build.planKey + '" title="' + o.cancelBuildText + '" class="build-stop">' + o.cancelBuildText + '</a></li>'
                    }
                    el += '</ul>';
                }
                el += '</li>';
            }
        } else {
            el = '<li id="b' + build.buildResultKey + '" class="buildRow"><div class="buildInfo">' + build.planName + '</div><span class="message ' + build.messageType.toLowerCase() + '">' + build.messageText + '</span></li>';
        }
        return (el.length == 0 ? false : AJS.$(el));
    },
    /**
     * Checks if an activity stream is available, and if it is (and there are no activity stream comment forms open -
     * which would indicate the user is probably in the middle of adding a comment) then trigger a refresh.
     */
    updateActivityStream: function () {
        var o = CurrentActivity.options;
        if (o.activityStream && AJS.$(".activity-item-comment-form:visible", document.getElementById("feedContainer-" + o.activityStream.feedId)).length == 0) {
            o.activityStream.populateFeed();
        }
    },
    /**
     * Checks the time the build was last updated with the time the last data was retrieved.
     * Removes the build if it's older than the last update.
     * @param li - List item containing the build
     * @returns {Boolean} indicating whether the build was removed or not
     */
    checkLastUpdated: function (li) {
        var b = AJS.$(li);
        if (b.data("lastUpdated") < CurrentActivity.updateTimestamp) {
            b.remove();
            return true;
        } else {
            return false;
        }
    },
    /**
     * Checks if build lists are empty and if so, hide the list and display a message, otherwise show the list and hide the message.
     */
    checkListsHaveBuilds: function () {
        var ca = CurrentActivity;
        if (ca.building.children().length == 0) {
            ca.building.hide();
            ca.noBuilding.show();
        } else {
            ca.building.show();
            ca.noBuilding.hide();
        }
        if (ca.queue.children().length == 0) {
            ca.queue.hide();
            ca.noQueued.show();
            // If queue is empty but "queue is out-of-date" message is being displayed then hide the message and re-enable sorting
            if (ca.queueOutOfDate.is(":visible")) {
                ca.queue.sortable("enable");
                ca.queueOutOfDate.hide();
            }
        } else {
            ca.queue.show();
            ca.noQueued.hide();
        }
    },
    /**
     * Retrieves the build JSON from the server and adds/removes/changes builds in the currently building and queue as needed.
     * @param callback - Allows the ability to execute a callback once the update has completed
     */
    updateBuilds: function (callback) {
        var ca = CurrentActivity,
            o = ca.options;
        ca.updateTimestamp = (new Date()).getTime();
        // Get builds from API
        AJS.$.ajax({
            url: o.getBuildsUrl,
            dataType: 'json',
            cache: false,
            success: function (json) {
                // If a callback was supplied to updateBuilds, fire it
                if (callback && AJS.$.isFunction(callback)) {
                    callback.call();
                }
                // Update agent summary text if available
                if (o.agentSummary) {
                    o.agentSummary.text(json.agentSummary);
                }
                // Go through each build returned and either update or insert as required
                AJS.$.each(json.builds, function () {
                    var build = AJS.$("#b" + this.buildResultKey);
                    if (build.length > 0) { // Check if the build already exists in building/queue
                        var msg;
                        if (this.status == "BUILDING") {
                            // Check if build has started, if so move to building
                            if (build.closest(".buildContainer")[0] == o.queueParent[0]) {
                                build.remove();
                                build = ca.generateListItem(this).appendTo(ca.building);
                            } else {
                                msg = AJS.$(".message", build[0]).html('<span class="progress-bar"></span><span class="progress-text">' + this.messageText + '</span>');
                                if (!msg.hasClass(this.messageType.toLowerCase())) {
                                    msg.attr("class", "message " + this.messageType.toLowerCase());
                                }
                                if (this.messageType == "PROGRESS") {
                                    AJS.$(".progress-bar", msg[0]).css("width", this.percentageComplete + "%");
                                }
                                if (this.hasBuildPermission && this.isBeingStopped) {
                                    var stopButton = AJS.$(".build-stop", build[0]);
                                    if (stopButton.length > 0) {
                                        stopButton.replaceWith(ca.disabledStopHTML);
                                    }
                                }
                            }
                        } else if (this.status == "QUEUED") {
                            msg = AJS.$(".message", build[0]).text(this.messageText).attr("title", (this.executableAgents ? ca.generateBuildableAgentsText(this.executableAgents) : null));
                            if (!msg.hasClass(this.messageType.toLowerCase())) {
                                msg.attr("class", "message " + this.messageType.toLowerCase());
                            }
                        }
                    } else {
                        if (this.status == "BUILDING") {
                            build = ca.generateListItem(this).appendTo(ca.building);
                        } else if (this.status == "QUEUED") {
                            build = ca.generateListItem(this).appendTo(ca.queue);
                        }
                    }
                    // Check if queue order is correct and reorder if required
                    if (!ca.isBeingSorted && typeof(this.queueIndex) != "undefined" && this.queueIndex != build.prevAll().length) {
                        build.insertBefore(ca.queue.children("li:eq(" + this.queueIndex + ")"));
                    }
                    build.data("lastUpdated", ca.updateTimestamp);
                });

                // Clean up builds not returned in JSON
                var numRemoved = 0;
                ca.building.children().each(function () {
                    if (ca.checkLastUpdated(this)) { numRemoved++; }
                });
                ca.queue.children().each(function () {
                    if (ca.checkLastUpdated(this)) { numRemoved++; }
                });
                if (numRemoved > 0) { ca.updateActivityStream(); }

                if (o.hasAdminPermission) {
                    // Refresh sorting
                    ca.queue.sortable("refresh");
                }

                ca.checkListsHaveBuilds();

                // Update again in 5 seconds
                ca.updateTimeout = setTimeout(ca.updateBuilds, 5000);
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                // Error occurred when doing the update, try again in 30 sec, making sure the callback is passed so that it will actually get executed when it finally succeeds
                ca.updateTimeout = setTimeout(function () { ca.updateBuilds(callback); }, 30000);
            }
        });
    },
    /**
     * Strips a build list item's id down to a build result key
     * @param buildListItemID - String containing the build list item's id - eg. bBAM-FUNC-1234
     * @returns {String} containing the build result key - eg. BAM-FUNC-1234
     */
    buildListItemIDToBuildResultKey: function (buildListItemID) {
        return buildListItemID.substr(1);
    },
    /**
     * Initialisation for the Current Activity
     * @param options - Object containing the options to overwrite our defaults
     */
    init: function (options) {
        var ca = CurrentActivity,
            o = ca.options;
        AJS.$.extend(o, options);
        ca.building = AJS.$("<ul/>").appendTo(o.buildingParent).hide();
        ca.queue = AJS.$("<ul/>").appendTo(o.queueParent).hide();
        ca.noBuilding = AJS.$("<p>" + o.emptyBuildingText + "</p>").appendTo(o.buildingParent).hide();
        ca.noQueued = AJS.$("<p>" + o.emptyQueueText + "</p>").appendTo(o.queueParent).hide();
        ca.queueOutOfDate = AJS.$("<p>" + o.queueOutOfDateText + "</p>").insertBefore(ca.queue).hide();
        ca.loadingBuilding = AJS.$('<p class="loading">' + o.fetchingBuildData + '</p>').appendTo(o.buildingParent);
        ca.loadingQueued = AJS.$('<p class="loading">' + o.fetchingBuildData + '</p>').appendTo(o.queueParent);
        ca.disabledStopHTML = '<span class="build-stop-disabled" title="' + o.cancellingBuildText + '">' + o.cancellingBuildText + '</span>';
        AJS.$(".buildActions a.build-stop").live("click", function (e) {
            var el = AJS.$(this),
                li = el.closest(".buildRow");
            AJS.$.post(this.href, function () {
                // Only remove from the queue immediately - currently building builds may take a while to stop and clean up - let the next updateBuilds() that runs clean it up
                if (li.closest(".buildContainer")[0] == o.queueParent[0]) {
                    li.remove();
                }
                ca.checkListsHaveBuilds();
            });
            el.replaceWith(ca.disabledStopHTML);
            e.preventDefault();
        });
        AJS.$("a", ca.queueOutOfDate[0]).click(function (e) {
            clearTimeout(ca.updateTimeout);
            ca.queue.empty().sortable("enable");
            AJS.$(this).parent().hide();
            ca.updateBuilds();
            ca.updateActivityStream();
            e.preventDefault();
        });
        if (o.hasAdminPermission) {
            ca.queue.sortable({
                handle: "span.handle",
                update: function (event, ui) {
                    var self = AJS.$(ui.item),
                        buildResultKey = ca.buildListItemIDToBuildResultKey(self.attr("id")),
                        checkListItemExists = function ($el) {
                            if ($el.length > 0) {
                                return ca.buildListItemIDToBuildResultKey($el.attr("id"));
                            } else {
                                return "";
                            }
                        },
                        prevBuildResultKey = checkListItemExists(self.prev()),
                        nextBuildResultKey = checkListItemExists(self.next());
                    AJS.$.post(o.reorderBuildUrl,
                        { buildResultKey: buildResultKey, prevBuildResultKey: prevBuildResultKey, nextBuildResultKey: nextBuildResultKey },
                        function (json) {
                            // If an error is returned show the queue out of date message. The next update will restore the correct queue order.
                            if (json.status == "ERROR") {
                                ca.queueOutOfDate.show();
                                ca.queue.sortable("disable");
                            }
                        }, "json");
                },
                start: function (event, ui) {
                    ca.isBeingSorted = true;
                },
                stop: function (event, ui) {
                    ca.isBeingSorted = false;
                }
            });
        }
        ca.updateBuilds(function () {
            ca.loadingBuilding.hide();
            ca.loadingQueued.hide();
        });
    }
};

/**
 * Agent Manager dropdown
 */
var AgentManager = {
    // Default options
    options: {
        contextPath: null,
        getAgentsUrl: null,
        enableAgentUrl: null,
        disableAgentUrl: null,
        enableAllAgentsUrl: null,
        disableAllAgentsUrl: null,
        viewAgentUrl: null,
        enableAgentText: null,
        disableAgentText: null,
        enableAllAgentsText: null,
        disableAllAgentsText: null,
        onlineAgentsText: null,
        defaultRemoteAgentSummaryText: null,
        onlineOnly: false,
        includeRemoteAgentSummary: false,
        $trigger: null
    },
    $agentList: null, // jQuery object referring to the list holding the agents
    $dialog: null, // jQuery object referring to the AUI Inline Dialog
    $remoteAgentSummary: null, // jQuery object referring to the paragraph containing the remote agent summary text
    /**
     * Generates the list item
     * @param agent - Agent object
     * @returns {Object} containing the list item or false if no list item is built
     */
    generateListItem: function (agent) {
        var am = AgentManager,
            o = am.options,
            el = "";
        el = '<li class="' + agent.type.toLowerCase() + ' ' + (agent.enabled ? "enabled" : "disabled") + '"><h3><a href="' + o.viewAgentUrl + '?agentId=' + agent.id + '">' + agent.name + '</a></h3><span>' + (agent.agentStatus == "Building" ? agent.agentStatus + ' - <a href="' + o.contextPath + agent.buildLogUrl + '">' + agent.buildResultKey + '</a>' : agent.agentStatus) + '</span> <button class="' + (agent.enabled ? "disable" : "enable") + '">' + (agent.enabled ? o.disableAgentText : o.enableAgentText) + '</button></li>';
        return (el.length == 0 ? false : AJS.$(el).data("agentId", agent.id));
    },
    /**
     * Updates the list of agents
     * @param $contents - jQuery element of the Inline Dialog's contents
     * @param $trigger - jQuery element that is triggering the popup
     * @param doShowPopup - function to display the popup
     */
    updateAgents: function ($contents, $trigger, doShowPopup) {
        var am = AgentManager,
            o = am.options;
        AJS.$.ajax({
            url: o.getAgentsUrl,
            data: { onlineOnly: o.onlineOnly, includeRemoteAgentSummary: o.includeRemoteAgentSummary },
            dataType: 'json',
            cache: false,
            success: function (json) {
                if (o.includeRemoteAgentSummary && json.remoteAgentSummary) {
                    am.$remoteAgentSummary.html(json.remoteAgentSummary);
                } else if (o.includeRemoteAgentSummary) {
                    am.$remoteAgentSummary.html(o.defaultRemoteAgentSummaryText);
                }
                am.$agentList.empty();
                AJS.$.each(json.agents, function () {
                    am.$agentList.append(am.generateListItem(this));
                });
                if (doShowPopup) {
                    doShowPopup();
                }
            }
        });
    },
    /**
     * Initialisation for the Agent Manager
     * @param options - Object containing the options to overwrite our defaults
     */
    init: function (options) {
        var am = AgentManager,
            o = am.options;
        AJS.$.extend(o, options);
        am.$dialog = AJS.InlineDialog(o.$trigger, o.$trigger.attr("id"), am.updateAgents, {
            onHover: true,
            width: 400,
            offsetX: 0,
            offsetY: 3,
            cacheContent: false
        });
        var $contents = am.$dialog.find(".contents").addClass("agentManager");
        am.$agentList = AJS.$('<ul />').appendTo($contents).before('<h2>' + o.onlineAgentsText + '</h2>');
        if (o.includeRemoteAgentSummary) {
            am.$remoteAgentSummary = AJS.$('<p>' + o.defaultRemoteAgentSummaryText + '</p>').insertBefore(am.$agentList);
        }
        var $buttons = AJS.$('<div class="buttons"></div>').appendTo($contents);
        AJS.$('<button>' + o.enableAllAgentsText + '</button>').appendTo($buttons).click(function () {
            var el = AJS.$(this);
            el.attr("disabled", "disabled");
            AJS.$.post(o.enableAllAgentsUrl, function () {
                el.removeAttr("disabled");
                am.updateAgents();
            });
        });
        AJS.$('<button>' + o.disableAllAgentsText + '</button>').appendTo($buttons).click(function () {
            var el = AJS.$(this);
            el.attr("disabled", "disabled");
            AJS.$.post(o.disableAllAgentsUrl, function () {
                el.removeAttr("disabled");
                am.updateAgents();
            });
        });
        // FF not firing buttons on click for some reason, mouseup is okay though?!!
        var dialogId = am.$dialog.attr("id");
        AJS.$("#" + dialogId + " button.enable").live("mouseup", function () {
            var el = AJS.$(this),
                li = el.closest("li");
            var agentId = li.data("agentId");
            el.attr("disabled", "disabled");
            AJS.$.post(o.enableAgentUrl, { agentId: agentId }, function () {
                el.text(o.disableAgentText).attr("class", "disable").removeAttr("disabled").parent().removeClass("disabled");
            });
        });
        AJS.$("#" + dialogId + " button.disable").live("mouseup", function () {
            var el = AJS.$(this),
                li = el.closest("li");
            var agentId = li.data("agentId");
            el.attr("disabled", "disabled");
            AJS.$.post(o.disableAgentUrl, { agentId: agentId }, function () {
                el.text(o.enableAgentText).attr("class", "enable").removeAttr("disabled").parent().addClass("disabled");
            });
        });
    }
};
