/* --------------------------------------------------------
 * class EventCompatibility
 * 
 * Provides cross-browser compatibility of events.  
 * -------------------------------------------------------- */
function EventCompatibility( oEvent ) {
  this.oEvent = oEvent || window.event;
}

EventCompatibility.prototype.getTarget = function() {
  return this.oEvent.target || this.oEvent.srcElement;
}

EventCompatibility.prototype.getCode = function() {
  return this.oEvent.keyCode || this.oEvent.which; 
}

/* --------------------------------------------------------
 * class TextboxEventHandler
 * -------------------------------------------------------- */
function TextboxEventHandler( oTextbox, oAutoSuggest ) {
  this.oTextbox = oTextbox;
  this.oAutoSuggest = oAutoSuggest;

  var oTextboxEventHandler = this;
  
  this.oTextbox.onkeydown = function( oEvent ) { oTextboxEventHandler.handleKeyDown( oEvent ) }
  this.oTextbox.onkeyup   = function( oEvent ) { oTextboxEventHandler.handleKeyUp( oEvent ) }  
  this.oTextbox.onblur    = function()         { oTextboxEventHandler.handleBlur() }
}

TextboxEventHandler.prototype.handleKeyDown = function( oEvent ) { 
  /* handles three of the keydown events: 
   * up arrow, down arrow, enter */
  var oEventCompatibility = new EventCompatibility( oEvent );  
 
  switch( oEventCompatibility.getCode() ) {
    case 40: //down arrow 
      this.oAutoSuggest.nextSuggestion();
      break;
    case 38: //up arrow
      this.oAutoSuggest.previousSuggestion();
      break;
    case 13: //enter
      if( this.oAutoSuggest.iCur == -1 ) {
        submitQuery();
      }
      else {
        this.oAutoSuggest.copyAndHide(this.oAutotSuggest.iCur);
      }
      break;            
  }
}

TextboxEventHandler.prototype.handleKeyUp = function( oEvent ) {
  var oEventCompatibility = new EventCompatibility( oEvent );  
  var code = oEventCompatibility.getCode();       
      
  // suppress non-alphanumeric key codes, except backspace (8) and delete (46)
  if( code == 8 || code == 46 || code == 32 || code >= 46 && code < 112 || code > 123 ) { 
    this.oAutoSuggest.textboxHasChanged();
  }
}

TextboxEventHandler.prototype.handleBlur = function() {
  this.oAutoSuggest.hideSuggestions(); 
}


/* --------------------------------------------------------
 * class SuggestionEventHandler
 * 
 * Each Suggestion-div-element is associated with the mousover 
 * and mousedown events through SuggestionEventHandler class.  
 * -------------------------------------------------------- */
function SuggestionEventHandler( oSuggestion, id, oAutoSuggest ) {
  this.oSuggestion = oSuggestion;
  this.id = id;
  this.oAutoSuggest = oAutoSuggest;

  var oSuggestionEventHandler = this;
  
  this.oSuggestion.onmouseover = function( oEvent ) { oSuggestionEventHandler.handleMouseOver( oEvent ); }
  this.oSuggestion.onmousedown = function( oEvent ) { oSuggestionEventHandler.handleMouseDown( oEvent ); }
}

SuggestionEventHandler.prototype.handleMouseOver = function( oEvent ) {
  var oEventCompatibility = new EventCompatibility( oEvent );  
  var oTarget = oEventCompatibility.getTarget();       
  this.oAutoSuggest.highlightSuggestion( this.id );
}

SuggestionEventHandler.prototype.handleMouseDown = function( oEvent ) {
  /* when the user clicks on the a suggestion, place
   * the query into a textbox and hide dropdown.
   * ----------------------------------------------- */
  this.oAutoSuggest.copyAndHide( this.id );
}
  

/* --------------------------------------------------------
 * class AutoSuggest
 * 
 * oTextbox   - DOM element for textbox containing a query
 * sLeft      - textbox distance from the left
 * cl         - cl=<cl> server-side target class name
 * -------------------------------------------------------- */
function AutoSuggest( oTextbox, sTextboxLeft, cl ) {
  var oAutoSuggest = this;
  this.cl = cl;  
  
  // textbox
  this.oTextbox = oTextbox;
  this.oTextboxEventHandler = new TextboxEventHandler( oTextbox, oAutoSuggest );
	
  // dropdown
  if( navigator.userAgent.search('MSIE' ) == -1 ) { 
    var ieWidthCorrection = 0;
  } else {
    // IE includes the border in width. Make textbox witdth cross-browser compatible
    var ieWidthCorrection = +2;
  }
  this.oDropDown = document.createElement('div');    
  this.oDropDown.className   = 'autosuggest';
  this.oDropDown.style.left  = sTextboxLeft;
  this.oDropDown.style.width = this.oTextbox.offsetWidth -2 + ieWidthCorrection + 'px'; // int->string conversion needed by FF
  document.getElementById('container').appendChild( this.oDropDown );  
  
  // suggestions
  this.aCachedSugg = [];
  this.aDisplSugg = [];
  this.iCur = -1; // The iCurrently selected suggestions.
  this.sendHttpRequest( '/front/ajax/ajax.php?cl=' + this.cl + '&op=init' ); // populate initially
  this.bPopulateSugg = true;  
}

AutoSuggest.prototype.textboxHasChanged = function() {
  /* User has typed new text box input, 
   * e.g. change from 'Lo' to 'Los'
   * ---------------------------------- */    
  this.updateDisplSugg();
    
  if( this.aDisplSugg.length == 0 ) {
    // no existing suggestions found. Request more suggestions.
    this.sendHttpRequest( '/front/ajax/ajax.php?cl=' + this.cl + '&op=refresh&p=' + this.oTextbox.value );
  }
  else {
    // found existing suggestions in array aSuggestions
    this.showSuggestions();  
  }
}

AutoSuggest.prototype.updateDisplSugg = function() {  
  /* Updates the display suggestions with the suggestions from the cache 
   * which still match the query in the textbox
   * ----------------------------------------------- */     
  var sQuery = this.oTextbox.value.toLowerCase();
  this.aDisplSugg = [];
    
  for( var i=0; i<this.aCachedSugg.length; i++ ) {
    if( this.aCachedSugg[i].toLowerCase().indexOf( sQuery ) == 0 ) {
      this.aDisplSugg.push( this.aCachedSugg[i] );
    } 
  }
}

AutoSuggest.prototype.showSuggestions = function() {
 /* Builds the sequence of suggestion divs 
  * and turns dropdown div on. 
  * -------------------------------------- */   
  var iTboxLen = this.oTextbox.value.length;
  var iDispLen = this.aDisplSugg.length;
  
  if( iTboxLen > 0 ) {
    this.oDropDown.innerHTML = '';  // remove the old suggestions
    
    for( var i=0; i < iDispLen && i < 15; i++ ) {
      // for each suggestion create one new Suggestion div
      var oSuggestion = document.createElement( 'div' );
    
      // HTML formating 
      var boldSubstr   = this.aDisplSugg[i].substr(0, iTboxLen );
      var normalSubstr = this.aDisplSugg[i].substr( iTboxLen, 30 );
      oSuggestion.className = 'suggestion';
      oSuggestion.innerHTML = '<p><b>' + boldSubstr + '</b>' + normalSubstr + '</p>';
      
      new SuggestionEventHandler( oSuggestion, i, this );
      
      this.oDropDown.appendChild( oSuggestion );
    }
    this.oDropDown.style.display = 'block';
  }
  else {
    this.hideSuggestions();
  }
}

AutoSuggest.prototype.nextSuggestion = function () {
 /* Highlights the next suggestion in the dropdown and
  * places the suggestion string into the textbox.
  * ---------------------------------------------- */
  var childNodes = this.oDropDown.childNodes;
  var iCur = this.iCur;

  if( childNodes.length > 0 && this.iCur < childNodes.length-1 ) {
    this.highlightSuggestion( ++iCur );
    this.oTextbox.value = this.aDisplSugg[iCur]; 
  }
}

AutoSuggest.prototype.previousSuggestion = function () {
 /* Highlights the previous suggestion in the dropdown and
  * places the suggestion into the textbox. */
  var childNodes = this.oDropDown.childNodes;
  var iCur = this.iCur;    

  if( childNodes.length > 0 && this.iCur > 0 ) {
    this.highlightSuggestion( --iCur );
    this.oTextbox.value = this.aDisplSugg[iCur]; 
  }
}

AutoSuggest.prototype.hideSuggestions = function () {
  this.oDropDown.style.display = 'none';
  this.iCur = -1;
}

AutoSuggest.prototype.highlightSuggestion = function( targetId ) {
  var childNodes = this.oDropDown.childNodes;
  
  if( this.iCur >= 0 ) { childNodes[this.iCur].className = 'suggestion'; }
  childNodes[targetId].className = 'active_suggestion';
  this.iCur = targetId;
}

AutoSuggest.prototype.copyAndHide = function( targetId ) {
  // copies the targeted suggestion into the textbox 
  // and hides the selection dropdown
  this.oTextbox.value = this.aDisplSugg[targetId];             
  this.hideSuggestions(); 
}

AutoSuggest.prototype.sendHttpRequest = function( url ) {
  var oAutoSuggest = this; 
  
  if( window.XMLHttpRequest ) {
    // branch for native XMLHttpRequest object
    this.oHttpRequest = new XMLHttpRequest();
  }
  else if (window.ActiveXObject) {
    this.oHttpRequest = new ActiveXObject('Microsoft.XMLHTTP');
  }    

  // attach time stamp to suppress caching in IE
  url = url + '&' + new Date().valueOf();
  
  this.oHttpRequest.onreadystatechange = function() { oAutoSuggest.handleHttpResponse(); }
  this.oHttpRequest.open( 'GET', url, true );
  this.oHttpRequest.send( null );
}

AutoSuggest.prototype.handleHttpResponse = function() {
  // only if req shows 'complete'
  if( this.oHttpRequest.readyState == 4 ) {
    switch( this.oHttpRequest.status ) {
      case 200:
        // only if 'OK'
        if( this.doParseResponse() ) {
          // Http response contained new suggestions which have been stored in the cache.
          this.updateDisplSugg();
        
          // while populating the suggestion cache there is no need to show suggestions
          if( this.bPopulateSugg ) 
            this.bPopulateSugg = false; 
          else
            this.showSuggestions();  
        }
        else {
          this.hideSuggestions();
        }
        break;
        
      case 0: 
        // happens during search.submit. Javascript becomes inactive but responses come back.
        // ignore
        break;
        
      default:
    }
  }
}


/* -------------------------------------------------------
 * class SubjectAutoSuggest   (extends AutoSuggest)
 * ------------------------------------------------------- */
function SubjectAutoSuggest( sTextboxLeft ) {
  SubjectAutoSuggest.prototype = new AutoSuggest( document.getElementById('p'), sTextboxLeft, 'SubjectAutoSuggest' ); 

  SubjectAutoSuggest.prototype.doParseResponse = function() {  
    var oDoc = this.oHttpRequest.responseXML.documentElement;
    var aSubject = oDoc.getElementsByTagName('s');
    
    for( var i=0; i < aSubject.length; i++ ) {
      var sSubject = aSubject[i].firstChild.data;
      this.aCachedSugg.push( sSubject );
    }
    return aSubject.length ? true : false;    
  }  
}


/* ------------------------------------------------------
 * class CityAutoSuggest (extends AutoSuggest)
 * ------------------------------------------------------ */
function CityAutoSuggest( sTextboxLeft ) {
  CityAutoSuggest.prototype = new AutoSuggest( document.getElementById('l'), sTextboxLeft, 'CityAutoSuggest' );

  CityAutoSuggest.prototype.doParseResponse = function() {
    var oDoc  = this.oHttpRequest.responseXML.documentElement;
    var aCity = oDoc.getElementsByTagName('c');
    
    for( i=0; i < aCity.length; i++ ) {
      var sCity = aCity[i].firstChild.data;
      this.aCachedSugg.push( sCity );
    }
    return aCity.length ? true : false;    
  }
}