/* Mario Latendresse, August 2006

The advanced query html page is used by the user to interactively
create an advanced query for a Pathway Tools server.  This query web page
is dynamically created by the JavaScript -- the display evolves
according to the values selected by the user.

The query page has two major sections: the objects to search for and
the ouput description -- appearing in that order on the web page from 
top to bottom. 

The output description is the simpler of the two:
it is a series of column description referring to objects constructed
in the first section. 

The search section is, in general, a series of search components. There
can be as many search components as desired by the user although it
typically has just a few. Each search component can have a where clause,
which may contain embedded where clauses.

A where clause is essentially a logical expression.
A logical expression is a series of terms and logical operators.
A term is two factors, left and right, with a relational operator.

A factor can be: two selectors, one for a variable (a letter) and
the other a slot name; or a text input field. If there is only one
possible variable accessible by the factor, e.g. only one search
component has been created so far in the query, then no variable
selector is displayed.

A frame object is simply called an object.

--------------

*/

 /* The type of the slots are described by a vector of at most three elements: 

    [0]) multiplicity: 0 => variable number of elements for the slot -- i.e., a list;
                       1 => single value

    [1]) basic type: STRING, NUMBER, BOOLEAN, FRAME
         or a descriptor of a more complex type: UNION, ENUMERATED
    [2]) a vector if the second element (i.e., [1]) is UNION or ENUMERATED.
         The vector is the list of enumerated cases or the list of union cases.
 */


// If the server is slow, this should be changed to true.
// We use this variable to possibly reduce the number of requests to the server.
var slowServer = false;

var slotsType = {};

// The content of these hash tables are set by the server based on the knowledge base schema.
// Typically this should be set by a server generated JavaScript library schema.js.

var classesSlots = {},  
    basicTypeToOperators = {}, 
    optionsDoc = {}, 
    dataSetsFreeForm = {}, 
    dataSetsStructuredForm = {};

/* All visible variables used by the query are stored in variables. Each element is a structure of the form

   {ci: an integer, the index of the search component containing the definition of the variable, 
    classType: a string describing the type
    topLevel: a boolean, true => this variable is at top level in the search component
    }. 

   These variables are named Zn where n is an integer.
*/
var variables = new Array();
var nbVariables;

// The variable in the first search component is often refer by name.
var firstVar = indexToVar(1);


var noComponents = 0;
var logicalOps     = ["selectLog", "add a condition", 
		      "&", "and", 
		      "|", "or", 
		      "nor", "or not", 
		      "nand", "and not"];
var booleanOptions = ["yes", "yes", "no", "no"]


// Repeat operators for basic types and frames
var repeatOps1 = ["forSomeObjects", "for some object ...",
		  "forAllObjects",  "for all objects ...",
                  "forOneObject",   "for exactly one object ...",
                  "forNoObjects",   "for no objects ...",
		  "card", "the number of objects of"
                 ];

// On Dec 3, 2008 it was decided that these four operators were redundant.
// TBD: other parts of this JavaScript file should be removed once
// it is certain that we will not need these anymore.
//                  "forSome", "at least one object of", 
//		  "forAll", "every object of", 
//		  "forOne", "exactly one object of", 
//		  "forNone", "for no object of" 
//                  ];

// Repeat operators for basic types
var repeatOps2 = ["forSome", "at least one element of", 
		  "forAll", "every element of", 
		  "forOne", "exactly one element of", 
		  "forNone", "for no element of", 
                  "card", "the number of elements of"];

// Insert search component operations
var insertSearchOps = ["insertSearch", "Select an operation to add an additional search component:",
		       // "addNewSearch",     "Start a new search, independent of the previous one",
                       "crossProduct", "Combine each previous result with the following results" 
                      ];

// debugging function to display a list of JavaScript objects.
function display(){
    for(var i = 0; i < arguments.length; i++) {
        java.lang.System.out.print(arguments[i]);
        java.lang.System.out.println("");
    }
}

// Get the value of variable varName in the URL. Return null if no variable is present.
function getUrlVar(varName){
  var urlInTwo = String(document.location).split('?');
  if(urlInTwo[1]) {
    var urlVars = urlInTwo[1].split('&');
    for (var i=0; i < urlVars.length; i++) {
      if (urlVars[i]) {
        var urlVarPair = urlVars[i].split('=');
        if (urlVarPair[0] && urlVarPair[0] == varName) return urlVarPair[1];
      }}}
  return null;
}

// Return a string of n unbreakable spaces.

function spaces(n){
    var s = "";
    for(var i=0; i < n; i++) s+="&nbsp;";
    return s;
}

function createVerticalSpace(){
    // Do not use div or p tag element, since series of these
    // does not translate by a series of vertical vertical spaces.
   var division = document.createElement('br');
   return division;
}

function colorComponent(i){
 return toRGB(255-Math.min(20*i,255), 150, Math.min(255,100+(i*20)));
}

function ii(){
    ii.i++;
    return ii.i
}

var alertedAjax = false; // true => an alert has been given about Ajax not being supported by the client browser

/* Do a HTTP GET to the strURL. The base location is the current server (e.g., biocyc.org).
   Arguments: strURL, a string representing the GET request URL, e.g., ('TABULATED'+)
   Returns: nothing, the function strResultFunc is called on the string returned by
            the GET request.
*/
function xmlhttpGet(strURL, strSubmit, f) {
    var xmlHttpReq = false;
        
    // Mozilla/Safari
    if (window.XMLHttpRequest) {
	xmlHttpReq = new XMLHttpRequest();
	//xmlHttpReq.overrideMimeType('text/plain');
    }
    // IE
    else if (window.ActiveXObject) {
	xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");
    }
    
    if(xmlHttpReq){
	xmlHttpReq.open("GET", strURL, true);
	xmlHttpReq.onreadystatechange = function() {
	    if (xmlHttpReq.readyState == 4) {
                // alert("response:"+xmlHttpReq.responseText+","+xmlHttpReq.status+","+xmlHttpReq.statusText);
		f(xmlHttpReq.responseText);
	    }
	}
	xmlHttpReq.send(strSubmit);
    }
    else if(!alertedAjax) {
	alert("Your browser does not support Ajax. This webpage will partially work.");
	alertedAjax = true;
    }
}


/* Arguments: ic, the component number where this select will go. 
              id, a string;
              options, a vector of strings or string-pairs;
              onChange, a closure of zero parameter;
              stringPairQ, a boolean, if true the options vector contains pairs of strings for each option;
              selectIndexOrValue, an integer or string giving the option to select by default.
              prefix, a string that should prefix the option value for the tooltip (showDocOption function).

   Returns: an HTML select object.
*/

function createSelect(ic, id, options, onChange, stringPairQ, selectIndexOrValue, prefix){
  var select =  document.createElement('select');

  // Do not use select.setAttribute('onChange', onChange) this will not work under IE.
  select.onchange = onChange;
  select.className = 'SAQP';
  select.setAttribute('style', 'background-color: white');
  // The documentation tip text is controlled by showDocOption and hideTooltip.
  // For Safari 1.3, the mouseover is active only for the selected item.
  // Apparently (as of March 11) onMouseOver does not work with IE for option.
  select.setAttribute('onMouseOver', "showDocOption(\""+prefix+"\",this, event.target)");
  select.setAttribute('onMouseOut', "hideTooltip()");
  // select.setAttribute('onDblClick', 'copyValueToTextArea(this, event.target.value)');
  select.id      = id;
  var incr = stringPairQ ? 2 : 1;
  for (var i=0; i < options.length; i += incr){
      var selectiq = (typeof(selectIndexOrValue) == "string" && 
                      options[i].toLowerCase() == selectIndexOrValue.toLowerCase()) 
                     || ((i/incr) == selectIndexOrValue);
      var option = new Option(options[i+incr-1], options[i], selectiq, selectiq);
      // option.setAttribute('click', "showDocOption(\"+prefix+\",this, event.target)");
      option.setAttribute('onMouseOut', "hideTooltip()");
      // For an option that refers to a slot that can contain frames, have a different class. (see style.css)
      if(stringSlotFrameQ(options[i])) option.className = "saqpSelectFrame";
      select.options[i/incr] = option;
      // The following is essential for the default selected option to work for IE.
      // It should be done after the above assigment is done, not before!
      if (selectiq) select.value = options[i];
  }
  return select;
}

/* Show the documentation of the option to its right. This function is typically
   triggered by a mouseOver event for that option (or select). Notice: some browser
   as Safari 1.3 does not trigger the mouseOver event for each option, but only
   for the select. Which means that for this browser the user should select first
   the option then mouseOver it to have the showDocOption triggered.
   
   Arguments: prefix, a string to prefix the target value.
              select, the HTML selector for which a tooltip info box should be provided.
              option, the option itself.
   Returns: show a tooltip documentation --- if one exists --- on the right of the selection.
 */
function showDocOption(prefix, select, option){
    if (option == null || option == undefined) return;
    if (select == null || select == undefined) return;
    var optionName = option.value;
    var selOptionName = prefix+optionName;

    // Do nothing if there is no documentation.
    if (optionsDoc[selOptionName] == undefined) return;

    var posxy = findRightPositionOption(select, option);
    // Use overlib library
    return overlib(optionsDoc[selOptionName]);
    // The following should be used if overlib is not used.
    //    showTextToolTipPos(posxy, optionName + ': '+ optionsDoc[selOptionName]);
}

// Show the tooltip box with text in it.
function showTextToolTip(object, text){
    if (object == null || object == undefined) return;
    var posxy = findRightPositionObject(object);
    showTextToolTipPos(posxy, text);
}

// Show the text in the tooltip element at position pos on the page.
function showTextToolTipPos(pos, text){
    var x = pos[0]; 
    var y = pos[1];
    var textBox = document.getElementById('tooltip');
    // textBox.setAttribute('style', 'display: inline; position: absolute; border: 1px solid black; background-color: lightyellow; padding: 2px; top:'+y+'px; left:'+x+'px');
    textBox.className = 'tooltip';
    textBox.style.top = y+'px'; 
    textBox.style.left = x+'px';
    textBox.style.display = 'inline';
    textBox.innerHTML = text;
}

// Return the [x, y] position of option o for selector select.
// We need the select element since it maybe scrolled, changing the
// relative position of option o.
function findRightPositionOption(select, option){
    var pos = findRightPositionObject(option);
    var posSelect = findRightPositionObject(select);
    // take into account any scrolling done in the select element.
    pos[1] -= select.scrollTop;;
    // To avoid the problem of a tooltip that goes above the select itself
    // takes the max of the option position y and the select position y.
    // This problem happens when hovering a selected element for which the option list
    // is scrolled.
    pos[1]  = Math.max(pos[1], posSelect[1]);
    return pos;
}

// Return the [x, y] position of object on the page.
// This function may no longer be usefull if the tooltip uses the overlib library.
// Keep it for future use.
function findRightPositionObject(object) {
    // In the case the whole page is scrolled.
   var x = -(document.body.scrollLeft);
   var y = -(document.body.scrollTop);
   var o = object;
   x += o.offsetWidth + x + 20; // so that it is on the right of the object.
   while(o != null) {
      x += o.offsetLeft;
      y += o.offsetTop;
      o = o.offsetParent;
    }
    return [x,y];
}


// Makes invisible the unique tooltip box.
function hideTooltip(){
//    The code is now based on the library overlib.
      return nd();
// Use the following two lines if overlib is not used.
//    var tooltip = document.getElementById('tooltip');
//    if (tooltip != null) tooltip.style.display = 'none';
}

/*  Create an input text element with appropriate id. These input texts are typically
    right factor of terms.

    Arguments: none.
    Returns: an input text object.
 */
function createInputText(){
    var id   = "input"+ii();
    var text =  document.createElement('input');
    text.type = "text";
    text.className = 'textBox';
    text.id   = id;
    return text;
}

/* Returns a span HTML object containing string s.

   Arguments: s, a string.
   Returns: a span HTML object.
*/
function createSpanText(s){
    var r = document.createElement("span");
    r.innerHTML = s;
    return r;
}

/* Some part of the query uses variables that are not visible to the user; these
   are embedded queries with internal variables. They all starts with "y" instead of "Z"
   for the user-visible ones. 

*/
var nbInternalVar;

function internVar(){
    nbInternalVar++;
    return "y"+nbInternalVar;
}

/* This function must be called to setup the initial 
   web page. It should be called when the page is loaded, 
   typically with the onload option of the body
   HTML tag; and when the reset button is activated by the user.
*/

function initSAQP(){

 basicTypeToOperators["FRAME"] = ["isa", "is an instance of class",
				  "isnota", "is not an instance of class",
                                  "=", "is equal to", 
                                  "!=", "is not equal to", 
                                  "in", "is in", 
                                  "isnotin", "is not in", 
				  "rin", "contains",
				  "nrin", "does not contain"
                                  ];

 basicTypeToOperators["RIGHT-FRAME-LIST"] = ["=", "is equal to", 
                                             "!=", "is not equal to", 
                                             "in", "is in", 
                                             "isnotin", "is not in" 
                                            ];

 // For a left slot operand that is a frame, e.g., gene. 
 basicTypeToOperators["LEFT-FRAME-SINGLE"] = ["isa", "is an instance of class",
                                              "isnota", "is not an instance of class",
					      "=", "is equal to",
                                              "!=", "is not equal to",
                                              "bindIt", "is an object ..."
                                             ];

 basicTypeToOperators["RIGHT-FRAME-SINGLE"] = ["=", "is equal to", 
                                               "!=", "is not equal to" 
                                              ];

 basicTypeToOperators["STRING"] = ["rinstringci",  "contains the substring",
                                   "instringci",   "is a substring of", 
                                   "nrinstringci", "does not contain the substring",
                                   "ninstringci",  "is not a substring of",
                                   "=ci",          "is equal to", 
                                   "!=ci",         "is not equal to",
                                   "~=",           "is similar to (regular expression)",
                                   "!~=",          "is not similar to (regular expression)",
                                   "rinstring",    "contains the substring         (case-sensitive)",          
                                   "instring",     "is a substring of              (case-sensitive)", 
                                   "nrinstring",   "does not contain the substring (case-sensitive)",
                                   "ninstring",    "is not a substring of          (case-sensitive)",
                                   "=",            "is equal to                    (case-sensitive)", 
                                   "!=",           "is not equal to                (case-sensitive)"
                                    ];

 basicTypeToOperators["NUMBER"] = ["=", "is equal to", 
				   "!=", "is not equal to", 
				   ">", "is greater than", 
                                   "<", "is smaller than",
                                   "<=", "is smaller than or equal to", 
                                   ">=", "is greater than or equal to"];

 basicTypeToOperators["BOOLEAN"]    = ["=", "is equal to", "!=", "is not equal to"];
 basicTypeToOperators["ENUMERATED"] = ["=", "is equal to", "!=", "is not equal to"];

 // Add documentation for some of the selects and operations defined in this file.

 optionsDoc["select:insertSearch"] = "Select an operation to add a search component to this page. This new search component will allow you to specify a completely new search, with a different or the same previous database(s). This new search component will allow you to create a condition based on the objects found in the previous component(s). This new search will be done <b>for each</b> result of the previous component(s). The combined results are output in the same table, one combined result per line.";

 // All variables visible by the user and used in the query are
 // maintained in the vector variables. Position 0 is not used for
 // any variable.

 nbVariables = 0;
 variables = new Array();
 variables.push(null);

 // Clear any error message or marker on the page.
 resetUserErrorMsg();

 // For internal variables not seen by the user. These variable names are prefixed by "y".
 nbInternalVar = 0;

 // ii.i is a global counter to help generate unique ids for HTML elements.
 // See function ii.

 ii.i = 0;

 // It is possible that the schema.js file has not been found on the
 // server or whatever computer this code is being executed.
 // It is an error in this case, so we alert the user and then
 // keep going with some values.

 if (dataSetsFreeForm.length == undefined  || dataSetsFreeForm.length == 0) {
   alert("It appears that no valid schema.js file is accessible from the current server. Either the file is not accessible or it has been improperly formed. I will try to run this JavaScript code anyway, but expect this BioVelo interface not to be functioning properly.");
  }

 removeAllComponents();

 var desiredState = getUrlVar('state') ? getUrlVar('state') : 'structured'; 
 if (desiredState == 'structured') {
   initFirstComponent();
   switchToStructuredAdvanced();
 }
 else {
   var queryString = getUrlVar('queryString');
   // The query string must be unescaped to remove the URL encoding schemes.
   switchToFreeFormAdvanced((queryString!=null) ? unescape(queryString) : queryString);
 }
 initOutputSpecification();
}


// Various answers from the server are cached. Currently, queries for the number of instances and slots
// not being nil are cached. Even if the server might cache these queries it is much more efficient to 
// avoid any query to the server.
function cacheServer(){
    this.nbInstances   = new Array();
    this.nbSlotsNonNil = new Array();
}

// Instanciate one cacheServer object for all cached requests (nb instances, etc.).
var cacheServerObj = new cacheServer();

// Apply closure f with the number of instances for className/databaseName.
cacheServer.prototype.applyNbInstances = function (databaseName, className, f){
    if(!isNaN(cacheServerObj.nbInstances[databaseName+className])) 
     f(cacheServerObj.nbInstances[databaseName+className]);
    else {
	// Ask the server, cache the answer, apply f with the answer.
	xmlhttpGet(queryNbInstances(databaseName, className),"", 
                   function (s) { if(s.length < 11) { cacheServerObj.nbInstances[databaseName+className] = s; f(s);}}); 
    }
}

// Apply closure f with the number of slots not being nil in className/databaseName.
// The closure f could do some side effects like updating values shown on the web page.
cacheServer.prototype.applyNbSlotsNonNil = function (databaseName, className, slotName, f){
    if(!isNaN(cacheServerObj.nbSlotsNonNil[databaseName+className+slotName])) 
    f(cacheServerObj.nbSlotsNonNil[databaseName+className+slotName]);
    else {
	// Ask the server, cache the answer, apply f with the answer.
	xmlhttpGet(queryNbNonNilSlots(databaseName, className, slotName),"", 
		   // If the string s, which represents a number as a string, is greater than 11,
		   // then it looks like something went wrong on the server side. It is probably
		   // an error message. Do not display such a long message.
                   function (s) { if(s.length < 11) 
		       { cacheServerObj.nbSlotsNonNil[databaseName+className+slotName] = s; f(s);}}); 
    }
}



/* Returns an array of operators that can be used as options in a operator selector such that the right
   operand is of the given type.

   Arguments: type, a type vector.  See slotsType for examples.
   Returns: an array of strings, each string being an operator.
 */

function rightTypeToOperators(type){
    if (basicTypeQ(type[1])) return basicTypeToOperators[type[1]];
    else if (type[1] == "UNION") return unionOperators(type[2]);
    else return rightBasicTypeMultToOperators("FRAME", type[0]);
}

/* Returns an array of operators that can be used as options in a operator selector such that the left
   operand is of the given type.

   Arguments: type, a type vector.  See slotsType for examples.
   Returns: an array of strings, each string being an operator.
 */

function leftTypeToOperators(type){
    if (basicTypeQ(type[1])) return basicTypeToOperators[type[1]];
    else if (type[1] == "UNION") return unionOperators(type[2]);
    else return basicTypeToOperators["LEFT-FRAME-SINGLE"];
}


/* Return an array of operators (strings) taking into account multiplicity and basic type
   based on a right operand.

   Arguments: basicType, a string.
              multiplicity, the multiplicity value coming from a type vector (e.g., 1, 0)
   Returns: an array of strings.
 */

function rightBasicTypeMultToOperators(basicType, multiplicity){
    if (multiplicity == 1 && basicType == "FRAME") 
	return basicTypeToOperators["RIGHT-FRAME-SINGLE"];
    else if (multiplicity == 0 && basicType == "FRAME") 
	return basicTypeToOperators["RIGHT-FRAME-LIST"];
    else return (basicTypeToOperators[basicType]);
}

/*  TBD: revise the notion of union of operators as it may not be needed to generate
    such a list of operators.

    Arguments: basicTypes, an array of basic types.
    Returns: an array of strings, each string being an operator.
*/

function unionOperators(basicTypes){
    var result = new Array();
    for(var i=0; i < basicTypes.length; i++){
        if (basicTypeQ(basicTypes[i])) 
	    var ops = basicTypeToOperators[basicTypes[i]];
        else var ops = basicTypeToOperators["FRAME"];
        for(var j=0; j < ops.length; j += 2)
            if(!(inArray(ops[j+1], result))) { 
                result.push(ops[j]);
                result.push(ops[j+1]);
            }
    }

    return result;
}

/* Returns the union of slot names of a set of class names. Note: the array contains the
   name of the slot AND the name shown to the user. That is, there two strings for each slot.

   Arguments: nameTypes, an array of strings -- some of the strings might be class name, 
              or other basic types (e.g. "STRING"), which are skipped.
   Returns: an array strings, two per slot: the first string is the name used for the query (BioVelo)
            the second is the string shown to the user. They must be maintained in that order.
*/

function sortPairStrings(a,b){return (a.nameShown - b.nameShown);}

function classNamesToSlots(nameTypes){
    if (nameTypes.length == 1) return classesSlots[nameTypes[0]];

    var tmpResult = new Array();
    var slotNamesSeen = new Array();
    for(var i=0; i < nameTypes.length; i++){
	if(classesSlots[nameTypes[i]] != null && classesSlots[nameTypes[i]] != undefined){
	    var slots = classesSlots[nameTypes[i]];
	    for(var j=0; j < slots.length; j += 2)
		if(!(inArray(slots[j+1], slotNamesSeen))) { 
                    // Each tmpResult is a pair of strings. It will be converted at the end.
		    tmpResult.push({ nameUsed: slots[j], nameShown: slots[j+1]});
                    slotNamesSeen.push(slots[j+1]);
		}
	}}

    // Sort according.
    tmpResult.sort(sortPairStrings);
    // Flatten the result of pairs of strings into an array of strings.
    var result = new Array(tmpResult.length*2);
    for (var i=0; i < result.length; i += 2) {
	result[i] = tmpResult[i / 2].nameUsed;
        result[i+1] = tmpResult[i / 2].nameShown;
    }
    return result;
}


/* This initialisation function should be called only after
   the first initial search component has been created since
   the slot selector that will be created depends on the
   first search component.

   We remove any column that may already exist since this function
   can be called to reset to its initial state the output specification.

*/

var outputSlotNames = new Array();

/* Each class name has its set of output specification slots defined
   in outputSlotNames. Use lower case letters only for the name of the classes.

   It does not depend on the database selected, except that the
   JavaScript code add slot Taxonomic-Range for all classes when searching in metacyc.
*/
outputSlotNames["genes"] = ["Name", "PRODUCT"];
outputSlotNames["all-genes"] = ["Name", "PRODUCT"];

outputSlotNames["rna"]   = ["Name", "GENE"];
outputSlotNames["proteins"] =  ["Name", "GENE"];
outputSlotNames["compounds"] = ["Name", "CHEMICAL-FORMULA"];
outputSlotNames["pathways"]  = ["Name", "REACTION-LIST" ];
outputSlotNames["reactions"] = ["Name", "EC-NUMBER"];

outputSlotNames["dna-binding-sites"] = ["Name", "ABS-CENTER-POS", "INVOLVED-IN-REGULATION"];
outputSlotNames["promoters"] =  ["Name", "BINDS-SIGMA-FACTOR", "ABSOLUTE-PLUS-1-POSITION" ];
outputSlotNames["people"] = ["Name", "FIRST-NAME", "MIDDLE-NAME", "LAST-NAME", "EMAIL",
			     "CREDITED-FOR"];

/* Returns an array of the output slots predefined for a class name.
   It should never be an empty array.
*/
outputSlotNames.get = function(className, databaseName){
    var result = new Array();
    var classNamel = className.toLowerCase();
    if(outputSlotNames[classNamel] != null && outputSlotNames[classNamel] != undefined) {
        // Do not rely on copy function has some browser may not have it.
        for (var i=0; i < outputSlotNames[classNamel].length; i++)
	    result.push(outputSlotNames[classNamel][i]);

	// There should be at least one slot defined.
	if (result.length == 0) result.push("Name");
        }
    else result.push("Name");

    // Add a slot for metaCyc
    if (databaseName.toLowerCase() == "meta") result.push("Taxonomic-Range");
    return result;
}


function initOutputSpecification(){
  // Set the output format to HTML
  var outputFormatHTML = document.getElementById("outputFormatHTML");
  outputFormatHTML.checked = true;

  var button   = document.getElementById('switchForm');

  // Deals with the columns only in SAQP mode.

  if (button.state == 'structured') {
      var outHeaders = document.getElementById("outHeaders");
      var outLine    = document.getElementById("outLine");
      // Only one search component exist at this point.
      var className  = document.getElementById("class1").value;
      var databaseName = document.getElementById("dataset1").value;
      
      // Remove all columns attached to outLine and outHeaders
      while (outLine.childNodes[0] != null) 
	  outLine.removeChild(outLine.childNodes[0]);
      
      while (outHeaders.childNodes[0] != null) 
	  outHeaders.removeChild(outHeaders.childNodes[0]);
      
      addColumnOutput.nbColumns = 0;
      // The number of columns and the slot pre-selected for them is based
      // on the class name used in the first search component.

      var predefinedSlots = outputSlotNames.get(className, databaseName);
      for(var i=0; i < predefinedSlots.length; i++) {
	  addColumnOutput(predefinedSlots[i], false);
      }
  }
}

/* Add a new column to the output specification. The added column
   is always for variable firstVar. If the given slot name does not
   exist for the class, then do not add a column.

   I: slotNametoselect, a string, which is the name of the slot 
                        to select in the new added column. If null,
                        let this function decide.
      allowRepeat, true => allow the same slot name to appear
                           more than once for the same variable.
   O: nothing.
*/

function addColumnOutput(slotNameToSelect, allowRepeat){
    var slotName = (slotNameToSelect == null) ? "Name" : slotNameToSelect;

    // Type of variable firstVar.
    var classNameSelected = variables[1].classType; 
    var slots = classesSlots[classNameSelected];
    // If slot to select is not in the possible slots of this class, do not add the column.
    if (!inArray(slotName, slots)) {
	return;
    }

    if (!allowRepeat)
	// If the column already exists for variable firstVar and slot
	// name slotNameToSelect (or Name) do not add a column.
	for(var j=1; j <= addColumnOutput.nbColumns; j++){
	    var slotNamel = slotName.toLowerCase();
	    var varColumnj = document.getElementById('varColumn'+j);
	    if(varColumnj == null || firstVar == varColumnj.value) {
		// Change the list of options for this variable.
		var slotColumnjName = document.getElementById("slotColumn"+j).value;
		if (slotNamel == slotColumnjName.toLowerCase())
		    // The column with this slot selected already exists for firstVar. 
		    // Just return.
		    return;
	    }}

    // Ok add the column...
    var outHeaders    = document.getElementById("outHeaders");
    var i = ++(addColumnOutput.nbColumns);

    // The addColumn may be missing, which would give null.
    var existAddColumn  = document.getElementById("addColumn");

    // First the header of the column is taken care of.
    var header        = document.createElement('td'); // 'td'
    header.id         = "header"+i;
    var headerText    = document.createElement('span');
    headerText.id     = "headerText"+i;
    headerText.innerHTML = "<b>Column "+i+"</b>";
    header.appendChild(headerText);

    // A clickRemove button is given only if more than one column is present.
    if(i > 1) { 
        var clickRemove   = createClickableImage("clickRemove"+i, "/x.gif",  
						 closureRemoveColumnOutput(i),
						 "Remove this column");
	// clickRemove.setAttribute("style", "background-color: white");
        // At this point there must be at least one column already appended to the table.
        header.appendChild(clickRemove);
    }
    else {
        // There is no column displayed, create the addColumn before proceeding.
        var addColumn  = createButton("", "add a column", function(){addColumnOutput(null,true)});
        // addColumn.style.color  = "white";
	addColumn.setAttribute("style", "background-color: white");
        var headerAddColumn    = document.createElement('td'); // 'td'
        existAddColumn         = headerAddColumn;;
        headerAddColumn.id     = "addColumn";
        headerAddColumn.appendChild(addColumn);
        outHeaders.appendChild(headerAddColumn);
    }

    // Add the sorting radio button
    var sortText = document.createElement('span');
    sortText.innerHTML = "<br><input type=radio id=sort"+i+
	" name=sorting value="+i+((i==1)?" checked":"")+">Sort based on this column";
    header.appendChild(sortText);
    
    // header is the new child to replace the existing addColumn
    outHeaders.replaceChild(header, existAddColumn);   
    // by adding it back it moves to the right on the display
    outHeaders.appendChild(existAddColumn);

    //==== The header has been added, now the content of the column is added.

    var outLine    = document.getElementById("outLine");
    var cell       = document.createElement('td');
    cell.id        = "outColumnCell"+i; 
    // Create a variable selector if more than one variable exists.
    // Create a slot selector in all cases.

    if (accessibleVarsOutput().length > 1) { 
         var s1 = createSelect(0, "varColumn"+i, accessibleVarsOutput(), varOutSelected(i), false, 0, "");
         cell.appendChild(s1);
    }

    // Create a slot selector. Note that the slot pre-selected is either Name or what is being provided
    // as input to this function.
    var s2 = createSelect(0, "slotColumn"+i, slots, function(){}, true, slotName, "slot:");
    cell.appendChild(s2);
    outLine.appendChild(cell);
    // Nb of non nils added asynchronously. It is assumed that firstVar is the default variable selected.
    slotNamesWithNbNonNils(varToDatabaseName(firstVar), s2.options, classNameSelected, classesSlots[classNameSelected], "up to ");
}

/* Remove column i from the output specification of the web page.

   Arguments: i, index of a column.
   Returns: display of the web page is modified.
*/

function removeColumnOutput(i){
    if (addColumnOutput.nbColumns == 1) return;
    var n = addColumnOutput.nbColumns;

    var outColumnCelli = document.getElementById("outColumnCell"+i);
    var outLine        = document.getElementById("outLine");
    var outHeaders     = document.getElementById("outHeaders");
    var headeri        = document.getElementById("header"+i);
    var slotColumni    = document.getElementById("slotColumn"+i);

    hideTooltip();

    if(accessibleVarsOutput().length > 1) {
        var varColumi  = document.getElementById("varColumn"+i);
        outColumnCelli.removeChild(varColumi);
    }
    
    outColumnCelli.removeChild(slotColumni);
    outHeaders.removeChild(headeri);
    outLine.removeChild(outColumnCelli);

    for(var j=i+1; j <= n; j++){
        var headerj = document.getElementById("header"+j);
        headerj.id = "header"+(j-1);
        var headerTextj = document.getElementById("headerText"+j);
        headerTextj.innerHTML = "<b>Column "+(j-1)+"</b>";
        headerTextj.id = "headerText"+(j-1);
        var sortj = document.getElementById("sort"+j);
        sortj.id = "sort"+(j-1);
        sortj.value = ((j-1)==1)?" checked":"";

        var outColumnCellj = document.getElementById("outColumnCell"+j);
        outColumnCellj.id = "outColumnCell"+(j-1);
        if(accessibleVarsOutput().length > 1) { 
          var varColumnj = document.getElementById("varColumn"+j);
          varColumnj.id = "varColumn"+(j-1);
          varColumnj.onchange = varOutSelected(j-1);
        }
        var slotColumnj = document.getElementById("slotColumn"+j);
        slotColumnj.id = "slotColumn"+(j-1);
        var clickRemovej   = document.getElementById("clickRemove"+j);
	// Transfer the events from column j to column j-1.
        // Mozilla 1.4 has a bug in closure creation, use this intermediate call to fix it.
        clickRemovej.onclick = closureRemoveColumnOutput(j-1);
        clickRemovej.id = "clickRemove"+(j-1);
    }

    addColumnOutput.nbColumns--;
}

function closureRemoveColumnOutput(j)
{ return function(){removeColumnOutput(j)}; }


// Returns a string to represent variable i.
function indexToVar(i){
    return "Z"+i;
}

// Returns an integer, which is the index in variables of varName.
function varToIndex(varName){
    return parseInt(varName.substr(1));
}

/* Returns the index of element e in array a if it is
   in it; otherwise returns -1.
 */
function posArray(e, a){
  for (var i=0; i< a.length; i++){
      if(a[i] == e || 
         (typeof(e) == "string" && 
          typeof(a[i]) == "string" && 
          a[i].toLowerCase() == e.toLowerCase())) return i;
  }
  return -1;
}

/* Returns the index of variable index i from 
   the variable arrays.
 */
function posArrayVar(i, a){
  for (var j=0; j < a.length; j++){
      if(a[j].i == i) return j;
  }
  return -1;
}

function inArray(e, a){
 if(posArray(e,a) == -1) return false;
 else return true;
}


function inArrayVar(i, a){
 if(posArrayVar(i,a) == -1) return false;
 else return true;
}

// varName, the name of a visible variable.
function varToDatabaseName(varName){
  return document.getElementById("dataset"+variables[varToIndex(varName)].ci).value;
}

/* Returns a variable index to be used in the query. We start with
   1, thereafter increasing by 1. Variable indexes are reused after
   they are removed from vector variables.

   Arguments: ci, index of component for that variable.
              classType, a string specifying the type of the variable.
              topLevel, Boolean, true => this variable is defined at the 
                        top level of a search componentcan
   Returns: index of new variable.
*/
function getNewVarIndex(ci, classType, topLevel, outputAccessible){
    var newVar = {ci: ci, classType: classType, topLevel: topLevel, outputAccessible: outputAccessible}
    for(var i = 1; i < variables.length; i++) {
        if(variables[i] == null) {
            // Reuse this index, since this index is no longer used.
            variables[i] = newVar;
            nbVariables++;
	    updateVarSelectors();
            return i;
        }
    }
    // Create a new variable entry.
    variables.push(newVar);
    nbVariables++;
    updateVarSelectors();
    return (variables.length - 1);
}

// Returns true if the variable name appears at the top level
// A variable name is always of the form "cn" where c is a single character
// and n is an integer.
function varAtTopLevelq(varName){
  return  variables[varToIndex(varName)].topLevel;    
}


function nbVars(){
    // note: variables.length is not the number of actual variables
    return nbVariables;
}

/* Remove the variable indexes from the expr so that
   they can be reused and all the variable selectors are updated accordingly.
   This function is made faster by getting all variables defined in expr,
   removes them from the variables vector, then update the variable selectors.

   Arguments: expr, an expression structure. (a forEachWhereClause structure, a term or logop)
   Returns: recycled the variables contain in the tree from expr.
*/

function removeVarsFromExpr(expr){
    var r = getAllVarsDownFromExpr(expr);
    for(var i = 0; i < r.length; i++) variables[r[i]] = null;
    nbVariables -= r.length;
    updateVarSelectors();
}

/* From expr finds all variables defined in it. This includes all forEach variables.

   Arguments: expr, an expression structure, either a term or logOp.
   Returns: array of integers. This array can be empty.
 */

function getAllVarsDownFromExpr(expr){
    var r = new Array();
    if (expr == null) return (r);
    else {
	if(expr.type == "logOp") {
	    r = getAllVarsDownFromExpr(expr.left);
	    r = r.concat(getAllVarsDownFromExpr(expr.right));
	}
	if (expr.type == "term" && expr.forEachWhereClause != null 
	    && expr.forEachWhereClause.whereClause != null) {
	    r = r.concat(getAllVarsDownFromExpr(expr.forEachWhereClause.whereClause.expr));
	    r.push(expr.forEachWhereClause.vari);
	}
	return r;
    }}

/* 
  Remove the variable with index i from the table of variable indices.
  Update the display of the web page.
*/

function removeVarIndex(i){
    variables[i] = null;
    nbVariables--;
    // All var selectors should be modified to reflect the removal of variable i.
    updateVarSelectors();  
}

/* One or more variables were created or deleted. The variable selectors
   throughout the web page should be modified to reflect this.
*/

function updateVarSelectors(){
    // Make the variable of the first component visible if more than one variable exists;
    // otherwise makes it invisible.
    if(nbVars() > 1) {
        var textAfterClass1 = document.getElementById("textAfterClass1");
        if(textAfterClass1 != null) textAfterClass1.innerHTML = " (let's call them "+firstVar+") ";
    }
    else {
        var textAfterClass1 = document.getElementById("textAfterClass1");
        if(textAfterClass1 != null ) textAfterClass1.innerHTML = "";
    }

    // Modify the output table specification to reflect variables.
    var varsOutAccessible = accessibleVarsOutput();

    if(varsOutAccessible.length == 1 && document.getElementById("varColumn1") != null) {
        // There are var selectors but only one variable exist, this must be firstVar. Remove the var selectors.
        for(var i=1; i <= addColumnOutput.nbColumns; i++) {
            var s1   = document.getElementById("varColumn"+i);
	    // If firstVar was not selected, then firstVar will be the new selected variable and
	    if (s1.value != firstVar) forceSelection(document.getElementById("slotColumn"+i), "Name");
            var cell = document.getElementById("outColumnCell"+i);
            cell.removeChild(s1);
        }
    }
    else
        if(varsOutAccessible.length > 1 && document.getElementById("varColumn1") == null) { 
            // The var selectors do not exist yet; create them.

            // The set of possible options are the accessible variables.
            var vars = accessibleVarsOutput();

            for(var i=1; i <= addColumnOutput.nbColumns; i++) {
                /*
                  Note: a bug in the creation of closures under 
                  Mozilla 1.4 is such that if a closure is created here with
                  variable i, it does not create an environment for that closure
                  with the actual value of i, but all closures refer to the
                  same i. The function varOutSelected creates the closure which
                  solves that bug.
                */
                
                onChange = varOutSelected(i);
                s1   = createSelect(0, "varColumn"+i, vars, onChange, false, 0, "");
                cell = document.getElementById("outColumnCell"+i);
                slotSelector = document.getElementById("slotColumn"+i);
                cell.insertBefore(s1, slotSelector);
            }
        }
        else if(varsOutAccessible.length > 1 &&  document.getElementById("varColumn1") != null)
            // The var selectors already exist, change the list of variables (select options)
            for(var i=1; i <= addColumnOutput.nbColumns; i++) {
                var vars = accessibleVarsOutput();
		// Try to keep the same variable selected.
                var s1 = document.getElementById("varColumn"+i);
                var varSelected    = s1.value;
		var varStillExistq = posArray(varSelected,vars);
                // Make sure it is either the first variable of vars or the already selected variable.
                modifyOptions(s1, vars, false, (varStillExistq == -1) ? 0 : varSelected, false);
		// The list of slots may change, if the variable no longer exists.
		// varOutSelected(i) creates a function which is called here.
		if (varStillExistq == -1) (varOutSelected(i))();
            }

    //==== Search the whereclauses and update the var selectors.
    for(var j=1; j <= noComponents; j++) {
        if(document.getElementById("cellComponent"+j) != null) {
            var whereClausej = document.getElementById("cellComponent"+j).whereClause;
            if(whereClausej != null) modifyVarSelectorsExpr(whereClausej.expr);    
        }
    }
}

/* Modify the variable selectors in expr down to the leaves.
   This modification maybe required since the set of defined variables 
   may have changed for expr. Currently it is perhaps redundant in many cases
   since all those variable selectors may disappear anyway -- but we are 
   systematic in cases where these selectors would stay on the display.

   A case where all variable selectors may disappear: a new slot for a foreach has been
   selected removing its associated variable; the where clause underneath will be removed.
 
   A case where these variable selectors may need to be changed: they
   refer to a top variable from another search component which is being
   removed.

  Arguments: expr, a term of logop structure.
  Returns: side effects of the modifications 
*/

function modifyVarSelectorsExpr(expr){
    if (expr == null) return;

    if (expr.type == "term") {
	var vars = accessibleVars(expr.father);
	// The left factor needs modifications?
	if (expr.varLeft != null) { 
	    var varSelected        = expr.varLeft.value;
	    var varSelectedExistsq = posArray(varSelected, vars);
	    modifyOptions(expr.varLeft, vars, false, firstVisibleVar(expr.father), false);
	    // If the selected var no longer exists, the list of slots may need to be changed with
	    // other type of consequences.
	    if (varSelectedExistsq == -1) slotLeftSelected(expr);
	}
	// The special where clause needs modifications?
	if (expr.forEachWhereClause != null) modifyVarSelectorsExpr(expr.forEachWhereClause.whereClause.expr);
	// The right factor needs mofications?
	if (expr.varRight != null) {
	    var varSelected        = expr.varRight.value;
	    var varSelectedExistsq = posArray(varSelected, vars);
	    modifyOptions(expr.varRight, vars, false, firstVisibleVar(expr.father), false);
	    // If the selected var no longer exists, the list of slots may need to be changed with
	    // other type of consequences.
	    if (varSelectedExistsq == -1) varRightSelected(expr);
	}
    }
    else if (expr.type == "logOp") {
	modifyVarSelectorsExpr(expr.left);
	modifyVarSelectorsExpr(expr.right);
    }
}

/* Given a node from a tree expression, returns an array of variable names (strings) that can
   be referred by this node.

   Arguments: treeNode, a term or expr structure. 
   Returns: an array of variable names (strings).
*/

function accessibleVars(treeNode){
    var i  = treeNode.ci;

    // Append the variables available from the where clauses and the 
    // other components high level variables.
    var r1 = accessibleForEachVars(treeNode);
    var r2 = accessibleVarsComponents(i);

    return ((r1.concat(r2)).sort());
}

/* Returns all variables defined from tn up to the root of the 
   first where clause in a search component. It gives the scope (visibility)
   of all variables from tn.

  Arguments: tn, a term or expr structure.
  Returns: an array of variable names (strings).
*/

function accessibleForEachVars(tn){
    if (tn == null) return new Array();
    else if (tn.forEachWhereClause == null) return accessibleForEachVars(tn.father); 
    else { 
        // The term tn has a foreach where clause, but no right side.
        var r = accessibleForEachVars(tn.father);
        // Add the foreach variable only if it is not in the process of being deleted.
	if (variables[tn.forEachWhereClause.vari] != null)
	    r.push(indexToVar(tn.forEachWhereClause.vari));
        return r;
    }
}

/* Arguments: i, the index of a component or a large value to signify "all components". 
   Returns: an array of variable names (strings) from the first level of all 
            components from 1 to i, or all of the the components if i is large enough.
 */

function accessibleVarsComponents(i){
    var r  = new Array();
    var cs = document.getElementById("components");

    for(var j=0; j < cs.childNodes.length; j++){
        var c = cs.childNodes[j];
        if (c.id.substring(0,13) == "lineComponent") {
            var k = parseInt(cs.childNodes[j].id.substr(13));
	    var vari = document.getElementById("cellComponent"+k).variable;
            if (variables[vari] != null) r.push(indexToVar(vari));
            if (k == i) return r;
        }}
    return r;
}

/*
   Returns the variables accessible for the output specification.
   These includes all the variables at levels 1 and 2 from each search
   component from the page.

*/

function accessibleVarsOutput(){
    var vars = new Array();
    for (var i=0; i < variables.length; i++){
	if (variables[i] != null && variables[i].outputAccessible) vars.push("Z"+i);
    }
    return vars;
}

/* Return the first visible variable when going from expr to the root going
   through several where clauses if necessary. 

   Arguments: expr, an expression structure, a term, a logOp. It must not be null.
   Returns: the index of an active variable in the scope of expr.
*/
function firstVisibleVar(expr){
    if (expr.type == "term" && expr.forEachWhereClause != null)
	return (expr.forEachWhereClause.vari);
    else if (expr.type == "logOp" && expr.whereClause.forEachWhereClause != null)
	return (expr.whereClause.forEachWhereClause.vari);
    else if (expr.father != null)
	return firstVisibleVar(expr.father);
    else return (document.getElementById("cellComponent"+expr.ci).variable);
}

/* Returns all variables used in the query. 
 
   Arguments: nothing.  
   Returns: an array of strings, i.e. variable names.
*/
function allVars(){
    var r = new Array();
    // Note: there is no valid variable at index 0.
    for(var i=1; i < variables.length; i++){
	if(variables[i] != null) r.push(indexToVar(i));
    }
    return r;
}

// Returns a string representing x as a two-digit hexadecimal number.
function hex(x){
    var hex = "0123456789ABCDEF";
    return hex.substr(x >> 4,1)+hex.substr(x&15, 1);
}

function toRGB(r,g,b){
    return "#"+hex(r)+hex(g)+hex(b);
}

function forceSelection(selector, name){
    selector.value = name;
}

/* 
   A search component is a major unit of the query page. It contains
   a dataset and a class from that dataset. A
   where clause can be added to it by the user. 
   Several search components can be added -- and removed --
   by the user as well.

   For the first search component no variable is displayed; but adding
   another search component does require the user to be able to
   reference one of the two search component objects -- variables are
   then displayed for the user to refer to the appropriate search
   component objects. This is even more so for more than two search
   components.

   Arguments: i, the index of the component to create.
              op, a string describing the type of operation for the search component
   Returns: a div element.
*/

function createSearchComponent(i, op){
    var division = document.createElement('div'); 
    division.id = "lineComponent"+i;

    var cell = document.createElement('div'); 
    cell.className = 'searchComponent';
    cell.id = "cellComponent"+i;
    cell.op = op;

    division.appendChild(cell);

    var text1 = document.createElement('span');

    switch (op) { 
    case "firstAdd" : text1.innerHTML = "Query database  "; break;
	// case "merge": text1.innerHTML = "merge with results from database "; break;
	// case "addNewSearch": text1.innerHTML = "and Query database "; break;
    case "crossProduct": text1.innerHTML = "for each previous result, query database ";
    }
    
    cell.appendChild(text1);

    cell.whereClause = null;
    // The database selected is the search database from the banner.

    var iDataset =  posArray(bannerOrgID(), dataSetsStructuredForm);
    // iDataset is -1 if bannerOrgID is not found in dataSetsStructuredForm.
    // Note: dataSetsStructuredForm contains two strings for each database name.
    //       Therefore, if found, iDataset is twice the index to select. 
    //       See below expression 'iDataset/2'.

    // Try ECOLI if unsucessful so far.
    if(iDataset == -1) iDataset = posArray('ECOLI', dataSetsStructuredForm); 
    // Finally, it is the first one if nothing found so far.
    if(iDataset == -1) iDataset = 0;
    var datasetSelect = createSelect(i, 'dataset'+i, dataSetsStructuredForm, 
				     function(){datasetSelected(i)}, 
				     true, iDataset/2, "db:");
    cell.appendChild(datasetSelect);
    var databaseName = datasetSelect.value;

    var textBeforeClassSelector = document.createElement('span');
    textBeforeClassSelector.innerHTML = " for  ";

    var classSelect = createSelect(i, "class"+i, restrictedClasses, function(){classSelected(i)}, true, 0, "class:");
    var className = classSelect.value;
    restrictedClassesWithNbInstances(databaseName, classSelect.options, restrictedClasses);


    // This is a top level (level 1) variable. It is accessible for the output specification.        
    cell.variable = getNewVarIndex(i, className, true , true);
    cell.appendChild(textBeforeClassSelector);  
    cell.appendChild(classSelect);

    // The text after the class selector
    var textAfterClassSelector = document.createElement('span');
    textAfterClassSelector.id = "textAfterClass"+i;
    if(i > 1) {
        // Make the variable name visible for this component 
        textAfterClassSelector.innerHTML = " (let's call them "+indexToVar(cell.variable)+") ";
    }
    else textAfterClassSelector.innerHTML = "";
    cell.appendChild(textAfterClassSelector);

    var selAddWhere = createButton("clickAddWhere"+i, 
	  		           "add a condition", 
                                   function(){addWhereClauseToComponent(i)});
    selAddWhere.style.background = 'white';
    cell.appendChild(selAddWhere);

    // All but the first component can be removed by the user.
    if(i != 1){
        //var clickRemoveSearch = createButton("removeSearch"+ii(), "remove", 
	//				     function(){removeSearchComponent(i)});
        var clickRemoveSearch = createClickableImage("removeSearch"+ii(), "/x.gif",
						     function(){removeSearchComponent(i)}, 
						     "Remove this search component");
	clickRemoveSearch.style.background = 'white';
        cell.appendChild(clickRemoveSearch);
    }
    return division;
}

/* Modify the text attribute of an array of options for class names with
   the number of instances. The array has an even number of strings as
   the global restrictedClasses in schema.js. At even indices are the
   string values used in BioVelo, at the odd indices the strings shown
   to the user.

   Arguments: options, array of option objects. 
   Returns: nothing, options[].text is changed for one or all options.
*/
function restrictedClassesWithNbInstances(databaseName, options, classNames){
    // The server is slow. Get number of instances only for the selected class.
    for(var i=0; i < options.length; i++) {
	// The class name as used by BioVelo.
	var className = options[i].value;
	// Nb of instances for the selected option only.
	if(!slowServer || options[i].selected) {
	    options[i].text = classNames[2*i+1]; 
	    // The number of instances comes from the server, probably cached.
	    cacheServerObj.applyNbInstances(databaseName, className, 
					    createFuncModifyOptionText(options[i], 
                                                                       classNames[2*i+1], "instances",""));
	}
	else options[i].text = classNames[2*i+1]; 
    }
}


/* Modify an array of options text attribute with the number of non nil values for
   each of these slots.
   
   Arguments: databaseName, a string, the database name to query.
              options, array of option objects.
              className, a string, the className to query.
	      slotNames, array of strings, slot names, two strings per slot. At even indices are
  	               the slot names to use for BioVelo, at odd indices the slot names to show
		       to the user.
              textPrefix, string to prefix the number appearing in the slot, e.g. "(up to )"
   Returns: nothing, but the array options is modified for attribute text.
*/
function slotNamesWithNbNonNils(databaseName, options, className, slotNames, textPrefix){
    for(var i=0; i < options.length; i++) {
	// The class name as used by BioVelo.
	var slotName = options[i].value;
	// Nb of non nil values for the selected option only.
	if (!slowServer || options[i].selected) {
	    options[i].text = slotNames[2*i+1]; 
		// The number of non nil values comes from the server, probably cached.
	    cacheServerObj.applyNbSlotsNonNil(databaseName, className, slotName, 
					  createFuncModifyOptionText(options[i], 
                                                                     slotNames[2*i+1], 
                                                                     "values", textPrefix));
	}
	else options[i].text = slotNames[2*i+1];
    }
}


/* Create a function to modify option.txt. Used in asynchronous tasks.

 I: text1, string to prefix the text in option.txt.
    text2, string that goes before ")", e.g., " values"
    text3, to add right after"(" only if s is a number larger than 0.
*/
function createFuncModifyOptionText(option, text1, text2, text3){
  return function(s){ if (s.length < 11 && s.length > 0) 
                         if (parseInt(s) > 0) option.text = text1+" ("+text3+s+" "+text2+")"
                         else option.text = text1+" ("+s+" "+text2+")"
                    };
}
 
function queryNbInstances(databaseName, className){
  return 'query?submit=Submit+Query&object=("TABULATED"+"%23[ajax:ajax<-'+databaseName+'^^'+className+']")&debug=t';
}

// Generate the string to query the web server to get the number of
// slots, from a database and class, having a non-nil value.
function queryNbNonNilSlots(databaseName, className, slotName){
    // TBD: use the new BioVelo operator ^! which accessed the raw value of the slot without special
    //      pre-processing.
  return 'query?submit=Submit+Query&object=("TABULATED"+"%23[ajax:ajax<-'+databaseName+'^^'+className+',ajax^'+slotName+']")&debug=t';
}


/* The selector of a class has been changed by the user.  This may
   need to change the list of options of several other slot selectors.
   It may have a cascade effect in changes on the display.

   Note the following case: another search component where clause refers to the variable
   associated with the class selector. That variable list of slots may change, resulting
   potentially in another slot selected which may erase a for each where clause
   created from that slot.

   There is only one class selector by search component.

   Arguments: i, the index of the component for which a class was selected.
   Returns: the display of the HTML page is modified according to this selection.
*/

function classSelected(i){
    // The class name selected    
    var select    = document.getElementById("class"+i);    
    var selectedIndex = select.selectedIndex;
    var className = select.value;

    // The variable name for that component. For the first component, no variable
    // is attached to the class slots.
    var vari    = document.getElementById("cellComponent"+i).variable;
    var varName = indexToVar(vari);

    variables[vari].classType = className;

    // Get the number of instances in the database for this class and insert the result
    // on the web page near the class name.
    var databaseName = document.getElementById("dataset"+i).value;

    // Asynchronously insert the number of instances for the selected
    // class.  It is done here only if the server is slow, otherwise
    // it is done when all the options are created.

    if (slowServer) {
     select.options[selectedIndex].text = restrictedClasses[2*selectedIndex+1];
     cacheServerObj.applyNbInstances(databaseName, className, 
                                    createFuncModifyOptionText(select.options[selectedIndex], 
							       restrictedClasses[2*selectedIndex+1],
                                                               ""));
    }

    // When the first (top search component) changes class, the output specification
    // changes according to that class with possible new columns.
    var predefinedSlots      = outputSlotNames.get(className, databaseName);
    var nbSlotNamesAvailable = predefinedSlots.length;
    var iSlotNameAvailable   = 0;

    // Search the output table specification for variable varName

    for(var j=1; j <= addColumnOutput.nbColumns; j++){
	var varNameSelected = (accessibleVarsComponents(1000).length == 1) ? firstVar :
	                      document.getElementById('varColumn'+j).value;
	if(varName == varNameSelected) {
	    // Change the list of options for this variable.
	    var slotColumnj = document.getElementById("slotColumn"+j);

	    if (varNameSelected == firstVar && iSlotNameAvailable < nbSlotNamesAvailable) { 
		var slotNameToSelect = predefinedSlots[iSlotNameAvailable];
		iSlotNameAvailable++;
		// Force the selection of the slot name whatever was selected before.
		modifyOptions(slotColumnj, classesSlots[className], true, slotNameToSelect, true);
 		slotNamesWithNbNonNils(databaseName, slotColumnj.options, className, classesSlots[className], "up to ");
	    }
	    else if (varNameSelected == firstVar) { 
		removeColumnOutput(j); j--;
	    }
	    else {
		modifyOptions(slotColumnj, classesSlots[className], true, "Name", false);
 		slotNamesWithNbNonNils(databaseName, slotColumnj.options, className, classesSlots[className], "up to ");
	    }
	}      
    }

    // Add the remaining columns necessary to have all 
    // predefined slots for this className. 

    for(var j=iSlotNameAvailable; j < nbSlotNamesAvailable; j++) {
	addColumnOutput(predefinedSlots[j], false);
    }

   
    // Search all where clauses for varName
    for (var j=1; j <= noComponents; j++) {
        if (document.getElementById("cellComponent"+j) != null) {
            var whereClausej = document.getElementById("cellComponent"+j).whereClause;
            if (whereClausej != null) modifyVarClass(j, whereClausej.expr, varName, className);    
        }
    }
}


/* A variable has changed the class it is bound to: the list of slots
   associated with this variable is changed in the expression tree rooted
   at expr.

   Arguments: i, the index of the component containing the expression expr.
              expr, an expression structure containing the varLeft selector
              varName, a string or null. If null, it means that all list of slots should be changed regardless 
                       of the varName associated with it.
              className, a string representing the new class of varName.

   Returns: various effects on the display of the web page.
 */
function modifyVarClass(i, expr, varName, className){
    if(expr == null) return;

    if(expr.type == "term") {
	// The left factor may be affected.
        if((varName == null && expr.varLeft == null) || (expr.varLeft != null && expr.varLeft.value == varName)) { 
	    // Verify that the slot selected value changes before calling slotLeftSelected
	    var oldSlotValue = expr.left.value;
	    var databaseName = document.getElementById("dataset"+i).value;
            modifyOptions(expr.left, classesSlots[className], true, "Name", false);
	    slotNamesWithNbNonNils(databaseName, expr.left, className, classesSlots[className],"");

	    var newSlotValue = expr.left.value;

            // Changing the list of slots may also modify the list of operators for that slot.
	    // The slotLeftSelected can remove an entire where clause, do this only
	    // if the slot value really changed. (For example changing Binding-Reactions
	    // to Reactions while the slotLeft is on Enzyme-Reactions should not remove any where
	    // clause)
            if (newSlotValue != oldSlotValue) slotLeftSelected(expr);
        }

	// The right factor may be affected.
        if (expr.right != null && expr.right.id.substring(0,5) != "input" 
            && (varName == null || (expr.varRight != null && expr.varRight.value == varName))) {
            modifyOptions(expr.right, classesSlots[className], true, "Name", false);
	    slotNamesWithNbNonNils(databaseName, expr.varRight.options, className, classesSlots[className],"");
	    adjustTypeRightFactor(expr);
        }

	// Go into the forEach where clause if one is present.

	if (expr.forEachWhereClause != null) {
	    modifyVarClass(i, expr.forEachWhereClause.whereClause.expr, varName, className);
	}
    }
    else { // This is a logOp node. Go to the left and right.
        modifyVarClass(i, expr.left,  varName, className);
        modifyVarClass(i, expr.right, varName, className);
    }
}


function initFirstComponent(){
    addSearchComponent(0);
}

// Remove from the display all search components.

function removeAllComponents(){
    var cs = document.getElementById("components");
    while(cs.childNodes[0] != null) cs.removeChild(cs.childNodes[0]);

    noComponents = 0;
}

/* Given the index i of a search component, find the
   next search component displayed after it on the web page.

  Arguments: cs, the head of the seach components.
             i, index of a search component.
  Returns: an HTML search component object.
*/ 

function nextSearchComponent(cs, i){
    for(var j=1 ; j < cs.childNodes.length; j++){
        if(cs.childNodes[j].id.substring(0,11) == "lineAddComp" && 
           parseInt(cs.childNodes[j].id.substr(11)) == i) 
           return (j+1 < cs.childNodes.length) ? cs.childNodes[j+1] : null;
    }
    alert("nextSearchComponent"+i+", internal error. Please fill a bug report.");
}


/* Add a new search component after component i. If i is 1, there
   are no previous components, so that component 1 is the initial
   component not inserted after any other component. Note that the
   component indices are not necessarily increasing from top to bottom
   when displayed since insertion occurs at any point -- and the variable
   noComponents never decreases even after a component is removed.

   Arguments: i, the index of an already existing component.
   Returns: create a new search component and display it after component i.
*/

function addSearchComponent(i) {
    noComponents++;
    var j        = noComponents; // The index of this new component
    var cs       = document.getElementById('components');
    // The possible operations are defined in vector insertSearchOps
    var op       = ( i > 0) ? document.getElementById('search'+i).value : "firstAdd";
    
    if (op == "insertSearch") return;
    if (i > 0) document.getElementById('search'+i).value = "insertSearch";

    // The main work is done by createSearchComponent.
    var initComp = createSearchComponent(noComponents,op);

    // Create a select where the user can insert yet another search component
    var line = document.createElement("div"); 
    line.id  = "lineAddComp"+noComponents;
    var cell = document.createElement("div"); 
    var addComp = createSelect(j, "search"+j, insertSearchOps, 
			       function(){addSearchComponent(j)}, true, 0, "select:");

    // addComp.style.background = 'white';
    cell.appendChild(createVerticalSpace());
    cell.appendChild(addComp);
    line.appendChild(cell);
    line.appendChild(createVerticalSpace());

    // There is no insertAfter() in DOM. You have to add
    // before the next component. If i is 0 then no component
    // exist yet.
    if(i != 0) { 
        var nextC = nextSearchComponent(cs,i);
        if(nextC == null) { 
            cs.appendChild(initComp);
            cs.appendChild(line);
        }
        else {
            cs.insertBefore(initComp, nextC);
            cs.insertBefore(line, nextC);
        }
    }
    else {
        cs.appendChild(initComp);
        cs.appendChild(line);       
    }
    // A new variable was created, need an update everywhere to take it into account.

    updateVarSelectors();
}

/* Remove the search component i from the web page. All variables defined
   in it are also unbound.

   Arguments: nothing.
   Returns: the display is affected as the component and all its where clause, if any, are removed.
            All variables defined in that component, including the where clause if any, are unbound.
 */

function removeSearchComponent(i) {
    var cs   = document.getElementById("components");
    var c1   = document.getElementById("lineComponent"+i);
    var c2   = document.getElementById("lineAddComp"+i);
    var cell = document.getElementById("cellComponent"+i);
    var cellVar = cell.variable;

    // Remove from the display.
    cs.removeChild(c1);
    cs.removeChild(c2);

    // Unbound all variables defined in this component.
    removeVarIndex(cellVar);
    if (cell.whereClause != null) removeVarsFromExpr(cell.whereClause.expr);
    hideTooltip();
}

/* A dataset has been selected for component i. */

function datasetSelected(i){
    var databaseName = document.getElementById('dataset'+i).value;
    var classSelect  = document.getElementById('class'+i);
    restrictedClassesWithNbInstances(databaseName, classSelect.options, restrictedClasses);
    // All slot selectors for this class need to be updated for the
    // correct number of non nil values with this new database
    // selected; and also the output specification may have to be
    // changed.
    classSelected(i);
}

/* Given a variable name, returns the class name associated to it. 
*/
function varToClassName(varName){
    return variables[varToIndex(varName)].classType;
}

/* A variable of a term has been selected. The select form for the slot
   should be modified for the right options. If the type does not
   change, nothing is done so that the previous selection is kept
   unchanged.  If the current selected slot exists for the new type
   than it is automatically reselected so that the user does not 
   have to select it again. If a new slot selection occur, the associated effects
   are applied -- for example, this may remove a for each clause.

   Arguments: selectVar, a select HTML element;
              selectSlot, a select HTML element;
              term, a term node of a logical expression.
   Returns: the list of options (slot names) for selectSlot are potentially modified to 
      reflect the type of the variable selected.
*/

function varLeftSelected(selectVar, selectSlot, term){
    var varNameSelected = selectVar.value;
    var slotSelected    = selectSlot.value;
    var className       = varToClassName(varNameSelected);

    var valueSelected   = modifyOptions(selectSlot, classesSlots[className], true, "Name", false);
    slotNamesWithNbNonNils(varToDatabaseName(varNameSelected), selectSlot.options, 
			   className, classesSlots[className], "");
    if (valueSelected != slotSelected) slotLeftSelected(term);
}

/* It is similar to function varLeftSelected, but for a right factor there is no
   major effect on the display except for the list of attributes (slots).
*/

function varRightSelected(term){
    var selectVar       = term.varRight;
    var varNameSelected = selectVar.value;
    var className       = varToClassName(varNameSelected);
    modifyOptions(term.right, classesSlots[className], true, "Name", false);
    slotNamesWithNbNonNils(varToDatabaseName(varNameSelected), term.right.options, 
			   className, classesSlots[className], "");
    adjustTypeRightFactor(term);
}

/* A variable for the output has been selected. Change the list
   of options for the slots if this is needed.

   Arguments: iC, column index of the selected var.  
   Returns: the list of options (slot names) for the slot selector of column i in the output specification
            is potentially modified to reflect the type of the variable selected.
*/

function varOutSelected(iC){
    return function() {
       var varNameSelected = document.getElementById("varColumn"+iC).value;
       var classSelected   = varToClassName(varNameSelected);
       var slotColumni     = document.getElementById("slotColumn"+iC);
       modifyOptions(slotColumni, classesSlots[classSelected], true, "Name", false);
       slotNamesWithNbNonNils(varToDatabaseName(varNameSelected), slotColumni.options, 
			      classSelected, classesSlots[classSelected], "up to ");
    }
}

/* Returns a span HTML object that is clickable by the user.

    Arguments: text, a string to display and over which the user can click.
               onClick, a closure of zero parameters to call when the text is clicked.
    Returns: a span HTML object.
*/

function createClickableText(id, text, onClick){
    //  alert(onClick);
  var span =  document.createElement('span');
  span.style.cursor  = "pointer"; // on Mozilla this will be a hand pointer.
  span.innerHTML   = text;
  span.id = id;
  // span.setAttribute('onClick', onClick);
  span.onclick = onClick;
  
  // span.onMouseOver = function(){alert('a')}; // IE
  return span; 
}

// Return an input button element with given id, text and onClick event.
function createButton(id, text, onClick){
    //  alert(onClick);
  var button   = document.createElement("input");
  button.id    = id;
  button.type  = 'button';
  button.value = text;

  // span.setAttribute('onClick', onClick);
  button.onclick = onClick;
  
  // span.onMouseOver = function(){alert('a')}; // IE
  return button; 
}

// Return a clickable image element with given id, src image, text tooltip and onClick event.
// Arguments: src, a URL as a string.
//
function createClickableImage(id, src, onClick, textToolTip){
    var img = document.createElement('img');
    img.style.cursor = "pointer";
    img.id  = id;
    img.src = src;
    img.onclick = onClick;
    // img.onMouseOver = function(){showTextToolTip(img, textToolTip)}; // IE
    img.setAttribute('onMouseOver', "return overlib('"+textToolTip+"');");
    img.setAttribute('onMouseOut', "return nd()");
    return img;
}

/* Returns a div element that will contain the entire where clause. A
   where clause can be in a term (the foreach part of that term) or in
   a search component. If it is in a term, the term is provided as a parameter.
   The major element of a where clause is the member expr which contains the root
   of the tree of the logical expression of that where clause.

   Arguments: i, index of the component containing this whereClause. 
              classNames, an array of classe names; for example ["Reactions", "Proteins", "RNA"];
              term, non null if the where clause goes inside a forEach for that term;
              headerText, a string that goes at the beginning of the displayed where clause.

   Returns: a div element with headerText and expr component.
 */

function createInitialWhereClause(classNames, term, i, headerText){
   var c                 = document.getElementById("cellComponent"+i);
   var whereClause       = document.createElement('div');
   whereClause.id        = "whereClause"+i;
   whereClause.className = 'whereClause';
   var listOfSlots       = classNamesToSlots(classNames);
   var databaseName      = document.getElementById('dataset'+i).value;

   // The default slot selected should be 'name' if it exists.
   var slotNameSelected = "NAME";
   var selectSlot       = createSelect(i, "slotLeft"+ii(), listOfSlots, null, 
				       true, slotNameSelected, "slot:");
   // The actual slot name selected.
   var slotName         = selectSlot.value;
   // The number of slots having a non-nil value
   // TBD: not quite correct when classNames has several class names. Possible
   // (expensive) correction: add all the numbers for all classes in classNames.
   slotNamesWithNbNonNils(databaseName, selectSlot.options, classNames[0], listOfSlots, "");

   // The left variable selector.

   var vars             = (term == null) ? accessibleVarsComponents(i) : accessibleVars(term);   
   var selectVarLeft    = createSelect(i, "slotLeftVar"+ii(), vars, null, false, 
				       indexToVar((term==null) ? c.variable : firstVisibleVar(term)), "var:");

   var relOps           = leftTypeToOperators(slotsType[slotNameSelected])
   var selectRelOp      = createSelect(i, "relOp"+ii(), relOps, null, true, 0, "op:");   

   var right = createConstantRightFactor(i, slotsType[slotNameSelected], relOps[0], null, null);
   var left = { type: "term", ci: i, repeatOp: null, forEachWhereClause: null, father: null,
                varLeft: selectVarLeft,  left: selectSlot, relOp : selectRelOp, 
                varRight: null, right: right, switchRight: null };
   selectVarLeft.onchange = function(){varLeftSelected(selectVarLeft, selectSlot, left)};
   selectSlot.onchange = function(){slotLeftSelected(left)};
   left.relOp.onchange = function(){relOpSelected(left)};

   var switchRight  = createButton("switch"+ii(), "Switch to variable entry", 
				   function(){ switchRightFactor(whereClause, left) });
   switchRight.style.background = 'white';
   left.switchRight = switchRight;
   
   // Initialize the where clause

   whereClause.headerText = headerText; // Keep the header for future display 

   var rootExpr     = {type: "logOp", ci: i, left: left, op: null, prec: null, right: null, father: term, whereClause: whereClause};
   left.father      = rootExpr;
   whereClause.expr = rootExpr;
   var selectLogOp  = createSelect(i, "logOp"+ii(), logicalOps, 
                                   function(){ logOpSelected(whereClause, rootExpr) }, true, 0, "op:");   
   rootExpr.op  = selectLogOp;
   return whereClause;
}


// Redisplay the where clause indenting it by n spaces.
// Arguments: where, a div HTML element.
function restartDisplayWhereClause(where, n) {
    hideTooltip();
    removeExprWhere(where);
    displayWhereClause(where, n);
}

// Returns the concatenation of string s, n times.
function repeatStr(s, n){
    var r = "";
    for(var i = 0; i < n; i++) r += s;
    return r;
}

// Returns an HTML span object of n non-breakable/non-squashable spaces.
function horizontalSpace(n){
    var r = document.createElement('span');
    r.innerHTML = repeatStr("&nbsp;",n);
    return r;
}

// Display the where clause indenting it by n spaces.
// Arguments: where, a div HTML element.
function displayWhereClause(where, n){
    where.appendChild(createVerticalSpace());
    var span = document.createElement('span');
    where.appendChild(horizontalSpace(n));
    span.innerHTML = where.headerText;
    where.appendChild(span);
    appendExprToWhere(where, where.expr, n);
}

// Removes the where clause.
function removeExprWhere(where){
    // Notice that we repeatedly remove on index 0, since once a child is removed,
    // all indices are shifted towards 0.
    while(where.childNodes.length > 0) where.removeChild(where.childNodes[0]);
}

// Append to the display of the where clause the expression expr, indenting it by n spaces.
function appendExprToWhere(where, expr, n){
   if (expr == null ) return;
   else
   if (expr.type == "term") {
       // It is a term.
       if(expr.repeatOp != null) { 
	   where.appendChild(expr.repeatOp);
	   // Put "in" if a for each exists.
	   if (expr.forEachWhereClause != null) where.appendChild(createSpanText("&nbsp;in&nbsp;"));
       }
       if(expr.varLeft != null && (expr.varLeft.options.length > 1)) where.appendChild(expr.varLeft);
       // The slot selector for the left factor.
       where.appendChild(expr.left);

       if(expr.forEachWhereClause != null) {
	   // There are two quite different cases: one where there is a single value
	   // and the other where the slot is a list of frames.
           if(expr.relOp  != null && (expr.repeatOp == null)) where.appendChild(expr.relOp);
           // A foreach where clause introduces an embedded where clause. In that case
           // there is no right term for expr.
	   where.appendChild(createSpanText("&nbsp;of type&nbsp;"));
           where.appendChild(expr.forEachWhereClause.classSelect);
           where.appendChild(createSpanText(" (let's call&nbsp;"+ ((expr.repeatOp == null) ? "it&nbsp;" : "them&nbsp; ")
					    +indexToVar(expr.forEachWhereClause.vari)+") "));
           where.appendChild(expr.forEachWhereClause.whereClause);
           // A foreach where clause introduces an embedded where clause
           restartDisplayWhereClause(expr.forEachWhereClause.whereClause, n+2);
       }
       else {
           if(expr.relOp    != null) where.appendChild(expr.relOp);
           if(expr.varRight != null && expr.varRight.options.length > 1) where.appendChild(expr.varRight);
           if(expr.right    != null) where.appendChild(expr.right);
           if(expr.switchRight != null) where.appendChild(expr.switchRight);
       }
   }  
   else {
       appendExprToWhere(where, expr.left, n);
       where.appendChild(createVerticalSpace());
       where.appendChild(horizontalSpace(n));
       where.appendChild(expr.op);
       if (expr.prec != null) where.appendChild(expr.prec);
       appendExprToWhere(where, expr.right, n);
   }
}

/* Does the string s corresponds to a basic type (not a frame type)? 

   Arguments: s, a string.
   Returns: boolean.
*/

function basicTypeQ(s){
    return (s == "STRING" || s == "NUMBER" || s == "BOOLEAN" || s == "ENUMERATED");
}

/* Return true if the string is a slot name that can contain frames; false otherwise.
 */

function stringSlotFrameQ(s) {
    return (slotsType[s] && typeFrameQ(slotsType[s]));
}

/* Return true if the type can contain frames; false otherwise.
 */

function typeFrameQ(type){
    if (basicTypeQ(type[1])) return false;
        else if (type[1] != "UNION") return true;
        else for(var i=0; i < type[2].length; i++) if (!basicTypeQ(type[2][i])) return true;
        else return false;
}

/* Add an initial where clause to search component i. The initial where clause is created
   by this function. An initial where clause contains one term and one yet to be selected
   logical operator. New terms will be added when the logical selector is selected by the
   user.

   Arguments: i, an integer, the index of a search component.
   Returns: the display of the where clause.
*/

function addWhereClauseToComponent(i){
    var lineComponent = document.getElementById('lineComponent'+i);
    var cellComponent = document.getElementById('cellComponent'+i);
    var classNames    = new Array()
    classNames.push(document.getElementById('class'+i).value);

    var whereClause = createInitialWhereClause(classNames, null, i, "Where"+spaces(2));

    // Repace the clickable text to add a where clause.    
    var clickAddWhere = document.getElementById('clickAddWhere'+i);

    var clickRemoveWhere = createButton("clickRemoveWhere"+i, 
     					"remove condition",
					function(){removeWhereClauseFromComponent(i)});
    clickRemoveWhere.style.background = 'white';
    cellComponent.replaceChild(clickRemoveWhere, clickAddWhere);

    cellComponent.whereClause = whereClause;
    cellComponent.appendChild(whereClause);
    restartDisplayWhereClause(whereClause, 0);
}

/* This function does the opposite of addWhereClauseToComponent.
   This is usually triggered by clicking a HTML text.

   Arguments: i, a component index.
   Returns: the where clause is removed on the display and in the
      object of search component i.
*/

function removeWhereClauseFromComponent(i){
   var ci          = document.getElementById("cellComponent"+i);
   var whereClause = ci.whereClause;

   removeVarsFromExpr(ci.whereClause.expr);
   ci.whereClause = null;
   ci.removeChild(whereClause);

   var clickRemoveWhere = document.getElementById('clickRemoveWhere'+i);
   var clickAddWhere = createButton("clickAddWhere"+i, "add a condition", 
                                     function(){addWhereClauseToComponent(i)});
   clickAddWhere.style.background = "white";
   ci.replaceChild(clickAddWhere, clickRemoveWhere);
}

/* When a logical operator is selected, which implies creation of another subcondition,
   add one more term of type "logOp" and a term of type "term" to its right. The code
   must take into account a reselection of the boolean operator, in
   which case no term should be added.

   An expression has a left and right subtrees. The left is displayed
   first, so that the root is displayed last.

   Example, an expression (((d-c)*b)+a) is represented as a (left leaning) tree:

                             ---
                           /
                          +
                        /  \
                       *    a
                     /  \
                    -    b
                  /  \
                 d    c

   It is displayed vertically as 
                   
                    d
                 -  c
                 *  b
                 +  a
                --- 

   The root where the label "---" is located is the point of insertion of a new term
   when the logical operator is selected to something else than "---" (e.g., "and").

   A forEach where clause replaces the right side of a term (that is no right side
   is present when a forEach exist for a term) . For example,

      where for each x2 in C1
            we have s1 = 1
            and for each x3 in c2
                we have s2 = 2
            ---

   The expression structure is,

     { left:  C1, forEachWhereClause: { whereClause: the expr condition "we have", vari: 2 }, 
       right: null, ... }

   Arguments: whereClause, the where clause containing the expression expr;
              expr, the expression that contains the logical operator selector
                    that was selected. It is a node of type "logOp".
  
*/

function logOpSelected(whereClause, expr){
    var i            = expr.ci;
    var databaseName = varToDatabaseName(indexToVar(i));
    var className    = varToClassName(indexToVar(i));
    var logOp        = expr.op;

    // Remove logOp (so its right side will be gone) from the tree by moving up its left side.
    if(logOp.value == "remove") {
        if(expr.father != null) {
            expr.father.left = expr.left;     // Move up the left side in the tree
            expr.left.father = expr.father;   // The left side father is not expr anymore, but the father of expr. 
	    // remove all the vars that may be defined on the right side of expr. (possible?)
	    removeVarsFromExpr(expr.right);
        }
        restartDisplayWhereClause(document.getElementById('cellComponent'+i).whereClause,0);
        return;
    }

    logOp.options[0] = new Option("remove condition", "remove", false, false);

    // If the right side is non existent, then a term should be added.
    // This can only happened if expr is at the root of the entire
    // conditional expression.

    if (expr.right == null) {
        // The fuse term functionality has not been implemented to keep the interface
        // simpler. It was supposed to provide a mean to change the logical operator precedence.
        // But this is not necessary since the user can rewrite the logical expression in the
        // prefedined associativity provided by the interface.
        //
        // var precedence   = createClickableText("fuse Term",  function(){fuseTerms(expr)});
        // expr.prec  = precedence;

        // Construct a new term.
        var trySelectingThisSlot = posArray("Name", classesSlots[className]);
        var slotIndexSelected    = (trySelectingThisSlot >= 0) ? (trySelectingThisSlot/2) : 0;
        var selectSlotLeft   = createSelect(i, "slotLeft"+ii(), classesSlots[className], null, true, 
					    slotIndexSelected, "slot:");   
	slotNamesWithNbNonNils(databaseName, selectSlotLeft.options, className, classesSlots[className],"");

        var typeSlot0        = slotsType[classesSlots[className][slotIndexSelected*2]];
        var relOps           = rightTypeToOperators(typeSlot0);
        
        var vars             = accessibleVars(expr);

	// Select the first visible variable of the current scope.
        var selectVarLeft    = createSelect(i, "slotLeftVar"+ii(), vars, null, false, 
					    indexToVar(firstVisibleVar(expr)), "var:");
        
	// The term created goes to the right of expr.

        var right = { type: "term", ci: i, repeatOp: null, forEachWhereClause: null, father: expr,
                      varLeft: selectVarLeft,  left: selectSlotLeft, relOp: null, 
                      varRight: null, right: null, switchRight: null};

        selectVarLeft.onchange = function(){varLeftSelected(selectVarLeft, selectSlotLeft, right)};
        expr.right = right;
        selectSlotLeft.onchange = function(){slotLeftSelected(right)};
        var selectRelOp   = createSelect(i, "relOp"+ii(), relOps, function(){relOpSelected(right)}, 
					 true, 0, "op:");   
        right.relOp = selectRelOp;

        right.repeatOp    = repeatOp;

	// The repeat operator if any.
        var multiplicity  = typeSlot0[0];
        var framesQ       = typeFrameQ(typeSlot0);
        var repeatOp      = (multiplicity == 0) ? createSelect(i, "repeatOp"+ii(), framesQ ? repeatOps1 : repeatOps2, 
                                                               function(){repOpSelected(right, false);}, true, 0, "op:")
                                                : null;

        var rightFactor  = createConstantRightFactor(i, typeSlot0, relOps[0], repeatOp, null);
        right.right = rightFactor;

	var switchRight   = createButton("switch"+ii(), "Switch to variable entry",  
					 function(){switchRightFactor(whereClause, right)});
	switchRight.setAttribute('style', 'background-color: white');
	right.switchRight = switchRight;

        var rootExpr = {type: "logOp", ci: i, left: expr, op: null, prec: null, right: null, 
			father: whereClause.expr.father, 
			// This is the whereClause containing this node, not a whereClause under this node.
                        whereClause: whereClause};
        expr.father      = rootExpr;
        whereClause.expr = rootExpr;
        var selectLogOp  = createSelect(i, "logOp"+ii(), logicalOps, 
                                        function(){ logOpSelected(whereClause, rootExpr) }, true, 0, "op:");     
        rootExpr.op      = selectLogOp;
	// Redisplay the entire whereClause of this search component.
        restartDisplayWhereClause(document.getElementById("cellComponent"+expr.ci).whereClause, 0);
	// Force the application of selecting the vatiable 
	// as if it were changed so that the effects ripple through the interface.
	(selectVarLeft.onchange)();
    }
}

/* This function will switch the right factor of a term. It goes
   from a text input or enumerated form to a select form (with variable
   and slot selectors) or vice versa.

   Arguments: whereClause, the whereClause containing the term.
              term, the term containing the right factor.

   Returns: nothing. The effect is to change the look of the right factor by
      redisplaying the entire where clause.
*/

function switchRightFactor(whereClause, term){
    var i     = term.ci; // the component index of the term.
    var divi  = document.getElementById('cellComponent'+i);

    if(term.right.id.substring(0, 5) == "input" || term.right.id.substring(0, 5) == "enume" || 
       term.right.id.substring(0, 5) == "boole" || term.right.id.substring(0, 5) == "selec")  { 
        // Create a variable right factor
        var vars             = accessibleVars(term);
	var indexVarSelected = firstVisibleVar(term);
	var varName          = indexToVar(indexVarSelected);
        var selectVarRight   = createSelect(i, "slotVarRight"+ii(), vars, null, false, 
					    varName, "var:");
        selectVarRight.onchange = function(){varRightSelected(term)};
        term.varRight = selectVarRight; 
	term.switchRight.value = "Switch to constant entry";
        // Create the selector of the attribute
        var className = varToClassName(varName); 
        term.right    = createSelect(i, "slotRight"+ii(), classesSlots[className], 
				     function(){slotRightSelected(term)}, true, "Name", "slot:");   
	//adjustTypeRightFactor(term);
        restartDisplayWhereClause(document.getElementById("cellComponent"+term.ci).whereClause, 0);
    }
    else if (term.right.id.substring(0, 5) == "slotR") {
	// Create a constant right factor
        var type   = slotsType[term.left.value];
        term.right = createConstantRightFactor(i, type, term.relOp.value, term.repeatOp, term);
        term.varRight = null;
	term.switchRight.value = "Switch to variable entry";
        restartDisplayWhereClause(document.getElementById("cellComponent"+term.ci).whereClause, 0);
    } 
}


/* The left slot of the term has been selected (or modified by some other events).
   The operator list should be modified to reflect the
   type of the slot selected and if a forEach clause is attached to the term it should be removed.

   The simple case is a slot with a single value. The overall syntax and look
   will not change in that case.

   In the case of a slot with multiple values there are two very different
   classes of operator that can be applied:
     
      1) An operator that introduces another level of iteration on each
         element of the slot. This is the case for operators forAllObjects, etc.
         These are listed in vectors repeatOps1.
         In that case the appearance of the
         interface changes as another where level is introduced.
         For each element the user can specify a complex conditional (where clause)
         for different slots. (which itself may introduce further level of
         where clauses)

      2) An operator that is applied to every element but that does not treat
         each element as an instance with slots. Such a case occurs if 
         the elements are not frames or that frame ids are taken as strings.
         These are in repeatOps2.

   Arguments: term, a term structure for which the left factor slot changed.
   Returns: modifications to the display of the term.
*/

function slotLeftSelected(term){
   var slotSelected  = term.left;
   var slotName      = (slotSelected.value).toUpperCase(); // The value selected which is a slot name.
   var relOpSelect   = term.relOp;
   var multiplicity  = slotsType[slotName][0];

   // Rebuild the term from the type of the slot selected. Only the left factor remain intact.
   term.repeatOp = (multiplicity == 0) ? 
       createSelect(term.ci, "repeatOp"+ii(), 
		    typeFrameQ(slotsType[slotName]) ? repeatOps1 : repeatOps2, 
		    function(){repOpSelected(term, false);}, true, 0, "op:")
       : null;

   // If it is a repeat operator that has been introduced, 
   // force the expansion of any of its sub foreach clause based on its current selection.
   if(term.repeatOp != null) {
       repOpSelected(term, true);
   }
   else {
       // Remove any forEach clause from this term. None should appear at this point.
       if (term.forEachWhereClause != null) {
	   removeVarsFromExpr(term);
	   term.forEachWhereClause = null;
       }
   }

   // Modify the relational operator element according to the type of the selected slot.
   var ops = leftTypeToOperators(slotsType[slotName]);
   // TBD: Change nb non nils.
   modifyOptions(relOpSelect, ops, true, 0, false);

   // Modify the right factor according to the slot selected and the default operator.
   adjustTypeRightFactor(term);
   // term.varRight = null;
   // term.right    = createConstantRightFactor(term.ci, slotsType[slotName], ops[0]);

   // Redisplay the entire where clause containing that term.
   var whereClause = document.getElementById("cellComponent"+(term.ci)).whereClause;
   restartDisplayWhereClause(whereClause, 0);
}

/* The slot (attribute) of a right operand has been selected, typically from the user.
   This change implies:
       1) The list of operators between the left and this operand can change.


 */
function slotRightSelected(term){
    var relOpSelect    = term.relOp;    
    var slotSelected   = term.right;
    var relOpSelect    = term.relOp;
    var slotName       = slotSelected.value; // The value selected which is a slot name.
    // Modify the relational operator element according to the type of the selected slot.
    var ops = rightTypeToOperators(slotsType[slotName]);
    modifyOptions(relOpSelect, ops, true, 0, false);
}

/* Modify the right factor according to the type of the left factor and the operator
   selected from term.

   If the right factor is a variable selection, the net effect is to reconstruct the
   list of slots selectable for the right factor.

   Otherwise, if the left factor is of enumerated type, the right factor will be
   of this type, etc.

*/
function adjustTypeRightFactor(term) {
    var slotName = (term.left.value).toUpperCase();
    var leftType = slotsType[slotName];
    var whereClause = document.getElementById("cellComponent"+term.ci).whereClause

    if(term.relOp.value == "isa" || term.relOp.value == "isnota"){
	// This an instance relational operator. 
	// The right factor cannot be a variable or a free text box. We force a switch to a list of class.
	term.right = createConstantRightFactor(term.ci, slotsType["FRAME-ID"], term.relOp.value, null, term);
	// And any switch to variable button is removed.
	term.switchRight = null;
	term.varRight = null;
        return;
    }
    
    if (term.varRight != null) {
	// There is a variable on the right. Change the list of selectable slots according to the type of left factor.
	var activeSlots = slotsOfType(classesSlots[varToClassName(term.varRight.value)],
				leftType);
	grayOutOptions(term.right, activeSlots, 0);
    }
    else { 
	// Go back to the constant entry for the right factor.
	term.right  = createConstantRightFactor(term.ci, leftType, term.relOp.value, term.repeatOp, term);
	term.varRight = null;
	// A switch to var button.
	var vars = accessibleVars(term);
	term.switchRight  = createButton("switch"+ii(), "Switch to variable entry", 
					 function(){ switchRightFactor(whereClause, term) });
	term.switchRight.style.background = 'white';
    }
}

// Activate only the options that are in activateOptions. Disabling options
// make them look gray on most browsers.
function grayOutOptions(select, activateOptions){
    var iSlotSelected = select.selectedIndex;
    var reselect = ! inArray(select.options[iSlotSelected].value, activateOptions);
	
    for(var i=0; i < select.options.length; i++) 
	if (inArray(select.options[i].value, activateOptions)) {
	    select.options[i].disabled = false;
	    if (reselect) { select.selectedIndex = i;
		reselect = false;
	    }
	}
	else select.options[i].disabled = true;
}

/* Return an array of slot values (for select options) that are compatible 
   with type.

   Arguments: slots, array of pairs (value, visible-string) of slot names;
              type, a vector type.
   Returns: an array of strings value.
*/
function slotsOfType(slots, type) {
    r = new Array();
    // Note that the increment is 2, to go from value to value.
    for(var i=0; i < slots.length; i += 2){
	if (compatibleType(type, slotsType[slots[i].toUpperCase()])) {
	    r.push(slots[i]); 
	}
    }
    return r;
}

/* Return true if type1 is compatible with type2.

   The basic types are only compatible between themselves, e.g. STRING is not compatible
   with NUMBER and BOOLEAN.

   Two UNION types are compatible only if one of the listed types exists in the other.
   A type is compatible with a UNION one, if that type occurs as of the type listed
   in the union list.

   Two enumerated types are compatible only if their listed values are equal.
   All the rest are considered compatible.

   Arguments: type1 and type2 are type vectors.
   Returns: true iff type1 is compatible with type2.
 */
function compatibleType(type1, type2){
    if(type1[1] == "STRING" || type1[1] == "NUMBER" || type1[1] == "BOOLEAN" ||
       type2[1] == "STRING" || type2[1] == "NUMBER" || type2[1] == "BOOLEAN" )
	return (type1[1] == type2[1]); 
    else if (type1[1] == "UNION" && type2[1] == "UNION") {
	for(var i=0; i < type1[2].length; i++) {
	    if (inArray(type1[2][i], type2[2])) return true;
	}
	return false;
    }
    else if (type1[1] == "ENUMERATED" && type2[1] == "ENUMERATED") {
	return (vectorSetEqual(type1[2], type2[2]));
    }
    else if (type1[1] == "UNION") {
	for(var i=0; i < type1[2].length; i++) {
	    if (compatibleType(type1[2][i], type2)) return true;
	}
	return false;
    }
    else if (type2[1] == "UNION") {
	for(var i=0; i < type2[2].length; i++) {
	    if (compatibleType(type2[2][i], type1)) return true;
	}
	return false;
    }
    // This is likely two types that refer to a class.
    else return true;
}

function vectorSetEqual(v1, v2){
    if (v1.length != v2.length) return false;
    for(var i=0; i < v1.length; i++) if (v1[i] != v2[i]) return false;
    return true;
}

/* Create a right factor to enter a constant based on the type. The type of right factor
   returned can be determined by the first five letters of the id. So it
   is important to maintain these five letters different for each type.
   This code does not create the variable selector.

   Arguments: i, index of component;
              type, a type vector, the type of right factor desired.
              opSelected, a string representing the relation operator on the left of that box.
              repeatOp, the value of the repeat operator for the term. It may be null.
              term, the term where the right factor will be inserted. It may be null.
   Returns: an HTML object.
*/
function createConstantRightFactor(i, type, opSelected, repeatOp, term){
    if (repeatOp != null) {
        if (repeatOp.value == "card") 
	    // Avoid erasing any input by the user if it is already a free input box.
	    if (term != null && term.right != null && term.right.id.substring(0,5)=="input") 
		return (term.right);
	    else return createInputText(); 
    }

    if(type[1] == "BOOLEAN") {
        return createSelect(i, "boolean"+ii(), booleanOptions, function(){}, true, 0, "");
    }
    else if (type[1] == "ENUMERATED") {
        return createSelect(i, "enumerated"+ii(), type[2], function(){}, false, 0, "");
    }
    else if (opSelected == "isa" || opSelected == "isnota") {
        return createSelect(i, "selectClass"+ii(), classes, function(){}, true, 0, "");     
    }
    else {
	// Avoid erasing any input by the user if it is already a free input box.
	if (term != null && term.right != null && term.right.id.substring(0,5)=="input") 
	    return (term.right);
	else return createInputText(); 
	
    }
}

/* Modify the list of options for the selector select if the list of options is new. 
   Force the selection of the option to valueOrIndexToSelect only if the current
   value selected in select is not present in options -- in that case this
   value should be selected so that the selection remains the same.

   Arguments: select, an HTML select object;
              options, a vector of strings or pairs of strings;
              stringPairQ, a boolean, true => options is a vector of pairs of strings.
              valueOrIndexToSelect, an integer or a string. 
              forceSelection, boolean, true => always select the slot given by valueOrIndexToSelect 

   Returns: return the option value selected, a string from vector options.
            (and rebuild the list of options, this will show on the display.)
 */

function modifyOptions(select, options, stringPairQ, valueOrIndexToSelect, forceSelection){
   var incr = (stringPairQ ? 2 : 1);

   // Do we have the same list of options in select?
   var theSame = true;
   if((options.length/incr) == select.options.length)
       // They have the same length, compare element by element for equality.
       for(var i=0; i < options.length; i += incr) {
	   if(options[i].value != select.options[i/incr].value) theSame = false;
       }
   else theSame = false;
   
   // If the list of options is exactly what already exists and there is
   // no need to select a new slot, just return the current selection.
   if (theSame && !forceSelection) {
       return (select.value); // the current selection remains as is.
   }
   else {
       // Recreate the list of options of the selector select.
       var valueSelected = select.value;
       var selectKq;
       
       // Set the index to select 
       var indexToSelect = (posArray(valueSelected,options) >= 0) ? 
	   posArray(valueSelected,options) : ((posArray(valueOrIndexToSelect, options) >= 0) 
					      ? posArray(valueOrIndexToSelect, options)
					      : ((typeof(valueOrIndexToSelect) == "string") ? 0 : valueOrIndexToSelect));
       // Eliminate all current options
       for(var k=0; k < select.options.length; k++){
	   select.options[k] = null;
       }
       // Set new list of options
       for(var k=0; k < options.length; k += incr){
	   // Set the option that user will see: the selected option.
	   // The first argument of Option is what the user sees, the second the value
	   // returned by select.value when that option is selected.
	   selectKq = (k == indexToSelect);
	   select.options[k/incr] = new Option(options[k+incr-1], options[k], selectKq, selectKq);
	   if (stringSlotFrameQ(options[k])) select.options[k/incr].className = "saqpSelectFrame";
       }
       select.options.length = (options.length)/incr;
       
       // The following was needed to force the correct selection on Mozilla and Safari for some cases.
       // Assigning true to the new Option was not enough. A bug in these browsers (e.g., IE).
       select.value = options[indexToSelect];
       return options[indexToSelect];
   }
}

/* Relational operator of term has been modified. The right factor is
   modified if some relational operator has been selected and the right
   side is not of the correct type. It may also be that a variable should
   be introduced for a single frame; or vice versa.
   
   Arguments: whereClause, the highest where clause that contains term.
              term, a term structure.
   Returns: a redisplay of the where clause according to the relational operator selected.
*/

function relOpSelected(term){
    var whereClause = document.getElementById("cellComponent"+term.ci).whereClause;
    // Is the relational operator selected a binding operation?

    if (term.relOp.value == "bindIt") {
	// A variable is introduced for the left factor slot object.
	createInternalWhereClause(term, "bindIt");
    }
    else 
	// Remove any where clause attached to the term.
	if (term.forEachWhereClause != null) {
	    removeVarsFromExpr(term);
	    term.forEachWhereClause = null;
	}
    
    adjustTypeRightFactor(term);	
    restartDisplayWhereClause(whereClause, 0);
}

/* Is term inside a forEachWhereClause?
   To find out, go up the tree structure using the father up links,
   if at any point a father has a non null forEachWhereClause,
   the term is inside a forEachWhereClause.
*/
function termInsideForEachWhereClauseq(term){
    if (term == null || term.father == null) return false;
    if (term.father.forEachWhereClause  != null ) return true;
    return termInsideForEachWhereClauseq(term.father);
}


/* Create a new internal where clause ("we have" clause) 
   for an instance of repeat operator or "bindIt" operation.
   It introduces a new user visible variable.

   Arguments: term, a term structure, its type is "term". 
              op, one of the instance of repeat operators or "bindIt".
   Returns: nothing, modify term to gave a new Where clause.
*/

function createInternalWhereClause(term, op){
    var slotName   = (term.left.value).toUpperCase();

    // Remove the right factor
    term.right       = null; 
    term.varRight    = null;
    term.switchRight = null;
    
    // The slotName may refer to several classes.
    var classNames =  new Array();
    if(slotsType[slotName][1] == "UNION") classNames = slotsType[slotName][2];
    // There is only one class otherwise
    else classNames.push(slotsType[slotName][1]);

    // Remove the vars if there is already a forEachWhereClause.
    if (term.forEachWhereClause != null) removeVarsFromExpr(term);
    
    // The variable is initially associated with the first class name.
    // It may not be accessible from the output specification.

    var vari = getNewVarIndex(term.ci, classNames[0], false, !termInsideForEachWhereClauseq(term));
    term.forEachWhereClause = { whereClause: null, vari: vari, classSelect: null }; 


    term.forEachWhereClause.whereClause = createInitialWhereClause(classNames, term, term.ci, 
								   (op == "bindIt") ? spaces(20) : "we have"+spaces(2));
    if(classNames.length == 1) {
	// Just create a text with the class name in it, not a selector.
	var textClass = document.createElement('span');
	textClass.innerHTML = " "+classNames[0]+" ";
	textClass.id        = "textClassForEach"+ii();
	term.forEachWhereClause.classSelect = textClass;
    }
    else {
	// This is a selector for "the for each variable class type".
	var forEachClassSelect = createSelect(term.ci, "forEachClass"+ii(), classNames, 
					      function(){forEachClassSelected(term)}, false, 0, "");
	term.forEachWhereClause.classSelect = forEachClassSelect;
    }
}


/* The repeat operator has been modified for a term. Several elements
   of that term may have to be modified according to the operation
   selected. See the cases in the code itself.

   Arguments: term, the term structure containing the repeat operator selector.
              (we have term.type = "term").
              forceExpansionQ => force a removal of any forEachWhereClause and reconstruct it.
   Returns: modifies the relational operator, right factor, etc., of term, if required.
*/
function repOpSelected(term, forceExpansionQ){
    if(term.repeatOp.value == "forAllObjects" ||
       term.repeatOp.value == "forOneObject"  ||
       term.repeatOp.value == "forNoObjects"  ||
       term.repeatOp.value == "forSomeObjects" 
      ) {
        // We need to create a new generator for this term only if none exist.
        // The forEach operator applies only to slots that have frames.

	if (term.forEachWhereClause == null || forceExpansionQ) {
	    createInternalWhereClause(term, "forObjects");
	    adjustTypeRightFactor(term);
	    // Display the entire whereClause of the search component to have the new
	    // embedded whereClause displayed.
	    restartDisplayWhereClause(document.getElementById("cellComponent"+term.ci).whereClause, 0);
	}
    }
    else {
        // Set the relational operator selector to the type of the slot selected
        var slotName    = (term.left.value).toUpperCase();
	var whereClause = document.getElementById("cellComponent"+term.ci).whereClause;
        // The operator list is for numbers when cardinality is selected, otherwise it is base on the type of the left operand.
        modifyOptions(term.relOp, 
		      (term.repeatOp.value == "card") ? basicTypeToOperators["NUMBER"] : leftTypeToOperators(slotsType[slotName]), 
		      true, 0, false);
        // Remove the foreachwhere clause if there is one.
	if (term.forEachWhereClause != null) {
	    removeVarsFromExpr(term);
	    term.forEachWhereClause = null;
	}
        // Initially, the right factor is a variable with a switch option.
	adjustTypeRightFactor(term);
        // Redisplay the where clause from the top.
        restartDisplayWhereClause(whereClause, 0);
    }
}

/* The type of a forEach variable has been selected. This occurs if the slot associated with the
   forEach variable has a union type of frames. All slot selectors associated with the for each variable
   is potentially modified as the variable type may change. The only location where the variable
   may be referenced is under term -- transverse term and apply the class modification for that variable. 

   This may also affect the output specification where that variable is selected.

   Arguments: term, the term containing the forEach that has been selected.
   Returns: slot options associated with the var may change.
*/
function forEachClassSelected(term){
    var varName   = indexToVar(term.forEachWhereClause.vari);  // The variable name that has changed type.
    var className = term.forEachWhereClause.classSelect.value; // Its selected type (a frame class)
    modifyVarClass(term.ci, term.forEachWhereClause.whereClause.expr, varName, className);

    // Search the output table specification for variable varName
    for(var j=1; j <= addColumnOutput.nbColumns; j++){
        if(accessibleVarsComponents(1000).length == 1 || 
           varName == document.getElementById('varColumn'+j).value) {
            // Change the list of options for this variable.
            var slotColumnj = document.getElementById("slotColumn"+j);
            modifyOptions(slotColumnj, classesSlots[className], true, "Name", false);      
        }      
    }
}


// ====== Translation into a BioVelo query ===================

/* Construct the query string from the web form.

   Some details about case sensitive symbols and strings:

    1) The class names case must be maintained to have a correct query.   
       For example, the class "Reactions" must have an upper case R. All the
       class names are assumed to be properly stored in the interface such that
       no translation is needed. (the proper names come from schema.js)

    2) Slot names are taken as is from the interface assuming that they are
       correctly spelled in the interface. (the slot names come from schema.js)

   Some details about the relational operators:

   There is a major complication in generating the BioVelo query that
   comes from accessing, in the output specification of SAQP,
   variables that come from repeat operators. Accessing such a
   variable implies that the list generated by the repeat operators is
   bound to a variable that can be accessed in the head of the
   query. Not all such list can be accessed, so the problem is to
   identify which such variables can be accessed and when to generate
   the list with a binding ( that is when using the := operator).

   Arguments: format, either "HTML" or "TABULATED".
   Returns: a string representing the query.
 */
function webFormToQueryString(format){
    var body = "";
    cs = document.getElementById("components");

    /* Get all search component description.  Take into account the
       operation (e.g., cross product, merge) between search
       components.
       
     */
    for(var i=0; i < cs.childNodes.length; i++){
        if(cs.childNodes[i].id.substring(0,13) == "lineComponent") {
            var j    = parseInt(cs.childNodes[i].id.substr(13));
            var cell = document.getElementById("cellComponent"+j);
            // The initial generator with the variable, dataset and the class selected
            // create a string of the form varName <- datasetName^^className
            var datasetName = document.getElementById("dataset"+j).value;
            var className   = document.getElementById("class"+j).value;
            var varName     = indexToVar(document.getElementById("cellComponent"+j).variable);
	    var searchOp    = cell.op;

	    // Currently only two possible operations, which are the same thing.
            if (searchOp == "crossProduct" || searchOp == "firstAdd") 
		body += ", "+varName+"<-"+datasetName+"^^"+className;
		    
            // The where clause of that component, if it exists.
            if (cell.whereClause != null) {
		var t2 = exprToQueryString(cell.whereClause.expr, false);
		body += ", "+mergeBindings(t2.bindings,t2.condition);
	    }
        }
    }
    var bodyFinal = body.substr(1);

    var head = "";
    // Gather the output specification 
    for(var i=1; i <= addColumnOutput.nbColumns; i++) {
        var varName  = (accessibleVarsOutput().length == 1) ? firstVar
	               : document.getElementById("varColumn"+i).value;
        var slotName = document.getElementById("slotColumn"+i).value;
	var accessOp = (format == "HTML")? "^?" : "^";
	// If the variable is at the top level of a search component, 
	// just access the slot of the variable. If it is at a lower level
	// the corresponding list that was constructed is accessed.
	if(varAtTopLevelq(varName))
	    // Use the operator ^? to access the value and generate a link to the object containing the value. 
	    head += ", "+varName+accessOp+slotName;
	// We need to access the list that was built for this non-top level variable.
	// Such a list is named lj, where j is the index of the variable.
	// [( ... [(e^?slotname, ): e <- lj], ... ) : ] 
	else head += ", [e"+accessOp+slotName+": e <- l"+varToIndex(varName)+"]";
    }

    // Remove the first comma and insert into a pair of parentheses.
    var headFinal = "("+head.substr(2)+")";
    var completeQuery = "["+headFinal+" : "+bodyFinal+"]";

    return completeQuery;
}


/* Translate an expression into a query string.

   Arguments: expr, a term or logOp expression-node.
              subExprq, true => expr is part of a larger expression, do not create bindings, 
                                that is, do not use :=.
   Returns: an structure of two values: { bindings as a string, condition as a string };
 */

function exprToQueryString(expr, subExprq){
    if (expr == null ) return {bindings:"", condition:""};

    if (expr.type == "term") {
	// It is a leaf.
	if (expr.repeatOp != null) 
	    // The variable is internal and not visible to the user.
	    if (expr.repeatOp.value != "forAllObjects" &
		expr.repeatOp.value != "forSomeObjects" &
		expr.repeatOp.value != "forNoObjects" &
		expr.repeatOp.value != "forOneObject" 
		) {
		// For cardinality, the expression has the form "#left-operand rel-op number"
		// no internal variable (so no bindings) or other complex condition is necessary.
		if (expr.repeatOp.value == "card") {
		    return {bindings: "",
			    condition: "(#"+factorLeftToQueryString(expr)+") "+expr.relOp.value+" "+
					  factorRightToQueryString(expr, "NUMBER")};
		}
		var iVar = internVar();
		var leftString = factorLeftToQueryString(expr);
		var leftType   = leftFactorType(expr); 
		var condition  = exprLeftOpRight(iVar, expr.relOp.value, 
						 factorRightToQueryString(expr, leftType));
		var forAll = "(#["+iVar+" : "+iVar+" <- "+leftString+", "+condition+"])";
		var t2 = {bindings: "", condition : ""};
		
		switch(expr.repeatOp.value){
		    // The forall is not an embedded query, it works correctly that way.
		case "forAll" : t2.condition = forAll+" = "+"#("+leftString+")"; break;
		    // The following are not that efficient since we look at all elements
		    // although this is not necessary in all cases.
		case "forNone": t2.condition = forAll+" = 0"; break;
		case "forSome": t2.condition = forAll+" > 0"; break;
		case "forOne" : t2.condition = forAll+" = 1";
		}
		return t2;
	    }
	    else { 
		// One of the operator on objects: forAllObjects, etc.
		// We construct a nested query as in other 'for' cases.
		// But the variable is visible to the user, and that should be the one to use.
		var varIndex   = expr.forEachWhereClause.vari;
		var iVar       = indexToVar(varIndex);
		var leftString = factorLeftToQueryString(expr);
		// The condition of the where clause must not generate any bindings as
		// we cannot bring them to the top level. So, call with "true".
		var t2        = exprToQueryString(expr.forEachWhereClause.whereClause.expr, true);
		if (t2.bindings != "") markBoxError("exprToQueryString, a set of bindings were created inside a condition: "+t2.bindings);
		// Only the condition is set, there are no bindings.
                var condition = t2.condition;
		// Use the following variable name to bind the constructed list.
		// This list might be referred to in the output specification.
		var listVar   = "l"+varIndex;
		// The condition is enclosed in parentheses as it may contain logical
		// "or" operators.
		
		var listExpr  = "["+iVar+" : "+iVar+" <- "+ leftString +", ("+condition+")]";
		// TBD: The binding should be generated only if the variable is accessed
		//      in the output specification. It can be faster not to generate it.
		//      This is actually better if the BioVelo compiler does this optimization.
		t2.bindings = (subExprq) ? "" : listVar+" := "+listExpr;
		var listExprCondition = (subExprq) ? listExpr : listVar;

		switch(expr.repeatOp.value){
		case "forAllObjects"  : 
		    t2.condition = "#("+leftString+") = #"+listExprCondition; break;
		case "forSomeObjects" : 
		    t2.condition = " 0 < #"+listExprCondition; break;
		case "forOneObject"   : 
		    t2.condition =  "1 = #"+listExprCondition; break;
		case "forNoObjects"   : 
		    t2.condition =  "0 = #"+listExprCondition; break;
		}
		return t2;
	    }
	else // No repeatOp is used
	    return {bindings: "", condition: basicTermToQueryString(expr)};
	    
    }
    else // This is not a term, it may have a logical operator.
	if (expr.type == "logOp") { 
	    // There is really a logical operator if there is a right term.
	    if (expr.right != null) {
		var leftExpr    = exprToQueryString(expr.left, subExprq); 
		var rightExpr   = exprToQueryString(expr.right, subExprq);
		var t2          = {bindings: mergeBindings(leftExpr.bindings, rightExpr.bindings), condition: ""};
		var leftString  = leftExpr.condition;
		var rightString = rightExpr.condition;
		var logOp       = expr.op.value;
		// The default grouping of the where expression for logical operators
		// is from the top to bottom as it appears on the page, that is from the leaf
		// to the root. (Currently the tree leans on the left).
		switch (logOp) {
		case 'nand'  : t2.condition = ("("+leftString+") & !("+rightString+")"); break;		
		case 'nor'   : t2.condition = ("("+leftString+") | !("+rightString+")"); break;		
		case '&'     : t2.condition = ("("+leftString+") & ("+rightString+")"); break;
		case '|'     : t2.condition = ("("+leftString+") | ("+rightString+")");	break;
		default: markBoxError(expr, "GUI internal error: encountered an unknown logical operator "+logOp);
		}
		return t2;
	    }
	    // There is no right operand, just return the value of the left.
	    else return exprToQueryString(expr.left, subExprq);
	}
	else markBoxError(expr, "GUI internal error: encountered a term of unknown type "+expr.type);
}	

// Two strings of bindings.
// Returns the concatenation of bindings as a string.
function mergeBindings(b1, b2){
    // Add a comma between bindings only if b1 is not empty;
    return (b1.length > 0)? b1+", "+b2: b2
}

// Returns a string representing the left factor of expr.
function factorLeftToQueryString(expr){
    return (((expr.varLeft != null) ? expr.varLeft.value : firstVar)+"^"+expr.left.value);
}

/* Returns a string representing the right factor of expr.

   Arguments: expr, an expr structure.
              expectedType, a string representing a type (e.g., "NUMBER", "STRING", "BOOLEAN", "OBJECT").
   Returns: a string representing the expr.right factor.
*/
function factorRightToQueryString(expr, expectedType){
    if (expr.right.value == "") {
	// A completely empty value is a user error.
	userError(expr.right, "Error: please enter a value in the red box(es).", "#F55965");
        return "";
    }

    // If it is a var selector then build a var reference
    if (expr.right.id.substring(0,5) == "slotR") 
	return (((expr.varRight != null) ? expr.varRight.value : firstVar)+"^"+expr.right.value);

    // Otherwise pick the value from the right box.
    if (expectedType == "NUMBER" && stringType(expr.right.value) != "NUMBER") {
	userError(expr.right, "Error: please enter a number in the blue box(es).", "#6F85F3");
	return "";
    }

    // This can be a free input text or a selector value (i.e., enumerated type).

    if (expectedType == "STRING" || expectedType == "ENUMERATED" || expectedType == "BOOLEAN")
        // Escape some special characters to keep it as a valid Lisp string.
	return ('"'+(expr.right.value.replace(/\\/g,'\\\\')).replace(/"/g,'\\"')+'"');
        // In the case of expectedType object, the entered value is presumed to be the name
        // of a frame. Always use double-quote as this name may contain special characters.
	else if (expectedType == "FRAME") { 
             datasetName = document.getElementById('dataset'+expr.ci).value;
             return '('+datasetName+'~"'+expr.right.value+'")';
        }
        else return expr.right.value;
}

/* Convert the expression expr into a query string.

   Arguments: expr, an expression structure.
   Returns: a string representing expr.
*/
function basicTermToQueryString(expr){
    var leftType    = leftFactorType(expr);
    var leftString  = factorLeftToQueryString(expr);

    // Take care of the bindIt (':=') operator.

    if (expr.relOp.value == "bindIt") {
	var continuation = exprToQueryString(expr.forEachWhereClause.whereClause.expr, true);
	if (t2.bindings != "") markBoxError("basicTermToQueryString, a set of bindings were created inside a condition: "+t2.bindings);
        var condition    = continuation.condition;
        var vari         = indexToVar(expr.forEachWhereClause.vari);
	// The scope of the variable vari will extend passed the where clause; but that
	// does not create a variable name clashing problem.
	return vari + " := "+leftString +","+continuation.bindings+", ("+condition+")";
    }
    else {
	var rightString = factorRightToQueryString(expr, leftType);
	var rightType   = stringType(rightString);
	return exprLeftOpRight(leftString, expr.relOp.value, rightString);
    }
}

/* Take care of translating the operator op into an existing operator
   of the query language. An implicit negation and an interchange of the factors
   may have to be done.

   Arguments: leftString, the left factor as a string.
              op, a string representing an operator from the structured user interface.
              rightString, the right factor as a string.
   Returns: a string representing the expression with left factor, operator, and right factor.
            This string conforms to the query language.
*/
function exprLeftOpRight(leftString, op, rightString){
    switch(op){
	// Implicit negation for some operators.
    case "!=ci":        return "!("+leftString +" =ci "       +rightString+")";
    case "isnotin":     return "!("+leftString +" in "        +rightString+")";
    case "isnota":      return "!("+leftString +" isa "       +rightString+")";
    case "ninstring":   return "!("+leftString +" instring "  +rightString+")";
    case "ninstringci": return "!("+leftString +" instringci "+rightString+")";
	
	// Reverse the left and right operands for some operators.
    case "nrinstring":  return "!("+rightString+" instring "  +leftString+")";
    case "nrinstringci":return "!("+rightString+" instringci "+leftString+")";
    case "nrinstring":  return "!("+rightString+" instring "  +leftString+")";
    case "nrinstringci":return "!("+rightString+" instringci "+leftString+")";
    case "nrin":        return "!("+rightString+" in "        +leftString+")";
    case "rin":         return      rightString+" in "        +leftString;
    case "rinstring":   return      rightString+" instring "  +leftString;
    case "rinstringci": return      rightString+" instringci "+leftString;

    default: return (leftString+" "+op+" "+rightString);
    }
}

// Return the most specific type, starting from "NUMBER", "BOOLEAN", "FRAME", then "STRING" by default.
function leftFactorType(expr){
    var slotName = (expr.left.value).toUpperCase();
    var type     = slotsType[slotName];

    if (expr.relOp.value == "isa" || expr.relOp.value == "isnota") return "THING";

    if (type[1] == "NUMBER" || (type[1] == "UNION" && inArray("NUMBER",type[2]))) {
	return "NUMBER";
    }
    if (type[1] == "BOOLEAN" || (type[1] == "UNION" && inArray("BOOLEAN",type[2]))) {
	return "BOOLEAN";
    }
    if (inArray(type[1], classes)) return "FRAME";

    // Enumerated are strings.
    return "STRING";
}

// Return "NUMBER" if s can be interpreted as a number, otherwise it is a string.
function stringType(s) {
    if(isAnumber(s)) return "NUMBER";
    else return "STRING";
}

// Return true if string s can be interpreted as a number.
function isAnumber(s) {
    // This trick detects  non-number in string s. It should handled
    // correctly + or - signs, decimal point, and signed exponent.
    return !isNaN(s);
}


/* Prepare and insert the query and format in the hidden variable 'object'. 
   If no error is encountered, the object variable will have the form (format query);
   that is a list of two strings, one for the format and the other for the query.
   See page aquery.html with the definition of hidden variable object.

   The submit can come from the structured form or the free form.

   Arguments: form, the HTML form element from which the submit button was clicked.
   Side-effects: the hidden variable 'object' is set to (format query).
   Returns: true if the query can be sent; false when it should not.
*/

function sendQuery(form){
    // set iSortColumn to cover the case that no radio button for sorting is set.
    var query, format, iSortColumn=1; 

    resetUserErrorMsg();

    var formats = document.getElementsByName('outputFormat');	
    for (var i=0; i < formats.length; i++)
	if(formats[i].checked) format = formats[i].value;
    
    if (structuredForm()) {
	try {
	    query = webFormToQueryString(format);
            // do not send query if user error were given.
            if (userError.markedObjects.length > 0) return false;
	} 
	catch (err) {
	    return false; // do not send query
	}
    }
    else {
        query   = document.getElementById('mainBox').value;
	if (query == "") { 
	    alert("Error, no query was entered in the text area."); 
	    return false; // do not send query
	}
    }

  // Escape the '\' and '"' characters so that the string remains a Lisp string when
  // a pair of double quote is put around the query.
  var queryEsc = (query.replace(/\\/g,'\\\\')).replace(/"/g,'\\"');

  // For the structured page we inserted a sort, but for
  // the Free Form query page, the user specifies its own sort.

  if (structuredForm()) {
    var sortColumns = document.getElementsByName('sorting');

    for (var i=0; i < sortColumns.length; i++)
       if (sortColumns[i].checked) iSortColumn = i+1;

    // The sorting is applied last.
    var queryEscSort = ((format == "HTML") ? 'html-sort-ascending(' : 'sort(') +queryEsc+','+iSortColumn+')';
    // The server receives a tuple of two elements: the format of the output
    // and the query, both as strings. The hidden variable object
    // is in the page aquery.html. You must use the name 'object' for the cwest server.

    form['object'].value = '("'+format+'" "'+queryEscSort+'")';
  }
  // For free form, no default sorting is inserted.
  else form['object'].value = '("'+format+'" "'+queryEsc+'")';
  return true;
}

// Return true if the structured form is active; false otherwise.
function structuredForm(){
    var b = document.getElementById('switchForm');
    return (b.state == "structured");
}

/* Bring the form back to its initial state */
function resetForm(){
    if (structuredForm()) initDynamicPage();
    else {
	// alert(document.getElementById('mainBox').innerHTML);
	document.getElementById('mainBox').value = "";
    }
}

/* Internal error handling. We currently do nothing with the object o.
   But we could provide a better error message by using it.

   Arguments: o, an HTML object.
              msg, a string describing the error to throw.
*/

function markBoxError(o, msg) {
    // Show an alert box.
    alert(err);
    throw(msg);
}

/* User error handling. We currently do nothing with the object o.
   But we could provide a better error message by using it.

   Arguments: o, an HTML object.
              msg, the message to show to the user.
*/

function userError(o, msg, color) {
    var errorMsgContainer = document.getElementById('errorMsgContainer');
    // Do not show the same message twice.
    if (!inArray(msg, userError.msgs)) {
        errorMsgContainer.innerHTML = errorMsgContainer.innerHTML+
                                     ((errorMsgContainer.innerHTML !="")?"<br>":"")+msg;
        userError.msgs.push(msg);
    }
    // mark the background with a color, but save its style before proceeding.
    userError.markedObjects.push({object: o, style: o.style});
    o.style.backgroundColor = color; 
    // throw(msg);
}
userError.markedObjects = [];
userError.msgs = [];

function resetUserErrorMsg(){
 var errorMsgContainer = document.getElementById('errorMsgContainer'); 
 errorMsgContainer.innerHTML = "";
 // Reinstate the style of all objects that were marked red.
 for(var i=0; i < (userError.markedObjects).length; i++) {
    (userError.markedObjects[i].object).setAttribute('style', userError.markedObjects[i].style);
 }
 userError.markedObjects = [];
 userError.msgs = [];
}

/* Free form advanced query page.

   The user can switch from the free input text form to the structured form.
   This is accomplished by hidding or making visible parts of the form and switching
   the button functionalities.
*/

function switchToFreeFormAdvanced(queryString){
    var cs = document.getElementById('advancedQuery');
    var whichPage = document.getElementById('whichPage');
    whichPage.innerHTML = "Free Form Advanced Query Page"; 
    // Use display:none which not only makes it invisible, but its height becomes null.
    cs.style.display = 'none';
    var b1   = document.getElementById('switchForm');
    b1.state = 'freeForm';
    b1.value = 'Switch to the Structured Advanced Query Page';
    b1.onclick = function(){switchToStructuredAdvanced()};
    var very   = document.getElementById('veryAdvancedQuery');
    attachDocumentationFreeForm();
    very.style.display = 'inline'; 

    // Initialize the main box of the query with the query string, if any.
    if (queryString != null) document.getElementById('mainBox').value = queryString;
}

function switchToStructuredAdvanced(){
    var cs = document.getElementById('advancedQuery');
    var whichPage = document.getElementById('whichPage');
    whichPage.innerHTML = "Structured Advanced Query Page"; 
    cs.style.display = 'inline';
    var button   = document.getElementById('switchForm');
    button.state = 'structured';
    button.value = 'Switch to the Free Form Advanced Query Page';
    button.onclick = function(){switchToFreeFormAdvanced()};
    var very = document.getElementById('veryAdvancedQuery');
    very.style.display = 'none'; 
    // Perhaps the page was not reached through the SAQP but the FFAQP.
    if (document.getElementById('search1') == null) { 
       initFirstComponent();
       initOutputSpecification();
    }
}

/* This function initializes the list of options for several select
   elements to help the user create a query in the free form page.

   There are selectors to see which databases, classes, slots and operators
   are available.
   
 */
function attachDocumentationFreeForm(){
    var dbs = document.getElementById('databasesDoc');
    modifyOptions(dbs, dataSetsFreeForm, true, 0, false);
    var classesDoc = document.getElementById('classesDoc');
    modifyOptions(classesDoc, classes, true, 0, false);
    var slotsDoc = document.getElementById('slotsDoc');
    modifyOptions(slotsDoc, classesSlots[classes[0]], true, "Name", false);
    var opsDoc = document.getElementById('opsDoc');
    modifyOptions(opsDoc, selOperators, false, 0, false);
}

function changeOptionsDoc(classSelect){
    var slotsDoc = document.getElementById('slotsDoc');    
    modifyOptions(slotsDoc, classesSlots[classSelect.value], true, "Name", false);
}

// Copy the value of the selection to the text area (the user query).
function copyValueToTextArea(select){
    var textArea = document.getElementById('mainBox');

    if (document.selection) {
	textArea.focus();
	var cursor = document.selection.createRange();
	cursor.text = select.value;
    }
    else if (textArea.selectionStart || textArea.selectionStart == '0') {
	var startPos = textArea.selectionStart; 
	var endPos = textArea.selectionEnd; 
	textArea.value = textArea.value.substring(0, startPos)+ select.value+ 
	    textArea.value.substring(endPos, textArea.value.length); 
    } 
    else textArea.value += select.value; 
    // TBD: forces the cursor to be after the text. Not all Browsers handle the previous
    // insertion the same -- some put the cursor after the insertion point some before.
}



