// =================================================================================
//
// global connection variables
//
// =================================================================================


var omixedResourceName = "terminizer";
var omixedUserName     = "guest";
var omixedPassword     = "guest";


// =================================================================================
//
// flash plugin detection (from: MooTools)
//
// =================================================================================


var FlashDetect = 
{
    init: function () 
    {
        var versionString = null;

        if( navigator.plugins && navigator.mimeTypes.length )
        {
	    var x = navigator.plugins["Shockwave Flash"];
	    
	    if( x && x.description ) 
	    {
		versionString = x.description;
	    }
        } 
        else 
	{
	    if ( window.ActiveXObject )
	    {
		try 
		{
		    var x = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
		    versionString = x.GetVariable("$version");
		} 
		catch( e )
		{
		    //ignore
		}
	    }
	}
	
	if ( versionString ) 
        {
	    this.hasPlugin = true;

	    var versionNumbers = versionString.match( /[0-9]+/g );   // detect numbers in the string

	    if( ( versionNumbers != null ) && ( versionNumbers.length > 1 ) )
	    {
		// we'll use the first number, as Flash version numbers are things like '10.r4' meaning version 10, release 4

		this.version = versionNumbers[ 0 ];

		//alert( "flash version '" + this.version + "'" );

	    }
        }
        else 
        {
	    this.version = 0;
	    this.hasPlugin = false;
        }
    }
}

FlashDetect.init();


// =================================================================================
//
// browser detection (from: http://www.quirksmode.org/js/detect.html)
//
// =================================================================================

var BrowserDetect = {
	init: function () 
	{
	    this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
	    this.version = this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || "an unknown version";
	    this.OS = this.searchString(this.dataOS) || "an unknown OS";
        },
	searchString: function (data) 
	{
	    for (var i=0;i<data.length;i++)	
	    {
		var dataString = data[i].string;
		var dataProp = data[i].prop;
		this.versionSearchString = data[i].versionSearch || data[i].identity;
		if (dataString) 
		{
		    if (dataString.indexOf( data[i].subString) != -1 )
			return data[i].identity;
		}
		else if (dataProp)
		{
		    return data[i].identity;
		}
	    }
	},
	
	searchVersion: function (dataString) 
	{
	    var index = dataString.indexOf( this.versionSearchString );
	    if (index == -1) 
	    {
		return;
	    }
	    return parseFloat( dataString.substring( index + this.versionSearchString.length + 1 ) );
	},
	dataBrowser: [
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
 		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};

BrowserDetect.init();

// =================================================================================
//
// general purpose utilites
//
// =================================================================================


function Utils_setBusy( isBusy )
{
  document.body.style.cursor = isBusy ? "wait" : "default";
}

function Utils_escapeQuotes( stringValue )
{
    return stringValue.replace( /\"/g, "\\\"" ).replace( /\'/g, "\\'" );
}


// =================================================================================
//
// height tzar  - make sure the components fit sensible into the height of the browser
//
// =================================================================================

function Utils_getViewportSize() 
{
    var myWidth = 0, myHeight = 0;
    if( typeof( window.innerWidth ) == 'number' ) 
    {
	//Non-IE
	myWidth = window.innerWidth;
	myHeight = window.innerHeight;
    } 
    else 
    {
	if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) 
	{
	    //IE 6+ in 'standards compliant mode'
	    myWidth = document.documentElement.clientWidth;
	    myHeight = document.documentElement.clientHeight;
	} else
	{
	    if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) 
	    {
		//IE 4 compatible
		myWidth = document.body.clientWidth;
		myHeight = document.body.clientHeight;
	    }
	}
    }
    return { height:myHeight, width:myWidth };
}

function Utils_makeNoteOfViewportSize() 
{
    var viewportSize = Utils_getViewportSize();
    
    document.getElementById( 'browserViewportHeight' ).value = viewportSize.height;
    document.getElementById( 'browserViewportWidth' ).value = viewportSize.width;
    
    //alert("bark!" + document.getElementById( 'browserViewportWidth' ).value + "x" + document.getElementById( 'browserViewportHeight' ).value );
}

// =================================================================================
//
// switching between the different result display modes
//
// =================================================================================

function DataEntry_initialisePage()
{
    var message = "";

    if( BrowserDetect.browser == 'Explorer' && BrowserDetect.version < 7 )
    {
	message +=( "<DIV>This site requires functionality which your browser does not have - some features will be disabled.<DIV>" );
    }
    
    if( FlashDetect.hasPlugin == false )
    {
	message +=( "<DIV>Some features of this service require the Adobe Flash&reg; plugin - you can download it from <A HREF=\"http://www.adobe.com/products/flashplayer/\" TARGET=\"_blank\">here</A>.</DIV>" );
    }
    else
    {
	if( FlashDetect.version < 9 )
	{
	    message +=( "<DIV>Some features of this service require Adobe Flash&reg; plugin, version 9 or better, but you have version " + FlashDetect.version + ".<BR/>You can download the latest version from <A HREF=\"http://www.adobe.com/products/flashplayer/\" TARGET=\"_blank\">here</A>.</DIV>" );
	}
	
    }

    if( message.length > 0 )
    {
	var wrappedMessage = "<DIV STYLE=\"border: 1px solid #BF9660; padding: 8px; margin: 24px;\">" + message + "</DIV>";
	
	document.getElementById( "warningBox" ).innerHTML = wrappedMessage;
    }



    DataEntry_populateOntologySelector();
    DataEntry_populateSampleTextSelector( "samplePlosJuly2008", plosComputationalBiologyJuly2008Abstracts );

    document.getElementById( "useAllOntologiesRadioButton" ).checked = true;
    document.getElementById( "sourceText" ).select();
    document.getElementById( "sourceText" ).focus();
}

function DataEntry_hideOrShowOntologySelector()
{
    var hideIt = document.getElementById( "useAllOntologiesRadioButton" ).checked;
    
    document.getElementById( "ontologySelectorContainer" ).style.display = hideIt ? "none" : "block";
}

function DataEntry_selectAllOntologies( category, isSelected )
{
    for( var a = 0; a < organisedOntologyData.length; a++ )
    { 	 
	if( category == organisedOntologyData[ a ][ 0 ][ 1 ] )
	{
	    for( var i = 0; i < organisedOntologyData[ a ].length; i++ )
	    {
		document.getElementById( "selector." + a + "." + i ).checked = isSelected;
	    }
	}
    }
}

function DataEntry_populateOntologySelector()
{
    // we want two columns, with a controlled split point
    
    var splitPoint = 2;
    
    var buffer = "<TABLE><TR><TD VALIGN=\"TOP\">";
    
    for( var a = 0; a < organisedOntologyData.length; a++ )
    { 	 
	if( a == splitPoint )
	{
	    buffer += "</TD><TD STYLE=\"padding-left:30px;\" VALIGN=\"TOP\">";
	}
	
	var category = organisedOntologyData[ a ][ 0 ][ 1 ];
	
	buffer += "<B>" + category + "</B>";
	
	buffer += "&nbsp;&nbsp;<INPUT TYPE=\"BUTTON\" CLASS=\"tinyButton\" ONCLICK=\"DataEntry_selectAllOntologies('" + category + "',true)\" VALUE=\"all\"/>";
	buffer += "&nbsp;<INPUT TYPE=\"BUTTON\" CLASS=\"tinyButton\" ONCLICK=\"DataEntry_selectAllOntologies('" + category + "',false)\" VALUE=\"none\"/>";
	buffer += "<BR/>";
	
	for( var i = 0; i < organisedOntologyData[ a ].length; i++ )
	{
	    var shortName = organisedOntologyData[ a ][ i ][ 2 ];
	    var longName  = organisedOntologyData[ a ][ i ][ 0 ];
	    
	    //alert( shortName + ":" + longName );
	    
	    buffer += "<SPAN CLASS=\"ontologySwitch\"><INPUT ID=\"selector." + a + "." + i + "\" NAME=\"ontology:" + shortName + "\" TYPE=\"CHECKBOX\" >" + longName;
	    buffer += "&nbsp;<SPAN CLASS=\"text2\">(" + shortName + ")</SPAN>";
	    buffer += "</SPAN></BR>";
	} 
    }
    
    buffer += "</TD></TR></TABLE>";
    
    document.getElementById( "ontologySelectorContainer" ).style.display = "none";
    document.getElementById( "ontologySelectorContainer" ).innerHTML = buffer;
}


function DataEntry_insertSample()
{
    var selectedAbstractID = document.getElementById( "samplePlosJuly2008" ).value; 
    
    if( selectedAbstractID.indexOf( "plos:" ) == 0 )
    {
	var index = selectedAbstractID.substring( 5 ) * 1;  // ensure we have a number
	
	if( index < 0 )
	{
	    document.getElementById( "sourceText" ).value = "";
	}
	else
	{
	    document.getElementById( "sourceText" ).value = 
		plosComputationalBiologyJuly2008Abstracts[ index ][ 0 ] + 
		".\n\n" + 
		plosComputationalBiologyJuly2008Abstracts[ index ][ 1 ];
	}
    }
}

function DataEntry_populateSampleTextSelector( selectorID, sampleTextArray )
{
    var selector = document.getElementById( selectorID ); 
    
    selector.options[ 0 ] =  new Option( "", "plos:-1" );  // used to indicate "the blank selection"
    
    for( var a = 0; a < sampleTextArray.length; a++ )
    {
	var possiblyLongText = sampleTextArray[ a ][ 0 ];

	if( possiblyLongText.length > 70 )
	{
	}
	
	selector.options[ a + 1 ] =  new Option( sampleTextArray[ a ][ 0 ], "plos:" + a );
    }
}


// =================================================================================
//
// switching between the different result display modes
//
// =================================================================================


function PageControl_setDisplayFormat( formatName )
{
    Refiner_hideMenu();  // hide the refiner

    tt_HideInit();

    if( formatName == 'rdf' )
    {
	RdfView_updateView();
    }

    document.getElementById( 'htmlInlineResultContainer' ).style.display = ( formatName == 'inlineHtml' ) ? 'block' : 'none';
    document.getElementById( 'htmlListResultContainer' ).style.display = ( formatName == 'listHtml' ) ? 'block' : 'none';
    document.getElementById( 'rdfResultContainer' ).style.display = ( formatName == 'rdf' ) ? 'block' : 'none';
    document.getElementById( 'xmlResultContainer' ).style.display = ( formatName == 'xml' ) ? 'block' : 'none';

    if( formatName == 'inlineHtml' )
    {
	// remove any cached tooltip texts for the same reason
	InlineView_resetTooltips();

	// update the text in the floaters in case the user has changed stuff using the list view
	InlineView_initialiseFloaters();

	// we need to do the layout *after* the element has been made visible
	InlineView_layoutFloaters();
    }

    PageControl_synchronizeContextSensitiveHelp( 'results' );
}


function PageControl_displayInfoPopup( targetUrl )
{
    // document.getElementById( "infoPopupContainer" ).innerHTML = "<I>loading...</I>";

    document.getElementById( "infoPopupContainer" ).src = targetUrl;

    var popupWidth = 650;  
   
    var helpPanelTargetHeight = 450;  // 
    
    var availableSize = Utils_getViewportSize();

    var helpPanelTargetHeight = 450;  // 
    var helpPanelFeasibleHeight = availableSize.height * 0.75;

    var helpPanelHeight = ( helpPanelTargetHeight < helpPanelFeasibleHeight ) ? helpPanelTargetHeight : helpPanelFeasibleHeight;

    var topPosn = ( availableSize.height - helpPanelHeight ) / 2;
    
    //  ensure that the top of the popup is onscreen so that the
    //  [close] button is visible
    
    if( topPosn < 0 ) { topPosn = 0; }
    
    //  ensure that the right hand side of the popup is onscreen so
    //  that the [close] button is visible
    
    var leftPosn = ( availableSize.width - popupWidth ) / 2;
    
    if( ( leftPosn + popupWidth ) > availableSize.width ) { leftPosn = ( availableSize.width - ( popupWidth + 10 ) ); }
    
    var popup = document.getElementById( "infoPopupWrapper" );
    
    popup.style.minHeight = popup.style.maxHeight = popup.style.height = ( helpPanelHeight + 'px' );

    popup.style.zIndex =  '1195';
    popup.style.top =  topPosn + 'px';
    popup.style.left = leftPosn + 'px';
    popup.style.position = 'absolute';
    popup.style.display = 'inline';

    //alert( "shown at " + topPosn  +"px," + leftPosn + "px" );
}

function PageControl_hideInfoPopup()
{
    //document.getElementById( 'infoPopupWrapper' ).style.left = '-10000px';
    document.getElementById( 'infoPopupWrapper' ).style.display = 'none';
}

function PageControl_displayContextSensitiveHelp( contextName )
{
    if( contextName == 'results' )
    {
	if( document.getElementById( 'htmlInlineResultContainer' ).style.display == 'block' )
	{
	    PageControl_displayInfoPopup( '/help/InlineView.html' );
	}
	else
	{
	    if( document.getElementById( 'htmlListResultContainer' ).style.display == 'block' )
	    {
		PageControl_displayInfoPopup( '/help/ListView.html' );
	    }
	    else
	    { 
		if( document.getElementById( 'rdfResultContainer' ).style.display == 'block' )
		{
		    PageControl_displayInfoPopup( '/help/RdfView.html' );
		}
		else
		{
		    PageControl_displayInfoPopup( '/help/ServerResponseView.html' );
		}
	    }
	}
    }
    else
    {
	PageControl_displayInfoPopup( '/help/Contents.html' );
    }
}

function PageControl_synchronizeContextSensitiveHelp( contextName )
{
    if( document.getElementById( 'infoPopupWrapper' ).style.display !=  'none')  // is it onscreen?
    {
	PageControl_displayContextSensitiveHelp( contextName );
    }
}

// =================================================================================
//
// pop-ups and controls for refining a term
//
// =================================================================================


var Refiner_globalState = new Array(); // general purpose dumping ground for maintaining state during refinement


function Refiner_setBrowsingMode( browseMode )
{
    var previousBrowseMode = Refiner_globalState.currentBrowseMode;

    Refiner_globalState.currentBrowseMode = browseMode;


    if( browseMode == 'graphical' )
    {
	if( ( FlashDetect.hasPlugin == false ) || ( FlashDetect.version < 9 ) )
	{
	    alert( "The graphical ontology browser requires Adobe Flash version 9 or better." );

	    return;
	}
    }

    // alert( 'Refiner_setBrowsingMode: ' + browseMode + ' ... NOTE: sync is disabled' );
       
    if( browseMode == 'graphical' )
    {
	// switch from tabular to graphical

	document.getElementById( 'tabularTermBrowser' ).style.display = 'none';
	GraphViewer_injectSwfObject();
    }
    else
    {
	if( previousBrowseMode == 'graphical' )
	{
	    try
	    {
		Refiner_updateTabularView( escape( GraphViewer_gvapplet.getCenterTitle() ) );
	    }
	    catch( exception )
	    {
		alert( exception );
	    }
	}

	GraphViewer_killSwfObject();

	document.getElementById( 'tabularTermBrowser' ).style.display = 'inline';
 

   }
    
}


/*
  'matchIndex' and 'termNumber' are indices into the 'termsPerMatch' table 
 */
function Refiner_displayMenu( matchIndex, termNumber )
{
    if( BrowserDetect.browser == 'Explorer' && BrowserDetect.version < 7 )
    {
	alert( "The ontology browser requires Internet Explorer version 7 or greater (or any other modern web-browser)" );
	return;
    }

    // save the initial state

    var itemID = termsPerMatch[ matchIndex ][ 1 ][ termNumber ];

    Refiner_globalState.matchIndex  = matchIndex;
    Refiner_globalState.termNumber = termNumber;

    Refiner_globalState.termIndex = TermList_getTermIndexFromTermID( itemID );
    
    Refiner_globalState.matchNumber = TermList_getMatchNumberFromMatchIndex( Refiner_globalState.termIndex, matchIndex );

    Refiner_globalState.currentBrowseMode = null;
    Refiner_globalState.history = Array();

    Refiner_globalState.initialItemID = itemID;                      // the starting point

    Refiner_globalState.itemID = Refiner_globalState.initialItemID;  // the current termID

    // hide any WZ-ToolTips

    tt_HideInit();

    document.getElementById( 'setBrowsingToTabular' ).checked = true;
    
    Refiner_setBrowsingMode('tabular');
   
    var popup = document.getElementById( 'refineView' );

    //position the refiner centrally

    var viewportSize = Utils_getViewportSize() ;


    // the size of the refinement popup is fixed in the Floaters.jsp file

    // NEW: the size is now set based on the size of vht eviewport
    // (which is passed to Terminizer.jsp by the static pages)

    // (can we discover it programatically from here or do just live with it being hardcoded?)
    
    var popupHeight = document.getElementById( 'viewportHeight' ).value;  

    //popupHeight += 70;


    var popupWidth  = 555;  

    
    var topPosn  = ( viewportSize.height - popupHeight ) / 2;
    var leftPosn = ( viewportSize.width - popupWidth ) / 2;

    //alert("popup:" + popupWidth + "x" + popupHeight + ", viewport:" +  viewportSize.width + "x" + viewportSize.height + "@" + leftPosn + "," + topPosn );
    
    popup.style.zIndex =  '316';
    //popup.style.width =  popupWidth + 'px';
    //popup.style.height = popupHeight + 'px';
    popup.style.top =  topPosn + 'px';
    popup.style.left = leftPosn + 'px';
    popup.style.position = 'absolute';
    popup.style.display = 'inline';
    
    // and start things off

    Refiner_populateMenuStepOne();
}

function Refiner_hideMenu()
{
    var popup = document.getElementById( 'refineView' );

    //popup.style.left = '-10000px';
    popup.style.display = 'none';

    Utils_setBusy( false );
}

/*
   given the short name (as returned from server via the itemID) and
   find the equivalent long name by looking in the table
   'organisedOntologyData' which lives in "ontology-info.js"
*/
function Refiner_getOntologyLongNameFromShortName( ontologyShortName )
{
    for( var i = 0; i < organisedOntologyData.length; i++ )
    {
	for( var j = 0; j < organisedOntologyData[ i ].length; j++ )
	{
	    if( organisedOntologyData[ i ][ j ][ 2 ] == ontologyShortName )
	    {
		return organisedOntologyData[ i ][ j ][ 0 ];
	    }
	}
    }
    
    return 'Unknown ontology (' + ontologyShortName + ')';
}


function Refiner_getOntologyShortNameFromItemID( client, itemID )
{
    var itemTypeName = client.parseItemID( itemID )[ 'type' ];

    // itemTypes are always something of the for 'xxx Term';

    return itemTypeName.substring( 0, itemTypeName.indexOf( ' ' ) );
}


function Refiner_populateMenuStepOne()
{
    Utils_setBusy( true );

    if( Refiner_globalState.client == null )
    {
	Refiner_globalState.client = new OmixedClient();
    }

    Refiner_globalState.client.setAsynchronousOperation();

    //alert( 's1: ok' );

    if( Refiner_globalState.client.isConnected() == false )
    {
	Refiner_globalState.client.connect( omixedResourceName, omixedUserName, omixedPassword, Refiner_populateMenuStepTwo );
    }
}

function Refiner_populateMenuStepTwo( errorMessage )
{
    if( errorMessage == null )
    {

	//alert( 's2: ok' );

	// things pointing to the main item are the narrower terms

	// Refiner_globalState.client.getInboundLinks( Refiner_globalState.itemID, Refiner_populateMenuStepThree );

	Refiner_globalState.client.itemFind( 'ITEM_ID', '<Link><ItemID>' + ( Refiner_globalState.itemID ) + '</ItemID></Link>', '', Refiner_populateMenuStepThree );

	Refiner_globalState.targetItemType = Refiner_globalState.client.parseItemID( Refiner_globalState.itemID )[ "type" ];
    }
    else
    {
	Utils_setBusy( false );

	alert( 's2:' + errorMessage[ 'ErrorMessage' ] );
    }
}

function Refiner_populateMenuStepThree( errorMessage, resultNode )
{
    if( errorMessage == null )
    {
	// actually, we dont just want all the inbound links (because
	// that includes all of the synonyms) - we just want the
	// inbound links of a certain itemType
	
	// what itemType are we refining?

	
	var inboundItemIDs = new Array();
	
	if( resultNode != null )
	{
	    //alert( "s3:" + resultNode );

	    // we should an <ItemIDList> with a big lump of text as its contents

	    var innerNode = resultNode.getFirstChild();

	    if( innerNode != null )
	    {
		var resultItemIdBlock = innerNode.getNodeValue().toString();
		
		if( resultItemIdBlock != null )
		{
		    var resultArray = resultItemIdBlock.split( '\n' );
		    
		    if( resultArray != null )
		    {
			//alert( "s3:" + resultArray );
			
			for( var i = 0; i < resultArray.length; i++ )
			{
			    if( Refiner_globalState.client.parseItemID( resultArray[ i ] )[ "type" ] == Refiner_globalState.targetItemType )
			    {
				inboundItemIDs.push( resultArray[ i ] );
			    }
			}
		    }
		}
	    }
	}

	
	Refiner_globalState.inboundItemIDs = inboundItemIDs;
	
	//alert( "s3: ok : " + resultArray );
		
	// now for the outbound links, get the itemXML:

	Refiner_globalState.client.itemFindReturnAsObjects( '<ItemID>' + ( Refiner_globalState.itemID ) + '</ItemID>', '', Refiner_populateMenuStepFour );
    }
    else
    {
	Utils_setBusy( false );
	
	alert( "s3:" +  errorMessage[ 'ErrorMessage' ] );
    }
}

function Refiner_populateMenuStepFour( errorMessage, itemDetailsArray )
{
    if( errorMessage == null )
    {
	// store the itemDetails object for the currently selected
	// item so we have it available if the refinement is accepted

	Refiner_globalState.currentItemDetails = itemDetailsArray[ 0 ];

	if( Refiner_globalState.currentItemDetails == null )
	{
	    Utils_setBusy( false );
	    
	    alert( "No information available for '" + Refiner_globalState.itemID + "'" );
	    
	    return;
	}

	//alert( "s4:" + Refiner_globalState.currentItemDetails );

	// set the title of the refiner popup to the name of the ontology being browser
	
	var itemTypeName = Refiner_globalState.currentItemDetails.itemType; 

	var ontologyShortName = Refiner_getOntologyShortNameFromItemID( Refiner_globalState.client, Refiner_globalState.itemID ); ;

	var ontologyLongName = Refiner_getOntologyLongNameFromShortName( ontologyShortName );
	
	document.getElementById( "refineTitlePane" ).innerHTML = ontologyLongName;


	// and the definition of the current term
	
	var attrPairArray = Refiner_globalState.currentItemDetails.attrs;

	var definition = "<I>no definition available</I>";

	for( var a = 0; a < attrPairArray.length; a++ )
	{
	    if( attrPairArray[ a ][ "name" ] == "definition" )
	    {
		definition = attrPairArray[ a ][ "value" ];
	    }
	}

	document.getElementById( "refineCurrentSelectionDefinition" ).innerHTML = definition;
	

	// the current selection
	
	document.getElementById( "refineViewTopPane" ).innerHTML =  "<DIV><B>" + Refiner_globalState.client.parseItemID( Refiner_globalState.itemID )[ "name" ] + "</B></DIV>";


	// broader terms
	
	var count = 0;

	if( itemDetailsArray != null )
	{
	    var broaderTermIDs = "";
	    
	    if( itemDetailsArray[ 0 ].links[ "is_a" ] != null )
	    {
		for( var b = 0; b < itemDetailsArray[ 0 ].links[ "is_a" ].length; b++ )
		{
		    broaderTermIDs += "<DIV>" + Refiner_makeLink( itemDetailsArray[ 0 ].links[ "is_a" ][ b ] ) + "</DIV>";
		    count++;
		}
	    }
	    
	    if( itemDetailsArray[ 0 ].links[ "part_of" ] != null )
	    {
		for( var b = 0; b < itemDetailsArray[ 0 ].links[ "part_of" ].length; b++ )
		{
		    broaderTermIDs += "<DIV>" + Refiner_makeLink( itemDetailsArray[ 0 ].links[ "part_of" ][ b ] ) + "</DIV>";
		    count++;
		}
	    }
	    
	    if( itemDetailsArray[ 0 ].links[ "located_in" ] != null )
	    {
		for( var b = 0; b < itemDetailsArray[ 0 ].links[ "located_in" ].length; b++ )
		{
		    broaderTermIDs += "<DIV>" + Refiner_makeLink( itemDetailsArray[ 0 ].links[ "located_in" ][ b ] ) + "</DIV>";
		    count++;
		}
	    }

	    if( itemDetailsArray[ 0 ].links[ "related_to" ] != null )
	    {
		for( var b = 0; b < itemDetailsArray[ 0 ].links[ "related_to" ].length; b++ )
		{
		    broaderTermIDs += "<DIV>" + Refiner_makeLink( itemDetailsArray[ 0 ].links[ "related_to" ][ b ] ) + "</DIV>";
		    count++;
		}
	    }

	    //alert( "broader: " + broaderTermIDs );

	    document.getElementById( "refineViewMiddlePane" ).innerHTML = broaderTermIDs;
	}

	if( count == 0)
	{
	    document.getElementById( "refineViewMiddlePane" ).innerHTML = "<DIV>no terms</DIV>";
	}
	
	// narrower terms
	
	if( ( Refiner_globalState.inboundItemIDs == null ) || ( Refiner_globalState.inboundItemIDs.length == 0 ) )
	{
	    document.getElementById( "refineViewBottomPane" ).innerHTML = "<DIV>no terms</DIV>";
	}
	else
	{
	    var childIDs = "";
	    
	    for( var c = 0; c < Refiner_globalState.inboundItemIDs.length; c++ )
	    {
		childIDs += "<DIV>" + Refiner_makeLink( Refiner_globalState.inboundItemIDs[ c ] ) + "</DIV>";
	    }

	    document.getElementById( "refineViewBottomPane" ).innerHTML = childIDs;
	}
	
	// history of recent terms

	var historicalIDs = "";

	for( var h = Refiner_globalState.history.length; h > 0; h-- )
	{
	    historicalIDs += "<DIV>" + Refiner_makeLink( Refiner_globalState.history[ h - 1 ] ) + "</DIV>";
	}

	document.getElementById( "refineViewHistoryPane" ).innerHTML = historicalIDs;


    }
    else  // an error occured in the call to getItemsAsObjects()
    {
 	Utils_setBusy( false );
	
	alert( 's4:' + errorMessage[ 'ErrorMessage' ] );
	
	Refiner_globalState.currentItemDetails = null;
    }

    Utils_setBusy( false );
    
    Refiner_globalState.client.disconnect();  
}


/*
   take an itemID and start the process up displaying the inbound and outbound links in the tabular view
 */
function Refiner_updateTabularView( escapedItemID )
{
    // save the current selection in the history list (assuming it's different to the last item in the list)
    //
    if( Refiner_globalState.history[ Refiner_globalState.history.length -1 ] != Refiner_globalState.itemID )
    {
	Refiner_globalState.history.push( Refiner_globalState.itemID );
    }
    
    // set the new item as the current focus
    Refiner_globalState.itemID = unescape( escapedItemID );

    // and update the various lists
    Refiner_populateMenuStepOne();
}


function Refiner_makeLink( itemID )
{
    return "<A HREF=\"#\" ONCLICK=\"Refiner_updateTabularView('" + escape( itemID ) + "')\">" + Refiner_globalState.client.parseItemID( itemID )[ "name" ] + "</A>";
}   


function Refiner_getAttrValue( attrArray, attrName )
{
    if( attrArray != null )
	for( var a = 0; a < attrArray.length; a++ )
	    if( attrArray[ a ][ "name" ] == attrName )
		return attrArray[ a ][ "value" ];
    return null;
}

/*
  accept the currently selected item as the new term for the match that was used to start off the refinement process
 */
function Refiner_acceptRefinement()
{
    Utils_setBusy( false );

    Refiner_hideMenu();
    
    // if we finished the refinement whilst in graphical mode, we need
    // to update the 'Refiner_globalState' object with the item that
    // was selected by the graphical browser (whereas, if we are using
    // the tabular browser, we already have an item details object
    // available)

    if( Refiner_globalState.currentBrowseMode == 'graphical' )
    {
	//
	// what we want is:
	//   Refiner_globalState.itemID  (easy, the graphviz applet knows this)
	// and 
	//  Refiner_globalState.currentItemDetails  (harder, we'll need to ask the omixed server)
	//

	Refiner_globalState.itemID = GraphViewer_gvapplet.getCenterTitle();
	
	// so we'll call getItemsAsObjects() in synchronous mode

	Refiner_globalState.client.setSynchronousOperation();
	
	//alert( 'acceptRefinement: connecting...' );

	Refiner_globalState.client.connect( omixedResourceName, omixedUserName, omixedPassword, null, null );

	//alert( 'acceptRefinement: connected' );

	var queryXml = '<ItemID>' + ( Refiner_globalState.itemID ) + '</ItemID>';
	var queryOptions = '';

	//alert( 'acceptRefinement: getting item details...' );

	Refiner_globalState.client.itemFindReturnAsObjects( queryXml, 
							    queryOptions,
							    
							    // because we are operating in synchronous mode, we can use an anonymous function as the callback
							    
							    function( errorMessage, itemDetailsArray )
							    {
								if( errorMessage == null )
								{
								    Refiner_globalState.currentItemDetails = itemDetailsArray[ 0 ];
								}
								else
								{
								    alert( errorMessage[ 'ErrorMessage' ] );
								}
							    } );
	
	Refiner_globalState.client.disconnect();

	Refiner_globalState.client.setAsynchronousOperation();
    }

    
    // we now have all of the data that we will need:

    var newItemID = Refiner_globalState.itemID;

    var itemName = Refiner_globalState.client.parseItemID( newItemID )[ "name" ];

    var ontologyShortName = Refiner_getOntologyShortNameFromItemID( Refiner_globalState.client, newItemID );

    var ontologyLongName = Refiner_getOntologyLongNameFromShortName( ontologyShortName );

    var itemAccession = Refiner_getAttrValue( Refiner_globalState.currentItemDetails.attrs, "ID" );

    var itemDefinition = Refiner_getAttrValue( Refiner_globalState.currentItemDetails.attrs, "definition" );

    if( itemDefinition == null )
    {
	itemDefinition = "no definition available";
    }


    // update the client-side data structures  (see docs/client-side-data-structures.txt for more info)

    // 1. term info

    termInfo[ newItemID ] = [ itemName, itemAccession, ontologyShortName, ontologyLongName, itemDefinition ];
    
    // 2. inline match info popup  (this will force the tooltip to be rebuilt next time it's needed)
    
    tooltipString[ Refiner_globalState.matchIndex ] = null;

    // 3. termsPerMatch : easy, just replace the itemID with the new one

    termsPerMatch[ Refiner_globalState.matchIndex ][ 1 ][ Refiner_globalState.termNumber ] = newItemID;

    // 4. matchesPerTerm

    // firstly, obliterate the details of the old term match by
    // removing the corresponding element from the index and state
    // arrays
    //
    // if the old term match was the only match for that term, then we
    // can remove the row altogether, otherwise, we just remove the
    // details of this particular match
    
    if( matchesPerTerm[ Refiner_globalState.termIndex ][ 2 ].length == 1 )
    {
	matchesPerTerm.splice( Refiner_globalState.termIndex, 1 );

	// we know that we now have one less term matching in this ontology
	
	var oldOntologyShortName = Refiner_getOntologyShortNameFromItemID( Refiner_globalState.client, Refiner_globalState.initialItemID );

	matchCountsPerOntology[ oldOntologyShortName ]--;
    }
    else
    {
	matchesPerTerm[ Refiner_globalState.termIndex ][ 1 ] = matchesPerTerm[ Refiner_globalState.termIndex ][ 1 ] - 1;
	matchesPerTerm[ Refiner_globalState.termIndex ][ 2 ].splice( Refiner_globalState.matchNumber, 1 );    // used like this, splice() removes an element
	matchesPerTerm[ Refiner_globalState.termIndex ][ 3 ].splice( Refiner_globalState.matchNumber, 1 );
    }

    // now create a match entry for the new term (which might either be an entirely new term, or it might already have a row in matchesPerTerm)

    var newTermIndex = TermList_getTermIndexFromTermID( newItemID );

    var stateForNewMatch = 1;    // set the initial state to 'accepted'

    if( newTermIndex == -1 ) 
    {
	// it's a new term so we must create a new row in matchesPerTerm
	//
	// it is important to put it in the right place - the
	// matchesPerTerm[] array is sorted on a per-ontology basis
	// (see MatchResultSorter.jsp) and we should maintain this
	// ordering by inserting the new entry into the right ontology
	
	var insertPoint = -1;

	var ontologyShortNameAtInsertPoint = null;
	
	while( ( ontologyShortNameAtInsertPoint != ontologyShortName ) && ( ++insertPoint < matchesPerTerm.length ) )
	{
	    var itemIdAtInsertPoint = matchesPerTerm[ insertPoint ][ 0 ];

	    ontologyShortNameAtInsertPoint = Refiner_getOntologyShortNameFromItemID( Refiner_globalState.client, itemIdAtInsertPoint );
	}

	// and insert the new row at the right place  (again using the splice() method)

	matchesPerTerm.splice( insertPoint, 0, [ newItemID, 1, [ Refiner_globalState.matchIndex ], [ stateForNewMatch ] ] ); 

	// and finally, update the match count for the new ontology

	matchCountsPerOntology[ ontologyShortName ]++;
    }
    else
    {
	// its a new match for an existing term, update the row for that term
	
	matchesPerTerm[ newTermIndex ][ 1 ] = matchesPerTerm[ newTermIndex ][ 1 ] + 1;
	matchesPerTerm[ newTermIndex ][ 2 ].push( Refiner_globalState.matchIndex );
	matchesPerTerm[ newTermIndex ][ 3 ].push( stateForNewMatch );
    }

    // et voila, all that remains is to redraw everything

    InlineView_setFloaterContents( Refiner_globalState.matchIndex );

    TermList_display();
}

/*
function Refiner_deleteArrayIndex( inputArray, indexToRemove )
{
    return Array.concat( inputArray.slice( 0, indexToRemove ), 
}
*/

// =================================================================================
//
// positioning and highlighting of detected terms
//
// =================================================================================


/*
    iterate over each of the matches and place the floating DIV near the first token of that match
 */
function InlineView_layoutFloaters()
{
    var contentElement = document.getElementById( "contentArea1" );
    
    var yOffset = 4;

    for( var matchIndex = 0; matchIndex < termsPerMatch.length; matchIndex++ )
    {
	var tokenIndex = termsPerMatch[ matchIndex ][ 0 ][ 0 ];
	
	var targetElementID = "t" + tokenIndex.toString();
	var floatingElementID = "m" + matchIndex.toString();

	var targetElement   = document.getElementById( targetElementID );
	var floatingElement = document.getElementById( floatingElementID );
	
	var leftPosn = contentElement.offsetLeft + targetElement.offsetLeft;
	var topPosn = contentElement.offsetTop + targetElement.offsetTop + yOffset;
	
	// avoid falling off the right-hand side of the window
	/*
	  if( window.innerWidth < ( leftPosn + item.offsetWidth ) )
	  {
	  leftPosn = window.innerWidth - item.offsetWidth;
	  }
	*/
	
	floatingElement.style.position = 'absolute';
    
	floatingElement.style.top  = ( topPosn + targetElement.offsetHeight ) + "px";
	floatingElement.style.left = leftPosn + "px";
	
	// osscillate up and down a bit to try to avoid nearby matches hiding one another

	yOffset = ( yOffset == 4 ) ? 8 : 4;
    }

    //alert( "layoutFloaters() done" );
}

/*
    set the correct state for the floating DIVs based on the current accept/reject settings
 */
function InlineView_initialiseFloaters()
{
    for( var matchIndex = 0; matchIndex < termsPerMatch.length; matchIndex++ )
    {
	InlineView_setFloaterContents( matchIndex );
    }
}

function InlineView_setFloaterContents( matchIndex )
{
    var floatingElementID = "m" + matchIndex.toString();
    var floatingElement = document.getElementById( floatingElementID );
    
    var termIDsForMatch = termsPerMatch[ matchIndex ][ 1 ];
    
    var selected = 0;
    var rejected = 0;
    var possible = 0;
    
    var descriptiveText = null;
    
    var availableTermIDs = new Array();
    
    for( var t = 0 ; t < termIDsForMatch.length; t++ )
    {
	var termID = termIDsForMatch[ t ];
	
	var state = TermList_getStateForInstance( matchIndex, termID );
	
	if( state == 1 ) // selected
	{
	    selected++;
	}
	else
	{
	    if( state == 2 ) // rejected
	    {
		rejected++;
	    }
	    else
	    {
		possible++;
	    }
	}
	
	if( state != 2 )
	{
	    availableTermIDs.push( termID );
	}
    }
    
    var floaterContents = "?";
    
    if( selected == 0 ) 
    {
	if( possible == 0 )
	{
	    // nothing selected or possible, all are rejected
	    
	    floaterContents = "<SPAN CLASS=\"rejectedTermMarkup\">" + InlineView_makeTermText( termIDsForMatch ) + "</SPAN>";
	}
	else
	{
	    // one or more possibles (and maybe some rejected)
	    
	    floaterContents = "<SPAN CLASS=\"possibleTermMarkup\">" + InlineView_makeTermText( availableTermIDs ) + "</SPAN>";		
	}
    }
    else
    {
	// one (or more?) selected
	//
	// (cBd is a "background colour slightly darker than page")

	floaterContents = "<SPAN CLASS=\"cBd selectedTermMarkup\">" + InlineView_makeTermText( availableTermIDs ) + "</SPAN>";		
    }    
    
    floatingElement.innerHTML = floaterContents;
}


/*
  scan an array of termIDs and generate the associated term names(s) 
 */
function InlineView_makeTermText( termIDs )
{
    if( termIDs.length == 1 )
    {
	// easy, its just a single term

	return termInfo[ termIDs[ 0 ] ][ 0 ];
    }
    else
    {
	return termIDs.length + " matches";

	//
	//var buffer = termIDs.length.toString();
	//
	//for( var t = 0; t < termIDs.length; t++ )
	//{
	//    buffer += ( t > 0 ) ? "," : " [";
	//    buffer += termInfo[ termIDs[ t ] ][ 0 ];
        //}
	//   
	//return buffer + "]";
    }
}


/*
    highlight ( or unhighlight ) the tokens which correspond to the specified match
 */
function InlineView_highlight( matchIndex, highlight )
{
    var tokenIndices = termsPerMatch[ matchIndex ][ 0 ];     // the first element of the termsPerMatch is the array of token indices for this match

    for( var t = 0 ; t < tokenIndices.length; t++ )
    { 
	var tokenElement = document.getElementById( "t" + tokenIndices[ t ] );
	
	tokenElement.className = highlight ? "highlight" : "normal";
    }
}


// =================================================================================
//
// generating the popups for selecting between possible matches, or
// for seeing the information about a term
//
// =================================================================================

/*
    dump all of the cached tooltip texts so they will be rebuilt next time they are asked for 
 */
function InlineView_resetTooltips()
{
    for( var matchIndex = 0; matchIndex < termsPerMatch.length; matchIndex++ )
    {
	tooltipString[ matchIndex ] = null;
    }
}

/*
    builds the popup which describes a match, which might consist of multiple terms, each in a different state
 */
function InlineView_makeToolTipText( matchIndex )
{
    if( tooltipString[ matchIndex ] == null )
    {
	var selected = 0;
	var rejected = 0;
	var possible = 0;
	
	var termIDsForMatch = termsPerMatch[ matchIndex ][ 1 ];

	for( var t = 0 ; t < termIDsForMatch.length; t++ )
	{
	    var termID = termIDsForMatch[ t ];

	    var state = TermList_getStateForInstance( matchIndex, termID );

	    if( state == 1 ) // selected
	    {
		selected++;
	    }
	    else
	    {
		if( state == 2 ) // rejected
		{
		    rejected++;
		}
		else
		{
		    possible++;
		}
	    }
	}

	var htmlBuffer = ""; // "S:" + selected + " R:" + rejected + " P:" + possible;
	
	htmlBuffer += 
	    "<TABLE  WIDTH=\"100%\" STYLE=\"border-spacing:0px;padding:0px;margin:0px;\" CELLSPACING=\"0\">" +
	    "<TR WIDTH=\"100%\" STYLE=\"background:#BF9660;\">" + 
	    "<TD ALIGN=\"LEFT\"><A HREF=\"javascript:PageControl_displayInfoPopup( '/help/InlineView.html' );\"><IMG BORDER=\"0\" TITLE=\"Help\" ALT=\"Help\" SRC=\"/images/helpButton.png\"/></A></TD>" +
	    "<TD WIDTH=\"99%\" CLASS=\"text3\" STYLE=\"max-height:30px;padding-left:8px;padding-right:8px;\" ALIGN=\"CENTER\"><DIV><B>Term Selector</B></DIV></TD>"  +
	    "<TD ALIGN=\"RIGHT\"><A HREF=\"javascript:tt_HideInit();\"><IMG BORDER=\"0\" TITLE=\"Close\" ALT=\"Close\" SRC=\"/images/closeButton.png\"/></A></TD>" + 
	    "</TR></TABLE>";
	
	if( possible > 1 )
	{
	    htmlBuffer += 
		"<DIV STYLE=\"width:100%;padding:0px;margin:0px;border-spacing:0px;padding-top:5px;padding-bottom:5px;\" CLASS=\"dottedUnderline\">" + 
		"More than one possible term found. Please pick one of the following:" +
		"</DIV>";
	}

	// wrap the body in a limited size DIV (note the max-width is set to slightly narrower than the width that will be set for the popup tooltip)

	htmlBuffer += "<DIV STYLE=\"max-height:500px;max-width:580px;width:580px;min-width:580px;overflow:auto;\">";  
	
	// add the term information and a 'select' button for each of the possible matches
	
	for( var termNumber = 0 ; termNumber < termIDsForMatch.length; termNumber++ )
	{
	    var tableClass = ( termNumber > 0 ) ? "popupInfoTable dottedOverline" : "popupInfoTable";
	    
	    var termID = termIDsForMatch[ termNumber ];
	    
	    var state = TermList_getStateForInstance( matchIndex, termID );
	   
	    var cssClass = ( state == 2 ) ? "popupInfoValue popupInfoValueForRejectedMatch" : "popupInfoValue";

	    // htmlBuffer += "[" + matchIndex + ":" + termNumber + "]";

	    htmlBuffer += 
		"<TABLE CLASS=\"" + tableClass + "\">" +
		"<TR><TD CLASS=\"popupInfoName\">Ontology</TD>" +
		"<TD CLASS=\"" + cssClass + "\">" + termInfo[ termID ][ 3 ] + "</TD></TR>" +   // ontologyLongName
		"<TR><TD CLASS=\"popupInfoName\">Term</TD>" +
		"<TD CLASS=\"" + cssClass + "\">" + termInfo[ termID ][ 0 ] + "</TD></TR>" +   // itemName
		"<TR><TD CLASS=\"popupInfoName\">Accession</TD>" +
		"<TD CLASS=\"" + cssClass + "\">" + termInfo[ termID ][ 1 ] + "</TD></TR>" +   // accessionNumber
		"<TR><TD CLASS=\"popupInfoName\">Definition</TD>" +
		"<TD CLASS=\"" + cssClass + "\">" + termInfo[ termID ][ 4 ] + "</TD></TR>" +   // definitionText
		"<TR><TD CLASS=\"popupInfoName\">Actions</TD><TD CLASS=\"popupInfoValue\">";

	    var rejectButton = "<INPUT CLASS=\"smallButton\" TYPE=\"BUTTON\" ONCLICK=\"InlineView_rejectAllTerms(" + matchIndex + "," + termNumber + ")\" VALUE=\"Reject\"/>";

	    var modifyButton = "<INPUT CLASS=\"smallButton\" TYPE=\"BUTTON\" ONCLICK=\"InlineView_modifyTerm(" + matchIndex + "," + termNumber + ")\" VALUE=\"Modify\"/>";

	    if( state == 0 ) // possible
	    {
		htmlBuffer += "<INPUT CLASS=\"smallButton\" TYPE=\"BUTTON\" ONCLICK=\"InlineView_selectTerm(" + matchIndex + "," + termNumber + ")\" VALUE=\"Accept\"/>";
		
		if( possible == 1 )
		{
		    // this is the only possible term for this match, offer up the 'reject' button
		    //
		    htmlBuffer += rejectButton;
		}

		htmlBuffer += modifyButton;
	    }
	    else
	    {
		if( state == 1 ) // selected
		{
		    htmlBuffer += rejectButton;
		    htmlBuffer += modifyButton;
		}
		else
		{
		    // rejected - offer an unreject button

		    htmlBuffer += "<INPUT CLASS=\"smallButton\" TYPE=\"BUTTON\" ONCLICK=\"InlineView_restoreTerm(" + matchIndex + "," + termNumber + ")\" VALUE=\"Restore\"/>";
		}
	    }
	    
	    htmlBuffer += "</TD></TR></TABLE>";
	}
	
	htmlBuffer += "</DIV>";    // end of the limited size DIV
	
	if( possible > 1 )
	{
	    htmlBuffer += 
		"<DIV STYLE=\"width:100%;padding:0px;margin:0px;border-spacing:0px;padding-top:5px;\" CLASS=\"dottedOverline\">" + 
		"...or if none of these are suitable, press <INPUT CLASS=\"smallButton\" TYPE=\"BUTTON\" ONCLICK=\"InlineView_rejectAllTerms('" + matchIndex + "')\" VALUE=\"Reject\"/> to reject all of them." + 
		"</DIV>";
	} 

	// and store the tooltip

	tooltipString[ matchIndex ] = htmlBuffer;

    }

    return tooltipString[ matchIndex ];
}

/*
   launch the refiner
 */
function InlineView_modifyTerm( matchIndex, termNumber )
{
    Refiner_displayMenu( matchIndex, termNumber );
}


/*
    handles the user picking one of the possible terms for the specified match

    and therefore implicitly rejecting all of the other terms for that match
 */
function InlineView_selectTerm( matchIndex, selectedMatchNumber )
{
    // this is basically exactly the same code as InlineView_rejectAllTerms()

    var termIDs = termsPerMatch[ matchIndex ][ 1 ];   // we want to reject the instance of nearly of all of these terms at the specified matchIndex
    
    for( var t = 0; t < termIDs.length; t++ )
    {
	var state = ( selectedMatchNumber == t ) ? 1 : 2;   // select the one identified by 'selectedMatchNumber', reject all others

	var termID = termIDs[ t ];

	var termIndex = TermList_getTermIndexFromTermID( termID );         // we need to know the termIndex to be able to look into matchesPerTerm[]

	var matchIndicesForThisTerm = matchesPerTerm[ termIndex ][ 2 ];    // the set of matchIndices for all occurences of this term

	for( var matchNumber = 0; matchNumber < matchIndicesForThisTerm.length; matchNumber++ )
	{
	    if( matchIndicesForThisTerm[ matchNumber ] == matchIndex )
	    {
		// this is the correct instance...
		
		TermList_setState( termIndex, matchNumber, state );  
	    }
	} 
    }

    // force the tooltip string for this match to be rebuilt (next time it's asked for)

    tooltipString[ matchIndex ] = null;

    // ::TODO::  hide and redisplay the tooltip...

    tt_HideInit();

    // change the contents of the floater for this match

    InlineView_setFloaterContents( matchIndex );

    TermList_updateOntologyStatus();
   
}

/*
    restore a term to 'possible' status after it has been rejected
 */
function InlineView_restoreTerm( matchIndex, selectedMatchNumber )
{
    // this is basically the same code as InlineView_rejectAllTerms()

    var termIDs = termsPerMatch[ matchIndex ][ 1 ];
    
    var termID = termIDs[ selectedMatchNumber ];
    
    var termIndex = TermList_getTermIndexFromTermID( termID );         // we need to know the termIndex to be able to look into matchesPerTerm[]
    
    var matchIndicesForThisTerm = matchesPerTerm[ termIndex ][ 2 ];    // the set of matchIndices for all occurences of this term
    
    for( var matchNumber = 0; matchNumber < matchIndicesForThisTerm.length; matchNumber++ )
    {
	if( matchIndicesForThisTerm[ matchNumber ] == matchIndex )
	{
	    TermList_setState( termIndex, matchNumber, 0 );  
	}
    } 

    // force the tooltip string for this match to be rebuilt (next time it's asked for)

    tooltipString[ matchIndex ] = null;

    // ::TODO::  hide and redisplay the tooltip...

    tt_HideInit();

    // update the other bits of interface

    InlineView_setFloaterContents( matchIndex );

    TermList_updateOntologyStatus();
}


/*
    handles the user rejecting all of the possible matched terms at for match number 'matchIndex'
 */
function InlineView_rejectAllTerms( matchIndex )
{
    var termIDs = termsPerMatch[ matchIndex ][ 1 ];   // we want to reject instances of these terms at the specified matchIndex

    for( var t = 0; t < termIDs.length; t++ )
    {
	var termID = termIDs[ t ];

	var termIndex = TermList_getTermIndexFromTermID( termID );         // we need to know the termIndex to be able to call TermList_setState( )

	var matchIndicesForThisTerm = matchesPerTerm[ termIndex ][ 2 ];    // the set of matchIndices for all occurences of this term

	for( var matchNumber = 0; matchNumber < matchIndicesForThisTerm.length; matchNumber++ )
	{
	    if( matchIndicesForThisTerm[ matchNumber ] == matchIndex )
	    {
		// this is the correct instance...
		
		TermList_setState( termIndex, matchNumber, 2 );   // 2 == rejected
	    }
	} 
    }

    // force the tooltip string for this match to be rebuilt next time it's asked for

    tooltipString[ matchIndex ] = null;

    // hide the tooltip

    tt_HideInit();

     // update the other bits of interface
   
    InlineView_setFloaterContents( matchIndex );

    TermList_updateOntologyStatus();
}


// =================================================================================
//
// the RDF marked-up view
//
// =================================================================================

function RdfView_updateView()
{
    document.getElementById( "rdfResultInjectionZone" ).innerHTML = RdfView_generateRdf( true );
}

function RdfView_generateRdf( isHtmlEncoded )
{
    // we want to be able to generate real RDF (for sending to
    // clients) and HTML-encoded RDF (for display in the browser)

    // we'll use the tokenPlusCruft array to build a XML'ised version of
    // original text and insert RDF information as we go along

    // we'll need to build a lookup table so we can discover whether
    // there are any matches at the current token index

    var hasMatch = new Object();
    
    for( var tokenIndex = 0 ; tokenIndex < tokenPlusCruft.length; tokenIndex++ )
    {
	hasMatch[ tokenIndex ] = -1;
    }

    for( var matchIndex = 0 ; matchIndex < termsPerMatch.length; matchIndex++ )
    {
	var firstTokenIndexForThisMatch = termsPerMatch[ matchIndex ][ 0 ][ 0 ];

	hasMatch[ firstTokenIndexForThisMatch ] = matchIndex;
    }
    
    var header = 
	"<?xml version=\"1.0\"?>\n" + 
	"<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:terminizer=\"http://terminizer.org/markup\">\n";

    var footer = 
	"</rdf:RDF>";

    var buffer = "";

    if( isHtmlEncoded )
    {
	buffer = "<DIV CLASS=\"rdfMarkupContainer\">" + RdfView_encodeXmlForHtml( header );
    }
    else
    {
	buffer = header;
    }

    for( var tokenIndex = 0 ; tokenIndex < tokenPlusCruft.length; tokenIndex++ )
    {
	buffer += RdfView_makeTokenMarkup( tokenPlusCruft[ tokenIndex ], isHtmlEncoded );

	if( hasMatch[ tokenIndex ] >= 0 )
	{
	    // we are having one or more matches here
	    
	    var matchIndex = hasMatch[ tokenIndex ];
	    
	    buffer += RdfView_makeRdfForMatch( matchIndex, isHtmlEncoded );
	}
    }

    if( isHtmlEncoded )
    {
	buffer += RdfView_encodeXmlForHtml( footer ) + "</DIV>";
    }
    else
    {
	buffer += footer;
    }
 
    return buffer;
}

function RdfView_encodeXmlForHtml( theString )
{
    return theString.replace( /\&/g, '&amp;' ).replace( /\</g, '&lt;' ).replace( /\n/g, '<BR/>' ).replace( /\ /g, '&nbsp;' );
}

function RdfView_makeTokenMarkup( tokenText, isHtmlEncoded )
{
    var markup = "<terminizer:Text>" + tokenText + "</terminizer:Text>\n";

    return isHtmlEncoded ? RdfView_encodeXmlForHtml( markup ) : markup;
}

function RdfView_makeRdfForMatch( matchIndex, isHtmlEncoded )
{
    var termIDs = termsPerMatch[ matchIndex ][ 1 ];

    var rdfMarkup = "";

    for( var t = 0; t < termIDs.length; t++ )
    {
	var termID = termIDs[ t ];

	var termInfoForThisTerm = termInfo[ termID ];

	var state = TermList_getStateForInstance( matchIndex, termID );

	var matchStatus = 'SuggestedMatch';
	var elementTintColour = '#283653';

	if( state == 1 )
	{
	    matchStatus = 'AcceptedMatch';
	    elementTintColour = '#288653';
	}
	else
	{
	    if( state == 2 ) 
	    {
		matchStatus = 'RejectedMatch';
		elementTintColour = '#783653';
	    }
	}
	
	var rdfForTerm = 
	    "<rdf:Description rdf:about=\"" + termInfoForThisTerm[ 1 ] + "\">\n" +
	    " <rdf:type rdf:resource=\"http://terminizer.org/" + matchStatus + "\"/>\n" +
	    " <terminizer:" + matchStatus + ">\n" +
	    "  <terminizer:OntologyEntry>\n" +
	    "   <terminizer:Source>" + termInfoForThisTerm[ 3 ] + "</terminizer:Source>\n" +
	    "   <terminizer:Name>" + termInfoForThisTerm[ 0 ] + "</terminizer:Name>\n" +
	    "   <terminizer:Accession>" + termInfoForThisTerm[ 1 ] + "</terminizer:Accession>\n" +
	    "  </terminizer:OntologyEntry>\n" +
	    " </terminizer:" + matchStatus + ">\n" +
	    "</rdf:Description>";


	if( isHtmlEncoded )
	{
	    rdfMarkup +="<DIV STYLE=\"color:" + elementTintColour + "\">";
	    rdfMarkup += RdfView_encodeXmlForHtml( rdfForTerm );
	    rdfMarkup +="</DIV>";
	}
	else
	{
	    rdfMarkup += rdfForTerm;
	}
    }

    return rdfMarkup;

}



// =================================================================================
//
// generation and interaction with the "list of terms" visualisation
//
//   we have the 'matchesPerTerm' data structure which is a table giving
//   each of the terms that were matched and the number of times they
//   matched etc.  (see docs/client-side-data-structures.txt for more info)
//
// =================================================================================


function TermList_display()
{
    var defaultNumberOfTokensToIncludeAtEitherEndOfContextStrings = 6;

    var buffer = "";

    if( matchesPerTerm.length == 0 )
    {
	buffer += "<DIV>No matches found.</DIV>";
    }

    
    var currentOntologyLongName = "";

    for( var i = 0 ; i < matchesPerTerm.length; i++ )
    {
	var termID = matchesPerTerm[ i ][ 0 ];

	var matchCountForThisTerm = matchesPerTerm[ i ][ 1 ];  // ::TODO:: can figure this out from matchesPerTerm[ i ][ 2 ] so it is redundant

	var termName = termInfo[ termID ][ 0 ];

	var ontologyLongName = termInfo[ termID ][ 3 ];

	var ontologyShortName = termInfo[ termID ][ 2 ];

	if( currentOntologyLongName != ontologyLongName )
	{
	    // close the previous ontology container div

	    if( currentOntologyLongName != "" )
	    {
		buffer += "</DIV></DIV>";
	    }

	    var matchCountForThisOntology = matchCountsPerOntology[ ontologyShortName ];
	    
	    var rejectText = 'reject it';

	    if( matchCountForThisOntology > 1 )
	    {
		rejectText = ( matchCountForThisOntology == 2 ) ? 'reject both terms' : 'reject all terms';
	    }

	    
	    var matchDescription = "";

	    if( matchCountForThisOntology == 1 )  // ::TODO:: make this work for a "smaller number of terms"
	    {
		var focusEvent = "ONMOUSEOVER=\"Tip( TermList_makeToolTipText(" + i + "),STICKY,false,WIDTH,-600,BGCOLOR,'#CCB28F',BORDERCOLOR,'#806F59')\"";
		
		var blurEvent = "ONMOUSEOUT=\"UnTip()\"";
		
		var cssStyle = "STYLE=\"font-size:75%;margin-right:6px;background:#CCB28F;margin-top:3px;margin-bottom:3px;\"";   // use the 'c1' background colour
		
		matchDescription = "found <SPAN CLASS=\"possibleTermMarkup\"" + focusEvent + " " + blurEvent + " " + cssStyle + ">" + termName + "</SPAN>";
	    }
	    else
	    {
		matchDescription = matchCountForThisOntology + " terms found";
	    }

	    
	    // if we are updating the display (after a term has been modified)
	    // then we want to maintain the open/closed state of the existing
	    // per-ontology DIVs

	    var previousDiv = document.getElementById( "termListOntologyContainer:" + ontologyShortName );
	    
	    var isVisible = ( ( previousDiv != null ) && ( previousDiv.style.display == 'block' ) );

	    var displayMode         = isVisible ? 'block' : 'none';
	    var initialTogglebutton = isVisible ? '/images/collapseButton.png' : '/images/expandButton.png';

	    // generate a container div for this ontology

	    buffer += "<DIV STYLE=\"margin-top:6px;border:1px solid #CCB28F;\">";
	    buffer += "<DIV CLASS=\"c4\" STYLE=\"white-space:nowrap;padding:3px;padding-left:6px;\" >";
	    buffer += "<IMG ONCLICK=\"TermList_toggleVisibiltyForOntology('" + ontologyShortName + "')\" ID=\"termListOntologyToggleButton:" + ontologyShortName + "\" SRC=\"" + initialTogglebutton + "\" STYLE=\"vertical-align:middle;margin-right:6px;\" />";
	    buffer += "<SPAN ONCLICK=\"TermList_toggleVisibiltyForOntology('" + ontologyShortName + "')\" STYLE=\"margin-right:12px;\"><B>" + ontologyLongName + "</B> (" + ontologyShortName + ")</SPAN>";
	    buffer += "<SPAN ONCLICK=\"TermList_toggleVisibiltyForOntology('" + ontologyShortName + "')\" STYLE=\"margin-right:8px;\">" + matchDescription + "</SPAN>";
	    buffer += "<SPAN ONCLICK=\"TermList_toggleVisibiltyForOntology('" + ontologyShortName + "')\" ID=\"termListOntologyStatus:" + ontologyShortName + "\" STYLE=\"margin-right:8px;\"></SPAN>";
	    buffer += "<INPUT ID=\"termListOntologyRejectButton:" + ontologyShortName + "\" TYPE=\"BUTTON\" CLASS=\"tinyButton\" VALUE=\"" + rejectText + "\" ONCLICK=\"TermList_rejectAllMatchesForOntology('" + ontologyShortName + "')\"/>";
	    buffer += "</DIV>";

	    buffer += "<DIV ID=\"termListOntologyContainer:" + ontologyShortName + "\" STYLE=\"display:" + displayMode + "\">";

	    currentOntologyLongName = ontologyLongName;
	}

	var focusEvent = "ONMOUSEOVER=\"Tip( TermList_makeToolTipText(" + i + "),STICKY,true,WIDTH,-600,BGCOLOR,'#CCB28F',BORDERCOLOR,'#806F59')\"";

	var  blurEvent = "ONMOUSEOUT=\"UnTip()\"";

	buffer += "<DIV STYLE=\"padding:4px; padding-top:12px;\">";
	buffer += "<DIV CLASS=\"text1\" STYLE=\"margin-bottom:4px;\"><SPAN CLASS=\"text4 possibleTermMarkup\" " + focusEvent + " " + blurEvent + " STYLE=\"font-size:100%;margin-right:6px;\"><B>";
	buffer += termName;
	buffer += "</B></SPAN> found ";
	
	if( matchCountForThisTerm == 1 ) 
	{
	    buffer += "once";
	}
	else
	{
	    if( matchCountForThisTerm == 2 ) 
	    {
		buffer += "twice";
	    }
	    else
	    {
		buffer += ( matchCountForThisTerm + " times" );
	    }
	}

	buffer += "</DIV>";
	
	buffer += "<DIV STYLE=\"padding-left:24px;padding-top:5px;padding-bottom:5px;\">";

	if( matchCountForThisTerm > 1 )
	{
	    var countingNoun = ( matchCountForThisTerm == 2 ) ? 'both' : 'all';

	    buffer += "<DIV>";
	    buffer += "<INPUT TYPE=\"BUTTON\" CLASS=\"tinyButton\" VALUE=\"accept " + countingNoun + "\" ONCLICK=\"TermList_adjustAllMatches(" + i + ",1)\">";
	    buffer += "<INPUT TYPE=\"BUTTON\" CLASS=\"tinyButton\" VALUE=\"reject " + countingNoun + "\" ONCLICK=\"TermList_adjustAllMatches(" + i + ",2)\">";
	    buffer += "<INPUT TYPE=\"BUTTON\" CLASS=\"tinyButton\" VALUE=\"clear " + countingNoun + "\" ONCLICK=\"TermList_adjustAllMatches(" + i + ",0)\">";
	    buffer += "</DIV>";
	}

	buffer += "</DIV>";

	var contextStringBuffer = "<DIV STYLE=\"font-size:75%;padding-left:24px;\">";
 
	var matchIndices = matchesPerTerm[ i ][ 2 ];

	for( var m = 0 ; m < matchIndices.length; m++ )
	{
	    //contextStringBuffer += "[" + i + ":" + m + "]";

	    contextStringBuffer += "<DIV ID=\"termListMatchContextContainer:" + i + "." + m + "\">";
	    contextStringBuffer += TermList_getContextString( i, m,  defaultNumberOfTokensToIncludeAtEitherEndOfContextStrings );
	    contextStringBuffer += "</DIV>";
	}

	contextStringBuffer += "</DIV>";

	buffer += contextStringBuffer;
	
	buffer += "</DIV>";
    }

    document.getElementById( "htmlListResultInjectionZone" ).innerHTML = buffer;
}

//
// matchIndices is the set of matchIndices for this term (i.e. the indices in the termsPerMatch array for this term)
//
// matchedTokenIndices is a 2-d array identifying the points where the term
// matched the source text (see Terminzer.jsp for details)
//
function TermList_getContextString( termIndex, matchNumber, extraTokensToIncludeAtEitherEnd )
{
    var buffer = "";

    var maxTokenIndexPermitted = tokenPlusCruft.length;

    var matchIndex = matchesPerTerm[ termIndex ][ 2 ][ matchNumber ]; ///[ matchIndices[ matchNumber ];
    
    var matchedTokenIndices = termsPerMatch[ matchIndex ][ 0 ];
    
    var actualTokenHits = new Array();
    
    // find the min/max token index that this match covers (for terms which matched a single token, min==max)
    
    var indexForFirstToken = matchedTokenIndices[ 0 ] ;
    
    var minTokenIndexForExtract = indexForFirstToken;
    var maxTokenIndexForExtract = indexForFirstToken;
    
    actualTokenHits[ indexForFirstToken ] = true;
    
    for( var tokenNumber = 1 ; tokenNumber < matchedTokenIndices.length; tokenNumber++ )
    {
	var tokenIndex = matchedTokenIndices[ tokenNumber ];
	
	if( tokenIndex < minTokenIndexForExtract )
	{
	    minTokenIndexForExtract = tokenIndex;
	}
	if( tokenIndex > maxTokenIndexForExtract )
	{
	    maxTokenIndexForExtract = tokenIndex;
	}
	
	actualTokenHits[ tokenIndex ] = true;
    }
    
    //buffer += "<DIV>" + minTokenIndexForExtract + " ... " + maxTokenIndexForExtract + "</DIV>";
    
    // now we know the span of the source text which this match encompasses, we can build the little bit of sample text
    
    minTokenIndexForExtract -= extraTokensToIncludeAtEitherEnd;
    
    if( minTokenIndexForExtract < 0 )
    {
	minTokenIndexForExtract = 0;
    }
    
    maxTokenIndexForExtract += extraTokensToIncludeAtEitherEnd;
    
    if( maxTokenIndexForExtract >= maxTokenIndexPermitted )
    {
	maxTokenIndexForExtract = maxTokenIndexPermitted - 1;
    }
    
    // generate the accept/reject buttons
    
    var currentState = matchesPerTerm[ termIndex ][ 3 ][ matchNumber ];
    var acceptImage = ( currentState == 1 ) ? '/images/acceptButtonHigh.png' : '/images/acceptButtonLow.png';
    var rejectImage = ( currentState == 2 ) ? '/images/rejectButtonHigh.png' : '/images/rejectButtonLow.png';
    
    var partialButtonID = termIndex.toString() + "." + matchNumber.toString();
    
    var paddingTop = ( matchNumber  > 0 ) ? "4" : "0";
    
    buffer += "<DIV CLASS=\"text2\" STYLE=\"padding-top:" + paddingTop + "px\">";
    
    buffer += "<IMG STYLE=\"vertical-align:middle;padding-right:5px;\" SRC=\"" + acceptImage + "\"" + 
	" ID=\"termListAcceptButton." + partialButtonID + 
	"\" TITLE=\"Accept this match\" ALT=\"Accept this match\" ONCLICK=\"TermList_toggleAccept(" + termIndex + "," + matchNumber + ")\"/>";
    
    buffer += "<IMG STYLE=\"vertical-align:middle;padding-right:15px;\" SRC=\"" + rejectImage + "\"" + 
	" ID=\"termListRejectButton." + partialButtonID + 
	"\" TITLE=\"Reject this match\" ALT=\"Reject this match\" ONCLICK=\"TermList_toggleReject(" + termIndex + "," + matchNumber + ")\"/>";
    
    buffer += "<IMG STYLE=\"vertical-align:middle;padding-right:15px;\" SRC=\"/images/modifyButton.png\"" + 
	" TITLE=\"Modify this match\" ALT=\"Modify this match\" ONCLICK=\"TermList_modifyMatch(" + termIndex + "," + matchNumber + ")\"/>";

    buffer += "...";
    
    // accumulate these tokens (plus their cruft)
    
    for( var t = minTokenIndexForExtract; t <= maxTokenIndexForExtract; t++ )
    {
	
	if( actualTokenHits[ t ] == true )
	{
	    buffer += "<SPAN CLASS=\"text4\"><B>" + tokenPlusCruft[ t ] + "</B></SPAN>";
	}
	else
	{
	    buffer += tokenPlusCruft[ t ];
	}
    }
    
    buffer += "...<IMG STYLE=\"vertical-align:middle;padding-left:7px;\" SRC=\"/images/widenButton.png\"" + 
	" TITLE=\"Show more context for this match\" ALT=\"Show more context for this match\" ONCLICK=\"TermList_increaseContextQuantity(" + termIndex + "," + matchNumber + "," + extraTokensToIncludeAtEitherEnd + ")\"/></DIV>\n";
    
    return buffer;
}

function TermList_increaseContextQuantity( termIndex, matchNumber, currentNumberOfExtraTokens )
{
    var newContents = TermList_getContextString( termIndex, matchNumber, currentNumberOfExtraTokens + 6 );

    var elementID = "termListMatchContextContainer:" + termIndex + "." + matchNumber;

    document.getElementById( elementID ).innerHTML = newContents;
}




/*
  used in the conversion between matches in inline view and in list view
*/
function TermList_getTermIndexFromTermID( termID )
{
    // ::TODO:: optimise this using a mapping from termID to termIndex
    // (or make matchesPerTerm into an hashtable indexed by termID)
    
    for( var termIndex = 0; termIndex < matchesPerTerm.length; termIndex++ )
    {
	if( matchesPerTerm[ termIndex ][ 0 ] == termID )
	{
	    return termIndex;
	}
    }
    return -1;
}

/*
  used in the conversion between matches in inline view and in list view
*/
function TermList_getMatchNumberFromMatchIndex( termIndex, matchIndex )
{
    var matchIndicesForThisTerm = matchesPerTerm[ termIndex ][ 2 ];    // the set of matchIndices for all occurences of this term

    for( var matchNumber = 0; matchNumber < matchIndicesForThisTerm.length; matchNumber++ )
    {
	if( matchIndicesForThisTerm[ matchNumber ] == matchIndex )
	{
	    return matchNumber;
	}
    }

    return -1;
}

function TermList_getStateForInstance( matchIndex, termID )
{
    var termIndex = TermList_getTermIndexFromTermID( termID );
	
    var matchIndicesForThisTerm = matchesPerTerm[ termIndex ][ 2 ];    // the set of matchIndices for all occurences of this term
    
    for( var matchNumber = 0; matchNumber < matchIndicesForThisTerm.length; matchNumber++ )
    {
	if( matchIndicesForThisTerm[ matchNumber ] == matchIndex )
	{
	    return matchesPerTerm[ termIndex ][ 3 ][ matchNumber ];   // the state corresponding to this match
	}
    }

    return -1;  // ::TODO:: should be an exception?
}


/*
  constructs the text which will appear in a pop-up tooltip for a term
  specified by 'termIndex'. the text is simply an HTML TABLE element
  populated with values grabbed from the 'termInfo' lookup table
 */
function TermList_makeToolTipText( termIndex )  // the termIndex is the index into the matchesPerTerm array
{
    var termID = matchesPerTerm[ termIndex ][ 0 ];

    var termInfoArray = termInfo[ termID ];

    // remember: never ever put a CR/LF between the 'return' keyword and the the rvalue...

    return "<TABLE WIDTH=\"100%\" STYLE=\"border-spacing:0px;padding:0px;margin:0px;\" CELLSPACING=\"0\">" +
	"<TR WIDTH=\"100%\" STYLE=\"background:#BF9660;\">" + 
	"<TD ALIGN=\"LEFT\"><A HREF=\"javascript:PageControl_displayContextSensitiveHelp('results');\"><IMG BORDER=\"0\" TITLE=\"Help\" ALT=\"Help\" SRC=\"/images/helpButton.png\"/></A></TD>" +
	"<TD WIDTH=\"99%\" CLASS=\"text3\" STYLE=\"max-height:30px;padding-left:8px;padding-right:8px;\" ALIGN=\"CENTER\"><DIV><B>Term Information</B></DIV></TD>" +
	"<TD ALIGN=\"RIGHT\"><A HREF=\"javascript:tt_HideInit();\"><IMG BORDER=\"0\" TITLE=\"Close\" ALT=\"Close\" SRC=\"/images/closeButton.png\"/></A></TD>" + 
	"</TR></TABLE>" +
	"<TABLE CLASS=\"popupInfoTable\">" +
	"<TR><TD CLASS=\"popupInfoName\">Ontology</TD>" +
	"<TD CLASS=\"popupInfoValue\">" + termInfoArray[3] + "</TD></TR>" +  // ontologyLongName
	"<TR><TD CLASS=\"popupInfoName\">Term</TD>" +
	"<TD CLASS=\"popupInfoValue\">" + termInfoArray[0] + "</TD></TR>" +  // itemName
	"<TR><TD CLASS=\"popupInfoName\">Accession</TD>" +
	"<TD CLASS=\"popupInfoValue\">" + termInfoArray[1] + "</TD></TR>" +  // accessionNumber
	"<TR><TD CLASS=\"popupInfoName\">Definition</TD>" +
	"<TD CLASS=\"popupInfoValue\">" + termInfoArray[4] + "</TD></TR>" +  // definitionText
	"</TABLE>";
}


/*
    termIndex identifies which term we are referring to ( i.e. which row of matchesPerTerm[] to look at )

    matchNumber identifies which of the matches of this term that we are referring to  ( this is not the same as matchIndex )
 */
function TermList_modifyMatch( termIndex, matchNumber )
{
    // we need to convert to 'termIndex', 'matchNumber' into  'matchIndex','termNumber'
    // in order to be able to call Refiner_displayMenu()

    var termID = matchesPerTerm[ termIndex ][ 0 ];

    var matchIndex = matchesPerTerm[ termIndex ][ 2 ][ matchNumber ];
    
    var termNumber = -1;

    for( var t = 0; t < termsPerMatch[ matchIndex ][ 1 ].length; t++ )
    {
	if( termsPerMatch[ matchIndex ][ 1 ][ t ] == termID )
	{
	    termNumber = t;
	}
    }
    
    if( termNumber < 0 )
    {
	alert( "TermList_modifyMatch(): Unable to map term indices." );
    }
    else
    {
	Refiner_displayMenu( matchIndex, termNumber );
    }
}


/*
   set all matches for the specified 'termIndex' to the specified 'state'
   (handles the 'reject all', 'accept all' and 'clear all' buttons)
 */
function TermList_adjustAllMatches( termIndex, state )
{
    var matchIndices = matchesPerTerm[ termIndex ][ 2 ];

    for( var m = 0 ; m < matchIndices.length; m++ )
    {
	TermList_setState( termIndex, m, state );
    }
}

/*
   matchNumber identifies which of the matches of this term that we are referring to   (it is not the same as matchIndex)
 */
function TermList_setState( termIndex, matchNumber, state )
{
    var partialButtonID = termIndex.toString() + "." + matchNumber.toString();

    var acceptButton = document.getElementById( 'termListAcceptButton.' + partialButtonID );
    var rejectButton = document.getElementById( 'termListRejectButton.' + partialButtonID );
    
    acceptButton.src = ( state == 1 ) ? '/images/acceptButtonHigh.png' : '/images/acceptButtonLow.png';
    rejectButton.src = ( state == 2 ) ? '/images/rejectButtonHigh.png' : '/images/rejectButtonLow.png';

    matchesPerTerm[ termIndex ][ 3 ][ matchNumber ] = state;

    // although we could call TermList_updateOntologyStatus() from
    // here at this point, we'll require that operation to be
    // explcitly lauched, otherwise we'll be generating lots of
    // unneccessary work e.g. when we did a 'reject all', we'd end up
    // calling TermList_updateOntologyStatus() once for each of the
    // rejected terms, rather than just calling it once after we'd
    // modified all of the states
}

/*
   matchNumber identifies which of the matches of this term that we are referring to   (it is not the same as matchIndex)
 */
function TermList_toggleAccept( termIndex, matchNumber )
{
    var currentState = matchesPerTerm[ termIndex ][ 3 ][ matchNumber ];

    var newState = ( currentState == 1 )  ? 0 : 1;
    
    TermList_setState( termIndex, matchNumber, newState );

    TermList_updateOntologyStatus();
}

/*
   matchNumber identifies which of the matches of this term that we are referring to   (it is not the same as matchIndex)
 */
function TermList_toggleReject( termIndex, matchNumber )
{
    var currentState = matchesPerTerm[ termIndex ][ 3 ][ matchNumber ];

    var newState = ( currentState == 2 )  ? 0 : 2;
    
    TermList_setState( termIndex, matchNumber, newState );

    TermList_updateOntologyStatus();
}

/*
  given an ontology short name, scan all matches and set the state to "rejected" for any frmo that ontology
 */
function TermList_rejectAllMatchesForOntology( targetOntologyShortName )
{
    for( var i = 0 ; i < matchesPerTerm.length; i++ )
    {
	var termID = matchesPerTerm[ i ][ 0 ];

	var ontologyShortName = termInfo[ termID ][ 2 ];

	if( targetOntologyShortName == ontologyShortName )
	{
	    var matchIndices = matchesPerTerm[ i ][ 2 ];
	    
	    for( var m = 0 ; m < matchIndices.length; m++ )
	    {
		TermList_setState( i, m, 2 );    // 2 == rejected
	    }
	}
    } 

    TermList_updateOntologyStatus();
}

/*
  expand or collapse the disable DIV for a given ontology short name
 */
function TermList_toggleVisibiltyForOntology( ontologyShortName )
{
    tt_HideInit();  // hide any tooltips that might be active

    var isVisible = ( document.getElementById( "termListOntologyContainer:" + ontologyShortName ).style.display == 'block' );

    document.getElementById( "termListOntologyContainer:" + ontologyShortName ).style.display = isVisible ? 'none' : 'block';

    document.getElementById( "termListOntologyToggleButton:" + ontologyShortName ).src = isVisible ? '/images/expandButton.png' : '/images/collapseButton.png';
}

/*
  update the status report in the list view, i.e. if no terms from
  this ontology are selected we disable the "reject all" button to
  give the user a quick indication of the state of each of the
  ontologies
 */
function TermList_updateOntologyStatus()
{
    var activeMatchCountsPerOntology = new Object();

    // we have the 'matchCountsPerOntology' table available to tell us
    // the possible set of short ontology names that were involved
    
    for( var ontologyName in matchCountsPerOntology )
    {
	activeMatchCountsPerOntology[ ontologyName ] = 0;
    }

    // count how many non-rejected terms there are per ontology

    for( var termIndex  = 0 ; termIndex < matchesPerTerm.length; termIndex++ )
    {
	var termID = matchesPerTerm[ termIndex ][ 0 ];

	var ontologyShortName = termInfo[ termID ][ 2 ];

	for( var matchNumber = 0; matchNumber < matchesPerTerm[ termIndex ][ 3 ].length; matchNumber++ )
	{
	    var state = matchesPerTerm[ termIndex ][ 3 ][ matchNumber ];
	    
	    if( state != 2 )
	    {
		activeMatchCountsPerOntology[ ontologyShortName ]++;
	    }
	}

    }
    
    // now adjust the ontology groups controls based on whether each
    // ontology group has all of its matches rejected or not

    for( var ontologyShortName in activeMatchCountsPerOntology )
    {
	var hasAnyToReject = ( activeMatchCountsPerOntology[ ontologyShortName ] > 0 );
	
	document.getElementById( "termListOntologyRejectButton:" + ontologyShortName ).style.display = hasAnyToReject ? 'inline' : 'none';

	document.getElementById( "termListOntologyStatus:" + ontologyShortName ).innerHTML = hasAnyToReject ? '' : '<I>(all terms rejected)</I>';
    }
   
    
}

    


// =================================================================================
//
// setup and callbacks for the GraphViz Flapplet
//
// =================================================================================

var GraphViewer_isInitialised = false;

var GraphViewer_gvapplet = null;

function GraphViewer_injectSwfObject()
{
    try
    {
	if ( swfobject.hasFlashPlayerVersion( '9.0.0' ) ) 
	{
	    // set up the listener

	    GraphViewer_initApplet();


	    // the name of the DOM element into which we shall inject the SWF object
	    
	    var targetElementId = 'myFlashObject';
	    
	    // make sure that our container is available 
	    
	    var container = document.getElementById( targetElementId );
	    
	    if ( container == null ) 
	    {
		var div = document.createElement( 'div' );
		div.setAttribute( 'id', targetElementId );
		document.getElementById( 'flappletInsertZoneContainer' ).appendChild( div );

		if( document.getElementById( targetElementId ) == null )
		{
		    alert( 'fatality: unable to create SWF injection container' );
		}
	    }
	    else
	    {
		alert( 'grievous:SWF injection container already exists' );
	    }


	    var availableHeight = document.getElementById( 'viewportHeight' ).value;
	    
	    //alert( 'availableHeight:' + availableHeight );

	    // embed the SWF into the newly created container

	    swfobject.embedSWF( "/graphviz_applet.swf", "myFlashObject", "600", availableHeight, "9.0.0", null, null, { allowfullscreen : "true" }, {} );

	    //alert( 'inject ok' );


	}
    }
    catch( exception )
    {
	alert( "Whilst creating or injecting SWF: " + exception );
    }
}

function GraphViewer_killSwfObject()
{
    try
    {
	swfobject.removeSWF( 'myFlashObject' );
    }
    catch( exception )
    {
	alert( "Whilst removing SWF: " + exception );
    }
}

function GraphViewer_initApplet()
{
    if( GraphViewer_isInitialised )
    {
	//alert( "init ignored" );

	return;
    }

    try
    {
	//alert( "init begins" );

	GraphViewer_gvapplet = org.omixed.main.GraphVizAppletJS;
	
	GraphViewer_gvapplet.addAppletConnectedListener( GraphViewer_setupApplet );

	GraphViewer_gvapplet.addNodeClickListener( GraphViewer_onNodeClick );
	GraphViewer_gvapplet.addOnCompleteListener( GraphViewer_onComplete ); 

	GraphViewer_isInitialised = true;
    }
    catch( exception )
    {
	alert( "Whilst initialising Flapplet: " + exception );
	return;
    }
}

/* called (via a callback from GraphViewer_gvapplet ) once the GraphVizAppletJS has allegedly made contact with the SWF player */
function GraphViewer_setupApplet()
{
    try
    {
	//alert( "setupApplet begins" );
	
	GraphViewer_gvapplet.setDisplayProperty( "backgroundGradientFillColors", [ 0xccb28f,0xccb28f,0xccb28f,0xccb28f ] );
	GraphViewer_gvapplet.setDisplayProperty( "backgroundGradientFillRatios", [ 0,0,0,0 ] );
	GraphViewer_gvapplet.setDisplayProperty( "backgroundGradientFillAlphas", [ 1,1,1,1 ] );
	GraphViewer_gvapplet.setDisplayProperty( "scrollBarColor", 0x806f59 );
	GraphViewer_gvapplet.setDisplayProperty( "scrollBarThickness", 12 );
	GraphViewer_gvapplet.setDisplayProperty( "borderEllipseThickness", 1 );
	GraphViewer_gvapplet.setDisplayProperty( "defaultEllipseBorderColor", 1 );
	GraphViewer_gvapplet.setDisplayProperty( "defaultEllipseBorderColor", 0xc5a477 );
	GraphViewer_gvapplet.setDisplayProperty( "defaultEllipseFillColors", [ 0x596780, 0x6e7e9d, 0xcfd7e6 ]  );
	GraphViewer_gvapplet.setDisplayProperty( "defaultEllipseFillRatios", [ 95, 160, 255 ]  );
	GraphViewer_gvapplet.setDisplayProperty( "defaultEllipseFillAlphas", [ 0.75,0.75,0.75  ]  );
	GraphViewer_gvapplet.setDisplayProperty( "defaultEllipseFontColor", 0x283653 );
	GraphViewer_gvapplet.setDisplayProperty( "defaultEdgeFontColor", 0x6e7e9d );

	//alert( "display properties set" );
	
	var availableHeight = document.getElementById( 'viewportHeight' ).value;

	GraphViewer_gvapplet.setDimensions( 0, 0, 600, availableHeight );

	GraphViewer_drawGraph( Refiner_globalState.itemID );
    }
    catch( exception )
    {
	alert( "Whilst initialising Flapplet: " + exception );
	return;
    }
}

function GraphViewer_drawGraph( itemID )
{
    Utils_setBusy( true );
    
    //alert( "drawGraph begins" );

    // this shouldn't happen, but just in case it does...
    
    if( Refiner_globalState.client.isConnected() )
    {
	Refiner_globalState.client.disconnect();
    }

    Refiner_globalState.client.connect( omixedResourceName, omixedUserName, omixedPassword, GraphViewer_connectCallback, itemID );
}

function GraphViewer_connectCallback( errorMessage, itemID )
{
    if( errorMessage == null )
    {
	try
	{
	    //alert( "connected to server, sessionID is " + Refiner_globalState.client.getSessionID() );

	    // the problems start here...
	    
	    GraphViewer_gvapplet.setupClient( Refiner_globalState.client.getSessionID(), omixedResourceName, omixedUserName );

	    var queryOptions =
		{
		maxInboundDepth : 3,
		maxOutboundDepth : 2
		};
	    
	    var layoutOptions = 
		{
		Grankdir : "TB",
		layoutMethod : "dot", // "neato", "twopi"
		//outputFormat : "svg", // the applet can parse either 'svg' or 'xdot',
		outputFormat : "xdot", // the applet can parse either 'svg' or 'xdot',
		Goverlap : "false",
   	        GLen: "100"
		};


	    //alert( "setUpClient() happened ok" );
	    
	    GraphViewer_gvapplet.drawItemGraph( Refiner_globalState.client.getSessionID(), itemID, queryOptions, layoutOptions );
	}
	catch( exception )
	{
	    Utils_setBusy( false );
	    alert( "Connected ok, but problem with Flapplet setup: " + exception );
	}
    }
    else
    {
	Utils_setBusy( false );
        alert( "Connect failed:" + errorMessage[ 'ErrorMessage' ] );
    }
}

function GraphViewer_onComplete( arg)
{
    if( arg == 'rendered graph and map' )
    {
//	alert( arg );
	
        Utils_setBusy( false );
	
	if( Refiner_globalState.client.isConnected() )
	{
	    Refiner_globalState.client.disconnect();
	}
    }
}

function GraphViewer_onNodeClick( itemID )
{
    // add the current selection to the history list
    
    var currentItemID = GraphViewer_gvapplet.getCenterTitle();
    
    if( Refiner_globalState.history[ Refiner_globalState.history.length -1 ] != currentItemID )
    {
	Refiner_globalState.history.push( currentItemID );
    }
    
    // and draw a new graph for the freshly selected itemID
    
    GraphViewer_drawGraph( itemID );
    
}


