$(document).ready(function() {
    updateDocumentTitle();
    checkExpandedTableLoadState();
    markupExpandedTablePageControl();
    
    $("#proteinTable td.sectionHeader, #proteinTable td.sectionName")
    .css("background-image", "url(/mpstruc/images/tableControlBG.png)")
    .css("background-repeat", "repeat-y")
    .css("background-position", "left")
    .css("border-radius", "6px");

    if (expandTblOnLoad) {
        // Leave the table expanded, but mark it up.
        markupOpenTableControls();
    } else {
        // Collapse the protein table and mark up the controls:
        hideProteinTableRows();
        markupClosedTableControls();
        
        // If url we got to this page with includes a fragment ('#'), expand
        // appropriate sections.
        var tableAnchorUrlFrag;
        if (tableAnchorUrlFrag = location.href.match(/#id_(\w|:)*$/)) {
            // Show the collapsed table
            $("table#proteinTable tbody").show();
            // then,
            openTableToAnchor(tableAnchorUrlFrag[0]);
        } else {
            // Show the collapsed table.
            $("table#proteinTable tbody").show();
        }
    }
    
    setUpTextSearch();
    updateCurrentSearchTextThreshold();
    $("#searchTextThreshold").keyup(updateCurrentSearchTextThreshold);
    $("#searchTextThreshold").change(updateCurrentSearchTextThreshold);
    
    // Intercept clicks on links into the protein table and make sure the table
    // is expanded to the appropriate anchor id.
    interceptLocalTableLinks();
    
    setUpPdbLinksCluetips();
    setUpSbkbLinksCluetips();
    setUpBibliographyCluetips();
    setUpUniqueProteinsCluetip();
    setUpNewInterfaceWordCluetip();

    setUpPdbDropdownMenu();
});


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Stuff for expand/unexpand protein table


// Resorting to using native javascript 'nextSibling' means that' on some browsers''
// white space may be treated as 'text' nodes.  This provides a check.
// See discussion at http://stackoverflow.com/questions/4051612/javascript-next-sibling
function mpstrucIsWhitespace(node) {
    return node.nodeType === 3 && /^\s*$/.test(node.data);
}

// Escape a jQuery select ID that has characters used in CSS notation.
function escapeIdString(myid) { 
   return myid.replace(/(:|\.)/g,'\\$1');
 }


var EXPAND_TABLE_PARAM_NAME = "expandTblOnLoad";
var BOOKMARK_EXPANDED_TABLE_ICON_HTML = "<img class='bookmark' title='Create a bookmark for a fully expanded protein table version of this page' src='/mpstruc/images/faviconClearBG.png' alt='' width='16' height='16' />";

var EXPAND_ICON_HTML = "<img class='expandIcon expansionControlIcon' title='Expand this section' src='http://blanco.biomol.uci.edu/img/toggle-small-expand.png' alt='' width='16' height='16' />";
var DOUBLE_EXPAND_ICON_HTML = "<img class='doubleExpandIcon expansionControlIcon' title='Fully expand this section (this may take a moment)' src='http://blanco.biomol.uci.edu/img/toggle-small-double-expand.png' alt='' width='18' height='16' />";
var TRIPLE_EXPAND_ICON_HTML = "<img class='tripleExpandIcon expansionControlIcon' title='Expand entire table (this will take a few moments)' src='http://blanco.biomol.uci.edu/img/toggle-small-triple-expand.png' alt='' width='21' height='16' />";
var UNEXPAND_ICON_HTML = "<img class='unexpandIcon expansionControlIcon' title='Unexpand this section' src='http://blanco.biomol.uci.edu/img/toggle-small-unexpand.png' alt='' width='16' height='16' />";
var DOUBLE_UNEXPAND_ICON_HTML = "<img class='doubleUnexpandIcon expansionControlIcon' title='Unexpand this section (this may take a moment)' src='http://blanco.biomol.uci.edu/img/toggle-small-double-unexpand.png' alt='' width='18' height='16' />";
var TRIPLE_UNEXPAND_ICON_HTML = "<img class='tripleUnexpandIcon expansionControlIcon' title='Unexpand entire table (this will take a few moments)' src='http://blanco.biomol.uci.edu/img/toggle-small-triple-unexpand.png' alt='' width='21' height='16' />";

var BOOKMARK_ICON_HTML = "<img class='bookmark' title='Create a bookmark for this section of the table' src='/mpstruc/images/faviconSmallerClearBG.png' alt='' width='16' height='16' />";

var FADEIN_TIME = 500;
var FADEOUT_TIME = 500;

var handlingTableVisEvent = false;


function hideProteinTableRows() {
    // First, hide what we want.
    handlingTableVisEvent = true;
    $("tr.protein").hide();
    $("tr.sectionName").hide();
    handlingTableVisEvent = false;
}


// Make sure document.title reflects expanded or unexpanded table url (leave any
// trailing portion intact).
// Return result.
function updateDocumentTitle() {
    var simpleTitle = "Membrane Proteins of Known Structure";
    var expandedTableTitleExtension = ("1" === getUrlVar(EXPAND_TABLE_PARAM_NAME)) ? " - Expanded" : "";
    var docTitleHash = "";
    if (window.location.hash && window.location.hash !== "#!") {
        docTitleHash = " - " + window.location.hash.replace("#id_", "");
        docTitleHash = docTitleHash.replace(/_/g, " ");
    }
    document.title = simpleTitle + expandedTableTitleExtension + docTitleHash;
    return document.title;
}


// Add event handler to table header icon to set up bookmarking of an expanded-
// table version of the page.
function markupExpandedTablePageControl() {
    handlingTableVisEvent = true;
    
    $('<div class="tableControl"></div>')
    .prependTo("#proteinTable th.tableHeader:first-child");
    
    $("#proteinTable th.tableHeader:first-child div.tableControl")
    .prepend($(BOOKMARK_EXPANDED_TABLE_ICON_HTML).css('cursor', 'pointer').click(expandedTableBookmarkHandler));
    
    handlingTableVisEvent = false;
}


function markupClosedTableControls() {
    handlingTableVisEvent = true;
    
    // Somewhere to put the buttons:
    $('<div class="tableControl"></div>')
    .prependTo("tr.sectionHeader td:first-child, tr.sectionName td:first-child");

    $("tr.sectionHeader div.tableControl")
    .prepend($(TRIPLE_EXPAND_ICON_HTML).css('cursor', 'pointer').click(expandEntireTableHandler))
    .prepend($(DOUBLE_EXPAND_ICON_HTML).css('cursor', 'pointer').click(fullyExpandFamilyHandler))
    .prepend($(EXPAND_ICON_HTML).css('cursor', 'pointer').click(expandFamilyHandler))
    .closest("tr").find(".sectionHeaderBody").click(sectionHeaderToLocationHashHandler);
    
    $("tr.sectionName div.tableControl")
    .prepend($(BOOKMARK_ICON_HTML).css('cursor', 'pointer').click(createLocalBookmarkHandler))
    .prepend($(EXPAND_ICON_HTML).css('cursor', 'pointer').click(expandSubfamilyHandler))
    .closest("tr").find(".sectionNameBody").click(sectionNameToLocationHashHandler);
    
    handlingTableVisEvent = false;
}


function markupOpenTableControls() {
    // Somewhere to put the buttons:
    $('<div class="tableControl"></div>')
    .prependTo("tr.sectionHeader td:first-child, tr.sectionName td:first-child");

    $("tr.sectionHeader div.tableControl")
    .prepend($(TRIPLE_UNEXPAND_ICON_HTML).css('cursor', 'pointer').click(unexpandEntireTableHandler))
    .prepend($(DOUBLE_UNEXPAND_ICON_HTML).css('cursor', 'pointer').click(fullyUnexpandFamilyHandler))
    .prepend($(UNEXPAND_ICON_HTML).css('cursor', 'pointer').click(unexpandFamilyHandler))
    .closest("tr").find(".sectionHeaderBody").click(sectionHeaderToLocationHashHandler);
    
    $("tr.sectionName div.tableControl")
    .prepend($(BOOKMARK_ICON_HTML).css('cursor', 'pointer').click(createLocalBookmarkHandler))
    .prepend($(UNEXPAND_ICON_HTML).css('cursor', 'pointer').click(unexpandSubfamilyHandler))
    .closest("tr").find(".sectionNameBody").click(sectionNameToLocationHashHandler);
}



function expandFamily(controlIconNode) {
    // Direct javascript iteration is supposed to be an order of magnitude faster
    // than the jQuery nextUntil() approach.  Not so sure that's actually the case
    // (probably applies to earlier version of jQuery).
    // See Bob's comment at http://api.jquery.com/nextUntil/
    if (!controlIconNode) return;
    
    var $origImg = $(controlIconNode);
    var trNode = $origImg.closest("tr")[0];
    while (trNode = trNode.nextSibling) {
        if (mpstrucIsWhitespace(trNode)) {continue;}
        if ($(trNode).hasClass("sectionHeader")) {break;}
        if ($(trNode).hasClass("sectionName")) {
            $(trNode).fadeIn(FADEIN_TIME);
        }
    }
    var $unexpandImg = $(UNEXPAND_ICON_HTML);
    $unexpandImg.click(unexpandFamilyHandler).css('cursor', 'pointer');
    $origImg.unbind().replaceWith($unexpandImg);
}
function expandFamilyHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    var $sectionHeader = $(this).closest("tr");
    $(this).closest("tr").fadeTo("fast", 0.25).end().fadeTo("fast", 0.25, function() {
        expandFamily(this);
    });
    $sectionHeader.fadeTo("fast", 1.0);
    handlingTableVisEvent = false;
}


function unexpandFamily(node) {
    if (!node) return;
    
    var $origImg = $(node);
    var trNode = $origImg.closest("tr")[0];
    while (trNode = trNode.nextSibling) {
        if (mpstrucIsWhitespace(trNode)) {continue;}
        if ($(trNode).hasClass("sectionHeader")) {break;}
        if ($(trNode).hasClass("sectionName")) {
            unexpandSubfamily($(trNode).find("td img.unexpandIcon")[0])
            $(trNode).fadeOut(FADEOUT_TIME);
        }
    }
    var $origImgParent = $origImg.parent();
    var $expandImg = $(EXPAND_ICON_HTML);
    $expandImg.click(expandFamilyHandler).css('cursor', 'pointer');
    $origImg.unbind().replaceWith($expandImg);
    
    $expandImg = $(DOUBLE_EXPAND_ICON_HTML);
    $expandImg.click(fullyExpandFamilyHandler).css('cursor', 'pointer');
    var $imgToReplace = $origImgParent.find(".doubleUnexpandIcon");
    if ($imgToReplace) {
        $imgToReplace.unbind().replaceWith($expandImg);
    }
}
function unexpandFamilyHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    
    var $sectionHeader = $(this).closest("tr");
    $(this).closest("tr").fadeTo("fast", 0.25).end().fadeTo("fast", 0.25, function() {
        unexpandFamily(this);
    });
    $sectionHeader.fadeTo("fast", 1.0);
    
    handlingTableVisEvent = false;
}


function expandSubfamily(node) {
    if (!node) return;
    var $origImg = $(node);
    var trNode = $origImg.closest("tr")[0];
    while (trNode = trNode.nextSibling) {
        if (mpstrucIsWhitespace(trNode)) {continue;}
        var $wrappedTrNode = $(trNode);
//        if ($wrappedTrNode.hasClass("sectionName")) {break;}
        if ($wrappedTrNode.hasClass("sectionName") || $wrappedTrNode.hasClass("sectionHeader")) {break;}
        $wrappedTrNode.fadeIn(FADEIN_TIME);
    }
    var $unexpandImg = $(UNEXPAND_ICON_HTML);
    $unexpandImg.click(unexpandSubfamilyHandler).css('cursor', 'pointer');
    $origImg.unbind().replaceWith($unexpandImg);
}
function expandSubfamilyHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    
    var $sectionHeader = $(this).closest("tr");
    $(this).closest("tr").fadeTo("fast", 0.25).end().fadeTo("fast", 0.25, function() {
        expandSubfamily(this);
    });
    $sectionHeader.fadeTo("fast", 1.0);
    
    handlingTableVisEvent = false;
}


function unexpandSubfamily(node) {
    if (!node) return;
    var $origImg = $(node);
    var $origImgParent = $origImg.parent();
    var trNode = $origImgParent.closest("tr")[0];
    while (trNode = trNode.nextSibling) {
        if (mpstrucIsWhitespace(trNode)) {continue;}
        var $wrappedTrNode = $(trNode);
        if ($wrappedTrNode.hasClass("sectionName") || $wrappedTrNode.hasClass("sectionHeader")) {break;}
        $wrappedTrNode.fadeOut(FADEOUT_TIME);
    }
    var $expandImg = $(EXPAND_ICON_HTML);
    $expandImg.click(expandSubfamilyHandler).css('cursor', 'pointer');
    $origImg.unbind().replaceWith($expandImg);
}
function unexpandSubfamilyHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    
    var $sectionHeader = $(this).closest("tr");
    $(this).closest("tr").fadeTo("fast", 0.25).end().fadeTo("slow", 0.25, function() {
        unexpandSubfamily(this);
    });
    $sectionHeader.fadeTo("fast", 1.0);
    
    handlingTableVisEvent = false;
}


function expandEntireTable() {
    $("#proteinTable tr.sectionHeader").each(function() {
        var $sectionHeader = $(this);
        fullyExpandFamily($sectionHeader.find("img.doubleExpandIcon")[0]);
        $sectionHeader.find("img.tripleExpandIcon").unbind().replaceWith(TRIPLE_UNEXPAND_ICON_HTML);
        $sectionHeader.find("img.tripleUnexpandIcon").click(unexpandEntireTableHandler).css('cursor', 'pointer');
        $sectionHeader.fadeTo("fast", 1.0);
    });
}
function expandEntireTableHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    $('body').css('cursor','progress');
    
    var $button = $(this);
    $("#proteinTable tr.sectionHeader").fadeTo("fast", 0.5);
    $button.fadeTo("fast", 0.25, function() {
        expandEntireTable();
    });
    
    
    $('body').css('cursor','default');
    handlingTableVisEvent = false;
}


function unexpandEntireTable() {
    $("#proteinTable tr.sectionHeader").each(function() {
            var $sectionHeader = $(this);
            fullyUnexpandFamily($sectionHeader.find("img.doubleUnexpandIcon")[0]);
            $sectionHeader.find("img.tripleUnexpandIcon").unbind().replaceWith(TRIPLE_EXPAND_ICON_HTML);
            $sectionHeader.find("img.tripleExpandIcon").click(expandEntireTableHandler).css('cursor', 'pointer');
            $sectionHeader.fadeTo("fast", 1.0);
        });
}
function unexpandEntireTableHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    $('body').css('cursor','progress');
    
    var $button = $(this);
    $("#proteinTable tr.sectionHeader").fadeTo("fast", 0.5);
    $button.fadeTo("fast", 0.25, function() {
        unexpandEntireTable();
    });
    
    $('body').css('cursor','default');
    handlingTableVisEvent = false;
}


function fullyExpandFamily(node) {
    if (!node) return;
    var $origImg = $(node);
    var $origImgParent = $origImg.parent();
    var trNode = $origImg.closest("tr")[0];
    
    // Adapted from http://www.nczonline.net/blog/2009/08/11/timed-array-processing-in-javascript/:
    // Give the browser some time slices to do some rendering.
    setTimeout(function() {
        if (!trNode) {return;}
        var start = +new Date();  // start time for a time slice
            do {
                trNode = trNode.nextSibling;
                if (trNode && mpstrucIsWhitespace(trNode)) {
                    continue;
                }
                var $wrappedTrNode = $(trNode);
                if ($wrappedTrNode.hasClass("sectionName")) {
                    expandSubfamily($wrappedTrNode.find("td img.expandIcon")[0]);
                    $wrappedTrNode.fadeIn(FADEIN_TIME);
                }
            } while (!$(trNode).hasClass("sectionHeader") && (trNode.nextSibling) && (+new Date() - start < 50));
            
        if (trNode.nextSibling && (!$(trNode).hasClass("sectionHeader"))) {
            setTimeout(arguments.callee, 25);
        }
    }, 25);
    
    var $unexpandImg = $(DOUBLE_UNEXPAND_ICON_HTML);
    $unexpandImg.click(fullyUnexpandFamilyHandler).css('cursor', 'pointer');
    $origImg.unbind().replaceWith($unexpandImg);
    
    var $imgToReplace = $origImgParent.find(".expandIcon");
    if ($imgToReplace) {
        $unexpandImg = $(UNEXPAND_ICON_HTML);
        $unexpandImg.click(unexpandFamilyHandler).css('cursor', 'pointer');
        $imgToReplace.unbind().replaceWith($unexpandImg);
    }
}
function fullyExpandFamilyHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    $('body').css('cursor','progress');
    
    var $sectionHeader = $(this).closest("tr");
    $(this).closest("tr").fadeTo("fast", 0.25).end().fadeTo("fast", 0.25, function() {
        fullyExpandFamily(this);
    });
    $sectionHeader.fadeTo("fast", 1.0);
    
    $('body').css('cursor','default');
    handlingTableVisEvent = false;
}


function fullyUnexpandFamily(node) {
    if (!node) return;
    var $origImg = $(node);
    var $origImgParent = $origImg.parent();
    var trNode = $origImg.closest("tr")[0];

    // Adapted from http://www.nczonline.net/blog/2009/08/11/timed-array-processing-in-javascript/:
    // Give the browser some time slices to do some rendering.
    setTimeout(function() {
        if (!trNode) {return;}
        var start = +new Date();
            do {
                trNode = trNode.nextSibling;
                if (mpstrucIsWhitespace(trNode)) {
                    continue;
                }
                var $wrappedTrNode = $(trNode);
                if ($wrappedTrNode.hasClass("sectionName")) {
                    unexpandSubfamily($wrappedTrNode.find("td img.unexpandIcon")[0]);
                    $wrappedTrNode.fadeOut(FADEOUT_TIME);
                }
            } while (!$(trNode).hasClass("sectionHeader") && (trNode.nextSibling) && (+new Date() - start < 50));

        if (trNode.nextSibling && (!$(trNode).hasClass("sectionHeader"))) {
            setTimeout(arguments.callee, 25);
        }
    }, 25);
    
    var $expandImg = $(DOUBLE_EXPAND_ICON_HTML);
    $expandImg.click(fullyExpandFamilyHandler).css('cursor', 'pointer');
    $origImg.unbind().replaceWith($expandImg);
    
    $expandImg = $(EXPAND_ICON_HTML);
    var $imgToReplace = $origImgParent.find(".unexpandIcon");
    if ($imgToReplace) {
        $expandImg.click(expandFamilyHandler).css('cursor', 'pointer');
        $imgToReplace.unbind().replaceWith($expandImg);
    }
}
function fullyUnexpandFamilyHandler() {
    if (handlingTableVisEvent) return;
    handlingTableVisEvent = true;
    $('body').css('cursor','progress');
    
    var $sectionHeader = $(this).closest("tr");
    $(this).closest("tr").fadeTo("fast", 0.25).end().fadeTo("fast", 0.25, function() {
        fullyUnexpandFamily(this);
    });
    $sectionHeader.fadeTo("fast", 1.0);
    
    
    $('body').css('cursor','default');
    handlingTableVisEvent = false;
}


////////////////////////////////////////////////////////////////////////////////
// Adjunct stuff for dealing with expand/unexpand protein table issues -
// local links and URLs with pdb ids


// Assume we come into openTableToAnchor() with a URL fragment of the form "#id_*".
// If the first thing after that can be identified as a pdb id, process that.
// Otherwise, it must be an anchor for a sectionHeader (Family) or sectionName
// (Subfamily).
function openTableToAnchor(tableAnchorUrlFrag) {
    if (!String(tableAnchorUrlFrag).match(/^#id_*/)) {return;}
    
    handlingTableVisEvent = true;
    
    var pdbId = String(tableAnchorUrlFrag).match(/^#id_[0-9][a-zA-Z0-9]{3}_/);
    if (pdbId) {
        // Don't care here about using the pdb code, just that we have one.
        // Get the subfamily and family info.
        var subfamilyIdAndFamilyId = String(tableAnchorUrlFrag).split(/^#id_[0-9][a-zA-Z0-9]{3}_/)[1];
        var ids = String(subfamilyIdAndFamilyId).split("_");
        var subfamilyId = ids[0];
        var familyId = ids[1];
        var url = myURLroot + "listAll/subfamilyAndFamilyNamesToHtmlIds";
        $.getJSON(
            url,
            {familyId : familyId, subfamilyId : subfamilyId},
            function(data) {
                if (data.data.familyHtmlId) {
                    expandFamily($("#" + escapeIdString(data.data.familyHtmlId)).parent().find(".tableControl .expandIcon")[0]);
                }
                if (data.data.subfamilyHtmlId) {
                    expandSubfamily($("#" + escapeIdString(data.data.subfamilyHtmlId)).parent().find(".tableControl .expandIcon")[0]);
                }
                setTimeout(function() {
                    window.location.hash = tableAnchorUrlFrag;
                    $.scrollTo($(tableAnchorUrlFrag));   // At least Safari seems to need this.
                }, 50);
            });
    } else {
        // Must be an anchor for a family or subfamily.
        // Have to worry about some characters appearing in id (conflict with jQuery selectors,
        // See http://docs.jquery.com/Frequently_Asked_Questions#How_do_I_select_an_element_by_an_ID_that_has_characters_used_in_CSS_notation.3F):
        var escapedTableAnchorUrlFrag = escapeIdString(tableAnchorUrlFrag);
        var familyIdFrag;
        if (escapedTableAnchorUrlFrag.indexOf(":") >= 0) {
            var $closestTr = $(escapedTableAnchorUrlFrag).closest("tr");
            var $expandIcon = $closestTr.find(".tableControl .expandIcon");
            expandSubfamily($expandIcon[0]);
            
            familyIdFrag = escapedTableAnchorUrlFrag.split(":")[0];
        } else {
            familyIdFrag = escapedTableAnchorUrlFrag;
        }
        expandFamily($(familyIdFrag).closest("tr").find(".tableControl .expandIcon")[0]);
        
        setTimeout(function() {
            window.location.hash = tableAnchorUrlFrag;
            $.scrollTo($(escapedTableAnchorUrlFrag));   // At least Safari seems to need this.
        }, 50);
    }
    handlingTableVisEvent = false;
}


function interceptLocalTableLinks() {
    $("a[href^='#id_']").each(function() {
        $(this).click(function() {
            var anchorId = $(this).attr("href");
            openTableToAnchor(anchorId);
        });
    });
}


function sectionNameToLocationHashHandler() {
    window.location.hash = $(this).closest("td").find("a").attr("id");
}


function sectionHeaderToLocationHashHandler() {
    window.location.hash = $(this).closest("td").find("a").attr("id");
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Stuff for bookmark assistance

function createLocalBookmarkHandler(event) {
    var $localAnchor = $(this).closest("td").find("a");
    window.location.hash = $localAnchor.attr("id");
    
    // The rest is very loosely adapted from http://plugins.jquery.com/project/jBrowserBookmark:
    var browserName = getBrowser();
    event.preventDefault();
    updateDocumentTitle();

    if (browserName === 'msie') {
        window.external.AddFavorite(window.location, document.title);
        
    } else if (browserName === "opera" && versionOpera() < 11) {
        // Try to check this.  A 'sidbar' bookmark in firefox opens like a separate frame.
        // Really don't want that behavior.
        $(this).attr('rel', 'sidebar').attr('title', document.title).attr('href', window.location);
        
    } else {
        var hotkey = getHotkey(browserName);
        var functionButton = ['COMMAND', 'CTRL'];
        var alertText = 'Press [key]-[hotkey] to bookmark this page.';
        if (/^mac*/.test(navigator.platform.toLowerCase())) {
            alertText = alertText.replace('[key]', functionButton[0]);
        } else {
            alertText = alertText.replace('[key]', functionButton[1]);
        }
        alertText = alertText.replace('[hotkey]', hotkey);
        alert(alertText);
    }
}


// Get the name of the users browser.
// @return {string}  name of the browser
function getBrowser() {
    if ($.browser.msie) {
        return 'msie';
    } else if ($.browser.mozilla) {
        return 'firefox';
    } else if ($.browser.opera) {
        return 'opera';
    } else if ($.browser.safari && /chrome/.test(navigator.userAgent.toLowerCase())) {
        return 'chrome';
    } else if ($.browser.safari) {
        return 'safari';
    } else if (/konqueror/.test(navigator.userAgent.toLowerCase())) {
        return 'konqueror';
    }
    // Default:
    return '';
}
    
// Get the browsers key for adding the bookmark manually.
// @param {string} browserName
// @return {string} hotkey to access browser bookmarks.
function getHotkey(browserName) {
    switch (browserName) {
        case 'konqueror':
            return 'b';
            break;
        case 'opera':
            // Opera prior to 9 used CONTROL + T for bookmarking.
            return (versionOpera() < 9) ? 't' : 'd';
            break;
        default:
            return 'd';
            break;
    }
}
    
// Get the version of Opera.
// @return {int}  version number of Opera browser.
function versionOpera() {
    version = navigator.userAgent.substring(navigator.userAgent.toLowerCase().indexOf('version/') + 8);
    return parseInt(version.substring(0, version.indexOf('.')));
}


function supports_history_api() {
  return !!(window.history && history.pushState);
}


// If a user wants to bookmark the page that has the protein table fully expanded
// at load, we need to provide a url with a query parameter to indicate that.  With
// html5 history api capable browsers, use pushState() - that allows bookmarking the
// proper url without reloading the whole page.  With other browsers, we need some
// evidence that we're providing that kind of url so we can provide an alert() on
// the fresh page, as done on the html5 refreshed (but not reloaded) page.
// I'm using the #! (hashbang) that Google promoted for a related issue.  There
// needs to be a check of the url for this on the reload (see checkExpandedTableLoadState()).
function expandedTableBookmarkHandler(event) {
    if (supports_history_api()) {
        var url = "?" + EXPAND_TABLE_PARAM_NAME + "=1";
        window.history.pushState({state:1}, document.title, url);
        updateDocumentTitle();
        expandEntireTable();
        var browserName = getBrowser();
            var hotkey = getHotkey(browserName);
            var functionButton = ['COMMAND', 'CTRL'];
            var alertText = 'Press [key]-[hotkey] to bookmark this page.';
            if (/^mac*/.test(navigator.platform.toLowerCase())) {
                alertText = alertText.replace('[key]', functionButton[0]);
            } else {
                alertText = alertText.replace('[key]', functionButton[1]);
            }
            alertText = alertText.replace('[hotkey]', hotkey);
            alert(alertText);
    } else {
        window.location.hash = "#!";
        window.location.search = "?" + EXPAND_TABLE_PARAM_NAME + "=1"
    }
}

// If we're loading the page with the query param EXPAND_TABLE_PARAM_NAME === 1
// and location.hash = "#!", the user requested to create a bookmark for the page
// with the table fully expanded.
function checkExpandedTableLoadState() {
    if (getUrlVar(EXPAND_TABLE_PARAM_NAME) === "1" &&  window.location.hash === "#!") {
        window.location.hash = '';
        $(window).scrollTop();  // Try to clear window.location.hash
        var browserName = getBrowser();
        var hotkey = getHotkey(browserName);
        var functionButton = ['COMMAND', 'CTRL'];
        var alertText = 'Press [key]-[hotkey], once page has re-loaded, to bookmark this page.';
        if (/^mac*/.test(navigator.platform.toLowerCase())) {
            alertText = alertText.replace('[key]', functionButton[0]);
        } else {
            alertText = alertText.replace('[key]', functionButton[1]);
        }
        alertText = alertText.replace('[hotkey]', hotkey);
        alert(alertText);
    }
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Stuff for tool tips
     
var TIMES_TO_SHOW = 2;
var pdbTooltipExecsCounter = 0;
var sbkbTooltipExecsCounter = 0;

function getPDBTooltip() {
    return "Links to the Protein Data Bank Site";
}

function setUpPdbLinksCluetips() {
    $('a.pdbLinksHeader').cluetip(
        getPDBTooltip(),
        {
            hoverIntent: true,
            mouseOutClose: true,
            width: 240,                // consistent with hovertip width
            showTitle: false,
            cluetipClass: 'jtip',
            positionBy: 'topBottom',
            onActivate: function (e) {
                if (pdbTooltipExecsCounter < TIMES_TO_SHOW) {
                    return true;
                } else {
                    $('a.pdbLinksHeader').css('cursor', 'pointer');
                    return false;
                }
            },
            onShow: function(ct, c) {
            },
            onHide: function(ct, c) {   // Show tooltip limited times.
                pdbTooltipExecsCounter++;
                if (pdbTooltipExecsCounter > TIMES_TO_SHOW - 1) {
                    $('a.pdbLinksHeader').cluetip('destroy');
                }
            }
        });
}
            
function getSBKBTooltip() {
    return "Links to the Structural Biology Knowledgebase Site";
}

function setUpSbkbLinksCluetips() {
    $('a.sbkbLinksHeader').cluetip(
        getSBKBTooltip(),
        {
            hoverIntent: true,
            width: 240,
            showTitle: false,
            cluetipClass: 'jtip',
            positionBy: 'topBottom',
            onActivate: function (e) {
                if (sbkbTooltipExecsCounter < TIMES_TO_SHOW) {
                    return true;
                } else {
                    $('a.sbkbLinksHeader').css('cursor', 'pointer');
                    return false;
                }
            },
            onShow: function(ct, c) {
            },
            onHide: function(ct, c) {   // Show tooltip limited times.
                sbkbTooltipExecsCounter++;
                if (sbkbTooltipExecsCounter > TIMES_TO_SHOW - 1) {
                    $('a.sbkbLinksHeader').cluetip('destroy');
                }
            }
        });
}


// MOVED TO bibliographyClueTips.js
//function getFullBibliographyText(context) {
//    return $(context).nextAll(".bibliographyCluetip").filter(":first").html();
//}
//
//function setUpBibliographyCluetips() {
//    $("a.pubMedRef").each(function() {
//        $(this).cluetip(
//            getFullBibliographyText(this),
//            {
//                sticky: true,
//                hoverIntent: true,
//                width: 240,
//                showTitle: false,
//                cluetipClass: 'jtip',
//                clickThrough: true,
//                closeText: 'x',
//                positionBy: 'topBottom'
//            }
//        );
//    });
//}


function getUniqueProteinsDefinitionTooltipText() {
    return $("#uniqueProteinsDefinition").html();
}
function setUpUniqueProteinsCluetip() {
$("#uniqueProteins")
    .find("span.hasToolTip")
    .css("color", "blue")
    .cluetip(
        getUniqueProteinsDefinitionTooltipText(),
        {
            hoverIntent: true,
            width: 400,
            showTitle: false,
            cluetipClass: 'jtip',
            positionBy: 'topBottom'
        }
    );
}


function getNewInterfaceTooltipText() {
    return $("#newInterfaceWordContent").html();
}
function setUpNewInterfaceWordCluetip() {
$("#newInterfaceWord")
    .find("p")
    .css("color", "blue")
    .cluetip(
        getNewInterfaceTooltipText(),
        {
            hoverIntent: true,
            width: 400,
            showTitle: false,
            cluetipClass: 'jtip',
            positionBy: 'topBottom'
        }
    );
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Stuff for dropdown menus:

function setUpPdbDropdownMenu() {
    $("ul.sf-menu").superfish({ 
        animation: {
            height:'show'
        },   // slide-down effect without fade-in 
        delay:     200  // 0.2 second delay on mouseout 
    });
}



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Parse out parameters from a page's URL:

// Get the parameters from the window.location.href URL.
// Return something like: {"me" : "myValue", "name2" : "SomeOtherValue"}
function getUrlVars() {
    var vars = [], hash;
    var hashes = window.location.search.slice(window.location.search.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++) {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}


// Get the value of a URL parameter by name.
function getUrlVar(name) {
    return getUrlVars()[name];
}


// Set a parameter value in in window.location.href
function setUrlVar(name, val) {
    if (window.location.search.indexOf(name) >= 0) {
        // If the parameter name is in the url, set its value
        var regex = new RegExp(name + "\\=[^\\&]");
        window.location.href = window.location.search.replace(regex, name + "=1");

      } else if (window.location.search) {
        // Add name, val, to existing parameters
          window.location.search = window.location.search.replace("?", "?" + name + "=1&");
          
    } else {
        // Append to url
        window.location.search = "?" + name + "=1";
    }
}


////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// Searching protein table text

var EXPAND_TEXT_SEARCH_ICON_HTML = "<img id='expandTextSearchIcon' class='textSearchControlIcon' title='Expand text search controls' src='http://blanco.biomol.uci.edu/img/toggle-small-expand.png' alt='' width='16' height='16' />";
var UNEXPAND_TEXT_SEARCH_ICON_HTML = "<img id='unexpandTextSearchIcon' class='textSearchControlIcon' title='Unexpand text search controls' src='http://blanco.biomol.uci.edu/img/toggle-small-unexpand.png' alt='' width='16' height='16' />";


function setUpTextSearch() {
    var thresholdDefaultVal = "1";
    
    $("#textSearchControlsTable tr th:first-child").prepend($('<div class="tableControl"></div>'));
    
    $("#textSearchControlsTable th:first-child div.tableControl").prepend($(EXPAND_TEXT_SEARCH_ICON_HTML)
    .css('cursor', 'pointer').click(expandTextSearchControlsHandler));
    
    $("#searchTextButton").click(searchTable);
    $('#searchTextField, #searchTextThreshold').keypress(function(e) {
        // "Enter" pressed?
        if (e.which == 10 || e.which == 13) {
            searchTable();
        }
    });
    $("#clearSearchText").click(function() {
        $("#searchTextField").val("");
        $("#searchTextThreshold").val(thresholdDefaultVal);
        $("#currentSearchTextThresholdVal").text(thresholdDefaultVal);
        $("#textSearchResultCount").text(" ");
        setTextSearchMode(false);
    });
}

function expandTextSearchControlsHandler() {
    $("#textSearchControlsTable tbody").fadeIn("slow");
    $("#textSearchDiv #expandTextSearchIcon").replaceWith(
        $(UNEXPAND_TEXT_SEARCH_ICON_HTML).click(unexpandTextSearchControlsHandler).css('cursor', 'pointer'));
    
}
function unexpandTextSearchControlsHandler() {
    $("#textSearchControlsTable tbody").fadeOut("slow");
    $("#textSearchDiv #unexpandTextSearchIcon").replaceWith(
        $(EXPAND_TEXT_SEARCH_ICON_HTML).click(expandTextSearchControlsHandler).css('cursor', 'pointer'));
}


function updateCurrentSearchTextThreshold() {
    var value = $("#searchTextThreshold").val();
    var intVal = parseInt(value);
    if (isNaN(intVal)) {
        $("#currentSearchTextThresholdVal").text("");
        return;
    }
    
    var max = $("#searchTextThreshold").attr("max");
    var intMax = parseInt(max);
    if (intVal > intMax) {
        intVal = intMax;
    } else {
        var min = $("#searchTextThreshold").attr("min");
        var intMin = parseInt(min);
        if (intVal < intMin) {
            intVal = intMin;
        }
    }
    $("#searchTextThreshold").val(intVal.toString());
    $("#currentSearchTextThresholdVal").text(intVal.toString());
    
    if (intVal == 0) {
        $("#searchTextRegExpCheckbox").removeAttr("disabled");
    } else {
        $("#searchTextRegExpCheckbox").attr("disabled", true);
    }
}


function setTextSearchMode(flag) {
    if (flag) {
        $(".protein").addClass("searchMode");
        $("#textSearchControlsTable").addClass("searchMode");
        $("#textSearchHeaderContent").html("Text Search of Table - <span id='textSearchActive'>Active</span>");
        $("th#textSearchHeader .tableControl .textSearchControlIcon").hide();
        $("#proteinTable .expansionControlIcon").hide();
    } else {
        $(".protein").removeClass("searchMode");
        $("#textSearchControlsTable").removeClass("searchMode");
        $("#textSearchHeaderContent").html("Text Search of Table");
        $("tr.sectionName").fadeOut(1);
        $(".protein").fadeOut(1);
        $("#searchTextRegExpCheckbox").attr("disabled", true).removeAttr("checked");
        $("th#textSearchHeader .tableControl .textSearchControlIcon").show();
        $("#proteinTable .expansionControlIcon").show();
        unexpandEntireTable();  // unexpand protein table
    }
}


function searchTable() {
    $('body').css('cursor','progress');
    $('#searchTextField').tableSearchUpdate('#proteinTable');
    $('body').css('cursor','default');
}

// Diff, Match and Patch Library, http://code.google.com/p/google-diff-match-patch/, Neil Fraser
var dmp = new diff_match_patch();


// Trim whitespace, from trim12 at http://blog.stevenlevithan.com/archives/faster-trim-javascript
function trim(str) {
    var	str = str.replace(/^\s\s*/, ''),
    ws = /\s/,
    i = str.length;
    while (ws.test(str.charAt(--i)));
    return str.slice(0, i + 1);
}


function stripAllExtraWhiteSpace(string) {
    var text = trim(string);           // outer whitespace
    return text.replace(/\s+/g, " ");  // inner whitespace
}

// Search text with pattern.  Position of pattern in text is returned, or -1 if
// not found.  
function mpstrucTextSearch(text, pattern) {
    var LOC = 5000;
    text = stripAllExtraWhiteSpace(text);
    if (dmp.Match_Threshold == 0) {
        if ($("#searchTextRegExpCheckbox").is(':checked')) {
            match = text.search(pattern);   // Find regexp match
        } else {
            match = text.indexOf(pattern);  // Find exact match
        }
    } else {
        match = text.toLowerCase().indexOf(pattern.toLowerCase());
    }
    if (match < 0) {
        // Didn't find anything that way, do fuzzy/approximate matching
        match = dmp.match_main(text.toLowerCase(), pattern.toLowerCase(), LOC);
    }
    return match;
}

jQuery.fn.tableSearchUpdate = function(tableBody) {
    var $sectionNameRows = $(tableBody).find("tr.sectionName");
    if (!($sectionNameRows.length)) {
        return this;
    }
    setTextSearchMode(true);
    var pattern = trim(jQuery(this).val());
    if ("" == pattern) {
        return this;
    }
    var intVal = parseInt($("#searchTextThreshold").val())
    if (isNaN(intVal)) {
        alert("No value for Fuzziness Threshold set.");
        return this;
    }
    dmp.Match_Threshold = intVal * 0.1;
    $("tr.sectionName").fadeOut(1);
    $("tr.protein").fadeOut(1);
    $("#textSearchResultCount").text("Search results row count:");
    
    var $hits = $sectionNameRows.map(function() {
        var $proteinHits;
        var match = mpstrucTextSearch($(this).text(), pattern);
        if (match >= 0) {
            // Found a match in the sectionName.  Make it's protein rows visible
            // and gather the protein rows for a result.
            $(this).fadeIn(1);
            $proteinHits = $(this).nextUntil(":not(.protein)").map(function() {
                // Show a table protein row
                $(this).fadeIn(1);
                return this;
            }).get();
            
        } else {
            // Look for a match in this sectionName's protein rows.
            $proteinHits = $(this).nextUntil(":not(.protein)").map(function() {
                var proteinMatch = mpstrucTextSearch($(this).text(), pattern);
                if (proteinMatch >= 0) {
                    // Show the table protein row
                    $(this).fadeIn(1);
                    // And it's subfamily/family ('sectionName') header row
                    var $subfamilyNameTr = $(this).prevUntil("tr.sectionName", "tr.protein").last().prev();
                    if ($subfamilyNameTr.size() == 0) {
                        // Must be first protein for subfamily
                        $subfamilyNameTr = $(this).prev();
                    }
                    $subfamilyNameTr.fadeIn(1);
                    
                    return this; // Add row to result set
                }
            }).get();
        }
        if ($proteinHits && $proteinHits.length > 0) {
            return $proteinHits;
        }
    });
    $("#textSearchResultCount").text("Search results row count: " + $hits.length);
}

