	// url that receives keyword
	var getSuggestionsUrl = "/includes/ajax/suggest.php?keyword=";
	// keyword for which HTTP request has been initiated
	var httpRequestKeyword = "";
	// last keyword which suggests have been requested
	var userKeyword = "";
	// number of suggestions received
	var suggestions = 0;
	// maximum number of characters displayed for suggestion
	var suggestionMaxLength = 50;
	// flag indicates if up or down arrow keys are used
	var isKeyUpDownPressed = false;
	// last suggestion used for autocomplete
	var autocompletedKeyword = "";
	// flag indicating if there are results for the keyword
	var hasResults = false;
	// cancel the evaluation with clearTimeout
	var timeoutId = -1;
	// the current selected suggestion
	var position = -1;
	// cache object containinng keywords
	var oCache = new Object();
	// cache object containinng ids
	var iCache = new Object();
	// the min and max positions of the suggestions
	var minVisiblePosition = 0;
	var maxVisiblePosition = 9;
	// display debug info when true
	var debugMode = true;
	// XML HTTP object
	var xmlHttpGetSuggestions = createXmlHttpRequestObject();
	// onload event handled by init function
	window.onload = init;


	// create xmlHttp object
	function createXmlHttpRequestObject() {
		var xmlHttp;
   		try { 
			xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");  
		} 
		catch(e) {
			try { 
				xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); 
			} 
			catch(e) {
				try { 
					xmlHttp = new XMLHttpRequest();
				} 
				catch(e) {}
			}
		}
   		if(!xmlHttp) {
	   		alert("XMLHttpRequest not supported");
		}
		else {
		   return xmlHttp;
		}
	}
	
	
	// initialise the page
	function init() {
		// retrieve input control for the keyword
		var oKeyword = document.getElementById("keyword");
		// prevent browser starting auto-fill
		oKeyword.setAttribute("autocomplete", "off");
		// reset the content and set its focus
		if(!oKeyword.value) {
			oKeyword.value = "";
			oKeyword.focus();
		}
		else {
			userKeyword = oKeyword.value;
		}

		// load the google map if we have long & lat
		if(longitude && latitude) {
			initializeMap();
		}

		// required to display correct tabs on hotel detail
		// as cannot use onLoad event handler (will stop this script as init() is loaded)
		if(location.hash) {
			var toShow = location.hash.substring(1,location.hash.length);
			if(toShow == 'general') {
				showLayer('tab1', 'detail');
			}
			if(toShow == 'photo') {
				showLayer('tab2', 'detail-photos');
			}
			if(toShow == 'map') {
				showLayer('tab3', 'detail-map');
			}
		}
		
		
		// set timeout for checking for updates in the keyword value
		setTimeout("checkForChanges()", 2000);
	}
	
	
	function removeValue() {
		noSuggestions = false;
	}
	
	
	// add to a keyword an array of values
	function addToCache(keyword, values, ids) {
		// create an new array entry in the cache
		oCache[keyword] = new Array();
		iCache[keyword] = new Array();
		// add values to keywords entry in cache
		for(i = 0; i < values.length; i++) {
			oCache[keyword][i] = values[i];
			iCache[keyword][i] = ids[i];
		}
	}
	
	
	// check 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(keyword) {
		// check if already in cache
		if(oCache[keyword]) {
			return true;
		}
		
		// 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 if we have the current prefix keyword in cache
			if(oCache[currentKeyword]) {
				// current keywords results already in the cache
				var cacheResults = oCache[currentKeyword];
				// current ids
				var cacheIdResults = iCache[currentKeyword];
				// the results matching the keyword in the current cache results
				var keywordResults = new Array();
				var idResults = new Array();
				var keywordResultsSize = 0;
				// try to find all matching results starting with the current prefix
				for(j = 0; j < cacheResults.length; j++) {
					if(cacheResults[j].indexOf(keyword) == 0) {
						keywordResults[keywordResultsSize++] = cacheResults[j];
						idResults[keywordResultsSize++] = cacheIdResults[j];
					}
				}
				// add all the keywords prefix results to cache
				addToCache(keyword, keywordResults, idResults);
				return true;
			}
		}
		// no match found
		return false;
	}
	
	
	// initiate http request to get suggestions for keyword
	function getSuggestions(keyword) {
		// continue if keyword isnt null and last key pressed wasnt up/down
		if(keyword != "" && !isKeyUpDownPressed) {
			// check if keyword is in cache
			//isInCache = checkCache(keyword);
			// keyword in cache
			/*
			if(isInCache == true) {
				// retrieve results from cache
				httpRequestKeyword = keyword;
				userKeyword = keyword;
				// display results in cache
				displayResults(keyword, oCache[keyword]);
			}
			else {
			*/
				// keyword not in cache - make a http request
				if(xmlHttpGetSuggestions) {
					try {
						// if not busy with another request
						if(xmlHttpGetSuggestions.readyState == 4 || xmlHttpGetSuggestions.readyState == 0) {
							httpRequestKeyword = keyword;
							userKeyword = keyword;
							xmlHttpGetSuggestions.open("GET", getSuggestionsUrl + encode(keyword), true);
							//xmlHttpGetSuggestions.open("GET", getSuggestionsUrl + keyword, true);
							xmlHttpGetSuggestions.onreadystatechange = handleGettingSuggestions;
							xmlHttpGetSuggestions.send(null);
						}
						else {
							// busy with another request
							// retain the keyword user wanted
							userKeyword = keyword;
							// clear prev timeouts
							if(timeoutId != -1) {
								clearTimeout(timeoutId);
							}
							// try again in 0.5 seconds
							timeoutId = setTimeout("getSuggestions(userKeyword);", 500);
						}
					}
					catch(e) {
						displayError("Can't connect to server:\n" + e.toString());
					}
				}
			//}
		}
	}
	
	
	// transforms all children of an xml node to an array
	function xmlToArray(resultsXml) {
		var resultsArray = new Array();
		// loop through xml nodes
		for(i = 0; i < resultsXml.length; i++) {
			resultsArray[i] = resultsXml.item(i).firstChild.data;
		}
		return resultsArray;
	}
	
	
	// handles the servers response containing the suggestions for the keyword
	function handleGettingSuggestions() {
		// if process is complete decide what to do with data
		if(xmlHttpGetSuggestions.readyState == 4) {
			// only if http status is ok
			if(xmlHttpGetSuggestions.status == 200) {
				try {
					updateSuggestions();
				}
				catch(e) {
					displayError(e.toString());
				}
			}
			else {
				displayError("problem retrieving data:\n" + xmlHttpGetSuggestions.statusText);
			}
		}
	}
	
	
	// process the servers response
	function updateSuggestions() {
		// get server response
		var response = xmlHttpGetSuggestions.responseText;
		//alert(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 elemnt
		response = xmlHttpGetSuggestions.responseXML.documentElement;
		nameArray = new Array();
		idArray = new Array();
		// have we any results for the keyword
		if(response.childNodes.length) {
			nameArray 	= xmlToArray(response.getElementsByTagName("name"));
			idArray 	= xmlToArray(response.getElementsByTagName("id"));
		}
		// check to see if other keywords are being searched for
		if(httpRequestKeyword == userKeyword) {
			// display the results
			displayResults(httpRequestKeyword, nameArray, idArray);
		}
		else {
			// add the results to cache
			addToCache(httpRequestKeyword, nameArray, idArray);
		}
	}
	
	
	
	// populates the list with the current suggestions
	function displayResults(keyword, results_array, id_array) {
		// start building the html table
		var div = "<table>";
		// if the searched for keyword is not in the cache then add it
		if(!oCache[keyword] && keyword) {
			addToCache(keyword, results_array, id_array);
		}
		// if the results are empty display this
		if(results_array.length == 0) {
			div += "<tr><td>No results found for <strong>"+keyword+"</strong></td></tr>";
			// set 0 results flag
			hasResults = false;
			suggestions = 0;
		}
		else {
			// display the results
			// reset the index of the currently selected suggestion
			position = -1;
			// reset up/down key press flag
			isKeyUpDownPressed = false;
			// set results flag
			hasResults = true;
			// get number of results from cache
			suggestions = oCache[keyword].length;
			// loop through results
			for(var i = 0; i < oCache[keyword].length; i++) {
				itemName = oCache[keyword][i];
				id = iCache[keyword][i];

				div += "<tr id='tr"+i+"' onmouseover='handleOnMouseOver(this);' " +
										"onclick='handleOnMouseClick(this);' " +
										"onmouseout='handleOnMouseOut(this);'>" + 
										"<td align='left' id='td"+i+"'>" + 
										"<input type='hidden' id='itr"+i+"' value='"+itemName+"' />" + 
										"<input type='hidden' id='idtr"+i+"' value='"+id+"' />";
				// check to see if the current suggestion length exceeds
				// the maximum number of characters that can be displayed
				if(itemName.length <= suggestionMaxLength) {
					// bold the matching prefix
					div += "<b>" + itemName.substring(0, httpRequestKeyword.length) + "</b>";
					div += itemName.substring(httpRequestKeyword.length, itemName.length) + "</td></tr>";
				}
				else {
					// check to see if the current keyword length exceeds
					// the maximum number of characters that can be displayed
					if(httpRequestKeyword.length < suggestionMaxLength) {
						// bold the matching prefix of item & keyword
						div += "<b>" + itemName.substring(0, httpRequestKeyword.length) + "</b>";
						div += itemName.substring(httpRequestKeyword.length, suggestionMaxLength) + "</td></tr>";
					}
					else {
						// bold the lot
						div += "<b>" + itemName.substring(0, suggestionMaxLength) + "</b></td></tr>";
					}
				}
			}
		}
		// end of table
		div += "</table>";
		// retrieve the suggest and scroll object
		var oSuggest = document.getElementById("suggest");
		var oScroll = document.getElementById("scroll");
		// scroll to top of list
		oScroll.scrollTop = 0;
		// update suggestions list and make visible
		oSuggest.innerHTML = div;
		oScroll.style.visibility = "visible";
		if(results_array.length > 0) {
			autocompleteKeyword();
		}
	}
	
	
	// periodically check if keyword has changed
	function checkForChanges() {
		// keyword object
		var keyword = document.getElementById("keyword").value;
		// is empty
		if(keyword == "") {
			// hide the suggestions
			hideSuggestions();
			// reset
			userKeyword = "";
			httpRequestKeyword = "";
		}
		// set timer for new check
		setTimeout("checkForChanges()", 2000);
		// any changes ?
		if((userKeyword != keyword) && (autocompletedKeyword != keyword) && (!isKeyUpDownPressed)) {
			getSuggestions(keyword);
		}
	}
	
	
	// handle keys presses
	function handleKeyUp(e) {
		// get event
		e = (!e) ? window.event : e;
		// get events target
		target = (!e.target) ? e.srcElement : e.target;
		if(target.nodeType == 3) {
			target = target.parentNode;
		}
		// get character code of pressed button
		code = (e.charCode) ? e.charCode : 
				((e.keyCode) ? e.keyCode : 
				  ((e.which) ? e.which : 0));

		// was event keyup ?
		if(e.type == "keyup") {
			isKeyUpDownPressed = false;
			// check if we are interested in the current chracater
			if((code < 13 && code != 8) || (code >= 14 && code < 32) || (code >= 33 && code <= 46 && code != 38 && code != 40) || (code >= 112 && code <= 123)) {
				// ignore
			}
			else 
				// enter is pressed
				if(code == 13) {
					if(position >= 0) {
						isKeyUpDownPressed = true;
						hideSuggestions();
					}
				}
			
			else 
				// down arrow move to next suggestion
				if(code == 40) {
					newTR = document.getElementById("tr"+(++position));
					oldTR = document.getElementById("tr"+(--position));
					// deselet the old suggestion
					if(position >= 0 && position < suggestions - 1) {
						oldTR.className = "";
					}
					// select the new suggestions and update the keyword
					if(position < suggestions - 1) {
						newTR.className = "highlightrow";
						updateKeywordValue(newTR);
						position++;
					}
					e.cancelBubble = true;
					e.returnValue = false;
					isKeyUpDownPressed = true;
					
					// scroll down if current window is no longer valid
					if(position > maxVisiblePosition) {
						oScroll = document.getElementById("scroll");
						oScroll.scrollTop += 18;
						maxVisiblePosition += 1;
						minVisiblePosition += 1;
					}
				}
				
			else
				// up arrow go to previous suggestion
				if(code == 38) {
					newTR = document.getElementById("tr"+(--position));
					oldTR = document.getElementById("tr"+(++position));
					// deselect the old position
					if(position >= 0 && position <= suggestions - 1) {
						oldTR.className = "";
					}
					// select new suggestion and update keyword
					if(position > 0) {
						newTR.className = "highlightrow";
						updateKeywordValue(newTR);
						position--;
						// scroll up if current window no longer valid
						if(position < minVisiblePosition) {
							oScroll = document.getElementById("scroll");
							oScroll.scrollTop -= 18;
							maxVisiblePosition -= 1;
							minVisiblePosition -= 1;
						}
					}
					else {
						if(position == 0) {
							position--;
						}
					}
					e.cancelBubble = true;
					e.returnValue = false;
					isKeyUpDownPressed = true;
				}
		}
	}
	
	
	// update the keyword value with the current selected suggestion
	function updateKeywordValue(oTr) {
		// keyword object
		var oKeyword = document.getElementById("keyword");
		// suggestion
		var oSuggestion = document.getElementById("i"+oTr.id).value;
		oKeyword.value = oSuggestion;

		// set the hidden field to the cityId
		var oCityId = document.getElementById("cityId");
		// id
		var oId = document.getElementById("id"+oTr.id).value;
		oCityId.value = oId;
	}
	
	
	// removes style from all suggestions
	function deselectAll() {
		for(i = 0; i < suggestions; i++) {
			var oCrtTr = document.getElementById("tr"+i);
			oCrtTr.className = "";
		}
	}
	
	
	// handle mouse over
	function handleOnMouseOver(oTr) {
		deselectAll();
		oTr.className = "highlightrow";
		position = oTr.id.substring(2, oTr.id.length);
	}
	
	// handle mouse click
	function handleOnMouseClick(oTr) {
		isKeyUpDownPressed = true;
		updateKeywordValue(oTr);
	}
	
	// handle mouse out
	function handleOnMouseOut(oTr) {
		oTr.className = "";
		position = -1;
	}
	
	
	// escape strings
	function encode(uri) {
		if(encodeURIComponent) {
			return encodeURIComponent(uri);
		}
		if(escape) {
			return escape(uri);
		}
	}
	
	
	
	// hide the layer containing the suggestions
	function hideSuggestions() {
		var oScroll = document.getElementById("scroll");
		oScroll.style.visibility = "hidden";
	}
	
	
	// select a range in the text object passed as param
	function selectRange(oText, start, length) {
		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();
	}
	
	
	// autocomplete the typed keyword
	function autocompleteKeyword() {
		// keyword object
		var oKeyword = document.getElementById("keyword");
		// reset position of current suggestion
		position = 0;
		// deselect all suggestions
		deselectAll();
		// highlight selected suggestion
		document.getElementById("tr0").className = "highlightrow";
		// update the keywords value with the 1st suggestion
		updateKeywordValue(document.getElementById("tr0"));
		// apply the type ahead style
		selectRange(oKeyword, httpRequestKeyword.length, oKeyword.value.length);
		// set the autocompleted word to the keywords value
		autocompletedKeyword = oKeyword.value;
	}
	
	
	// displays errors
	function displayError(message) {
		alert("Error accessing server "+ (debugMode ? "\n"+message : ""));
	}