/** 
 * Author and Version Information {{{
 * author: Antonio Ramirez http://webeaters.blogspot.com
 *
 * class: AutoComplete for Prototype 1.6.0
 *
 * version: 1.2.1 - 2007-11-11 
 * 		(based on AutoSuggest 2.1.3 - 2007-07-19)
 * version: 1.3.0 - 2008-01-03 by Andrew Nicols <andrew@nicols.co.uk>
 *  - Fixed incorrect title-casing - CSS is Case Sensitive!!!
 *  - Adjusted the way in which the Notifier images are loaded.
 *  - Changed json code to pass all json variables back instead of just id, value and name
 *  - Fixed 'GMAIL' code such that if valueSep is undefined, it is ignored
 *  - Changed the default for valueSep to null
 *  - Fixed the resetTimeout function
 *
 * REFERENCES AND THANKS 
 * this class is based on the work in AutoSuggest.js of
 * Timothy Groves - http://www.brandspankingnew.net
 * and adapted for use with prototype 1.6.0
 *
 * UPDATED by RŽéda HADJOUTI
 * GMAIL like AutoComplete (semicolon separator) Update
 *
 }}}*/

/**
 * Modified by Dehru Cromer for iggli 06/09
 * Modifications include categories of suggestions.
 * Using the prototype event model.
 */

var AutoComplete = Class.create();

AutoComplete.prototype = { // {{{
  Version: '1.3.0',
  REQUIRED_PROTOTYPE: '1.6.0',

  initialize: function (id, param) { // {{{
  	// check whether we have the appropiate javascript libraries
  	this.PROTOTYPE_CHECK();
	
    // Get the field we're watching.
    // It needs to be a valid field so throw an error if it's not valid or can't be found.
    this.fld = $(id);
    if (!this.fld)
    {
      throw("AutoComplete requires a field id to initialize");
    }
	
    // Init variables
    this.sInp 	= ""; // input value 
    this.nInpC 	= 0;	// input value length
    this.aSug 	= []; // suggestions array 
    this.iHigh 	= 0;	// level of list selection 
    this.highElement = null;// item highlighted
	
  // Parameter Handling {{{
	// Set the use specified options
	this.options = param ? param : {};
	// These are the default settings {{{
  var k, def = {
    valueSep:null,
    minchars:2,
    meth:"get",
    varname:"input",
    className:"autocomplete",
    timeout:50000000,
    delay:400,
    offsety:-5,
    shownoresults: true,
    noresults: "No results were found.",
    maxheight: 250,
    cache: true,
    maxentries: 25,
    onAjaxError:null,
    setWidth: false,
    minWidth: 100,
    maxWidth: 200,
    useNotifier: true,
    searchType: "all"
  };
  //}}}
  // Overlay any values which weren't user specified.
	for (k in def) 
	{
		if (typeof(this.options[k]) != typeof(def[k]))
			this.options[k] = def[k];
	}
  // End of Parameter Handling }}}

  // Not everyone wants to use the Notifier. Give them the option	
	if (this.options.useNotifier)
  {
    this.fld.addClassName('ac_field');
  }

	// set keyup handler for field
	// and prevent AutoComplete from client
	var p = this;
	
	// NOTE: not using addEventListener because UpArrow fired twice in Safari
	this.fld.onkeypress 	= function(ev){ return p.onKeyPress(ev); };
	this.fld.onkeyup      = function(ev){ return p.onKeyUp(ev); };
  // ARN-DEBUG Chances are we want to reset the timeout when they lose focus, at least that's what I prefer
	this.fld.onblur			  = function(ev){ p.resetTimeout(); return true; };	
  // ARN-DEBUG Not sure what this is about!
	this.fld.setAttribute("AutoComplete","off");

  }, //}}}

  convertVersionString: function (versionString){ // {{{
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
  }, // }}}

  PROTOTYPE_CHECK: function() { // {{{
    if((typeof Prototype=='undefined') || 
       (typeof Element == 'undefined') || 
       (typeof Element.Methods=='undefined') ||
       (this.convertVersionString(Prototype.Version) < 
        this.convertVersionString(this.REQUIRED_PROTOTYPE)))
       throw("AutoComplete requires the Prototype JavaScript framework >= " +
        this.REQUIRED_PROTOTYPE);
  }, // }}}

  // set responses to keypress events in the field
  // this allows the user to use the arrow keys to scroll through the results
  // ESCAPE clears the list
  // RETURN sets the current highlighted value
  // UP/DOWN move around the list

  onKeyPress: function (e) { // {{{
  	if (!e) e = window.event;
  	var key	= e.keyCode || e.wich;
  	

    switch(key)
    {
      case Event.KEY_RETURN:
    	if (this.highElement) {
    		this.doSetHighlightedValue(this.highElement);
    		Event.stop(e);
    	} else {
    		//show results that dissappeared
    		if (!$(this.acID)) {
  				this.getSuggestions(this.fld.value);
    		}
    		else 
    			Invite.MultiSearch.submitSearch();
    	}
        break;
      case Event.KEY_TAB:
    	if (this.highElement) {
    		this.doSetHighlightedValue(this.highElement);
    		Event.stop(e);
    	}
        break;
      case Event.KEY_ESC:
        this.clearSuggestions();
        break;
    }
    return true;
  }, //}}}

  onKeyUp: function (e) { // {{{
  	if (!e) e = window.event;
  	//search results being displayed?
  	
  	var key = e.keyCode || e.wich;
  	
  	if (key == Event.KEY_UP || key == Event.KEY_DOWN) 
  	{
  		this.changeHighlight(key);
  		//bring back the list.  I want it back.
  		if ( key == Event.KEY_DOWN) {
  			if (!$(this.acID))
  				this.getSuggestions(this.fld.value);
  		}
  		Event.stop(e);
  	} 
  	else this.getSuggestions(this.fld.value);
  	
	return true;
  }, //}}}

  getSuggestions: function(val) { // {{{
  	// input the same? do nothing
  	if(val==this.sInp) return false;
  	
  	// kill the old list
  	if($(this.acID)) $(this.acID).remove();
  	
  	this.sInp = val;
  	
  	// input length is less than the min required to trigger a request
  	// do nothing
  	if (val.length < this.options.minchars)
  	{
  		this.aSug 	= [];
  		this.nInpC	= val.length; 
  		return false;
  	}

  	// Here we will detect if there is a comma and the splitted value has a value to check
  	// comma stars a new search and val is converted to the new value after the comma
  	var ol	= this.nInpC; // old length
  	this.nInpC	= val.length ? val.length : 0;
  	
  	// if caching enabled, and we didn't receive the maxentries value
    // and user is typing (ie. length of input is increasing)
  	// filter results out of suggestions from last request
  	var l = this.aSug.length;
  	if( this.options.cache && ( this.nInpC > ol ) && l && ( l < this.options.maxentries ) )
  	{
  		var arr = new Array();
  		for (var i=0;i<l;i++) {
  			if (this.aSug[i].value.toLowerCase().indexOf(val.toLowerCase()) != -1)
        {
  				arr.push(this.aSug[i]);
        }
  		}
  		this.aSug = arr;
  		
  		// recreate the list
  		this.createList(this.aSug);
  	} else {
  		// do new request
  		var p = this;
  		//var input	= this.sInp; // send the converted new value (comma)
  		clearTimeout(this.ajID); // ajax id timer
  		this.ajID = setTimeout( function () {p.doAjaxRequest(p.sInp)}, this.options.delay);
  	}
    document.helper = this;	
  	return false;
  }, // }}}

  getLastInput : function(str) { // {{{
  	var ret = str;
  	if (undefined != this.options.valueSep) {
  		var idx = ret.lastIndexOf(this.options.valueSep);
      ret = idx == -1 ? ret : ret.substring(idx + 1, ret.length);
  	}
  	
  	return ret;
  }, // }}}

  doAjaxRequest: function (input) { // {{{
  	// we have to check here if there is a new splitted value (, or ;)
  	// always check against the last part of the comma and then check
  	// saved input is still the value of the field
  	if (input != this.fld.value) 
  		return false;
  	
  	// Gmail like : get only the last user's input
  	this.sInp = this.getLastInput(this.sInp);
 	
  	// create ajax request
  	// do we need to call a function to recreate the url?
  	if (typeof this.options.script == 'function')
  		var url = this.options.script(encodeURIComponent(this.sInp));
  	else
  		var url = this.options.script+this.options.varname+'='+encodeURIComponent(this.sInp);
  	
  	if(!url) return false;
  	
  	var p = this;
  	var m = this.options.meth;  // get or post?
    if( this.options.useNotifier )
    {
      this.fld.removeClassName('ac_field');
	    this.fld.addClassName('ac_field_busy');
    };
  	
  	var options = {
  		method: m,
  		parameters: {searchType:p.options.searchType},
  		onSuccess: function (req) { // {{{
        if( p.options.useNotifier )
        {
          p.fld.removeClassName('ac_field_busy');
          p.fld.addClassName('ac_field');
        };
        p.setSuggestions(req,input);
  		}, // }}}

  		onFailure: (typeof p.options.onAjaxError == 'function')? function (status) { // {{{
        if (p.options.useNotifier)
        {
          p.fld.removeClassName('ac_field_busy');
          p.fld.addClassName('ac_field');
        }
        p.options.onAjaxError(status)
      } : // }}}

      function (status) { // {{{
        if (p.options.useNotifier)
        {
          p.fld.removeClassName('ac_field_busy');
          p.fld.addClassName('ac_field');
        }
        //alert("AJAX error: "+status); 
      } // }}}
    }
  	// make new ajax request
  	new Ajax.Request(url, options);
  }, // }}}

  setSuggestions: function (req, input) { // {{{
	  // if field input no longer matches what was passed to the request
	  // don't show the suggestions
	  // here we need to check against the splitted values if any (, or ;)
    if (input != this.fld.value)
      return false;
	
    this.aSug = [];
    if(this.options.json) 
    { // response in json format?
      var jsondata = eval('(' + req.responseText + ')');
      //alert("json data: " + jsondata.results);
      this.aSug = jsondata.results;
    } else {
      // response in xml format?
      var results = req.responseXML.getElementsByTagName('results')[0].childNodes;
    
      for(var i=0;i<results.length;i++)
      {
        if(results[i].hasChildNodes())
          this.aSug.push(  { 'id':results[i].getAttribute('id'), 'value':results[i].childNodes[0].nodeValue, 'info':results[i].getAttribute('info') }  );
      }
    }
    this.acID = 'ac_'+this.fld.id;
    this.createList(this.aSug);
  }, // }}}

  createDOMElement: function ( type, attr, cont, html ) { // {{{
    var ne = document.createElement( type );
	
    if (!ne)
      return 0;
      
    for (var a in attr)
      ne[a] = attr[a];
    
    var t = typeof(cont);
    
    if (t == "string" && !html)
      ne.appendChild( document.createTextNode(cont) );
    else if (t == "string" && html)
      ne.innerHTML = cont;
    else if (t == "object")
      ne.appendChild( cont );

    return ne;
  }, // }}}
  
  createListItem: function(obj, i) {
	  //alert("createListItem: obj.value: " + obj.value + ", obj.info: " + obj.info);
	  // format output with the input enclosed in a EM elementFromPoint
      // (as HTML not DOM)
	  
      var val 	= obj.value;
      if (obj.type == "zip") {
    	val = "Find events in zip: " + obj.value;  
      }
	  var st 		= val.toLowerCase().indexOf(this.sInp.toLowerCase()); // HERE WE CHECK AGAINST THE SPLITTED VALUE IF ANY***
	  if (st > -1) {
		  var output 	= val.substring(0,st) + '<em>' + val.substring(st,st+this.sInp.length) + '</em>' + val.substring(st+this.sInp.length);
	  } else {
		  var output 	= val;
	  }
      var span	= this.createDOMElement('span',{},output,true); // type of, properties, output, isHTML?
			
      if(obj.info != '') // do we need to add extra info?
      {
                
        var small = this.createDOMElement('small',{}, obj.info);
        span.appendChild(small);
      }
      var a 	= this.createDOMElement('a',{href:'#'});
      
      var tl	= this.createDOMElement('span',{className:'tl'},'&nbsp;',true);
      var tr	= this.createDOMElement('span',{className:'tr'},'&nbsp;',true);
      
      a.appendChild(tl);
      a.appendChild(tr);
      a.appendChild(span); // add the object span into the link
			
      a.name = i+1;
      a.id = "ac_" + a.name;
      /*
       a.onclick 		= function () { // {{{
        p.setHighlightedValue();
        return false; 
      }; // }}}
      a.onmouseover	= function () { // {{{
        p.setHighlight("ac_" + a.name); 
      }; // }}} 
       */	
      var li = this.createDOMElement('li', {className:'ac_li'}, a); // add the link element to a li element
      li.obj = obj;//will reference the object if clicked on.
      
      //alert("returning li: " + li);
      return li;
	  
  }, // }}}
  
  
  createListMoreItem: function(obj) { // {{{
	  
	  var moreStr = obj.more + " more..."; 
	  var a = this.createDOMElement('a',{href:'#'}, moreStr, true);
	  var li = this.createDOMElement('li', {className:'ac_li total'}, a); // add the link element to a li element
	  li.obj = obj;//will reference the object if clicked on.
	  
	  return li;
	  
  }, // }}}
  
  createListItems: function(arr, statsObj, ul) {
	  //alert("arr: " + arr.length + ", ul: " + ul);
	  for (var i=0,l = arr.length; i<l; i++)
      {
        ul.appendChild(this.createListItem(arr[i], i));
      }
	  if (statsObj && statsObj.more && statsObj.more > 0) ul.appendChild(this.createListMoreItem(statsObj));
	  return ul;
  }, // {{{

  createList:	function(res) { // {{{
    // get rid of the old list if any  
  	if($(this.acID)) $(this.acID).remove();
  	
  	// clear list removal timeout
  	this.killTimeout();
  	
  	// if no results, and showNoResults is false, do nothing
  	//alert("artist: " + res.artist.length + ", city: " + res.city.length + ", venue: " + res.venue.length);
  	if (res.length == 0 && !this.options.shownoresults) return false;
  	
  	// create holding div
  	var div	= this.createDOMElement('div', {id:this.acID, className:this.options.className});
  	
  	// create div header
  	var hcorner = this.createDOMElement('div', {className: 'ac_corner'});
  	var hbar	= this.createDOMElement('div', {className: 'ac_bar'});
  	var header	= this.createDOMElement('div', {className: 'ac_header'});
  	header.appendChild(hcorner
  			);
  	header.appendChild(hbar);
  	div.appendChild(header);
  	
    // create and populate ul
    var ul	= this.createDOMElement('ul', {className:'ac_ul'});
    // no results?
    var total = res.artist.length + res.city.length + res.venue.length + res.zip.length;
    if (total == 0 && this.options.shownoresults)
    {
      var li = this.createDOMElement('li', {className: 'ac_warning'}, this.options.noresults );
      ul.appendChild(li);
    } else {
      // loop through res of suggestions creating an LI element for each of them
      if (res.artist.length > 0) {
    	  var ulArtist	= this.createDOMElement('ul', {className:'ac-ul-artist'});
    	  ul.appendChild(this.createListItems(res.artist, res.artistStats, ulArtist));
      }
      if (res.venue.length > 0) {
    	  var ulVenue	= this.createDOMElement('ul', {className:'ac-ul-venue'});
    	  ul.appendChild(this.createListItems(res.venue, res.venueStats, ulVenue));
      }
      if (res.city.length > 0) {
    	  var ulCity	= this.createDOMElement('ul', {className:'ac-ul-city'});
    	  ul.appendChild(this.createListItems(res.city, res.cityStats, ulCity));
      }
      if (res.zip) {
    	  var ulZip	= this.createDOMElement('ul', {className:'ac-ul-zip'});
    	  ul.appendChild(this.createListItems(res.zip, false, ulZip));
      }
    }
    
    div.appendChild(ul); // add the newly created list to the div element
    
    // create div footer
    var fcorner = this.createDOMElement('div', {className: 'ac_corner'});
  	var fbar	= this.createDOMElement('div', {className: 'ac_bar'});
  	var footer	= this.createDOMElement('div', {className: 'ac_footer'});
  	footer.appendChild(fcorner);
  	footer.appendChild(fbar);
  	div.appendChild(footer);
  	
  	// get position of target textfield
    // position holding div below it
    // set width of holding div to width of field 
    // if 
    
    var pos         = this.fld.cumulativeOffset();
    div.style.left 	= pos[0] + "px";
    div.style.top 	= pos[1] + this.fld.offsetHeight + "px";
    
    
    
    // set mouseover functions for div
    // when mouse pointer leaves div, set a timeout to remove the list after an interval
    // when mouse enters div, kill the timeout so the list won't be removed
    //
    var p 	= this; // pointer that we will need later on
    div.onmouseover 	= function(){ p.killTimeout() };
    div.onmouseout 		= function(){ p.resetTimeout() };
    
    // add DIV to document
    document.getElementsByTagName("body")[0].appendChild(div);
    
    // highlight first item if only 1 term and it's a zip
    this.iHigh = 0;
    if (total==1 && this.sInp.length == 5 && Number(this.sInp)) {
    	//this.iHigh = 1;
    	//this.setHighlight(this.iHigh);
    	this.setDefaultHighlight();
    }
    this.observeItems();
    
    // remove list after interval
    this.toID	= setTimeout(
      function () {
        p.clearSuggestions() 
      }, this.options.timeout
    );
	
  }, // }}}

  changeHighlight:	function(key) { // {{{
	var list = $$('li.ac_li');
    if (!list)
      return false;
	
    var n;
    n = (key == Event.KEY_DOWN || key == Event.KEY_TAB)? this.iHigh + 1 : this.iHigh - 1; // false assumed to be Event.KEY_UP
    n = (n > list.size())? list.size() : ((n < 1)? 1 : n);	
    
    this.doHighlight(list.toArray()[n-1]);
    this.iHigh = n;
    this.resetTimeout();
    
  }, // }}}
  
  setDefaultHighlight: function() { // {{{
	var item = $$('li.ac_li:first');
	
  	if (!item) return false;
  	if (!item[0]) return false;

  	this.doHighlight(item[0]);
  	
  }, // }}}
  
  setHighlight:		function(event) { // {{{
	var item = Event.findElement(event, 'li');
	this.doHighlight(item);
  	this.killTimeout();
  }, // }}}
  
  doHighlight: function(element) { // {{{
	if (!element) return;
	this.clearHighlight();
	element.addClassName('ac_highlight');
	this.highElement=element;
  }, // }}}

  clearHighlight:	function() { // {{{
  	if(this.highElement)
  	{
  		this.highElement.removeClassName('ac_highlight');
  		this.highElement=null;
  	}
  	
  }, // }}}

  setHighlightedValue:	function(event) { // {{{
	var element = Event.findElement(event, 'li');
	this.doSetHighlightedValue(element);
	//this involves having a character that starts a new search
	/*
		// Gmail like
    if (undefined != this.options.valueSep) {
      var str = this.getLastInput(this.fld.value);
      var idx = this.fld.value.lastIndexOf(str);
      str = this.aSug[ this.iHigh -1 ].value + this.options.valueSep;
      this.sInp = this.fld.value = idx == -1 ? str : this.fld.value.substring(0, idx) + str;
    } else {
      var str = this.getLastInput(this.fld.value);
      var idx = this.fld.value.lastIndexOf(str);
      str = this.aSug[ this.iHigh -1 ].value;
      this.sInp = this.fld.value = idx == -1 ? str : this.fld.value.substring(0, idx) + str;
    }
		
	// move cursor to end of input (safari)
	this.fld.focus();
	if(this.fld.selectionStart)
		this.fld.setSelectionRange(this.sInp.length, this.sInp.length);
	*/	
	
  }, // }}}
  
  doSetHighlightedValue: function(element) {
	//alert(this.getLastInput(this.sInp).length);
	/*
	 if(element.obj.type == 'zip' && this.getLastInput(this.sInp).length < 5) {
		//not a full zip
		return false;
	}
	*/
	
	this.clearSuggestions();
		
	// pass selected object to callback function, if exists
	if (typeof this.options.callback == 'function')
		this.options.callback(element.obj); // the object has the properties we want, it will depend of
  },
  
  observeItems: function() {
	  var items = $$('li.ac_li');
	  items.each( function(item) {
		  Event.observe(item, 'mouseover', this.setHighlight.bindAsEventListener(this));
		  Event.observe(item, 'click', this.setHighlightedValue.bindAsEventListener(this));
	  }, this);
  },

  killTimeout:	function() { // {{{
  	clearTimeout(this.toID);
  }, // }}}

  resetTimeout:	function() { // {{{
  	this.killTimeout();
  	var p = this;
  	this.toID = setTimeout(
      function () { 
        p.clearSuggestions();
      }, p.options.timeout
    );
    // ARN-DEBUG Added p.options.timeout back :|
  }, // }}}

  clearSuggestions:	function () { // {{{
	
    this.killTimeout();
    this.highElement=null;
    if ($(this.acID))
    {
      this.fadeOut(300,function () {
        $(this.acID).remove();
      } );
    }
  }, // }}}

  fadeOut:	function (milliseconds, callback) { // {{{
  	this._fadeFrom 	= 1;
  	this._fadeTo	= 0;
  	this._afterUpdateInternal = callback;
  	
  	this._fadeDuration	= milliseconds;
  	this._fadeInterval = 50;
  	this._fadeTime = 0;
  	var p = this;
  	this._fadeIntervalID = setInterval(
      function() {
        p._changeOpacity()
      }, this._fadeInterval
    );
  
  }, // }}}

  _changeOpacity: function() { // {{{
 
    if (!$(this.acID))
    {
  		this._fadeIntervalID=clearInterval(this._fadeIntervalID);
  		return;
  	} 
  	this._fadeTime += this._fadeInterval;
  	
  	var ieop = Math.round( (this._fadeFrom + ((this._fadeTo - this._fadeFrom) * (this._fadeTime/this._fadeDuration))) * 100)
  	var op = ieop / 100;
 
  	var el = $(this.acID);
  	if (el.filters) // internet explorer
  	{
      try {
        el.filters.item("DXImageTransform.Microsoft.Alpha").opacity = ieop;
      } catch (e) { 
        // If it is not set initially, the browser will throw an error.
        // This will set it if it is not set yet.
        el.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+ieop+')';
      }
    } else	{
      el.style.opacity = op;
    }
	
    if (this._fadeTime >= this._fadeDuration)
    {
      clearInterval( this._fadeIntervalID );
      if (typeof this._afterUpdateInternal == 'function')
        this._afterUpdateInternal();
    }

  } // }}}
 
} // }}}

/**
* These functions called to submit searches.
*/
Invite.MultiSearch = {
   searchBox: false,
	//When u click on the button.
	submitSearch: function() {
		if ($F('multi-search').length > 0) {
			$('multi-search').addClassName('ac_field_busy');
			window.location.href="/search.iggli?searchType=artist&searchTerm=" + $F('multi-search');
		}
	},
	//Through selecting an item in the multi-search
	submitSuggestedItem: function(obj) {
		//this is a more link of a specific type.
		//alert(Object.keys(obj));
		if(obj.more) {
			window.location.href="/search.iggli?searchType=" + obj.type + "&searchTerm=" + $F('multi-search');
		} else {
			if (obj.type == "artist") {
				window.location.href="/artist.iggli?artistId=" + obj.id;
			} else if (obj.type == "venue") {
				window.location.href="/venue.iggli?venueId=" + obj.id;
			} else {
				var searchTerm = "?searchTerm=";
				if (obj.type == "city") {
					searchTerm += obj.value;
				} else {
					//this is a zip search to whatsup.
					searchTerm += obj.value;
				}
				window.location.href="/whatsup.iggli" + searchTerm;
			}
			
		}
	},
	 /**
	  * This hooks up the input element
	 */
	observeSearchBox: function() {
		var multiSearchOptions = {
				script:'/ajax2/multi-search-autocomplete.iggli?json=true&limit=6&',
				varname:'searchTerm',
				json:true,
				shownoresults:true,
				maxresults:16,
				callback: Invite.MultiSearch.submitSuggestedItem }
		Invite.MultiSearch.searchBox=new AutoComplete('multi-search',multiSearchOptions);
	}
}



