/**
 * ExedJS Library
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://exedjs.googlecode.com/files/license.txt
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to open-source@exed.nl so we can send you a copy immediately.
 */
 
(function($) {

/*
	Create an auto-complete box for a text input element.
	
	Options:
	- cache:		Whether to use caching for repeated queries. Defaults to true.
					If you set this to 'prefix', then the autocompleter will attempt
					to minimize requests to the suggest function. It does so by remembering
					the results of partial queries, and filtering them for subsequent queries.
					Example: if a query has been made for 'sug', then these results will be cached.
					If the user then types 'sugges', then the results for 'sug' will be filtered
					for matches, but no new queries will be sent to the suggest function.
					Setting 'prefix' entails setting true, and setting false disables both.
	- cssClass:		The class prefix for all generated elements. Defaults to 'auto-complete'.
	- delay:		Delay for the typeWatch, in ms.
	- limit:		Maximum number of suggestions. Anything beyond this will be omitted. 
					Defaults to 10.
	- suggest: 		Function that provides suggested values.
					This function receives the current search value and a callback function
					as its arguments. After retrieving suggested values, it should then call 
					the callback function with the suggestions as the only argument. The 
					suggestions should be denoted as an array of strings. (Note that the 
					callback construction allows you to fetch suggestions asynchronously, 
					for example through an XHR call.)
					You may also provide a URI template, where {query} will be replaced
					by the current value of the input box. The script located at the
					specified URI must return a JSON string with the syntax described
					above.
	
	This plugin uses the following jQuery extensions:
	- ExedJS.typeWatch (included in the main ExedJS package)
	- If the jQuery Dimensions plugin is present, it is used. If not, we can manage without.
	
	@author Erik Wijdemans
	@author	Sebastiaan Besselsen
*/
$.fn.autoComplete = function(options)
{
	// don't let this escape!
	var input = $(this);
	
	// allow setting autocompletion on multiple boxes at once
	if (input.length != 1) {
		input.each(function () {
			$(this).autoComplete(options);
		});
		return this;
	}
	
	// default options
	var options = $.extend({
		cache: true,
		cssClass: 'auto-complete',
		suggest: function (query, callback) { callback([]); },
		delay: null,
		limit: 10
	}, options);
	
	// allow ExedPHP-style autocomplete boxes
	if (options.suggest == 'exedphp') {
		options.suggest = $.exedPHP.suggest();
	}
	
	// allow fetching suggestions from a URL
	if (typeof options.suggest == 'string') {
		(function () {
			var url = options.suggest;
			
			// fetch suggestions from a URL
			options.suggest = function (query, elem, callback) {
				var queryUrl = url.replace(/{query}/g, escape(query));
				$.getJSON(queryUrl, callback);
			};
		})();
	}
	
	// initialize caching
	(function () {
		if (!options.cache) {
			return;
		}
		
		var cache = { };
		
		var suggest = options.suggest;
		options.suggest = function (query, elem, callback) {
			if (cache[query]) {
				callback(cache[query]);
				return;
			}
			// prefix caching
			if (options.cache == 'prefix') {
				var partial, data;
				for (var i = query.length - 1; i >= 0; i--) {
					partial = query.substring(0, i);
					if (cache[partial]) {
						data = cache[partial];
						break;
					}
				}
				if (data) {
					var suggestions = $.grep(data, function (suggestion) {
						return (suggestion.indexOf(query) > -1);
					});
					cache[query] = suggestions;
					callback(suggestions);
					return;
				}
			}
			suggest(query, elem, function (data) {
				cache[query] = data;
				callback(data);
			});
		};
	})();
	
	// create the auto-complete box
	var box = $('<div>').addClass(options.cssClass).insertBefore(input).hide();
	
	var varBrowserWidth = parseInt(box.css('border-left-width')) + parseInt(box.css('border-right-width'));
	
	var boxCss = {
		'position': 'absolute'
	}
	
	var varBrowserHeight = 0;
	switch(true) {
		case $.browser.safari:
			varBrowserHeight = 2;
			break;
		case $.browser.msie:
			varBrowserHeight = 1;
			break;
	}
	
	// position the box correctly
	if (input.outerWidth) { // use dimensions plugin
		$.extend(boxCss, {
			'margin-top': input.outerHeight() + varBrowserHeight + 'px',
			'width': input.outerWidth() - varBrowserWidth + 'px'
		});
	}
	else {
		$.extend(boxCss, {
			'margin-top': input.height() + varBrowserHeight + 'px',
			'width': input.width() - varBrowserWidth + 'px'
		});
	}
	
	box.css(boxCss);
	
	// utility functions for changing the suggestions
	var cancel, apply, show, up, down, active, visible;
	(function () {
		var suggestionClass = options.cssClass + '-suggestion';
		
		active = { };
		
		// get the active suggestion
		active.get = function () {
			return $('.' + suggestionClass + '-active', box);
		};
		
		// unselect the active suggestion
		active.unset = function () {
			$('.' + suggestionClass + '-active', box)
				.removeClass(suggestionClass + '-active');
		};
		
		// set the active suggestion
		active.set = function (elem) {
			if (!elem.is('.' + suggestionClass)) {
				return false;
			}
			
			active.unset();
			
			// make item active, and scroll it into view
			elem.addClass(suggestionClass + '-active');
				//[0].scrollIntoView(true);
			
			return true;
		}
		
		// set the active suggestion from a text value
		active.setFromText = function (text) {
			var suggestion = $('.' + suggestionClass, box).filter(function () { return $(this).text() == text });
			if (suggestion.length > 0) {
				return active.set(suggestion);
			}
			return false;
		};
		
		// browse up and down through active elements
		var browse = function (direction) {
			return function () {
				var suggestion = active.get();
				if (suggestion.length == 1) {
					active.set(suggestion[direction]('.' + suggestionClass, box));
				} else {
					active.set(box.find('.' + suggestionClass).eq(0));
				}
			};
		};
		
		up = browse('prev');
		down = browse('next');
		
		// supply options to the autocompleter
		var fill = function (suggestions) {
			// get the active suggestion
			var activeText = active.get().text();
			var activeSuggestion;
			
			box.empty();
			var length = suggestions.length;
			$.each(suggestions.slice(0, options.limit), function (i, text) {
				var suggestion = $('<div>').html(text).addClass(suggestionClass).appendTo(box);
			});
		};
		
		// find out if the box is visible at the moment
		visible = function () {
			return box.is(':visible');
		};
		
		// hide box
		var hide = function (skipCheck) {
			if (!skipCheck && !visible()) { // do nothing if the box is invisible
				return;
			}
			box.hide();
		};
		
		// apply the current suggestion
		apply = function () {
			if (!visible()) {
				return true;
			}
			var suggestion = active.get();
			if (suggestion.length == 1) {
				input.val(suggestion.find('span:first').text());
				$('input#provincie').val(suggestion.find('span:last').text());
			}
			hide(true);
			if (suggestion.length == 1) {
				return false;
			}
			return true;
		};
		
		// cancel the suggestion box
		cancel = function () {
			hide();
		};
		
		// show suggestions
		show = function (suggestions) {
			var isVisible = visible();
			if (suggestions != undefined) {
				fill(suggestions);
				if (suggestions.length == 0) {
					cancel();
					return;
				}
			}
			if (!isVisible) {
				box.show();
			}
		};
	})();
	
	// bind event listeners to the box
	box.mouseover(function (e) {
		// select suggestions on mouseover
		active.set($(e.target));
	}).click(function (e) {
		var target = $(e.target).closest('.' + options.cssClass + '-suggestion');
		if (active.set(target)) {
			// fill in, hide the box and move on
			apply();
		}
		// stop bubbling
		e.stopPropagation();
		return false;
	});
	
	// hide the box when the user clicks outside it
	$(document).click(function (e) {
		// if we got here, then the box didn't catch the click
		cancel();
	});
	
	// bind event listeners to the input box
	input.keydown(function (e) {
		if (!visible()) {
			return true;
		}
		switch (e.keyCode) {
			case 27: // esc
				cancel();
				break;
			case 13: // enter
				e.stopPropagation();
				return apply();
				//return false;
				break;
			case 38: // up
				up();
				break;
			case 40: // down
				down();
				break;
		}
	});
	
	// add typewatch
	var querying = false;
	var watch = input.typeWatch(function (e) {
		if (e.keyCode == 13) { // enter
			// do not trigger on enter, since that could make the box reappear after selecting an item
			return;
		}
		var query = input.val();
		if (query == '' || query.length < 2) {
			cancel();
			return;
		}
		if (!querying) {
			// fetch new suggestions
			querying = true;
			options.suggest(query, input, function (data) {
				show(data);
				active.setFromText(query);
				querying = false;
			});
		}
	}, { delay: options.delay });
	
	// turn off browser's built-in autocomplete
	input.attr('autocomplete', 'off');
	
	return this;
}

if (!$.exedPHP) {
	$.exedPHP = { };
}

// ExedPHP-style autocomplete
$.exedPHP.suggest = function (options) {
	var options = $.extend({
		input: null,
		action: null
	}, options);
	
	return function (query, elem, callback) {
		// perform checks
		var name = elem.attr('name').replace(/\[.*\]$/, '');
		if (!name) {
			throw 'Input element must have a NAME to use suggest=exedphp.';
		}
		
		var action = options.action
			|| elem.parents('form').eq(0).attr('action')
			|| elem.parents()
				.filter(function () { return $(this).data('form-action') != null; })
				.eq(0).data('form-action')
			|| window.location.href;
		
		var data = {};
		data[name + '[search]'] = query;
		data[name + '[ajax]'] = 1;
		
		// post data to the form
		$.post(action, data, function (result) {
			var result = $(result);
			
			// find the element
			var dataInput = $('input[name=\'' + name + '[search]\']', result);
			if (!dataInput) {
				return callback([]);
			}
			
			var dataMatches = dataInput.nextAll('.matches').find('label').map(function () {
				return $(this).text();
			});
			
			// if we have matches, show them
			if (dataMatches.length > 0) {
				return callback(dataMatches);
			}
			
			// otherwise, only return data if we have an exact match
			var value = dataInput.val();
			if (value != '' && value == $('input[name=\'' + name + '[value]\']', result).val()) {
				return callback([ value ]);
			}
			
			callback([]);
		});
	};
};

})(jQuery);
