
if (!Fredhopper) {
	var Fredhopper = {};	
}

Fredhopper.Autocompleter = Class.create();
Object.extend(Object.extend(Fredhopper.Autocompleter.prototype, Ajax.Autocompleter.prototype), {
    initialize: function(element, update, suggestUrl, options){
        this.baseInitialize(element, update, options);
        this.options.asynchronous = true;
        this.options.onComplete = this.onComplete.bind(this);
        this.options.defaultParams = this.options.parameters || null;
        this.url = suggestUrl;
        this.entries = [];
        this.hidesuggestions = false;
        
		this.onKeyEvent = "keypress"; // by default listen for 'keypress' (FF works OK)
		
		if (navigator.appVersion.indexOf('MSIE') > 0) {
			// don't listen for 'keypress' because IE7/8 doesn't handle it like we expect
			// 'keydown' is what we need to handle up/down arrow keys to select suggestions
			Event.stopObserving(this.element, this.onKeyEvent);
			
			// register listener for 'keydown' since it is OK for IE
			this.onKeyEvent = "keydown";
			Event.observe(this.element, this.onKeyEvent, this.onKeyPress.bindAsEventListener(this));
		}
    },
	
    /**
     * Disables temporary the autocompleter
     */
    disable: function(){
		this.hide();
		Event.stopObserving(this.element, "blur");
		Event.stopObserving(this.element, this.onKeyEvent);
    },
    
    /**
     * Re-enables again the autocompleter
     */
    enable: function(){
		Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
		Event.observe(this.element, this.onKeyEvent, this.onKeyPress.bindAsEventListener(this));
    },
        
    getUpdatedChoices: function(){
        var entry = encodeURIComponent(this.options.paramName) + '=' + this.getToken();
        
        this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry;
        
        if (this.options.defaultParams) {
            this.options.parameters += '&' + this.options.defaultParams;
        }
        
        this.fireRequest();
    },
    
    /**
     * Makes the actual Ajax call with the current search term and
     * other predefined options.
     */
    fireRequest: function() {
    	new Ajax.Request(this.url, this.options);	
    },
    
    onComplete: function(request){
        this.updateChoices(request.responseText);
        initSuggestProductsSwitch();
    },
    
    getEntry: function(index){
        return this.entries[index];
    },
    
    /**
     * Repaints the suggest drop-down with the new content
     * It uses templates (see Prototype.Template) to build the final result.
     *
     * @param {JSON} choices
     */
    updateChoices: function(choices){
        if (!this.changed && this.hasFocus) {
			try {
				var result = eval("(" + choices + ")");
				
				var mainTemplate = new Template($prototype('main_template').value);
				var groupSeparator = $prototype('group_separator_template').value;
				
				var suggestHtml = '';
				
				// Indicates if this is the first suggestion block
				var first = true;
				
				// reset the entry count
				this.entryCount = 0;
				
				// preserve a reference to be able to reach the methods
				var self = this;
				
				result.suggestionGroups.each(function(suggestionGroup, index){
				
					// Only render if it contains any suggestions
					if (suggestionGroup.suggestions.length <= 0) {
						return;
					}
					
					var indexName = suggestionGroup.indexName; // "", "Categories" or "Suggested Products"
					var indexTitle = suggestionGroup.indexTitle; // "", "Categories" or "Suggested Products"
					if (indexTitle.match(".idx$") == '.idx') {
						indexTitle = indexTitle.substr(0, indexTitle.length - 4); // cut the optional trailing extension
					}
					
					var data = {
						"indexName": indexName,
						"indexTitle": indexTitle
					};
					
					if (indexName === ' ') {
						indexName = 'searchterm';
					}
					else {
						indexName = indexName.gsub(' ', '_');
					}
					
					var isDefaultTemplate = false;
					var templateElement = $prototype(indexName + '_template');
					if (!templateElement) {
						/*
						 * There is no template for this index file
						 * So use the default one
						 */
						templateElement = $prototype("default_template");
						isDefaultTemplate = true;
					}
					
					if (!templateElement) {
						logError("Cannot find a template for the index file with name '"+indexName+"'. Ignoring...");
						logInfo("Either provide a template with name '"+indexName+"_template' or at least 'default_template'");
						return;
					} else if (isDefaultTemplate) {
						logInfo("Cannot find a template for the index file with name '"+indexName+"'. Will use the default one ('default_template').");
					}
					
					var isDefaultItemTemplate = false;
					var templateItemElement = $prototype(indexName + '_item_template');
					if (!templateItemElement) {
						/*
						 * There is no template for the items/columns of this index file
						 * So use the default one
						 */
						templateItemElement = $prototype("default_item_template");
						isDefaultItemTemplate = true;
					}
					
					if (!templateItemElement) {
						logError("Cannot find a template for the items of index file with name '"+indexName+"'. Ignoring...");
						logInfo("Either provide a template with name '"+indexName+"_item_template' or at least 'default_item_template'");
						return;
					} else if (isDefaultItemTemplate) {
						logInfo("Cannot find a template for the items of index file with name '"+indexName+"'. Will use the default one ('default_item_template').");
					}
					
					var suggestionGroupTemplate = new Template(templateElement.value);
					
					var suggestionItemTemplate = new Template(templateItemElement.value);
					
					// Add divider if not first entry
					if (first) {
						first = false;
					}
					else {
						suggestHtml += groupSeparator;
					}
					
					/**
					 * Extends a template for items (<code>suggestionItemTemplate</code>) with placeholders 
					 * for the key and the value of any key in <code>suggestion</code> which has no
					 * a placeholder
					 * 
					 * @param {Prototype.Template} suggestionItemTemplate the original template 
					 * @param {JSONObject} suggestion an associative array with all key/value pairs for each line in a given index file
					 * @return {Prototype.Template} either the original template if there are no additional fields in the index file
					 * 		or a new template which is a concatenation of the original one and a dynamically created one
					 */
					// the code of this function is placed here because this way it's visibility is private
					function extendTemplate(suggestionItemTemplate, suggestion) {
						
						// this is not a Prototype.Template !
						var unknownItemFieldTemplate = $prototype('unknown_item_field_template');
						
						if (!unknownItemFieldTemplate) {
							logWarning("Could not find a template for the item fields which have no placeholders. Going to ignore unknown fields...");
							logInfo("Please provide a template with id 'unknown_item_field_template'.");
							return suggestionItemTemplate;
						}
						
						var placeholders = [];
						
						suggestionItemTemplate.template.scan(/#\{(\w+)\}/, function(placeholder) {
							placeholders.push(placeholder[1]);
						});
						
						var additionalTemplate = "";
						
						// make a copy to iterate over
						var oldSuggestion = new Hash(suggestion).toJSON().evalJSON();
						
						var counter = 0;
						for (var field in oldSuggestion) {
							if (placeholders.indexOf(field) === -1) {
								logWarning("There is no a placeholder for a field with name '"+field+"'. Going to add an auto-generated one...");
								var fieldKey = field + "_key" + counter;
								suggestion[fieldKey] = field;
								
								var fieldValue = field + "_value" + counter;
								suggestion[fieldValue] = suggestion[field];
								
								counter++;
								delete suggestion[field];
								
								var tmpUnknownItemFieldTemplate = unknownItemFieldTemplate.value;
								tmpUnknownItemFieldTemplate = tmpUnknownItemFieldTemplate.sub("___KEY___", fieldKey);
								tmpUnknownItemFieldTemplate = tmpUnknownItemFieldTemplate.sub("___VALUE___", fieldValue);
								
								if (additionalTemplate === '') {
									additionalTemplate += "<div style=\"padding-bottom: 20px;\">";
								}
								
								additionalTemplate += tmpUnknownItemFieldTemplate;
								placeholders = placeholders.without(field);
							}
						}
						
						if (additionalTemplate !== '') {
							additionalTemplate += "</div>";
						}
						
						var result = (additionalTemplate !== '')
							? new Template(suggestionItemTemplate.template + additionalTemplate)
							: suggestionItemTemplate;
						
						return result;
					}
	
					/**
					 * If there is no specific template for the fields of the current index file
					 * then show the suggestion members as key -> value pairs in 'default_item_template'
					 * Each unknown key will be converted to 'key_XXX' where XXX is the sequencial number of the key
					 * and its value will be respectively 'value_XXX'
					 * 
					 * @param {JSONObject} suggestion the suggestion which will be modified
					 */
					// the code of this function is placed here because this way it's visibility is private  
					function modifySuggestionToDefault(defaultItemTemplate, suggestion) {
						
						var defaultItem = $prototype('default_item_template');
						
						if (!defaultItem) {
							logWarning("There is no a template for the default item!");
							return '';
						}
						
						var fieldName2TemplateMap = {
							'searchterm' : 'searchterm_item_container_template',
							'fhLocation' : 'fhLocation_item_container_template',
							'secondId'   : 'secondId_item_container_template'
						};
							
						// make a copy to iterate over
						var oldSuggestion = new Hash(suggestion).toJSON().evalJSON();
						
						var defaultItemData = {};
						
						var alreadyRenderedFields = [];
						
						function loadDefaultTemplate(fieldName) {
							var result = '';
							for (var key in oldSuggestion) {
								if (key.startsWith(fieldName + '_')) {
									var idxOfUnderscore = key.indexOf('_');
									var linkTitleField = key.substr(idxOfUnderscore + 1);
									suggestion[fieldName + 'LinkTitle'] = suggestion[linkTitleField];
									alreadyRenderedFields.push(linkTitleField);
									suggestion[fieldName] = suggestion[key];
									alreadyRenderedFields.push(fieldName);
									alreadyRenderedFields.push(key);
									delete suggestion[key];
								} else if (fieldName === key) {
									suggestion[fieldName + 'LinkTitle'] = suggestion[fieldName].truncate(20);
								} else {
									continue;
								}
								
								logDebug('Going to add a container for field with name: "' + fieldName + '"');
								var fieldContainer = $prototype(fieldName2TemplateMap[fieldName]);
								if (fieldContainer) {
									var fieldContainerTemplate = new Template(fieldContainer.value);
									result = fieldContainerTemplate.evaluate(suggestion);
								}
								delete oldSuggestion[key];
							}
							return result;
						}
						
						for (var fieldName in fieldName2TemplateMap) {
							defaultItemData[fieldName + '_item_container'] = loadDefaultTemplate(fieldName);
						}

						if (oldSuggestion.nrResults) {
							logDebug('Going to add a container for field with name "nrResults"');
							var nrResultsContainer = $prototype('nrResults_container_template');
							if (nrResultsContainer) {
								var nrResultsContainerTemplate = new Template(nrResultsContainer.value);
								defaultItemData['nrResults_container'] = nrResultsContainerTemplate.evaluate(suggestion);
								alreadyRenderedFields.push('nrResults');
							}
							delete oldSuggestion['nrResults'];
						}

						// this is not a Prototype.Template !
						var unknownItemFieldTemplate = $prototype('unknown_item_field_template');
						
						var imageItemFieldTemplate = $prototype('image_item_container_template');
						
						if (unknownItemFieldTemplate) {
							var unknownItemTemplate = '';
							var imageItemTemplate = '';
						
							var isImageRegex = /\.(jpg|png|gif)$/i;
						
							var counter = 0;
							for (var key in oldSuggestion) {
								if (alreadyRenderedFields.indexOf(key) > -1) continue;
								
								logDebug('Going to render the value of key: "' + key + '"');
								var fieldValue = "value_"+counter;
								
								var itemFieldTemplate;
								if (Object.isString(suggestion[key]) && suggestion[key].search(isImageRegex) > -1) {
									suggestion[fieldValue] = suggestion[key];
									itemFieldTemplate = imageItemFieldTemplate.value;
								}
								else {
									suggestion[fieldValue] = suggestion[key];
									itemFieldTemplate = unknownItemFieldTemplate.value;
								}
								
								itemFieldTemplate = itemFieldTemplate.sub("___VALUE___", fieldValue);
								unknownItemTemplate += itemFieldTemplate;
								
								delete suggestion[key];
								counter++;
							}
							
							var unknownItemContent = new Template(unknownItemTemplate).evaluate(suggestion);

							defaultItemData['unknown_fields_container'] = unknownItemContent;
						}
						
						var defaultItemContent = new Template(defaultItem.value).evaluate(defaultItemData);
							
						return defaultItemContent;
					} // end of modifySuggestionToDefault
					
					var suggestionItems = '';
					
					suggestionGroup.suggestions.each(function(suggestion, itemIndex){
						
						if (isDefaultItemTemplate) {
							suggestionItems += modifySuggestionToDefault(suggestionItemTemplate, suggestion);
						}
						else {
							if (Fredhopper.Autocompleter.Wrapped && Fredhopper.Autocompleter.Wrapped[indexName]) {
							
								Fredhopper.Autocompleter.Wrapped[indexName].each(function(toWrap){
									if (suggestion[toWrap]) {
										var wrapped = toWrap + '_wrapped';
										suggestion[wrapped] = suggestion[toWrap];
									}
								});
							}
							
							if (Fredhopper.Autocompleter.ToRemove && Fredhopper.Autocompleter.ToRemove[indexName]) {
								
								Fredhopper.Autocompleter.ToRemove[indexName].each(function(toRemove) {
									if (suggestion[toRemove]) {
										delete suggestion[toRemove];
									}
								});
							}
							
							var extendedSuggestionItemTemplate = extendTemplate(suggestionItemTemplate, suggestion);
							suggestion.index = itemIndex;
							suggestionItems += extendedSuggestionItemTemplate.evaluate(suggestion);	
						}
						
					});
					data.suggestionItems = suggestionItems;
					suggestHtml += suggestionGroupTemplate.evaluate(data);
					
				}); // end iterating over suggestion groups
				this.update.innerHTML = mainTemplate.evaluate({
					'suggestionsHtml': suggestHtml
				});
				this.update.innerHTML.evalScripts();
				
				// Only show suggestions if not switched off
				if (this.hidesuggestions) {
					this.hideSuggestions();
				}
				
				Element.cleanWhitespace(this.update);
				Element.cleanWhitespace(this.update.down());
				
				this.entries = document.getElementsByClassName("suggestoption", this.update);
				this.entryCount = this.entries.length;
				
				for (var i = 0; i < this.entryCount; i++) {
					var entry = this.getEntry(i);
					entry.autocompleteIndex = i;
					this.addObservers(entry);
				}
				
				this.stopIndicator();
				this.index = -1;
				
				if (this.entryCount == 1 && this.options.autoSelect) {
					this.selectEntry();
					this.hide();
				}
				else {
					this.render();
				}
			} catch(e) {
				logError(e);
			}
		}
    },
	
    
    /**
     * Shows the suggestions box and the [hide] button
     */
    showSuggestions: function(){
        document.getElementsByClassName('suggestions')[0].show();
        autocompleter.hidesuggestions = false;
        autocompleter.update.removeClassName("hidesuggestions");
        autocompleter.element.focus();
    },
    
    /**
     * Hides the suggestions box and shows the [show] button
     */
    hideSuggestions: function(){
        document.getElementsByClassName('suggestions')[0].hide();
        autocompleter.hidesuggestions = true;
        autocompleter.update.addClassName("hidesuggestions");
        autocompleter.element.focus();
    }
    
});

/**
 * An extension of Fredhopper.Autocompleter that uses JSONP request instead of 
 * Ajax one 
 */
Fredhopper.JsonpAutocompleter = Class.create();
Object.extend(Object.extend(Fredhopper.JsonpAutocompleter.prototype, Fredhopper.Autocompleter.prototype), {
	
	/**
	 * Override the method that creates the call to the backend.
	 * This one uses JSONP instead of XmlHttpRequest
	 */
	fireRequest: function() {
		this.options.callbackParamName = this.options.callbackParamName || 'callback';
		this.options.onSuccess = this.options.onSuccess || function() {};

		new Ajax.JSONRequest(this.url, this.options);
	}
});


/**
 * Switches the state whether 'Suggested Products' should be shown
 * Saves the state in a cookie so the setting is preserved for the next visits
 *
 * Since the autocomplete drop-down hides after this change we set a special
 * property just to tell it to not do it for once
 */
function switchSuggestedProducts(){

    var suggestedProducts = document.getElementById('suggestedProducts');
    
    var areSuggestedProductsShown = suggestedProducts.style.display;
    
    var switchSuggestedProducts = document.getElementById('switchSuggestedProductsId');
    
    if (areSuggestedProductsShown === 'none') {
        suggestedProducts.style.display = "";
        switchSuggestedProducts.innerHTML = "Hide";
        setSuggestProductsCookie("true");
    }
    else {
        suggestedProducts.style.display = "none";
        switchSuggestedProducts.innerHTML = "Show";
        setSuggestProductsCookie("false");
    }
    
    disableHideOnClick();
}

/**
 * Saves a cookie with the current state of 'Suggested Products' link
 * @param {Object} value <tt>true</tt> or <tt>false</tt>
 */
function setSuggestProductsCookie(value){
    // save the cookie for one year 
    // because if the user save it and then don't touch the switcher for long time
    // it will show again when it expires. So 1 year is ~ good period
    setCookie("suggestProducts", value, 60 * 60 * 24 * 30 * 12, '/');
}

/**
 * Returns the current state of 'Suggested Products' link (Show or Hide)
 * If there is no cookie yet it returns <tt>true</tt> (i.e. products are being shown)
 */
function getSuggestProductsCookie(){
    var suggestProductsCookie = getCookie("suggestProducts");
    
    var result = "true"; // bu default show 'Suggested Products'
    if (suggestProductsCookie && suggestProductsCookie === "false") {
        result = "false";
    }
    
    return result;
}

/**
 * Initialize the state of 'Suggested Products' Show/Hide link
 * It extracts the state from the special cookie
 */
function initSuggestProductsSwitch(){

    var suggestProductsCookie = getSuggestProductsCookie();
    
    if (suggestProductsCookie === "false") {
        var suggestedProducts = document.getElementById('suggestedProducts');
        
        var switchSuggestedProducts = document.getElementById('switchSuggestedProductsId');
        
        suggestedProducts.style.display = "none";
        switchSuggestedProducts.innerHTML = "Show";
    }
}

/**
 * Tells the autocompleter to not hide the drop-down for once
 */
function disableHideOnClick(){

    if (autocompleter) {
        // clicking on "Show/Hide" hides the whole drop-down
        // with this property we skip this hiding
        // see controls.js -> hide()
        autocompleter.skipNextHide = true;
        autocompleter.element.focus();
    }
}
 
/**
 * Initialize Firebug logging methods if Firebug is not enabled/installed
 */	
if (!window.console) {
	var levels = ['error', 'warn', 'info', 'log'];
	window.console = {};
	levels.each(function(level) {
		window.console[level] = function() {}; // no-op
	});
	
}

/**
 * wrapper over Firebug's logging which will prepend 'Fredhopper.Suggest: ' before each message
 *  
 * @param {Error|String} error the error to log. Could be a (try/) catched Error object or a simple String 
 */		
function logError(error) {
	if (!error) return;
	
	var logMessage = '';
	if (error.fileName) {
		logMessage += 'File: ' + error.fileName;
	}
	if (error.lineNumber) {
		logMessage += ', line: ' + error.lineNumber;
	}
	if (error.message) {
		logMessage += ', message: ' + error.message;
	}
	if (logMessage === '') {
		logMessage = error;
	}
	
	console.error("Fredhopper.Suggest: " + logMessage);
}

function logInfo(info) {
	console.info("Fredhopper.Suggest: " + info);
}

function logWarning(warning) {
	console.warn("Fredhopper.Suggest: " + warning);
}

function logDebug(debug) {
	console.log("Fredhopper.Suggest: " + debug);
}
