/**
 * @filename suggest.js
 * @fileoverview Defines the Auto Suggest class that hanles all suggestions 
 * on both Krak and EGO
 */

var Suggest = Class.create({
	/**
	 * Creates a suggest object attached to the given textField
	 *
	 * @param  {Element}  textField  The input field to attach a suggest to
	 * @param  {Object}   options    See descriptions below
	 *
	 * Available options are:
	 *   {String}   what             Specifies what kind of data to fetch from the backend
	 *   {String}   type             In the dual field setup specifies which type to use.
	 *                               Available types are:
	 *                                 suggest_what
	 *                                 suggest_where
	 *                                 where_only
	 *                                 what_only
	 *   {String}   currentClassName This classname will be appended to the currently selected element from the list
	 *   {Number}   delay            Milliseconds to wait after the user stops typing before the request is sent
	 *   {Element}  form             Specifies the form to serialize to parameters when sending the request
	 */
	initialize: function(textField, options) {
		this.textField = $(textField);
		this.textField.setAttribute("autocomplete", "off");
		this.textField.focus();
		this.setCaretPosition();
		this.options = Object.extend({
			currentClassName: 'current',
			id_prefix: 's-',
			delay: 200,
			form: null
		}, options);

		this.suggestions = [];
		this.typedValue = "";
		this.timeout = null;
		this.lastSearch = null;

		this.createSuggestDiv();;
		this.hideSuggestDiv();
		this.positionSuggestDiv();

		this.textField.observe("keydown", this.navigate.bindAsEventListener(this));
		this.textField.observe("keyup", this.suggest.bindAsEventListener(this));
		this.textField.observe("blur", this.blur.bindAsEventListener(this));
		this.suggestDiv.observe("mouseover", this.hover.bindAsEventListener(this));
		this.suggestDiv.observe("click", this.click.bindAsEventListener(this));
		var resizeFunc = this.positionSuggestDiv.bindAsEventListener(this);
		document.observe("resize", resizeFunc);
		Event.observe(window, "resize", resizeFunc);
		document.observe("click", this.hideSuggestDiv.bind(this));
	},
	/**
	 * Creates the suggest element container and adds it to the document
	 */
	createSuggestDiv: function() {
		this.suggestDiv = new Element("div");
		this.suggestDiv.addClassName("suggest");
		$(document.getElementsByTagName("body")[0]).appendChild(this.suggestDiv);
	},
	/**
	 * Displays the suggest
	 */
	showSuggestDiv: function() {
		this.suggestDiv.style.display='block';
		this.positionSuggestDiv();
	},
	/**
	 * Hides the suggest
	 */
	hideSuggestDiv: function() {
		this.suggestDiv.style.display='none';
	},
	/**
	 * Positions the suggest just below the input field it is attached to
	 */
	positionSuggestDiv: function() {
		var pos = this.textField.cumulativeOffset();
		var size = this.textField.getDimensions();
		var top = (pos[1] + size.height).toString() + 'px';
		var left = pos[0] + 'px';
		this.suggestDiv.setStyle({"left": left, "top": top, 'width': (size.width-2) + 'px' });
	},
	/**
	 * Selects an element in the list
	 *
	 * @param  {Element}  element  The element to select
	 */
	select: function(element) {
		this.current = element;
		return element.addClassName(this.options.currentClassName);
	},
	/**
	 * Clears the selection of the currently selected element
	 */
	unselect: function() {
		if (this.current) {
			this.current.removeClassName(this.options.currentClassName);
			delete this.current;
		}
	},
	/**
	 * Remove all suggestions from the suggest container
	 */
	clearSuggestions: function() {
		this.suggestDiv.update();
		delete this.current;
	},
	/**
	 * Triggered by mouseover on the suggest container
	 * Selects the element below the mouse
	 *
	 * @param  {Event}  event  The triggered event object
	 */
	hover: function(event) {
		var element = event.element();
		if (element.match("li")) {
			this.unselect();
			this.select(element);
		}
	},
	/**
	 * Selects the active element and closes the suggest
	 *
	 * @param  {Event}  event  The triggered event object
	 */
	click: function(event) {
		var element = event.element();
		if (element.match("li."+this.options.currentClassName)) {
			var idx = parseInt(element.id.substring(this.options.id_prefix.length, element.id.length));
			this.textField.value = this.longNames[idx];
			this.hideSuggestDiv();
			this.textField.focus();
			this.setCaretPosition();
		}
	},
	/**
	 * When focus is lost we make sure to abort any active request
	 *
	 * @param  {Event}  event  The triggered event object
	 */
	blur: function(event) {
		if (this.request) {
			this.request.abort();
		}
	},
	/**
	 * Moves the caret to the end of the input field
	 */
	setCaretPosition : function() {
		var text = this.textField;
		// ie Support
		if (document.selection) {
			text.focus();
			var osel = document.selection.createRange();
			osel.moveStart('character', -text.value.length);
			osel.moveStart('character', text.value.length);
			osel.moveEnd('character', 0);
			osel.select();
		}
		// firefox support
		else if (text.selectionStart || text.selectionStart == '0') {
			text.selectionStart = text.value.length;
			text.selectionEnd = text.value.length;
			text.focus();
		}
	},
	/**
	 * Handle the keybord navigation using up/down or keyboard selection using tab/return
	 *
	 * @param  {Event}  event  The triggered event object
	 */
	navigate: function(event) {
		var key = event.charCode || event.keyCode;
		if (key === Event.KEY_UP || key === Event.KEY_DOWN) {
			if (!this.suggestDiv.visible()) {
				this.suggest(event);
				event.stop();
				return;
			}
			var lis = $A($(this.suggestDiv).getElementsByTagName("li"));

			// none selected		
			if (typeof(this.current) === "undefined") {
				if (key === Event.KEY_DOWN) {
					this.select(lis.first());
				} else if (key === Event.KEY_UP) {
					this.select(lis.last());
				}
			}
			// one selected (can't be more than one)
								     
			else if (this.current) {
				var index = lis.indexOf(this.current);
				this.unselect();
				if (key === Event.KEY_DOWN && (index + 1) < lis.size()) {
					this.select(lis[++index]);
				}
				else if (key === Event.KEY_UP && (index - 1) >= 0) {
					this.select(lis[--index]);
				}
			}
			if (this.current) {
				var idx = parseInt(this.current.id.substring(this.options.id_prefix.length, this.current.id.length));
				this.textField.value = this.longNames[idx];
			} else {
				this.textField.value = this.typedValue;
			}
			event.stop();
		}
		// Select with Tab or Enter key 
	  else if (key == Event.KEY_TAB || key == Event.KEY_RETURN) {
			clearTimeout(this.timeout);
			this.hideSuggestDiv();
			if (typeof(this.current) !== "undefined") {
				this.clearSuggestions();
				event.stop();
			}
		}
	},
	/**
	 * Act as a proxy initialting the remote request
	 *  - Do not send requests shorter two characters
	 *  - Do send two identical requests
	 *  - Send the request when the user has been idle for options.delay milliseconds
	 *
	 * @param  {Event}  event  The triggered event object
	 */
	suggest: function(event) {
		var key = event.charCode || event.keyCode;
		if (key !== Event.KEY_UP && key !== Event.KEY_DOWN && key !== Event.KEY_TAB && key !== Event.KEY_RETURN && key !== 16 && key !== Event.KEY_ESC) {
			this.typedValue = $F(this.textField);
			if (this.typedValue.length > 1) {
				if (this.typedValue != this.lastSearch) {
					clearTimeout(this.timeout);
					this.lastSearch = this.typedValue;
					this.timeout = setTimeout(this.remoteSuggest.bind(this), this.options.delay);
				}
			}
			else {
				clearTimeout(this.timeout);
				this.lastSearch = "";
				this.hideSuggestDiv();
				if (this.request) {
					this.request.abort();
				}
			}
			event.stop();
		} else if (key === Event.KEY_ESC) {
			this.hideSuggestDiv();
			this.clearSuggestions();
		}
	},
	/**
	 * Configures request parameters and sends the remote request
	 */
	remoteSuggest: function() {
		var parameters = {
			count: 10,
			debug: 0,
			lang: '',
			partner: "suggest",
			suggest_length: 38,
			tpl: "xml",
			unicode: 1
		};
		if (typeof(Eniro) !== "undefined" && Eniro.Page.country() === "fi" && Eniro.Page.lang() === "sv") {
			parameters.lang = Eniro.Page.lang();
		}
		if (this.options.form) {
			var hParams = $(this.options.form).serialize(true);
			parameters = Object.extend(parameters, hParams);
		}
		else {
			var hParams = {search_word: $F(this.textField)};
			parameters = Object.extend(parameters, hParams);
		}
		if (this.options.what) {
			parameters.what = this.options.what;
		}
		if (this.options.type) {
			if (parameters.search_word == '') {
				parameters.type = 'where_only';
			} else if (parameters.geo_area == '') {
				parameters.type = 'what_only';
			} else {
				parameters.type = this.options.type;
			}
		}
		var that = this;
		new Ajax.Request("/query", {
			method: 'get',
			parameters: parameters,
			onCreate: function(request) {
				// If we have hit the timeout and the AJAX request is active, abort it.				

			  that.request= request.transport;
				request['timeoutId'] = window.setTimeout(function() {
					if (request.transport.readyState === 1 || request.transport.readyState === 2 || request.transport.readyState === 3) {
						request.transport.abort();
					}
				}, 1000);
			},
			onComplete: function(response) {
				that.request = null;
				if (response.transport.readyState !== 4)  {
					return;
				}
				// only if "OK"
				if (response.transport.status === 200) {
					this.handleResponse(response);
				}
			  
				// Clear the timeout, the request completed ok

				window.clearTimeout(response['timeoutId']);
			}.bind(this)
		});
	},
	/**
	 * Parses the response and inserts it into the suggest container
	 *
	 * @param {Object} response
	 */
	handleResponse: function(response) {
		//Get item nodes from xml result.

	  var items = response.transport.responseXML.getElementsByTagName("item");

		if (items.length > 0) {
			this.longNames = new Array(items.length);
			this.clearSuggestions();
			var ul = new Element("ul");
			for (var i = 0 ; i < items.length ; i++) {
				var item_short = items[i].getElementsByTagName("item_short")[0];
				var item_full = items[i].getElementsByTagName("item_full")[0];
				var header_code = items[i].getElementsByTagName("header_code")[0];
				var node_value = unescape(item_full.firstChild.nodeValue);
				this.longNames[i] = node_value;
				var innerHTML = unescape(item_short.firstChild.nodeValue);
				var li = new Element("li", {'id': this.options.id_prefix + i});
				if (header_code && header_code.firstChild) {
					li.addClassName("heading");
				}
				li.appendChild(document.createTextNode(innerHTML));
				ul.appendChild(li);
			}
			this.suggestDiv.appendChild(ul);
			this.showSuggestDiv();
		}
		else {
			this.hideSuggestDiv();
		}
	}
});

Event.observe(document, "dom:loaded", function() {
	if (typeof(Eniro) !== "undefined") {
		// Eniro configuration
		// Check settings for activation
		
		var c = Cookie.get("egoSettings"), match;
		if (c) {
			match = c.match(/autoSuggest\u00A4(.*?)(?:\||$)/);
		}
		if (!c || !match || (c && match[1] != "suggestno")) {
			// Initializa suggest on YP tab		

		  if ((Eniro.Page.tab() === "company" || Eniro.Page.tab() === "yp") && !Eniro.Page.is_a("advance")) {
				if ($("search")) {
					new Suggest($("where") || $("company-where"), {type:'suggest_where', what: 'es_yp_sword', form:'search'});
					new Suggest($("search_word") || $("company-what"), {type:'suggest_what', what: 'es_yp_sword', form:'search'});
				}
			}
			// Initializa suggest on map tabs		

		  else if (Eniro.Page.tab() === "map") {
				if ($("map_address_form")) {
					new Suggest("where", {what:'es_geo'});
				}
				else if ($("routeForm")) {
					new Suggest("toSearch", {what:'es_geo'});
					new Suggest("fromSearch", {what:'es_geo'});
				}
				else if ($("map_yp_form")) {
					new Suggest("search_word", {type:'suggest_what', what: 'es_yp_sword', form:'map_yp_form'});
				}
			}
		}
	} else {
		// Krak configuration
		if (Site.Page.getTab() == "company") {
			new Suggest("search_word", {type:'suggest_what', what: 'es_yp_sword', form:'search'});
		}
	}
});
