/*
 * This autocompleter should be constructed with a given populator function.
 * The populator function should call the setChoices(choices) method on this autocompleter
 * where 'choices' is the array of choices to be displayed in the autocomplete div.
 */
Autocompleter.DWR = Class.create();
Autocompleter.DWR.prototype = Object.extend(new Autocompleter.Base(), {
  initialize: function(element, update, maxResults, minChars, delay) {
    this.baseInitialize(element, update, {});
    this.options.array = new Array(0);
    this.blur = true;
    this.mouseOff = false;
    this.lastIndex = 0;
    this.options.choices = maxResults;
    this.options.minChars = minChars;
    this.options.frequency = delay;

    // define a function to populate the drop down list
    this.populator = function(autocompleter, token) {
    
        var field = document.getElementById(element);
        
    	//var req = new XMLHttpRequest();
    	var req = getNewHTTPObject();
        
        var locationurl = "/getPossibleLocations.aspx?ss=" + field.value
        
        req.open("GET", locationurl, false);
        req.send(null);
        
    	eval(req.responseText);
    	autocompleter.setChoices(locations);
    };

    Event.observe(this.update, "mousedown", this.preventBlur.bindAsEventListener(this));
      // If we're not in IE, add the event to scroll
    if (!document.all)
        Event.observe(this.update, "scroll", this.allowBlur.bindAsEventListener(this));
    else
        Event.observe(this.update, "focus", this.allowBlur.bindAsEventListener(this));

    // use this even listener to catch mouse movement so that we can re-enable the
    // mouse hover function if it was disabled by the keyboard scrolling feature
    Event.observe(this.update, "mousemove", this.onMove.bindAsEventListener(this));
  },

  // called by the autocompleter on an event.
  getUpdatedChoices: function() {
    // Trim the user input before comparison against minChars so that spaces don't count in the length
    if(ypCore.trim(this.getToken()).length >= this.options.minChars)
    {
        this.populator(this, this.getToken()); // this is the populator specified in the constructor.
    }
  },

  // should be called by the populator (specified in the constructor)
  setChoices: function(array) {
    this.options.array = array;
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var list        = [];
        // These are symbols that might commonly appear within a location name - parts of a
        // location name immediately following these characters could be highlighted.
        var commonSymbols = ['/', '-', '(', ')', '\''];
        // Reg Exp. defines the characters that will be removed from the user's location clue.
        // ie. All chars that are NOT letters, space, forward-slash, dash or parentheses
        var invalidClueChars = /[^a-zA-Z\s\/\-()']+/g;
        // Reg Exp. defines the characters that will act as delimiters in the user's location clue.
        // by splitting the location clue, the right parts of the location name can be bolded.
        var clueDelimiters = /[\s\/\-()']+/g;
        var clue;

        // Trim whitespace from around the clue, and remove any invalid characters that have been
        // ignored for searching that affect the highlighting of clue terms in the location name.
        clue = ypCore.trim(instance.getToken());
        clue = clue.replace(invalidClueChars, "");

        for (var i = 0; i < instance.options.array.length &&
          list.length < instance.options.choices ; i++) {

          var location = instance.options.array[i];
          var locationWords = location.split(" ");

          // This section of code is responsible for adding the necessary highlighting to the
          // location results. The part of the location name that matches part of the specified
          // location clue will be wrapped in <strong> tags, so it will be displayed in bold.
          var clueTerms   = clue.split(clueDelimiters);
          for(var j = 0; j < locationWords.length; j++)
          {
              for(var k = 0; k < clueTerms.length;  k++)
              {
                  var clueTermPos = instance.options.ignoreCase ?
                    locationWords[j].toLowerCase().indexOf(clueTerms[k].toLowerCase()) :
                    locationWords[j].indexOf(clueTerms[k]);

                  // Only match if the clue term appears at the beginning of the location word or immediately following
                  // a common symbol.
                  if (clueTerms[k].length > 0 && (clueTermPos == 0 || (clueTermPos >= 0 && commonSymbols.indexOf(locationWords[j].substr(clueTermPos-1, 1)) != -1)))
                  {
                      var substr = locationWords[j].substr(clueTermPos, clueTerms[k].length);
                      locationWords[j] = locationWords[j].replace(substr, "<strong>" + substr + "</strong>");

                      // Clear this particular clue term so that it is not matched elsewhere in the string.
                      clueTerms[k] = "";
                  }
              }
          }

          location = "<li>" + locationWords.join(" ") + "</li>";

          list.push(location);
        }

        if (list.length > 10)
        	return "<ul class='scrollable'>" + list.join('') + "</ul>";
        else
        	return "<ul size='10'>" + list.join('') + "</ul>";
      }
    }, options || {});
  },

  markPrevious: function() {
  	// when using the keyboard make sure that
  	// the mouse hover listener is disabled
  	// otherwise the list will constantly scroll
  	// back to where the mouse curser is
  	this.mouseOff = true;
    if(this.index > 0) this.index--;
  },

  markNext: function() {
  	// when using the keyboard make sure that
  	// the mouse hover listener is disabled
  	// otherwise the list will constantly scroll
  	// back to where the mouse curser is
  	this.mouseOff = true;
    if(this.index < this.entryCount-1) this.index++;
  },

  scrollto: function() {
    var topItem = Position.cumulativeOffset(this.getEntry(0));
  	var selItem = Position.cumulativeOffset(this.getEntry(this.index));
  	this.update.firstChild.scrollTop=selItem[1]-(topItem[1]+20);

  },

  onHover: function(event) {
  	// The mouse hover event could be disabled if the
  	// user is scrolling through the list using the keyboard.
  	// In that case, we don't want to do anything because
  	// it might interfere with the keyboard selection.
  	if (!this.mouseOff) {
	    var element = Event.findElement(event, 'LI');
	    if(this.index != element.autocompleteIndex)
	    {
	        this.index = element.autocompleteIndex;
	        this.render();
	    }
	    Event.stop(event);
    }
  },

  // need this method to re-enable the mouse hover
  // listener if it was disabled by the keyboard actions
  onMove: function(event) {
  	this.mouseOff = false;
  },

  // This function overrides the render function from controls.js which proved to be extremely
  // inefficient when re-rendering the list because it was iterating over every element each time it
  // was called. Instead, the last selected index is retained so that each time, the selected class can
  // be removed from it, and added to the new index which in turn becomes the last selected index.
  render: function() {
      if(this.entryCount > 0) {

        Element.removeClassName(this.getEntry(this.lastIndex), "selected");
        Element.addClassName(this.getEntry(this.index), "selected");
        this.lastIndex = this.index;

        if(this.hasFocus) {
          this.show();
          this.active = true;
        }
      } else {
        this.active = false;
        this.hide();
      }

      // When the mouse has been turned off for up/down key scrolling, scroll to the current location
      // We don't want to scroll when the mouse is moved otherwise the list will scroll unexpectedly.
      if (this.mouseOff)
      {
          this.scrollto();
      }
    },

  // this function is triggered when the input text field
  // (i.e. this.element) loses focus, with normal behaviour
  // being to hide the options div, however, there are instances
  // when we want to temporarily focus on something else so
  // added additional logic to ensure that in those cases
  // the options div is not closed
  onBlur: function(event) {
    // document.selection is IE specific attribute, its type can tell
    // us if the this.element has focus (i.e. type == 'Text' means that
    // the input field is still in focus), if type == 'None' we know that
    // this.element has lost focus (this only happens when the scroll bar
    // is clicked)
    if (!this.blur) {
        return;
    }

    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;
  },

  preventBlur: function(event) {
      this.blur = false;
  },

  allowBlur: function(event) {
    this.blur = true;
    this.element.focus();
}

});
