/* URL to the PHP page called for receiving suggestions for a keyword*/
var getFunctionsUrl = "http://www.shamash.org/kosher/suggest.php?varname=";
//var getFunctionsUrl = "http://localhost:8502/kmaplive/suggest.php?varname=";
/* the keyword for which the most recent HTTP request has been initiated */
var httpRequestKeyword = "";
/* the varname for which the most recent HTTP request has been initiated */
var httpRequestVarname= "";
/* last keyword for which suggests have been requested */
var userKeyword = "";
/* array of suggestions received as results for the keyword */
var suggestions = new Object();
/* the maximum number of characters to be displayed for a suggestion */
var suggestionMaxLength = 50;
/* flag that indicates if the up or down arrow keys were pressed
   the last time a keyup event occurred  */
var isKeyUpDownPressed = false;
/* the last suggestion that has been used for autocompleting the keyword */
var autocompletedKeyword = "";
/* flag that indicates if there are results for the current requested keyword*/
var hasResults = false;
/* the identifier used to cancel the evaluation with the clearTimeout method. */
var timeoutId = -1;
/* the currently selected suggestion (by arrow keys or mouse)*/
var position = -1;
/* cache object containing the retrieved suggestions for different keywords */
var oCache = new Object();
/* the minimum and maximum position of the visible suggestions */
var minVisiblePosition = 0;
var maxVisiblePosition = 8;
// the autocomplete input with current focus
var curFocus = null;
// when set to true, display detailed error messages
var debugMode = true;
/* the XMLHttp object for communicating with the server */
var xmlHttpGetSuggestions = createXmlHttpRequestObject();

/* the onload event is handled by our init function */
window.onload = init;

// creates an XMLHttpRequest instance
function createXmlHttpRequestObject() 
{
  // will store the reference to the XMLHttpRequest object
  var xmlHttp;
  // this should work for all browsers except IE6 and older
  try
  {
    // try to create XMLHttpRequest object
    xmlHttp = new XMLHttpRequest();
  }
  catch(e)
  {
    // assume IE6 or older
    var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0",
                                    "MSXML2.XMLHTTP.5.0",
                                    "MSXML2.XMLHTTP.4.0",
                                    "MSXML2.XMLHTTP.3.0",
                                    "MSXML2.XMLHTTP",
                                    "Microsoft.XMLHTTP");
    // try every prog id until one works
    for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++) 
    {
      try 
 
      { 
        // try to create XMLHttpRequest object
        xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
      } 
      catch (e) {}
    }
  }
  // return the created object or display an error message
  if (!xmlHttp)
    ; // alert("Error creating the XMLHttpRequest object.");
  else 
    return xmlHttp;
}

/* function that initializes the page */
function init() 
{
  initVar();
  
  // set the timeout for checking updates in the keyword's value
  setTimeout(checkForChanges, 500);
} 

/* function that initializes each suggest element's controls */
function initVar() 
{
  var x=document.getElementsByTagName("input");
  for (i=0;i<x.length;i++) {
    if ( x[i].id.substr( 0, 8 ) == "keyword-" ) {
      // prevent browser from starting the autofill function
      x[i].setAttribute("autocomplete", "off");  
      // reset the content of the keyword and set the focus on it
      // Commented out 18-Jan-2009 by Mark Ferneau to allow default values
      // x[i].value = "";
    }
  }
}

/* function that adds to a keyword an array of values */
function addToCache(varname, keyword, values)
{
  // create a new array entry in the cache
  oCache[varname][keyword] = new Array();
  // add all the values to the keyword's entry in the cache
  for(i=0; i<values.length; i++)
    oCache[varname][keyword][i] = values[i];
}

/* 
   function that checks to see if the keyword specified as parameter is in 
   the cache or tries to find the longest matching prefixes in the cache 
   and adds them in the cache for the current keyword parameter
*/
function checkCache(varname, keyword)
{
  // check to see if the keyword is already in the cache
  if(oCache && oCache[varname] && oCache[varname][keyword])
    return true;
  else if ( oCache && !oCache[varname] )
    oCache[varname] = new Object();
  // create lowercase version of keyword - needed as .indexOf() is case-sensitive
  lowerKeyword = keyword.toLowerCase();
  // try to find the biggest prefixes
  for(i=keyword.length-2; i>=0; i--)
  {
    // compute the current prefix keyword 
    var currentKeyword = keyword.substring(0, i+1);
    // check to see if we have the current prefix keyword in the cache
    if(oCache[varname][currentKeyword])
    {            
      // the current keyword's results already in the cache
      var cacheResults = oCache[varname][currentKeyword];
      // the results matching the keyword in the current cache results
      var keywordResults = new Array();
      var keywordResultsSize = 0;            
      // try to find all matching results starting with the current prefix
      for(j=0;j<cacheResults.length;j++)
      {
 
        lowerCacheResult = cacheResults[j].toLowerCase();
        
        // If the substring exists, we have a match 
        if(lowerCacheResult.indexOf(lowerKeyword) != -1)               
          keywordResults[keywordResultsSize++] = cacheResults[j];
      }      
      // add all the keyword's prefix results to the cache
      addToCache(varname, keyword, keywordResults);      
      return true;  
    }
  }
  // no match found
  return false;
}

/* initiate HTTP request to retrieve suggestions for the current keyword */
function getSuggestions(keyword, varname) 
{  
  /* continue if keyword isn't null and the last pressed key wasn't up or 
     down */
  if(keyword != "" && keyword.length > 2 && !isKeyUpDownPressed)
  {
    // check to see if the keyword is in the cache
    isInCache = checkCache(varname, keyword);
    // if keyword is in cache...
    if(isInCache == true)          
    {   
      // retrieve the results from the cache          
      httpRequestKeyword=keyword;
      userKeyword=keyword;
      httpRequestVarname=varname;
      // display the results in the cache
      displayResults(keyword, oCache[varname][keyword], varname);                          
    }
    // if the keyword isn't in cache, make an HTTP request
    else    
    {    
      if(xmlHttpGetSuggestions)
      { 
        try
        {
          /* if the XMLHttpRequest object isn't busy with a previous
             request... */
          if (xmlHttpGetSuggestions.readyState == 4 || 
              xmlHttpGetSuggestions.readyState == 0) 
          {    
            httpRequestKeyword = keyword;
            httpRequestVarname = varname;
            userKeyword = keyword;
            url = getFunctionsUrl + encode(varname) + "&keyword=" + encode(keyword);
            xmlHttpGetSuggestions.open("GET", url, true);
            xmlHttpGetSuggestions.onreadystatechange = handleGettingSuggestions;
                                             
            xmlHttpGetSuggestions.send(null);
          }
          // if the XMLHttpRequest object is busy...
          else
          {
            // retain the keyword the user wanted             
            userKeyword = keyword;
            // clear any previous timeouts already set
            if(timeoutId != -1)
              clearTimeout(timeoutId);          
            // try again in 0.5 seconds     
            timeoutId = setTimeout("getSuggestions(userKeyword, varname);", 500);
          }
        }
        catch(e)
 
        {
          //displayError("Can't connect to server:\n" + e.toString());
        }
      }
    }    
  }
}

/* transforms all the children of an xml node into an array */
function xmlToArray(resultsXml)
{
  // initiate the resultsArray
  var resultsArray= new Array();  
  // loop through all the xml nodes retrieving the content  
  for(i=0;i<resultsXml.length;i++)
    resultsArray[i]=resultsXml.item(i).firstChild.data;
  // return the node's content as an array
  return resultsArray;
}

/* handles the server's response containing the suggestions 
   for the requested keyword  */
function handleGettingSuggestions() 
{
  //if the process is completed, decide what to do with the returned data
  if (xmlHttpGetSuggestions.readyState == 4) 
  {
    // only if HTTP status is "OK"
    if (xmlHttpGetSuggestions.status == 200) 
    { 
      try
      {
        // process the server's response
        updateSuggestions();
      }
      catch(e)
      {
        // display the error message
        displayError(e.toString()); 
      }  
    } 
    else
    {
      displayError("There was a problem retrieving the data:\n" + 
                   xmlHttpGetSuggestions.statusText);
    }       
  }
}

/* function that processes the server's response */
function updateSuggestions()
{
  // retrieve the server's response 
  var response = xmlHttpGetSuggestions.responseText;
  if ( response )
  // server error?
  if (response.indexOf("ERRNO") >= 0 
      || response.indexOf("error:") >= 0
      || response.length == 0)
    throw(response.length == 0 ? "Void server response." : response);
  // retrieve the document element
  response = xmlHttpGetSuggestions.responseXML.documentElement;
  // initialize the new array of functions' names
  responseArray = new Array();
  // check to see if we have any results for the searched keyword
 
  if(response.childNodes.length)
  {
    /* we retrieve the new responses from the document element as 
       an array */
    responseArray= xmlToArray(response.getElementsByTagName("name"));       
  }
  // check to see if other keywords are already being searched for
  if(httpRequestKeyword == userKeyword)    
  {
    // display the results array
    displayResults(httpRequestKeyword, responseArray, httpRequestVarname);
  }
  else
  {
    // add the results to the cache
    // we don't need to display the results since they are no longer useful
    addToCache(httpRequestVarname, httpRequestKeyword, responseArray );              

  }
}

/* return an string with a subportion of it in bold */
function boldSubstring( masterString, subString )
{
  lowerMaster = masterString.toLowerCase();
  lowerSub = subString.toLowerCase();
              
  // If the substring does not exist, return the original
  firstOccurrence = lowerMaster.indexOf(lowerSub);
  if( firstOccurrence == -1)
    return masterString;
  else {
    returnString = masterString.substring(0, firstOccurrence );
    returnString += "<b>";
    returnString += subString;
    returnString += "</b>";
    returnString += masterString.substring(firstOccurrence+subString.length, masterString.length); 
    return returnString;
  }               
}

/* populates the list with the current suggestions */
function displayResults(keyword, results_array, varname) 
{  
  // start building the HTML table containing the results  
  var div = "<table>"; 
  // if the searched for keyword is not in the cache then add it to the cache
  if((!oCache[varname] || !oCache[varname][keyword]) && keyword)
    addToCache(varname, keyword, results_array);
  // if the array of results is empty display a message
  if(results_array.length == 0)
  {
    div += "<tr><td>No results found for <strong>" + keyword + 
           "</strong></td></tr>";
    // set the flag indicating that no results have been found 
    // and reset the counter for results
    hasResults = false;
    suggestions[varname] = 0;
  }
  // display the results
  else
  {
    // resets the index of the currently selected suggestion
    position = -1;
    // resets the flag indicating whether the up or down key has been pressed
    isKeyUpDownPressed = false;
    /* sets the flag indicating that there are results for the searched 
       for keyword */
    hasResults = true;
    // get the number of results from the cache
    suggestions[varname] = oCache[varname][keyword].length;
    // loop through all the results and generate the HTML list of results
    for (var i=0; i<oCache[varname][keyword].length; i++) 
    {  
      // retrieve the current result
      crtFunction = oCache[varname][keyword][i];
      // start building the HTML row that contains the name
      div += "<tr id='tr-" + varname + "-" + i + 
             "' onmousedown='handleOnMouseClick(this);' onmouseover='handleOnMouseOver(this);' " + 
             "onmouseout='handleOnMouseOut(this);'>" + 
             "<td align='left'>";
        // Bold the substring   
        div += boldSubstring( crtFunction, httpRequestKeyword);
        div += "</td></tr>";
    }
  }
  // end building the HTML table
  div += "</table>";
  // retrieve the suggest and scroll object
  var oSuggest = document.getElementById("suggest-" + varname);  
  var oScroll = document.getElementById("scroll-" + varname);
  // set the scroll pane's height
  oScroll.style.height = "180px";
  // scroll to the top of the list
  oScroll.scrollTop = 0;
  // update the suggestions list and make it visible
  oSuggest.innerHTML = div;
  oScroll.style.visibility = "visible";
  // if we had results we apply the type ahead for the current keyword
//  if(results_array.length > 0)
//    autocompleteKeyword();      
}

/* function that periodically checks to see if the typed keyword has changed */
function checkForChanges()
{

  if ( curFocus != null ) {
    // retrieve the keyword object
    var keyword = document.getElementById("keyword-" + curFocus).value;
    // check to see if the keyword is empty
    varname = curFocus;
    if(keyword == "") 
    {
      // hide the suggestions
      hideSuggestions();
      // reset the keywords 
      userKeyword="";
      userVarname="";
      httpRequestKeyword="";
      httpRequestVarname="";
    }
    // check to see if there are any changes
    if((userKeyword != keyword) && 
       (autocompletedKeyword != keyword) && 
       (!isKeyUpDownPressed))
      // update the suggestions
      getSuggestions(keyword,varname);
  }
  // set the timer for a new check 
  setTimeout(checkForChanges, 500);
}

/* function that handles the keys that are pressed */
function handleKeyUp(e) 
{
  // get the event
  e = (!e) ? window.event : e;
  // get the event's target
  target = (!e.target) ? e.srcElement : e.target;
  if (target.nodeType == 3) 
    target = target.parentNode;
  // get the character code of the pressed button
  code = (e.charCode) ? e.charCode :
       ((e.keyCode) ? e.keyCode :
       ((e.which) ? e.which : 0));
  // check to see if the event was keyup
  if (e.type == "keyup") 
  {    
    isKeyUpDownPressed =false; 
    // check to see we if are interested in the current character
    if ((code < 13 && code != 8) || 
        (code >=14 && code < 32) || 
        (code >= 33 && code <= 46 && code != 38 && code != 40) || 
        (code >= 112 && code <= 123)) 
    {
      // simply ignore non-interesting characters
    }
    else
      /* if Enter is pressed we just leave the current item entered */
      if(code == 13)
      {
        oTr=document.getElementById("tr-"+ curFocus + "-" + position);
        if ( oTr ) {
          updateKeywordValue(oTr);
          // set the autocompleted word to the keyword's value
          autocompletedKeyword=oTr.value;
        }
        hideSuggestions();
        setBlur( curFocus );
        return false;
      }        
      else
        // if the down arrow is pressed we go to the next suggestion
        if(code == 40 && curFocus)
        {                   
          newTR=document.getElementById("tr-"+ curFocus + "-" + (++position));
          oldTR=document.getElementById("tr-"+ curFocus + "-" + (--position));
          // deselect the old selected suggestion   
          if(position>=0 && position<suggestions[httpRequestVarname]-1)
            oldTR.className = "";
   
          // select the new suggestion and update the keyword
          if(position < suggestions[httpRequestVarname] - 1)
          {
            newTR.className = "highlightrow";
            updateKeywordValue(newTR);
            position++;         
          }     
          e.cancelBubble = true;
          e.returnValue = false;
          isKeyUpDownPressed = true;        
          // scroll down if the current window is no longer valid
          if(position > maxVisiblePosition)
          {   
            oScroll = document.getElementById("scroll-" + curFocus);
            oScroll.scrollTop += 18;
            maxVisiblePosition += 1;
            minVisiblePosition += 1;
          }
        }
        else
          // if the up arrow is pressed we go to the previous suggestion
          if(code == 38 && curFocus)
          {       
            newTR=document.getElementById("tr-"+ httpRequestVarname + "-" + (--position));
            oldTR=document.getElementById("tr-"+ httpRequestVarname + "-" + (++position));
            // deselect the old selected position
            if(position>=0 && position <= suggestions[httpRequestVarname] - 1)
            {       
              oldTR.className = "";
            }
            // select the new suggestion and update the keyword
            if(position > 0)
            {
              newTR.className = "highlightrow";
              updateKeywordValue(newTR);
              position--;
              // scroll up if the current window is no longer valid
              if(position<minVisiblePosition)
              {
                oScroll = document.getElementById("scroll-" + httpRequestVarname );
                oScroll.scrollTop -= 18;
                maxVisiblePosition -= 1;
                minVisiblePosition -= 1;
              }   
            }     
            else
              if(position == 0)
                position--;
            e.cancelBubble = true;
            e.returnValue = false;
            isKeyUpDownPressed = true;  
          }     
  }
}

/* function that updates the keyword value with the value 
   of the currently selected suggestion */
function updateKeywordValue(oTr)
{
  // retrieve the keyword object
  var oKeyword = document.getElementById("keyword-" + curFocus );
  // retrieve the link for the current function
  if ( oTr.textContent == null ) 
    oKeyword.value = oTr.innerText;
  else 
    oKeyword.value = oTr.textContent;
}

/* function that removes the style from all suggestions*/
function deselectAll()
{ 
  for(i=0; i<suggestions[curFocus]; i++)
  {
    var oCrtTr = document.getElementById("tr-" + curFocus + "-" + i);
    oCrtTr.className = "";    
  }
}

/* function that handles the mouse entering over a suggestion's area 
   event */
function handleOnMouseOver(oTr)
{
  deselectAll();  
  oTr.className = "highlightrow";
  index = oTr.id.lastIndexOf( "-" ); 
  if ( index >= 0 )
    position = oTr.id.substring(index+1, oTr.id.length);
    
}

/* function that handles the mouse clicking on a suggestion's area event */
function handleOnMouseClick(oTr)
{
  updateKeywordValue(oTr);
  // set the autocompleted word to the keyword's value
  autocompletedKeyword=oTr.value;
}


/* function that handles the mouse exiting a suggestion's area event */
function handleOnMouseOut(oTr)
{
  oTr.className = "";   
  position = -1;
}

/* function that escapes a string */
function encode(uri) 
{
  if (encodeURIComponent) 
  {
    return encodeURIComponent(uri);
  }

  if (escape) 
  {
    return escape(uri);
  }
}

/* function that hides the layer containing the suggestions */
function hideSuggestions()
{
  var oScroll = document.getElementById("scroll-" + curFocus);
  if ( oScroll ) {
    oScroll.style.visibility = "hidden";
    oScroll.style.height = "0px";
  }  
}

/* function that selects a range in the text object passed as parameter */
function selectRange(oText, start, length)
{  
  // check to see if in IE or FF
  if (oText.createTextRange) 
  {
    //IE
    var oRange = oText.createTextRange(); 
    oRange.moveStart("character", start); 
    oRange.moveEnd("character", length - oText.value.length); 
    oRange.select();
 
  }
  else 
    // FF
    if (oText.setSelectionRange) 
    {
      oText.setSelectionRange(start, length);
    } 
  oText.focus(); 
}

/* function that autocompletes the typed keyword*/
function autocompleteKeyword()
{
  //retrieve the keyword object
  var oKeyword = document.getElementById("keyword-" + curFocus);
  // reset the position of the selected suggestion
  position=0;
  // deselect all suggestions
  deselectAll();
  // highlight the selected suggestion 
  rowID = "tr-" + curFocus + "-0";
  document.getElementById(rowID).className="highlightrow";  
  // update the keyword's value with the suggestion
  updateKeywordValue(document.getElementById(rowID));  
  // apply the type-ahead style
  selectRange(oKeyword,httpRequestKeyword.length,oKeyword.value.length);  
  // set the autocompleted word to the keyword's value
  autocompletedKeyword=oKeyword.value;
}

/* function that displays an error message */
function displayError(message)
{
  // display error message, with more technical details if debugMode is true
  //alert("Error accessing the server! "+ (debugMode ? "\n" + message : ""));
}

function setFocus(name) {
  if ( curFocus != name ) {
    if ( curFocus )
      hideSuggestions();
    curFocus = name;
  } 
}

function setBlur(name) {
  if ( curFocus == name ) {
    hideSuggestions();
    curFocus = null; 
  }
}

// This prevents "enter" in the autocomplete list from submitting the form
function HandleEnter(event)
{
  if(window.event) key = window.event.keyCode;     //IE
  else key = event.which;     //firefox
  
  if(key==13) return false;
  else return true;
}
