var g_mousedown = false;
var g_modeSelecting = false;
var g_currentTable = null;
var g_pageCompletelyLoaded = false;
var lastSelectedLine = null;
var firstTimeDisplayingInlineForm = true;
var frxWidth = null;
var commentList = new Array();
var commentDivPrefixes = ['general','revision','inline','above','defect'];

/**
 * CommentForm class
 *
 * Base commentForm class extend for specific forms
 */
function CommentForm(id,source) {
    var name = id;
    var form;
    var inited = false;
    var sourceForm = source;

    this.con = function(id,source) {
        name = id;
        sourceForm = source;
    };

    this.init = function() {
        if(!inited){
            var formSource = document.getElementById(sourceForm);
            if(formSource){
                form = formSource.cloneNode(true);
                form.setAttribute('id',name);
                form.setAttribute('name',name);
                // setAttribute does not work for name in IE! so manually replace the name
                form.innerHTML = form.innerHTML.replace(/source/,name);
                form.innerHTML = form.innerHTML.replace(/DefectFields/g,'DF');
                inited = true;
            }
        }
    };

    this.getForm = function() {
        this.init();
        return form;
    };

    this.setDefaultFocus = function() {
        var ta = form['newText'];
        if (ta && ta.focus) {
            ta.focus();
        }
    }
    this.clearForm = function() {
        var daForm = this.getForm();
        for(var i=0; i < daForm.elements.length ; i++){
            try{
                var formEl = daForm.elements[i];
                if(formEl.type == "checkbox"){
                    formEl.checked = "";
                    formEl.value = false;
                } else if ((formEl.type == "button") || (formEl.type == "submit")) {
                    // don't do anything to buttons
                    // (in IE6/7, clearing .value clears the button text)
                } else {
                    formEl.value = "";
                }
            }catch(e){
                //ignore. happens in safari/konqueror when comment form is cleared when not displayed
            }
        }
    }

    this.setFormVal = function(name, val) {
        this.init();
        try{
            var formEl = form[name];
            if(formEl.type == "checkbox"){
                if(val){
                    formEl.checked = "true";
                } else {
                    formEl.checked = "";
                }
            }
            formEl.value = val;
        }catch(e){
            //ignore. happens in safari/konqueror when comment form is cleared when not displayed
        }
    };

    this.setFormElementVis = function(name, val) {
        this.init();
        try{
            var formEl = form[name];
            formEl.style.display = val;
        }catch(e){
            //ignore. happens in safari/konqueror when comment form is cleared when not displayed
        }
    };
    
    this.showMetrics = function(prefix, show) {
        var nodeName = prefix + "DF";
        if ($(nodeName)) {
            toggleNodeAndImage(nodeName, show, !show, true);
        }
    }
}

/**
 * CommentForm class
 * This holds a single revision comment form and controls access to the form as well as providing
 * useful methods
 */
function RevisionCommentForm(n, s){

    this.con(n,s);

    this.setFromLines = function(fromLines){
        this.setFormVal("fromLineRange",fromLines);
    };

    this.setToLines = function(toLines){
        this.setFormVal("toLineRange",toLines);
    };

    this.setFrxId = function(id){
        this.setFormVal("frxId",id);
    };

    this.clearComment = function(){
        this.clearForm();
        this.showMetrics("revision", false);
        this.setFormElementVis("saveAsDraft", "");
    };

    this.populateFromComment = function(comment){
        this.setFormVal("newText",comment.message);
        this.setFormVal("commentId",comment.id);
        this.setFormVal("toLineRange",comment.toLineRange);
        this.setFormVal("fromLineRange",comment.fromLineRange);
        this.setFormVal("defect",comment.defect);
        for(var i = 0 ; i < comment.metrics.length ; i++){
            this.setFormVal(comment.metrics[i].label, comment.metrics[i].value);
        }
        this.showMetrics("revision", comment.defect);
        if(!comment.draft){
            this.setFormElementVis("saveAsDraft", "none");
        }
    };
}

RevisionCommentForm.prototype = new CommentForm();

function SimpleCommentForm(n, s){

    this.con(n,s);

    this.clearComment = function(){
        this.clearForm();
        this.showMetrics("general", false);
        this.setFormElementVis("saveAsDraft", "");
    };

    this.populateFromComment = function(comment){
        this.setFormVal("newText",comment.message);
        this.setFormVal("commentId",comment.id);
        this.setFormVal("defect",comment.defect);
        for(var i = 0 ; i < comment.metrics.length ; i++){
            this.setFormVal(comment.metrics[i].label, comment.metrics[i].value);
        }
        this.showMetrics("general", comment.defect);
        if(!comment.draft){
            this.setFormElementVis("saveAsDraft", "none");
        }
    };
}
SimpleCommentForm.prototype = new CommentForm();

function ReplyCommentForm(n, s){

    this.con(n,s);

    this.clearComment = function(){
        this.clearForm();
        this.setFormElementVis("saveAsDraft", "");
    };

    this.setReplyToId = function(replyToId){
        this.setFormVal("replyToId", replyToId);
    };

    this.populateFromComment = function(comment){
        this.setFormVal("newText",comment.message);
        this.setFormVal("commentId",comment.id);
        if(!comment.draft){
            this.setFormElementVis("saveAsDraft", "none");
        }
    };
}
ReplyCommentForm.prototype = new CommentForm();

function TableRow(id){
    var row = document.createElement('tr');
    var cell = document.createElement('td');

    row.setAttribute('id',id)
    row.appendChild(cell);

    this.setSpan = function(colSpan){
        cell.setAttribute('colSpan',colSpan);
    }

    this.getCell = function(){
        return cell;
    }

    this.getRow = function(){
        return row;
    }
}
/**
 * This is a CommentForm handler class that looks after inserting/removing a comment form inside a parent element
 * It also manages other elements you wish to hide or unhide while displaying the element
 */
function CommentFormWrangler(commentform){
    var that = this;
    var switchElement;
    var cForm = commentform;
    var insertParent;
    var insertPoint;
    var insertRow = false;
    var displaying = false;
    var wrapperDiv;
    var wrapperRow;
    var divWidth;
    var inited = false;
    var changeParentDisplay = false;

    function init(){
        if(!inited){
            wrapperDiv = document.createElement('div');
            wrapperRow = new TableRow('commentTR');
            wrapperDiv.setAttribute('class','commentForm');
            wrapperDiv.appendChild(cForm.getForm());
            inited = true;
        }
    }

    this.getCommentForm = function(){
        init();
        return cForm;
    }

    this.setWidth = function(width){
        if(wrapperDiv){
            wrapperDiv.style.width = width + "px";
        }
        divWidth = width;
    }

    this.unsetWidth = function(){
        if(wrapperDiv){
            wrapperDiv.style.width = '';
        }
        divWidth = null;
    }

    this.setInsertPoint = function(parent,insertAfter){
        init();
        if(!displaying){
            insertParent = parent;
            insertPoint = insertAfter;
            insertRow = (insertParent.nodeName == 'TABLE') || (insertParent.nodeName == 'TBODY');
            return;
        }
        throw("Can't (re)set insert point while it is displayed")
    }

    this.setSwitchElement = function(element){
        init();
        switchElement = element;
    }

    this.isDisplaying = function(){
        init();
        return displaying;
    }

    this.getParent = function() {
        return insertParent;
    }

    this.isDisplayingRow = function(){
        init();
        return insertRow;
    }

    function switchParentOn(){
        if(insertParent.style.display == 'none'){
            changeParentDisplay = true;
            insertParent.style.display = ''
        } else {
            changeParentDisplay = false;
        }
        if(switchElement){
            switchElement.style.display = 'none';
        }
    }

    function switchParentOff(){
        if(changeParentDisplay){
            insertParent.style.display = 'none'
        }
        changeParentDisplay = false;
        if(switchElement){
            switchElement.style.display = '';
        }
    }

    /*
     insert/append the element to the parent and hide all the switch elements
    */
    this.insertAndSwitch = function(){
        init();
        if(!displaying){
            if(insertRow){ //if Row the insertpoint MUST be a ROW
                wrapperRow.setSpan(insertPoint.cells.length);
                wrapperRow.getCell().appendChild(wrapperDiv);
                insertParent.insertBefore(wrapperRow.getRow(),insertPoint.nextSibling);
            } else {
                insertParent.insertBefore(wrapperDiv,(insertPoint ? insertPoint.nextSibling : null));
            }
            switchParentOn();
            displaying = true;
        }
    }

    this.removeAndSwitch = function(){
        init();
        if(displaying){
            if(insertRow){
                wrapperRow.getCell().removeChild(wrapperDiv);
                insertParent.removeChild(wrapperRow.getRow());
            } else {
                insertParent.removeChild(wrapperDiv);
            }
            switchParentOff();
            displaying = false;
        }
    }

    /* remove the element from current insertpoint restoring all current switch elements and
       put it in the new insert point with the new switch and display
    */
    this.exchange = function(parent, insertBefore, newSwitchElement){
        init();
        if(displaying){
            this.removeAndSwitch();
        }
        this.setSwitchElement(newSwitchElement);
        this.setInsertPoint(parent, insertBefore);
        this.insertAndSwitch();
    }
}

var rcFormWrangler = new CommentFormWrangler(new RevisionCommentForm('revisionCommentForm','revisionCommentFormTemplate'));
var scFormWrangler = new CommentFormWrangler(new SimpleCommentForm('simpleCommentForm','commentFormTemplate'));
var replyFormWrangler = new CommentFormWrangler(new ReplyCommentForm('replyCommentForm','replyFormTemplate'));

/* end of Object definitions */

var commentLock = false;

function setCBValue(cb){
    cb.value=cb.checked;
}

function selectTR(tr, select) {
    if (select) {
        tr.className = 'lineHighlighted';
        tr.selected="true";
    } else {
        tr.className = 'sourceLine';
        tr.selected="false";
    }
}

function checkInlineCommentBox(tableElement) {
    if(commentLock){
        return false;
    }
    var linesSelected = new Array();
    var fromLinesSelected = new Array();
    var lastSelected = null;
    var rows = tableElement.rows;
    for (var i=0; i < rows.length; i++) {
        if(rows[i].selected == "true") {
            var num = rows[i].getAttribute('id').split("_line")[1];
            if (num[0] == 'B') {
                var lines = num.split(",");
                fromLinesSelected.push(lines[0].replace(/^Both/, ""));
                linesSelected.push(lines[1]);
            } else if (num[0] == 'F') {
                fromLinesSelected.push(num.replace(/^From/, ""));
            } else {
                linesSelected.push(num);
            }
            lastSelected = rows[i];
        }
    }
    if (lastSelected != null) {
        //must display first before setting values or safari/konqueror bork
        rcFormWrangler.exchange(lastSelected.parentNode, lastSelected, null);
        //only if displaying for the first time do we want to clear the comment text
        //so that if they click a line after entering text it doesn't clear
        if(firstTimeDisplayingInlineForm){
            rcFormWrangler.getCommentForm().clearComment();
            rcFormWrangler.getCommentForm().setDefaultFocus();
            firstTimeDisplayingInlineForm = false;
        }
        rcFormWrangler.getCommentForm().setFromLines(convertLinesToRange(fromLinesSelected));
        rcFormWrangler.getCommentForm().setToLines(convertLinesToRange(linesSelected));
        rcFormWrangler.getCommentForm().setFrxId(lastSelected.getAttribute('id').split('_line')[0]);
        if(rcFormWrangler.isDisplayingRow()){
            rcFormWrangler.setWidth(getInlineCommentFormWidth());
        } else {
            rcFormWrangler.unsetWidth();
        }
        lastSelectedLine = lastSelected;
    } else {
        //clean up
        if(rcFormWrangler.getCommentForm().getForm().newText.value != ''){
            var popupDeleteResponse = window.confirm("You've deselected all lines\n\n" +
                                                     "Delete the comment?\n");
            if (popupDeleteResponse) {
                clearCommentBox();
            }
        } else {
            clearCommentBox();
        }
    }
    return false;
}

function displayCommentForm(handle, parentId, commentId, wrangler){
    var parent = $(parentId);
    //display before setting the new values because safari/konqueror don't let you change the values
    //if not attached to the document unlike IE and FF
    wrangler.exchange(parent, null, handle);
    if(commentId){
        wrangler.getCommentForm().populateFromComment(commentList[commentId]);
    } else {
        wrangler.getCommentForm().clearComment();
    }
    wrangler.getCommentForm().setDefaultFocus();
}

/* display a comment form as a child of parent with the details in the comment object
 *
 * handle the switch you want to hide while displaying the form
 * parent = div/containing block to display form in
 * comment = js comment object
*/
var editingRCHandle;
function displayRevisionCommentForm(handle, parentId, frxId, commentId){
    clearSelectedRows();
    //warning, hack
    if(commentId){ //means we're editing
        $(handle).style.display = "none";
        editingRCHandle = handle;
        handle = null;
        lastSelectedLine = null;
    }
    displayCommentForm(handle, parentId, commentId, rcFormWrangler);
    rcFormWrangler.getCommentForm().setFrxId(frxId);

    if(/^inline/.test(parentId)){
        rcFormWrangler.setWidth(getInlineCommentFormWidth());
        firstTimeDisplayingInlineForm = false;
    } else {
        rcFormWrangler.unsetWidth();
        firstTimeDisplayingInlineForm = true;
    }
    if(commentId){
        var lines = getCommentedLines(frxId, commentList[commentId].fromLineRange, commentList[commentId].toLineRange);
        for (var i=0; i < lines.length; i++){
            if(i == 0){ // if lines[0] exists
                g_currentTable = $(lines[0]).parentNode;
            }
            selectTR($(lines[i]), true);
        }
    }
}

function editComment(handle, parentId, commentId){
    displaySimpleCommentForm(handle, parentId, commentId);
}

function displaySimpleCommentForm(handle, parentId, commentId){
    displayCommentForm(handle, parentId, commentId, scFormWrangler);
}

function displayReplyCommentForm(handle, parentId, replyToId, commentId){
    if(replyFormWrangler.isDisplaying() && $(parentId) == replyFormWrangler.getParent()){
        return;//already displaying here so do nothing
    }
    displayCommentForm(handle, parentId, commentId, replyFormWrangler);
    //replyToId must be set or it ain't a reply
    replyFormWrangler.getCommentForm().setReplyToId(replyToId);
}

function insertAjaxReply(resp){
    replyFormWrangler.removeAndSwitch();
    if(resp.worked){
        //first find and remove any existing replies
        deleteComments("reply",resp.comment.id);
        //now insert replies where they should go
        for ( var i=0; i < commentDivPrefixes.length ; i++){
            var replyDiv = $(commentDivPrefixes[i]+"replys"+resp.replyToId);
            if(replyDiv){
                insertDivComment(resp.html[i],replyDiv);
            }
        }
        updateCommentList(resp.comment);
    }
}

function deleteComments(midFix,commentId){
    for (var i=0; i < commentDivPrefixes.length ; i++){
        var oldComment = $(commentDivPrefixes[i]+midFix+commentId);
        if(oldComment){
            oldComment.parentNode.removeChild(oldComment);
        }
    }
    clearComentedLines();
}
function modifyIndicatorDisplay(midFix,commentId, value){
    for (var i=0; i < commentDivPrefixes.length ; i++){
        var elName = commentDivPrefixes[i]+midFix+commentId;
        var element = $(elName);
        if(element){
            element.style.display = value;
        }
    }
}

function insertAjaxGeneralComment(resp){
    scFormWrangler.removeAndSwitch();
    if(resp.worked){
        var oldComment = $('generalcomment'+resp.comment.id);
        if(oldComment){
            oldComment.parentNode.removeChild(oldComment);
        }
        insertDivComment(resp.commentHtml,'generalComments');
        updateCommentList(resp.comment);
        updateCommentCount(resp.commentCount);
    }
}

function insertRevisionComment(resp){
    if(resp.worked){
        if(resp.whole){
            insertAjaxWholeRevComment(resp);
        }else{
            insertAjaxLineRevComment(resp);
        }
    }
}

function insertAjaxWholeRevComment(resp){
    rcFormWrangler.removeAndSwitch();
    var oldComment = $('revisioncomment'+resp.comment.id);
    if(oldComment){
        oldComment.parentNode.removeChild(oldComment);
    }
    insertDivComment(resp.commentHtml,'revision_comments_frxinner'+resp.frxId);
    updateCommentList(resp.comment);
    updateCommentCount(resp.commentCount);
}

function insertDivComment(html, container){
    var div = document.createElement('div');
    div.innerHTML = html;
    container = $(container);
    if(container){
        container.insertBefore(div, null);  //insert at end
    }
}

function insertAjaxLineRevComment(resp){
    clearCommentBox();
    var oldComment = $('inlinecomment'+resp.comment.id);
    if(oldComment){
        if(lastSelectedLine){
            oldComment.parentNode.innerHTML = "";
            editingRCHandle = null;
        } else {
            oldComment.parentNode.innerHTML = resp.inlineCommentHtml;
        }
    }
    if(lastSelectedLine){
        var frag = document.createDocumentFragment();
        var row = document.createElement('tr');
        var cell = document.createElement('td');
        var cs = frxColspans[resp.frxId];
        if (!cs) { // eg added files
            cs = 4;
        }
        cell.setAttribute('colSpan', cs);
        cell.innerHTML = resp.inlineCommentHtml;
        row.appendChild(cell);
        frag.appendChild(row);
        lastSelectedLine.parentNode.insertBefore(frag, lastSelectedLine.nextSibling);
        inlineCommentIds.push('inlinecomment'+resp.comment.id);
    }
    oldComment = $('abovecomment'+resp.comment.id);
    if(oldComment){
        oldComment.parentNode.removeChild(oldComment);
    }
    insertDivComment(resp.aboveCommentHtml,'inline_comments_frxinner'+resp.frxId);
    setCommentWidths('inlinecomment'+resp.comment.id, true);

    updateCommentList(resp.comment);
    updateCommentCount(resp.commentCount);
}

function updateCommentList(comment){
    commentList[comment.id] = comment;
}

function updateCommentCount(count) {
    tc = $('totalComments');
    if (count == 1) {
        tc.innerHTML = '1 comment';
    } else {
        tc.innerHTML = count + ' comments';
    }
}

function clearReplyCommentForm(){
    clearForm(replyFormWrangler);
}

function clearGeneralCommentForm(){
    clearForm(scFormWrangler);
}

function clearForm(wrangler){
    wrangler.removeAndSwitch();
    wrangler.getCommentForm().clearComment();
}

function clearCommentBox(){
    //a little inefficient, but effective.
    clearSelectedRows();
    rcFormWrangler.removeAndSwitch();
    if(editingRCHandle){
        $(editingRCHandle).style.display = '';
        editingRCHandle = null;
    }
}

function clearSelectedRows(){
    g_modeSelecting = false;
    if(g_currentTable){
        var rows = g_currentTable.rows;
        for (var i=0; i < rows.length; i++) {
            if(rows[i].selected == "true") {
                rows[i].selected = "false";
                rows[i].className = 'sourceLine';
            }
        }
    }
    rcFormWrangler.getCommentForm().clearComment();
    g_modeSelecting=false;
    g_currentTable = null;
    firstTimeDisplayingInlineForm = true;
}

function convertLinesToRange(lines){
    var lineStr = "";
    var count = 0;
    for (var i=0; i < lines.length; i++){
        if (count != 0 && lines[i] == (parseInt(lines[i-1],10)+1)) {
            if (lines[i] != (parseInt(lines[i+1],10)-1)){
                lineStr += "-"+lines[i];
                count = 0;
            } else {
                count++;
            }
        } else {
            if (i>0) { lineStr += ", " }
            lineStr += lines[i];
            count++;
        }
    }
    return lineStr;
}

function convertRangeToLines(ranges) {
    var range = ranges.split(",");
    var lines = new Array();
    for (var i=0; i < range.length; i++){
        var s = range[i].split("-");
        if (s.length == 1){
            lines.push(parseInt(s[0],10));
        } else {
            for (var b=0; b <= (s[1]-s[0]); b++) {
                lines.push(parseInt(s[0],10)+b);
            }
        }
    }
    return lines ;
}

function selectLine_down (obj) {
    g_mousedown = true;
    if (g_currentTable != obj.parentNode) {
        if (g_currentTable != null) clearSelectedRows();
        g_currentTable = obj.parentNode;
    }
    g_modeSelecting=obj.selected!="true";
    if (obj.selected=="true") {
        selectTR(obj, false);
    } else {
        selectTR(obj, true);
    }
    document.onselectstart = returnFalse;
    document.onmousedown = returnFalse;
}

function selectLine_over (obj){
    if (g_mousedown) {
        if (g_modeSelecting) {
            selectTR(obj, true);
        } else {
            selectTR(obj, false);
        }
    }
}

function selectLine_up (obj) {
    checkInlineCommentBox(obj.parentNode);
}

function setMouseUp () {
    document.onmousedown = returnTrue;
    document.onselectstart = returnTrue;
    g_mousedown = false;
}

function returnFalse() {
    return false
}

function returnTrue() {
    return true
}

var showInline = true;
var showTop = false;
var showRevisionComments = true;
var showSource = true;
function toggleComments(ids, commentIds, sel) {

    var view = typeof(sel) == 'object' ? sel.options[sel.selectedIndex].value : sel;

    switch (view){
        case 'none':
            if (showInline) {
                toggleAll(commentIds,false,false);
                showInline = false;
            }
            if (showTop) {
                toggleType(ids,'inline_comments_frxinner');
                showTop = false;
            }
            if (showRevisionComments) {
                toggleType(ids,'revision_comments_frxinner');
                showRevisionComments = false;
            }
            break;
        case 'inline':
            if (!showInline) {
                toggleAll(commentIds,false,false);
                showInline = true;
            }
            if (showTop) {
                toggleType(ids,'inline_comments_frxinner');
                showTop = false;
            }
            if (!showRevisionComments) {
                toggleType(ids,'revision_comments_frxinner');
                showRevisionComments = true;
            }
            if (!showSource) {
                toggleSource(ids, commentIds);
            }
            break;
        case 'top':
            if (showInline) {
                toggleAll(commentIds,false,false);
                showInline = false;
            }
            if (!showTop) {
                toggleType(ids,'inline_comments_frxinner');
                showTop = true;
            }
            if (!showRevisionComments) {
                toggleType(ids,'revision_comments_frxinner');
                showRevisionComments = true;
            }
            break;
    }
}

function toggleSource (ids, commentIds) {
    toggleType(ids,'sourcefrxinner');
    if (showSource){
        if (showInline && !showTop) {
            toggleComments(ids, commentIds, 'top');
            if($('select_source_top')) $('select_source_top').selected = 'true';
        }
        showSource = false;
        Element.removeClassName($('show_source_button'),'pressed')
    } else {
        Element.addClassName($('show_source_button'),'pressed')
        showSource = true;
    }
}

function toggleClassName (element, classname) {
    if (Element.hasClassName(element, classname)) {
        Element.removeClassName(element,classname)
    } else {
        Element.addClassName(element,classname)
    }
}

function toggleCommitMessages (btn) {
    toggleClassName(btn,'pressed');
    toggleType(frxIdsOnThisPage,'changeset_detailsfrxinner');
}

function changeWhitespace (url, ignore_Bl, select) {

    var blank_lines = '&bl=' + ignore_Bl;
    var expansion = '&ws=' + select.options[select.selectedIndex].value;

//    url = url + '&ws=' + select.options[sel.selectedIndex].value;

    url = url + blank_lines + expansion ;

//<a href="${url}?u=${frxDO.diffContext}&ws=${ignore_b}${ignore_b}&ignore_BL=${!ignore_BL}">Ignore Blank Lines</a>


    window.location = url;
}

function getCommentedLines(frxId, fromLineRange, toLineRange){
    var commentedLines = new Array();
    if (!g_modeSelecting && (fromLineRange != '' || toLineRange != '')){
        highlightedLines = new Array();
        var rows = $('sourceTable'+frxId).rows;
        var toLines = convertRangeToLines(toLineRange);
        var fromLines = convertRangeToLines(fromLineRange);
        var toLine = 0;
        var fromLine = 0;
        for (var i=0; i < rows.length; i++) {
            lineId = rows[i].getAttribute('id');
            if(lineId != null && lineId != '') {
                var num = lineId.split("_line")[1];
                if (num[0] == 'B') {
                    var lines = num.split(",");
                    fromLine = lines[0].replace(/^Both/, "");
                    toLine = lines[1];
                    for (var a=0; a < toLines.length; a++){
                        if (toLine == toLines[a]) {
                            commentedLines.push(lineId);
                        }
                    }
                    // no need to check from line, as both will always be represented by a to
                } else if (num[0] == 'F') {
                    fromLine = num.replace(/^From/, "");
                    for (var a=0 ; a < fromLines.length; a++){
                        if (fromLine == fromLines[a]) {
                            commentedLines.push(lineId)
                        }
                    }
                } else {
                    toLine = num;
                    for (var a=0 ; a < toLines.length; a++){
                        if (toLine == toLines[a]) {
                            commentedLines.push(lineId);
                        }
                    }
                }
            }
        }
    }
    return commentedLines;
}

var highlightedLines = new Array();

function showCommentedLines(frxId, fromLineRange, toLineRange){

    highlightedLines = getCommentedLines(frxId, fromLineRange, toLineRange);
    for (var i=0; i < highlightedLines.length; i++){
        document.getElementById(highlightedLines[i]).className='lineHighlighted';
    }
}

function clearComentedLines() {
    for (var i=0; i < highlightedLines.length; i++){
        document.getElementById(highlightedLines[i]).className='sourceLine';
    }
    highlightedLines = new Array;
}

//current (comment) can be null, e.g. in jump to first comment
function scrollToComment(current,direction){

    //if the next frx with comments isn't loaded load it first that way we know we'll get the next comment
    //small drawback is that if the next comment is in this frx there may be a delay

    var sourceCommentId = current ? current.replace(/[^0-9]*/,'') : "";
    var sourceFrxId = commentFrxMap['c'+sourceCommentId];
    var nextFrxId = sourceFrxId;
    if(sourceFrxId != 0){
        for(var i=0; i < frxIdsWithComments.length; i++){
            if(frxIdsWithComments[i]==sourceFrxId){
                if(i+1 < frxIdsWithComments.length){
                    nextFrxId = frxIdsWithComments[i+1];
                    break;
                }
            }
        }
    } else { //current comment isn't in an frx
        if(frxIdsWithComments.length > 0){
            nextFrxId = frxIdsWithComments[0];
        }
    }
    if(nextFrxId > 0){ // if there is an frx with comments to load
        if(!frxDiffMap[nextFrxId]){
            var onComp = function(originalRequest) {
                doScrollToComment(current, direction);
                return;
            }
            frxLoadDiff(nextFrxId, onComp);
            return;
        }
    }
    doScrollToComment(current, direction);
}
//current (comment) can be null
function doScrollToComment(current, direction){
    var sourceComment = $(current);  //current is the div id like revisioncomment43
    var destinationComment = null;
    var allDivs = document.getElementsByTagName('div');
    var comments = new Array;
    var yOffset = 10 //the number of px above the target comment to scroll to
    var lineCommentType = /^inlinecomment[0-9]/;
    if (showTop) {
        lineCommentType = /^abovecomment[0-9]/;
    }

    // create an array of comments (room for optimization?)
    for (var i=0; i < allDivs.length; i++) {
        if (lineCommentType.test(allDivs[i].id) ||
            /^revisioncomment[0-9]/.test(allDivs[i].id) ||
            /^generalcomment[0-9]/.test(allDivs[i].id)) {
            comments.push(allDivs[i]);
        }
    }
    if (direction == 'first') {
        destinationComment = comments[0];
    } else {
        for (var i=0; i < comments.length; i++) {
            if (sourceComment == comments[i]) {
                if (direction == 'previous') {
                    if (comments[i-1]) {
                        destinationComment = comments[i-1];
                    } else {
                        flashComment(firstChildDiv(comments[i]));
                        return false;
                    }
                } else if (direction == 'next') {
                    if (comments[i+1]) {
                        destinationComment = comments[i+1];
                    } else {
                        flashComment(firstChildDiv(comments[i]));
                        return false;
                    }
                } else {
                    destinationComment = comments[i];
                }
            }
        }
    }
    if (destinationComment == null) return false;
    // ensure destination comment is visible
    var destId = destinationComment.id.replace(/[0-9]+/,'');
    if (destId != 'generalcomment'){
        var innerFrx = getInnerFrx(destinationComment);
        if (innerFrx.style.display == 'none'){
             toggleBasic(innerFrx.id);
        }
        if (destId == 'inlinecomment') {
            yOffset = 100;
        }
    }

    var destY = findY(destinationComment) - yOffset;
    window.scrollTo(0,destY);
    // TODO: eyecandy - scroll rather than jump to comment
    flashComment(firstChildDiv(destinationComment));
    return true;
}

function findY (obj) {
	var curtop = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	}
	else if (obj.y)
		curtop += obj.y;
	return curtop;
}

function getInnerFrx(obj){
    thisNode = obj;
    while (thisNode.id.substring(0,8) != 'frxinner'){
        thisNode = thisNode.parentNode;
    }
    return thisNode;
}

function flashComment(obj){
    var originalColor = obj.style.backgroundColor;
    var fade = {
        clear : function(){
            obj.style.backgroundColor = originalColor;
        },
        half : function(){
            obj.style.backgroundColor = '#ffffd7';
        },
        full : function(){
            obj.style.backgroundColor = '#ffe7c6';
        }
    };
    fade.half();
    setTimeout(fade.full, 50);
    setTimeout(fade.half, 700);
    setTimeout(fade.clear, 750);
}

function firstChildDiv(obj){
// this is required because FF adds superfluous empty Text nodes
    var kids = obj.childNodes;
    for (var i=0; i < kids.length; i++) {
        if (kids[i].tagName == 'DIV') {
            return kids[i];
        }
    }
    return false;
}

function checkAnchor(){
    if (window.location.hash) {
        var anchor = window.location.hash.replace(/^\#/,'');
        if (commentFrxMap[anchor]) {
            var destId = anchor.replace(/^c/,commentPositionMap[anchor]);
            if (commentFrxMap[anchor] == 0) {
                scrollToComment(destId);
            } else {
                var onComp = function(originalRequest) {
                    setCommentWidths(null,true);
                    scrollToComment(destId);
                }
                frxLoadDiff(commentFrxMap[anchor],onComp);
                return commentFrxMap[anchor];
            }
        }
    }
    return false;
}

var postPostLoadFunc;

function defaultPostLoad(summarize){
    window.onresize = setCommentWidths;
    postPostLoadFunc = function() {
        // This is executed after all diffs are finished.
        if (summarize) {
            toggleSource(frxIdsOnThisPage, inlineCommentIds);
            expandSelected(frxIdsOnThisPage,frxIdsWithComments,'frxinner');
        }
        setCommentWidths(null,true);
        g_pageCompletelyLoaded = true;
    }
}

function revPostLoad(summarize) {
    defaultPostLoad(summarize);
    var anchoredFrx = checkAnchor();
    frxLazyLoad(optimizedFrxList(anchoredFrx));
}

function optimizedFrxList(alreadyLoadedFrx) {
    var a = frxIdsWithComments.concat(frxIdsWithoutComments);
    if (alreadyLoadedFrx) {
        a = a.without(alreadyLoadedFrx);
    }
    return a;
}

/**
 set comment widths to the browser window width instead of the width of the overflowing div
 it is contained in
 param: (optional) commentId, the individual comment you wish to set the width of as used in
  ajax updated comments
 param: (optional) forceUpdate, force the comment widths to be set even if the width hasn't
  changed as used in ajax updates of frx's 
*/
function setCommentWidths(commentId, forceUpdate) {
    var commentToUpdate = false;
    if(typeof(commentId) == 'string') {
        commentToUpdate = $(commentId);
    }
    var commentIndent = 52;
    if (frxIdsOnThisPage != '') {
        var currentFrxWidth = Element.getDimensions('reviewInfo').width;
        if (commentToUpdate) {
            commentToUpdate.style.width = frxWidth - commentIndent + 'px';
        } else if (forceUpdate || frxWidth != currentFrxWidth || !g_pageCompletelyLoaded) {
            frxWidth = currentFrxWidth;
            for (var i = 0; i < inlineCommentIds.length; i++) {
                var cmnt = $(inlineCommentIds[i]);
                if (cmnt) {
                    cmnt.style.width = frxWidth - commentIndent + 'px';
                }
            }
            if(rcFormWrangler.isDisplaying() && /^inline/.test(rcFormWrangler.getParent().id)){
                rcFormWrangler.setWidth(getInlineCommentFormWidth());
            }

        }
    }
    return true;
}

function getInlineCommentFormWidth() {
    var commentFormIndent = 72;
    // use the width of the Frx as most consistent element.
    if (frxIdsOnThisPage != '') {
        var currentFrxWidth = Element.getDimensions('frxouter'+frxIdsOnThisPage[0]).width;
        if (frxWidth != currentFrxWidth) {
            frxWidth = currentFrxWidth;
        }
    }
    return frxWidth - commentFormIndent;
}