/*
  A CTS reader for the Firefox browser
  copyright (c) 2005-6 Neel Smith

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  Neel Smith, College of the Holy Cross, nsmith@holycross.edu
*/
 

/*
  Dependencies:
  This application works with the XHTML in the file ctsr.html;
  it relies on id attributes in that file to manipulate pieces of the document.

  It relies on CSS in the associated file ctsstyle.css for
  proper showing/hiding of pieces of the document depending on the application's mode.
  
  It requires all the javascript libraries that are included in
  ctsr.html.
*/

/*
  The code in this file is organized in the following sections:

  - global structures and initialization
  - routines responding to asynchronous data loading
  - routines building forms and menu selectors
  - routines for managing application configuration in settings/configuration mode
  - routines responding to user selections in browser/reader mode
  - routines for managing application display
  - routines for updating cts settings
  - routines for emptying components of reader display 
  - utilities for working with debugging console 

 */

/* ************************************************ */
/* BEGIN global structures  and initialization */


/** 
 * Constructor for the global object tracking state and 
 * managing the application. 
 * @param showDebug Optional Boolean value, true if debugging console
 * should be displayed and posted to.
 * 
 */
function application(showDebug) {
    /**
     * Boolean variable true if debugging console should
     * be displayed and posted to.
     */
    this.showDebug = showDebug;
    this.showNotes = true;

    this.mode = null;

    this.ctsindex= null;
    this.indexType = null;
    this.indexID = null;

    this.appendedIndexURL = null;
    this.appendedIndexType = null;
    this.appendedIndexID = null;

    this.morphologyURL = null;

    this.teiXML = null;
    this.indexXML = null;

    this.serviceLabel = null;
    this.serviceID = null;

    this.preLoadCTS = false;
    this.preLoadPassage = false;

    // Need to now if CTS GetCapabilities
    // and CTS Index GetCapabilities data have
    // been loaded:
    this.catalogLoaded = false;
    this.idxCatalogLoaded = false;

    this.morphXSLTloaded = false;
    this.TEIXSLTloaded = false;
    this.notesXSLTloaded = false;

    this.TEIXSLT = null;
    this.morphXSLT = null;
    this.notesXSLT = null;

    this.passageLoaded = false;
    this.indexLoaded = false;

    this.serviceList = new Array();

    this.indexData = new Array();

    this.geoData = new Array();

    this.indexCatalog = new Array();


} // application object




/* ********************************************** */
/* BEGIN  routines for emptying components of reader display 
   These routines hierarchically empty parts of the display when user
   action changes a selection (of textgroup, work, etc...)
*/

/**
 * On selection of or change of service, empties all UI settings for
 * citation components.
 */
application.prototype.emptyAll=function() {
    //    ctsconnection.setTextGroup(null); 

    // empty display for textgroups:
    var selectBox = document.getElementById("textgroup");
    selectBox.options.length = 0;
    selectBox.options[0] = new Option("First select a service","");
    //and everything else:
    this.emptyTextGroup();
} // emptyAll


/**
 * On change of text group, empties UI settings for all citation
 * components other than text group.
 */
application.prototype.emptyTextGroup=function() {
    // empty display for works:
    var workBox = document.getElementById("work");
    workBox.options.length = 0;
    workBox.options[0] = new Option("First select a text group","");

    // and everything else
    this.emptyWork();
} //emptyTextGroup


/**
 * On change of work, empties UI displays for
 * versions, citation string, passage, and text display.
 */
application.prototype.emptyWork=function() {
    // empty version selection box
    var versionBox = document.getElementById("version");
    versionBox.options.length = 0;
    versionBox.options[0] = new Option("First select a work","");

    //    postDebug("emptyWork: set cite str = nulll str now");
    this.setCiteStr("");

    // and everything else
    this.emptyVersion();

} //emptyWork

/**
 * On change of version, empties UI display of passage and text display.
 */
application.prototype.emptyVersion=function() {
    // empty passage field:
    var passageInput = document.getElementById("passage");
    passageInput.value = null;

    // empty display field for text
    var res = document.getElementById("results");
    var resultsChildren = res.childNodes;
    /*
    postDebug("Need to remove " + resultsChildren.length +
	      "elements from results.");
    */
    for (var rescnt = resultsChildren.length -1; rescnt >=0; rescnt--) {
	res.removeChild(resultsChildren[rescnt]);
    }

    // Hide prev/next
    var prevSpan = document.getElementById("prevspan");
    prevSpan.className = "invisible";
    var nextSpan = document.getElementById("nextspan");
    nextSpan.className = "invisible";
} //emptyVersion

/* END  routines for emptying components of reader display */
/* ********************************************** */







/* ********************************************** */
/* BEGIN routines for managing application display */

/**
 * Displays on the page the current citation string from the cts object
 * and creates a citable link using CTS URN notation.
 */
application.prototype.displayCiteStr=function() {
    // display citation scheme
    var citestr = ctsconnection.getCiteString();

    var urn = ctsconnection.getURN();
    postDebug("show citestr " + citestr + " with URN " + urn);
    this.setCiteStr(citestr, urn);
} //displayCiteStr


/**
 * Manipulates document elements to display a citation string
 * on the page.
 * @param citestr The citation scheme to display as a String.
 * @param theURN The current URN to display as a String.
 */
application.prototype.setCiteStr=function(citestr, theURN) {
    var citepara = document.getElementById("citationscheme");
    var currChildren = citepara.childNodes;
    for (var rescnt = currChildren.length -1; rescnt >=0; rescnt--) {
	citepara.removeChild(currChildren[rescnt]);
    }
    var displayMessage;
    if (citestr == "") {
	displayMessage = "";
    } else {
	displayMessage  = "Citation scheme: " +  citestr;
    }

    if (theURN != null) {
	if (this.serviceID == null) {
	    displayMessage = displayMessage + " URN: " + theURN;
	} else {
	    displayMessage = displayMessage + ". Bookmarkable URN: ";
	}
    }
    var citationText = document.createTextNode(displayMessage);
    citepara.appendChild(citationText);

    if ((theURN != null) && (this.serviceID != null)) {

	var link = document.createElement("a");
	/*
	link.setAttribute("href", 
			  "ctsr.html?urn=" + theURN
			  + "&service=" + this.serviceID);
	*/
	link.setAttribute("href", "#");
	var longURL = "ctsr.html?urn=" + escape(theURN)
	    + "&service=" + this.serviceID;
	link.setAttribute("onclick",
			  "bookmarkPassage(); return false;"
			  );
	link.appendChild(document.createTextNode(theURN));


	citepara.appendChild(link);
    }
} //setCiteStr


/**
 * Displays a label for the currently selected CTS service
 * as a header of the document's main content section.
 */
application.prototype.setBodyHeader=function() {
    var contentDiv = document.getElementById("appbody");
    if (document.getElementById("servicelabel")) {
	contentDiv.removeChild(document.getElementById("servicelabel"));
    }
    var txtNode = document.createTextNode(this.serviceLabel);
    var newItem = document.createElement("h2");
    newItem.appendChild(txtNode);
    contentDiv.insertBefore(newItem,contentDiv.childNodes[0]);
    // set ID so we can nuke as needed later:
    contentDiv.childNodes[0].setAttribute("id","servicelabel");
} //setBodyHeader


/* END routines for managing application display */
/* ********************************************** */




/**
 * Loads contents of index request XML into an Array structure.
 * Should only be invoked after verifying that the index request XML
 * is fully loaded.
 * @returns An Array of Objects representing the structure of a
 * CTS simple index.
 */
application.prototype.loadIndexArray=function() {
    var indexArray = new Array();

    // test for null xml!
    var resList = this.indexXML.getElementsByTagName("record");
    var numRecords;
    if (resList) {
	numRecords = resList.length;
	postDebug("loadIndexArray: got " + numRecords + " records in request XML.");
    } else {
	return(indexArray);
	postDebug("loadIndexArray: No record elements.");
    }

    for (var resCnt = 0; resCnt < numRecords; resCnt++) {
	indexArray[resCnt] = new Object();

	    // test for existence...
	var valueList = resList[resCnt].getElementsByTagName("value");
	if (valueList.length > 0) {
	    var valueNode = valueList[0]; 

	    var indexList = valueNode.childNodes;
	    if (indexList.length > 0) {
		var indexValue = indexList[0].nodeValue;
		indexArray[resCnt].value = indexValue;

		var urnList = resList[resCnt].getElementsByTagName("urn");
		var urnNode  = urnList[0]; 

		// Catch match elements inserted by eXist
		// (better, filter them out first with XLST?)
		var matchList = urnNode.getElementsByTagName("match");
		var matchNode = matchList[0];
		var urnValue = matchNode.childNodes[0].nodeValue;
		indexArray[resCnt].urn = urnValue;
	    }
	}
    }
    postDebug("Created indexArray with " + indexArray.length + " records.");

    return(indexArray);
} // loadIndexArray






/**
 * Checks for presence of URL parameter indicating whether a specific
 * CTS service should be pre-loaded, a specific passage identified by
 * URN should be pre-loaded, or whether the app should start in a
 * non-default mode.
 */
application.prototype.checkParams=function() {
    if (netscape) {
	try {
	    netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
	} catch (e) {
	    throw("Permission UniversalBrowserRead denied: " + e);
	}
    }
    // Check for parameters to preset: 
    // 1) choice of service
    // 2) URN to load
    // 3) mode to start from
    var urlParameters  = document.location.search.substring(1,255);
    var parametersArray = urlParameters.split("&");
    for (var paramNum= 0 ; paramNum < parametersArray.length; paramNum++) {
	var paramParts = unescape(parametersArray[paramNum]).split("=");

	if ((paramParts[0] == "service")
	    && (paramParts[1] != "") ) {
	    this.serviceID =paramParts[1];

	    postDebug("CHECKPARAMS:  set serviceID to " + this.serviceID);

	    if (this.serviceList.length > 0) {
		this.setServiceFromID();
		if (this.preLoadPassage) {
		    this.preloadPassage();
		}

	    } else {
		postDebug ("this.serviceList not yet loaded ..");
	    }
	}

	if (paramParts[0] == "urn") {
	    postDebug("Also need to load specific URN " + paramParts[1]);

	    this.preLoadPassage = true;
	    ctsconnection.setValuesFromURN(paramParts[1]);

	    postDebug("Reconstruct a urn from " + paramParts[1]
		      + " and you get " + ctsconnection.getURN());


	    if (this.serviceList != null && this.serviceList.length > 0
		&& this.serviceLabel != null) {
		// OK to go ahead and load: 
		// otherwise, will get caught by function handling
		// loading of servicelist
		this.preloadPassage();
	    }

	}

	if (paramParts[0] == "mode") {
	    this.mode = paramParts[1];
	    postDebug ("Set mode to " + this.mode);
	}
    }
} //checkParams




/* **************************************** */

/** 
 * Submits a RetrieveSet request to a lonlat server for 
 * all points loaded in current indexData array, and registers
 * the <code>processLonLats</code> function as the handler to
 * notify with results of the asynchronous request.
 */
application.prototype.requestLonLats=function() {
    var requestURL =this.lonlatURL + "request=RetrieveSet&";
    for (var i = 0;i < this.indexData.length; i++) {
	requestURL = requestURL + "id=" + this.indexData[i].value + "&";
    }
    postDebug("LONLAT request = " + requestURL);

    var lonlatLoader = new net.urlLoader(requestURL,processLonLats);
}



/**
 * Sets up display, formatted for content-type of current index,
 *  as a series of <code>ul</code> elements.
 * Each <code>ul</code> is uniquely identified by an ID attribute 
 * with the URN value they index.
 */
application.prototype.setIndexDisplay=function () {
    if (this.indexData.length > 0) {
    var linksDiv = document.getElementById("links");
    
    // Cycle through indexData checking for 
    // multiple entries for a single URN:
    // make one UL per URN, with one or more
    // LIs per UL:
    var currentRef = this.indexData[0].urn;
    var currentDiv = document.createElement("div");
    currentDiv.className = "invisible";

    currentDiv.setAttribute("id", currentRef);
    var currentList = document.createElement("ul");

    for (var cnt = 1; cnt < this.indexData.length; cnt++) {
	if (this.indexData[cnt].urn != currentRef) {
	    currentDiv.appendChild(currentList);
	    currentList  = null;
	    currentList = document.createElement("ul");

	    linksDiv.appendChild(currentDiv);
		
	    currentRef = this.indexData[cnt].urn;
	    currentDiv = null;
	    currentDiv = document.createElement("div");
	    currentDiv.className = "invisible";
	    currentDiv.setAttribute("id", currentRef);

	}

	var listItem = document.createElement("li");

	// Now add type-specific info:
	if (this.indexType == 'lemmatized') {
	    var mNode = this.addMorphInfoNode(this.indexData[cnt].value,
					 this.indexData[cnt].urn);
	    listItem.appendChild(mNode);


	} else if (this.indexType == 'html') {
	    var notesNode = addHTMLNotes();
	    listItem.appendChild(notesNode);

	} else if (this.indexType == 'geoentities') {
/*
	    postDebug ("Add geoNode for "
		  + this.indexData[cnt].value + ":" +
		  this.indexData[cnt].urn);
*/

	    if (this.indexData[cnt].urn.length > 0) {
		var geoNode = this.addGeoNotes(this.indexData[cnt].value,
					  this.indexData[cnt].urn);
		listItem.appendChild(geoNode);
	    } else {
		alert ("NO URN FOR INDEX ENTRY");
	    }


	} else {
	    // Catch-all for any other index type:
	    listItem.appendChild(document.createTextNode ("Selection indexed to "));
	    var spanNode = document.createElement("span");
	    spanNode.className  = "spanSelected";
	    spanNode.appendChild
		(document.createTextNode(this.indexData[cnt].value));
	    listItem.appendChild(spanNode);
	    listItem.appendChild(document.createTextNode ("; "));


	} // type-specific index entry

	currentList.appendChild(listItem);
    }

    postDebug("setIndexDisplay : created final div in list with id " + currentDiv.getAttribute("id"));

    currentDiv.appendChild(currentList);
    linksDiv.appendChild(currentDiv);
    }
} //setIndexDisplay





/**
 * Takes information from one CTS Index entry of type geoentity
 * and creates DOM elements to display it with links
 * @param geoID The <code>value</code> element of the CTS Index entry.
 * @param fullURN The URN that <code>geoID</code> is mapped on to.
 * @returns A DOM <code>span</code> Element.
 */
application.prototype.addGeoNotes=function(geoID, fullURN) {
//    postDebug("Adding geo note for " + geoID + " from " + fullURN);
    var containerNode = document.createElement("span");
    containerNode.appendChild(document.createTextNode("Place identifier: "));

    var spanNode = document.createElement("span");
    spanNode.className  = "spanSelected";
    spanNode.appendChild(document.createTextNode(geoID));
    containerNode.appendChild(spanNode);

    containerNode.appendChild(document.createTextNode("; see details for "));
    var fullGeo = document.createElement("span");
    fullGeo.className = "search";

    var stringInText = fullURN.substring(fullURN.lastIndexOf(":") + 1, fullURN.length);

    var clickval = "fullGeo('" + geoID + "'); return false;"
    fullGeo.setAttribute("onclick",clickval);
    fullGeo.appendChild(document.createTextNode(stringInText));
    containerNode.appendChild(fullGeo);

    return(containerNode);
}//addGeoNotes


/**
 * Takes information mapping surface form to morphological lemma
 * and formats it as a DOM element for display.
 * @param lemma The lemma value to link to other resources.
 * @param form The surface form appearing in this text passage.
 * @returns A DOM <code>span</code> Element.
 */
application.prototype.addMorphInfoNode=function(lemma, form) {
    var morphNode = document.createElement("span");

    morphNode.appendChild(document.createTextNode ("Lemma:  "));
    var spanNode = document.createElement("span");
    spanNode.className  = "spanSelected";
    spanNode.appendChild
	(document.createTextNode(lemma));
    morphNode.appendChild(spanNode);
    morphNode.appendChild(document.createTextNode ("; "));



    // Add link for mophologically sensitive searching of this text
    // by this lemma:
    /*
      morphNode.appendChild(document.createTextNode(" search this text for "));
    var searchNode = document.createElement("span");
    searchNode.className = "search";

    searchNode.setAttribute("onclick","indexSearch(this); return false;");
    searchNode.appendChild(document.createTextNode(lemma));
    morphNode.appendChild(searchNode);
    */


    var stringInText = form.substring(form.lastIndexOf(":") + 1, form.length);


    // link to search
    morphNode.appendChild(document.createTextNode(" search for all occurrences of "));
    var searchSpan = document.createElement("span");
    searchSpan.className = "search";
    var searchClick = "searchIndex('"  + this.indexID + "', '" + lemma  +"'); return false;"
	searchSpan.setAttribute("onclick",searchClick);
    searchSpan.appendChild(document.createTextNode(lemma));
    morphNode.appendChild(searchSpan);
    morphNode.appendChild(document.createTextNode("; "));



    // Now add link for expansion of morph data.
    morphNode.appendChild(document.createTextNode(" see full analysis for "));
    var fullMorph = document.createElement("span");
    fullMorph.className = "search";

    var clickval = "fullMorphology('" + stringInText + "', '" +
	lemma + "'); return false;"
    fullMorph.setAttribute("onclick",clickval);
    fullMorph.appendChild(document.createTextNode(stringInText));
    morphNode.appendChild(fullMorph);


    morphNode.appendChild(document.createTextNode("; lookup "));
    var lsData = document.createElement("span");
    lsData.className = "search";
    var lsclick = "lewisShort('" + lemma + "'); return false;"
    lsData.setAttribute("onclick",lsclick);
    lsData.appendChild(document.createTextNode(lemma));
    morphNode.appendChild(lsData);
    morphNode.appendChild(document.createTextNode(" in Lewis-Short (from Perseus)"));


    return(morphNode);
} // addMorphNode



/**
 * Sets display of detailed note information for a chosen geographic object.
 * @param rageID Standard identifier of the object to show information about.
 */
application.prototype.fullGeo=function(rageID) {
    // Begin by emptying the footnotes div:
    var fn = document.getElementById("footnotes");
    var currChildren = fn.childNodes;
    for (var rescnt = currChildren.length -1; rescnt >=0; rescnt--) {
	fn.removeChild(currChildren[rescnt]);
    }

    var details = document.createElement("p");

    for (var i=0; i< this.geoData.length;i++) {
	postDebug("fullGeo: test " + this.geoData[i].id);	

	if (this.geoData[i].id == rageID) {
	    postDebug("IT EQUALS " + rageID);
	    var labelElem = document.createElement("strong");
	    labelElem.appendChild(document.createTextNode(this.geoData[i].label));
	    details.appendChild(labelElem);
	    details.appendChild
		(document.createTextNode
		 (" lon:lat " +
		  this.geoData[i].lon + ":" + this.geoData[i].lat
		  ));

	    if (gmaps != "") {
		// center map on this site:
		centerMap(i,this.geoData[i].lon,this.geoData[i].lat,this.geoData[i].label);
	    }
	}
    }

    fn.appendChild(details);
} //fullGeo




/**
 * Opens a new window with URL pointing to Perseus Lewis-Short dictionary
 * page for a given lemma.
 * @param lemma The item to look up in Lewis-Short's Latin Dictionary.
 */
application.prototype.lewisShort=function(lemma) {
    var lewisshortURL = "http://www.perseus.tufts.edu/cgi-bin/lexindex?lang=latin&amp;lookup=" + lemma;
    window.open(lewisshortURL,"Lews-Short");

    // THIS WORKS: NOW CONVERT TO FUNCTION SO WE CAN POP
    // A NEW WINDOW WITHOUT LOSING CONTEXT
    /*
    var lsLink = document.createElement("a");
    lsLink.setAttribute("href",lewisshortURL);
    lsLink.appendChild(document.createTextNode(lemma + " in Lewis-Short."));
    morphNode.appendChild(lsLink);
    morphNode.appendChild(document.createTextNode(" (from Perseus)"));
    */

}


// Inaugurate request for full morph data for 
// a given form and lemma
/**
 */
fullMorphology=function(form, lemma) {
    if (netscape) {
	try {
	    netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
	} catch (e) {
	    throw("Permission UniversalBrowserRead denied: " + e);
	}
    }
    if (!app.morphXSLTloaded) {
	alert("XSLT for morphology net yet loaded.  Please try again.");
    }

    var requestURL = app.morphologyURL + "form=" + form + "&lemma=" + lemma;
    postDebug ("request moprhology at  " + requestURL);

    var morphLoader = new net.urlLoader(requestURL,processFullMorph);
} //fullMorphology



application.prototype.sortByData=function(a, b) {
    return a.value >= b.value;
}

application.prototype.sortByURN=function(a, b) {
    return a.urn >= b.urn;
}


/**
 *
 */
application.prototype.filterDisplay=function() {
    // get either <p> or <l> elements of 
    // results div, and work through their
    // text nodes, replacing text with filtered text.
    postDebug("Filter display now.");

    // We only do this if we have already loaded filtered index data!
    if (this.indexData.length > 0) {
	postDebug("Go ahead and filter with index of " + 
		  this.indexData.length + " records.");


	var resultsDiv = document.getElementById("results");
	var allParas = resultsDiv.getElementsByTagName("p");

	if (allParas && allParas.length > 0) {
	    postDebug("Found " + allParas.length + " paragraph(s) to filter.");
	} else {
	    postDebug("No paragraphs to filter.");
	}


	// For each paragraph, collect all text Nodes into a
	// single string, allText, which we'll then tear apart with
	// regular expressions, and build up into a single HTML
	// string, newHTMLText
	var TEXT_NODE = 3;
	var newHTMLText  = "";
	for (var pCount = 0; pCount < allParas.length; pCount++) {
	    // 1. Create a single string of text nodes:
	    var chillun = allParas[pCount].childNodes;
	    var allText = "";
	    for (var childNum = 0; childNum < chillun.length; childNum++) {
		if (chillun[childNum].nodeType == TEXT_NODE) {
		    allText = allText + chillun[childNum].nodeValue;
		}
	    }

	    // 2. split the string into word chunks:
	    var wordList = allText.split(/[^a-zA-Z ]+/g);
	    postDebug("Paragraph " + pCount + ": concatenated text contains " + wordList.length + "alphabetic word runs.");
	    // Tidy this operation up: strip any leading non-alphabetic
	    // junk left over from the regexp split  by setting the starting 
	    // index of allText:
	    var startIdx = allText.indexOf(wordList[0]) ;
	    allText = allText.substring(startIdx,allText.length);


	    // 3. Cycle through the word chunks, and wrap each word with
	    // HTML if the word appears in the index.
	    // At the same time, we move along through the big string var
	    // allText, so that we can pick up punctuation and non-alphabetic
	    // characters to build back into our HTML string.
	    for (var i = 0; i < wordList.length;i++) {
		newHTMLText = newHTMLText + this.wrap(wordList[i]);
		var start = allText.indexOf(wordList[i]) ;
		var endpt = start + wordList[i].length;
		allText = allText.substring(endpt, allText.length);

		if (i < (wordList.length - 1) ) {
		    var newStart = allText.indexOf(wordList[i+1]);
		    newHTMLText = newHTMLText + allText.substring(0,newStart);
		    if (i != wordList.length - 2) {
			newHTMLText = newHTMLText + " ";
		    }

		} else if (!allText.match(/^[\s]*$/)) {
		    postDebug("ADD TRAILING NOW with allText at |" + allText + "|");
		    newHTMLText = newHTMLText + allText;
		}
		
	    }//for


	    postDebug("Completed wrapping text with index links.");

	    if (newHTMLText.match(/^[\s]*$/)) {
		postDebug("Empty paragraph:  do nothing.");


	    } else {
		// Replace any children of current paragraph with
		// the newly constructed HTML string.
		var currChildren = allParas[pCount].childNodes;
		for (var rescnt = currChildren.length -1; rescnt >=0; rescnt--) {
		    allParas[pCount].removeChild(currChildren[rescnt]);
		}
		allParas[pCount].innerHTML = newHTMLText;
	    }
	    newHTMLText = "";

	} // for each paragraph element in the displayed text


    } else {
	postDebug("No data loaded in index arrray yet.");
    }
} //filterDisplay

application.prototype.isIndexed=function(str) {
    // need to make this case-insensitive
    if (this.indexData.length < 1) {
	postDebug ("No index data loaded!");
	return(false);

    } else {
	for (var n=0; n< this.indexData.length; n++) {

	    var reStr = ":" + str +"$";
	    var rePattern = new RegExp(reStr,"");
	    if (this.indexData[n].urn.match(rePattern)) {
		if (this.indexType != "lemmatized") {
		    postDebug("isINDEXED: FOUND match for |" + str + "|"
			      + " at " +
			      this.indexData[n].urn);
		}

		return(true);
	    }
	}
	if (this.indexType == "lemmatized") {
	    postDebug("isINDEXED: FAILED to find |" + str + "|");
	}
    }

    return(false);
} //isIndexed




/**
 * Turns on highlighting for span of index div identified by 
 * <code>span</code> ID.
 * @param span The ID of the span to display.
 */
application.prototype.showMe=function(span) {
    var resultsDiv = document.getElementById("results");
    var allSpans = resultsDiv.getElementsByTagName("span");

    // Cycle through all tagged spans and turn off any current highlighting:
    for (spanCount=0;spanCount < allSpans.length; spanCount++) {
	if (allSpans[spanCount].className == "spanSelected") {
	    allSpans[spanCount].className = "linked";
	}
    }
    // and turn on highlighting for selected span (passed 
    // in as parameter)
    span.className = "spanSelected";

    // Use a regex to get sub-reference indexed values
    // to a more general URN (e.g., group/work only
    // + a sub-reference string)
    var reString = ctsconnection.getURN() + "\.?.*:" +
	span.childNodes[0].nodeValue;
    var regex = new RegExp(reString);

    postDebug("showMe:  "  + reString +
	      " built on " + span.childNodes[0].nodeValue);

    // Now find and highlight corresponding node in Notes div
    var linksDiv = document.getElementById("links");
    var subDivs =  linksDiv.getElementsByTagName("div");
    for (var i = 0; i< subDivs.length; i++) {
	//	postDebug("checking div " + subDivs[i].getAttribute("id");

	if (subDivs[i].className == "block") {
	    subDivs[i].className = "invisible";
	}

	// NS : do we want to accommodate matches on more general
	// URNs here?
	//	if (subDivs[i].getAttribute("id") == subURN) {

	if (subDivs[i].getAttribute("id").match(regex)) {
	    subDivs[i].className = "block";
	}
    }


    // and empty footnotes div:
    var fn = document.getElementById("footnotes");
    var currChildren = fn.childNodes;
    for (var rescnt = currChildren.length -1; rescnt >=0; rescnt--) {
	fn.removeChild(currChildren[rescnt]);
    }
} //showMe


application.prototype.removeAppended=function() {
    var appendixNode = document.getElementById("appended");
    var currChildren = appendixNode.childNodes;
    for (var rescnt = currChildren.length -1; rescnt >=0; rescnt--) {
	appendixNode.removeChild(currChildren[rescnt]);
    }
} // removeAppended


application.prototype.removeLinks=function() {
    postDebug("Empty links div, and set indexData array to null.");
    var linksDiv = document.getElementById("links");
    var subDivs =  linksDiv.getElementsByTagName("div");
    for (var i = subDivs.length -1 ; i >= 0 ; i--) {
	linksDiv.removeChild(subDivs[i]);
    }

    // also blow away footnotes:
    var fn = document.getElementById("footnotes");
    var currChildren = fn.childNodes;
    for (var rescnt = currChildren.length -1; rescnt >=0; rescnt--) {
	fn.removeChild(currChildren[rescnt]);
    }

    // and for good measure, appended notes, too:
    this.removeAppended();


    postDebug("LInks div now emptied.");
    this.indexData = null;
    this.indexData = new Array();


    if (gmaps != "") {
	map.clearOverlays();
    }

} //removeLinks



// Check words in a text string against the filter index.
// If present in the index, wrap with HTML span.
// 
application.prototype.wrap=function(str) {
    var wordArray = str.split(/[\s+]/g);
    var returnStr = "";
    for (var i=0; i< wordArray.length; i++) {

	if ((wordArray[i].match(/^[^a-zA-Z]+$/)) ||
	    (wordArray[i] == "")) {
	    //skip
	} else {
	    if (this.isIndexed(wordArray[i])) {
		// Wrap word in HTML span element.
		// Special cases of CSS class for well-known index types:
		if (this.indexType == "geoentities") {
		    returnStr = returnStr + 
			" <span class=\"geolinked\" onclick=\"showMe(this)\">"
			+ wordArray[i] + "</span>";

		} else {
		    returnStr = returnStr + 
			" <span class=\"linked\" onclick=\"showMe(this)\">"
			+ wordArray[i] + "</span>";
		}



	    } else {
		returnStr = returnStr + " " + wordArray[i];
	    }
	}
    }
    return (returnStr);
} // wrap


/**
 * Sets CSS class of previous/next buttons in UI
 * depending on status of <code>ctsconnection</code> object.
 */
application.prototype.adjustPrevNext=function() {
    // Now check prev/next values to hide as needed:
    var prevSpan = document.getElementById("prevspan");
    if (ctsconnection.getPrev()) {
	prevSpan.className = "inline";
    } else {
	prevSpan.className = "invisible";
    }

    var nextSpan = document.getElementById("nextspan");
    if (ctsconnection.getNext()) {
	nextSpan.className = "inline";
    } else {
	nextSpan.className = "invisible";
    }
} //adjustPrevNext





/**
 * Sends request to load passage prior to user interaction based on 
 * parameters submitted to initial load of page.
 */
application.prototype.preloadPassage=function() {
    document.title = "Preloading passage...";

    //    displayMode("browsepassage");
    if (this.serviceList.length > 0) {
	if (this.catalogLoaded) {
	    ctsconnection.retrievePassage(processPassageLoad);
	    this.removeLinks();
	    this.loadIndex(ctsconnection.getURN());
	    this.preLoadPassage = false;

	} else {
	    document.title = "Loading catalog in order to retrieve passage... ";
	    this.setStatus("Loading catalog in order to retrieve passage... ");
	}
    }

    // Set menu selections
    this.setTextGroupsMenu(ctsconnection.getTextGroup());
    this.setWorksMenu(ctsconnection.getTextGroup(), ctsconnection.getWork());
    // set passage box
    var passageInput = document.getElementById("passage");
    passageInput.value = ctsconnection.getPassage();
} //preloadPassage


/**
 * Changes message in status display area of web page.
 * @param msg String with message to display.
 */
application.prototype.setStatus=function(msg) {
    var statusDiv = document.getElementById("status");
    if (statusDiv.childNodes.length > 0) {
	statusDiv.removeChild(statusDiv.childNodes[0]);
    }

    if (msg != "") {
	statusDiv.innerHTML = "<p>" + msg + "</p>";
    } 
} //setStatus

/* ********************************************** */
/* BEGIN routines building forms and menu selectors */


// Check up to 25 times...
/**
 * Checks, up to 25 times, whether catalog has finished loading or not.
 * If it hasn't, delays for a second, then checks again.
 * @param count Count of number of times function has checked for loading
 * of catalog.
 */
application.prototype.checkCatalogLoaded=function(count) {
    postDebug("checkCataloaded: " + count);
    this.setStatus("");
    var maxTries = 25;
    if (ctsconnection.getCatalogArray() == 0) {
	if (count >= maxTries) {
	    alert ("Maxed out catalog check!")
	    this.catalogLoaded = false;

	} else {
	    this.setStatus ("Catalog not finished loading from server... please wait...");

	    // kill a  second, then try again ...
	    count++;
	    var timeout = setTimeout("checkCatalogLoaded(" + count + ")",500);
	    postDebug("After timeout, call catloaded with count= " + count);
	    //checkCatalogLoaded(count);
	}


    } else {
	postDebug ("Found CTS catalog with " + ctsconnection.getCatalogArray().length + " entreies.");
	this.catalogLoaded = true;
	document.title= "Catalog loaded: select textgroup";
	this.setStatus("");

	this.setTextGroupsMenu();

	if (this.preLoadPassage) {
	    postDebug("processCapsLoad: need to preload passage");
	    this.preloadPassage();
	}
    }
} //checkCatalogLoaded


/** 
 * Builds a popup menu for textgroups from the data in the ctsconnection 'catalog'
 * @param groupID ID of textgroup to check for.
 */
application.prototype.setTextGroupsMenu=function(groupID) {

    try {
	var selectBox = document.getElementById("textgroup");
	selectBox.options.length = 0;
	selectBox.options[0] = new Option("-- choose a textgroup --","");

	//	checkCatalogLoaded(1);
	if (this.catalogLoaded) {
	    var catalogArray = ctsconnection.getCatalogArray();

	    postDebug("Catalog successfully loaded with "
		      + catalogArray.length + " entries.");

	    for (var cnt=0;cnt < catalogArray.length;cnt++) {
		selectBox.options[cnt + 1] = new Option(catalogArray[cnt].label,
							catalogArray[cnt].id);
		if (catalogArray[cnt].id == groupID) {
		    selectBox.selectedIndex = (cnt + 1);
		}
	    }

	// Check for  list of legnth 1:
	if (catalogArray.length == 1) {
	    selectBox.selectedIndex = 1;
	    ctsconnection.setTextGroup(catalogArray[0].id);
	    app.setWorksMenu(catalogArray[0].id);
	}

	} else {
	    this.setStatus("");


	    //NSNS:
	    // NS:  Change this to a recursive check with time out option.
	    alert ("Catalog not yet loaded from CTS service.  Please wait a moment or check the URL you used for this page.");
	}
    } catch (e) {
	alert ("Problem with textgroups menu: " + e);
    }
} // setTextGroupsMenu



/**
 * Builds a popup menu for works from the data in the ctsconnection 'catalog'
 */
application.prototype.setWorksMenu=function(textGroupID,workID) {
    var works = ctsconnection.getWorksArrayByID(textGroupID);
    var workBox = document.getElementById("work");
    workBox.options.length = 0;

    workBox.options[0] = new Option("-- choose a work --","");

    if (works) {
	for (var workcount=0; workcount < works.length; workcount++) {
	    postDebug("found a work at " + workcount);
	    workBox.options[workcount + 1] = new Option
		(works[workcount].label,
		 works[workcount].id);
	    if (works[workcount].id == workID) {
		workBox.selectedIndex = workcount + 1;
	    }
	}

	// If only 1 item, pre-select it in the box,
	// and set the work value in the cts object :
	if (works.length == 1) {
	    workBox.selectedIndex = 1;
	    ctsconnection.setWork(works[0].id);
	    this.displayCiteStr();
	    this.setVersionsMenu();
	}
	
    } else {
	postDebug("works == null");
    }
} //setWorksMenu


/**
 * Builds a popup menu for versions of works from the data in the ctsconnection 'catalog'
 */
application.prototype.setVersionsMenu=function() {
    // Need to know this to ask for versions by id:
    var versionBox = document.getElementById("version");
    versionBox.options.length = 0;

    versionBox.options[0] = new Option("-- choose a version (optional) --","");

    var menuSize = 0;
    var itsEditions = ctsconnection.getEditionsArrayByID
	(ctsconnection.getTextGroup(), 
	 ctsconnection.getWork());

    var itsTranslations = ctsconnection.getTranslationsArrayByID
	(ctsconnection.getTextGroup(), 
	 ctsconnection.getWork());

    menuSize = itsTranslations.length + itsEditions.length;
    postDebug("Total of " + menuSize + "options to add.");


    if (menuSize == 1) { // preload:
	if (itsEditions.length == 1) {
	    ctsconnection.setTranslation(null);
	    ctsconnection.setEdition(itsEditions[0].id);

	    versionBox.options[1] = new Option
		(itsEditions[0].label,
		 "edition:" + itsEditions[0].id);
	    versionBox.selectedIndex = 1;

	    postDebug("Added " + itsEditions[0].label +
		      ", edition:" + itsEditions[0].id);

	} else {
	    ctsconnection.setEdition(null);
	    ctsconnection.setTranslation(itsTranslations[0].id);
	    versionBox.options[1] = new Option
		(itsTranslations[0].label,
		 "translation:" + itsTranslations[0].id);
	    versionBox.selectedIndex = 1;
	}
    } else { // build whole menu of versions ...be sure to distinguish
	// between trans and ed!
	var totalSlots = 1;
	for (var count=0; count < itsEditions.length; count++) {
	    versionBox.options[count + 1] = new Option
		("edition: " + itsEditions[count].label,
		 "edition:" + itsEditions[count].id);
	    totalSlots++;
	}

	postDebug("After edd., total = " + totalSlots);
	for (var tcount=0; tcount < itsTranslations.length; tcount++) {
	    versionBox.options[totalSlots] = new Option
		("translation: " + itsTranslations[tcount].label,
		 "translation:" + itsTranslations[tcount].id);
	    totalSlots++;
	}

    }
} //setVersionsMenu


/**
 * Builds menu with choice of services for settings/configuration mode
 * from structure loaded into application.serviceList at time of initialization
 * by window.onload
 */
application.prototype.setCTSMenu=function() {
    var selectBox = document.getElementById("service");
    selectBox.options.length = 0;
    selectBox.options[0] = new Option("-- choose a CTS --","");

    for(var count = 0; count < this.serviceList.length; count++) {
	selectBox.options[count + 1] = new Option
		    (this.serviceList[count].title,
		     this.serviceList[count].url);

	if (this.serviceList[count].id == this.serviceID) {
	    selectBox.selectedIndex = count + 1;
	}
    }
} // setCTSMenu





/**
 * Sets up contgents of popup menus dealing with indexes:
 * filter index menu, appended index menu, and search index menu.
 */
application.prototype.setFilterMenu=function() {
    var selectBox = document.getElementById("filteridx");
    selectBox.options.length = 0;
    selectBox.options[0] = new Option("-- choose an index --","");

    var idxArray = ctsidxservice.getCatalogArray();
    for(var count = 0; count < idxArray.length; count++) {
	selectBox.options[count + 1] = new Option
		    (idxArray[count].label,
		     idxArray[count].id);
    }

    // The same values are used for the appended index menu:
    var appendSelectBox = document.getElementById("appendidx");
    appendSelectBox.options.length = 0;
    appendSelectBox.options[0] = new Option("-- choose an index --","");

    var idxArray = ctsidxservice.getCatalogArray();
    for(var count = 0; count < idxArray.length; count++) {
	appendSelectBox.options[count + 1] = new Option
		    (idxArray[count].label,
		     idxArray[count].id);
    }

    // The same menu values are used yet again  for the index search menu:
    var selectBox2 = document.getElementById("queryindex");
    selectBox2.options.length = 0;
    selectBox2.options[0] = new Option("-- choose an index --","");

    for(var count = 0; count < idxArray.length; count++) {
	postDebug(count + ": " + idxArray[count].label);
	selectBox2.options[count + 1] = new Option
		    (idxArray[count].label,
		     idxArray[count].id);
    }

} // setFilterMenu



/* END routines building forms and menu selectors */
/* ********************************************** */


/**
 * Uses current setting for serviceID to set up application
 * interface.
 */
application.prototype.setServiceFromID=function () {

    if ((ctsconnection.ctsurl == null) || (ctsconnection.ctsurl != ctsurl.value)) {
	var theURL;
	for (var snum = 0; snum < this.serviceList.length; snum++) {
	    if (this.serviceList[snum].id  == this.serviceID) {
		theURL = this.serviceList[snum].url;
		this.serviceLabel = this.serviceList[snum].title;
	    }
	}


	if ((this.serviceLabel != null)
	    && (this.mode == "configure")) {
	    displayMode("browsepassage");
	}

	document.title = "Getting catalog ... ";

	try {
	    ctsconnection.selectService(theURL,processCapabilitiesLoad);	
	    postDebug("selected service for URL " + theURL);
	    this.emptyAll();

	    // plus empty service pop up:
	    var serviceBox = document.getElementById("service");
	    serviceBox.options.length = 0;
	    this.setCTSMenu();

	} catch (e) {
	    alert ("Can't select service from ID " + this.serviceID
		   + " with URL " + theURL);
	    postDebug("Error selecting service: " + e);
	}
    }
} //setServiceFromID



/**
 * Sends out a request for index values wihtint currently selected index for
 * a given URN.
 * @param urn The CTS URN used to query currently selected index.
 */
application.prototype.loadIndex=function(urn) {
    postDebug("LOAD index for " + urn + " of type " + this.indexType
	      + " for index " + this.ctsindex);

    if (netscape) {
	try {
	    netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
	} catch (e) {
	    throw("Permission UniversalBrowserRead denied: " + e);
	}
    }

    // This is the ctsindex to filter the text display with:
    if (this.ctsindex) {
	var indexURL = this.ctsindex

	// set request depnding on type of  index:
	if (this.indexType == "lemmatized") {
	    indexURL =  indexURL + "request=QueryIndex&indexID="
	    + this.indexID + "&urn=" + urn; 

	} else if (this.indexType == "geoentities") {
	    postDebug("geo query with indexID = " + this.indexID);
	    indexURL =  indexURL + "request=QueryIndex&indexID="
	    + this.indexID + "&urn=" + urn; 

	} else if (this.indexType == "html") {
	    //	    alert ("NEED TO GEN html type URL FOR URN " + urn );
	}


	postDebug("Request filter index with URL " + indexURL);
	var indexLoader = new net.urlLoader(indexURL,processIndexLoad);
    } else {
	postDebug("loadIndex:  this.ctsindex not defined.");
    }

    app.loadAppendedIndex(urn);
} //loadIndex



/**
 * Sends out a request for index values wihtint currently selected index for
 * a given URN.
 * @param urn
 */
application.prototype.loadAppendedIndex=function(urn) {
    if (netscape) {
	try {
	    netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
	} catch (e) {
	    throw("Permission UniversalBrowserRead denied: " + e);
	}
    }

    // This is a ctsindex to append footnote-style to the text display
    if (this.appendedIndexURL) {
	var indexURL = this.appendedIndexURL;

	// set request depnding on type of  index:
	if (this.indexType == "lemmatized" || this.indexType == "html") {
	    indexURL =  indexURL + "request=QueryIndex&indexID=" +
	    this.appendedIndexID + "&urn=" + urn; 
	}

	postDebug("Request appended index with URL " + indexURL);
	var indexLoader = new net.urlLoader(indexURL,processAppendedIndexLoad);
    } else {
	postDebug("loadIndex:  this.ctsindex not defined.");
    }
} // loadAPpendedIndex

