/**
 * jQuery cem suggestion plugin
 *
 * (C) 2011-2012 - Boxalino AG
 */

(function($) {
	$.fn.cemComplete = function(options) {
		function findZ(node, maxIndex) {
			var parent = node.parent(':not(body,html)');
			var index = parseInt(node.css('z-index'));

			if (!isNaN(index) && maxIndex < index) {
				maxIndex = index;
			}
			return (parent.length > 0 ? findZ(parent, maxIndex) : maxIndex);
		}

		this.each(
			function() {
				// update state
				var state = $(this).data('cemComplete.state') || {};

				// update options
				state.options = $.extend(
					state.options || {
						// options
						enabled: !$(this).attr('readonly'),
						opacity: 1.0,
						allowDefaultQuery: true,
						minLength: 1,
						openDelay: 30000,
						sourceDelay: 100,
						fadeDelay: 250,
						closeDelay: 1000,
						appendTo: 'body',
						cache: true,
						debug: false,

						// event handlers
						source: function(query, callback) {
							return function() { };
						},
						render: function(query, data) {
							return false;
						},
						layout: function(displayed) {
						},
						scroll: function(offset) {
						},
						highlight: function(source, offset, absolute) {
						},
						select: function(source) {
							return true;
						},
						search: function(query) {
						}
					},
					options
				);

				// update internal variables
				if (state.options.cache) {
					state.cache = state.cache || {};
				} else {
					delete state.cache;
				}
				state.highlightCounter = state.highlightCounter || 0;
				state.selectCounter = state.selectCounter || 0;

				// update menu
				state.menu = state.menu || $('<div></div>')
					.addClass('cem-autocomplete-menu')
					.hide()
					.css( {
						'position': 'absolute',
						'opacity': state.options.opacity,
						'z-index': findZ($(this), 0) + 1
					} ).hover(
						function() {
							state.hover = true;
							state.open.call(state);
						},
						function() {
							state.hover = false;
							state.close.call(state, false, true);
						}
					).appendTo($(state.options.appendTo));

				// update input element
				if (state.input == undefined) {
					state.defaultValue = $(this).val();
					state.input = $(this).addClass('cem-autocomplete-input')
						.unbind()
						.attr( {
							'role': 'textbox',
							'autocomplete': 'off',
							'aria-autocomplete': 'list',
							'aria-haspopup': 'true'
						} ).hover(
							function() {
								state.hover = true;
								if (!state.focus) {
									state.open.call(state, true);
								}
							},
							function() {
								state.hover = false;
								state.close.call(state, false, true);
							}
						).bind(
							'focus',
							function(event) {
								if (!state.focus) {
									state.focus = true;
									state.open.call(state);
								}
							}
						).bind(
							'blur',
							function(event) {
								state.focus = false;
								state.close.call(state, false, true);
							}
						).bind(
							'click',
							function(event) {
								state.open.call(state);
							}
						).bind(
							'keydown',
							function(event) {
								if (state.options.enabled) {
									state._key.call(state, event, true);
								}
							}
						).bind(
							'keypress',
							function(event) {
								if (state.options.enabled) {
									switch(event.keyCode) {
									//case 9:   // tab
									//case 27:  // escape
									//case 108: // numpad enter
									case 13:    // enter
									case 33:    // page up
									case 34:    // page down
									case 38:    // up
									case 40:    // down
										event.preventDefault();
										return;
									}
								}
							}
						);
				}

				// open handler (public)
				state.open = state.open || function(delayed) {
					if (this.options.enabled) {
						if (this.closeTask) {
							clearTimeout(this.closeTask);
							this.closeTask = null;
						}
						if (!this.active) {
							if (this.openTask) {
								clearTimeout(this.openTask);
								this.openTask = null;
							}
							if (delayed) {
								this.openTask = setTimeout(function() { state._open.call(state); }, this.options.openDelay);
							} else {
								this._open();
							}
						}
					}
				};

				// complete handler (public)
				state.complete = state.complete || function(force) {
					this.abort();
					this.searchTask = setTimeout(function() { state._completeAsync.call(state, force); }, this.options.sourceDelay);
				};

				// abort handler (public)
				state.abort = state.abort || function() {
					if (this.sourceTaskAbort) {
						if (this.options.debug) {
							this.sourceTaskAbort();
						} else {
							try { this.sourceTaskAbort(); } catch (e) { }
						}
						this.sourceTaskAbort = null;
					}
					if (this.searchTask) {
						clearTimeout(this.searchTask);
						this.searchTask = null;
					}
				};

				// close handler (public)
				state.close = state.close || function(force, delayed) {
					if (this.options.enabled && (force || !(this.hover || this.focus))) {
						if (this.openTask) {
							clearTimeout(this.openTask);
							this.openTask = null;
						}
						if (this.active) {
							if (this.closeTask) {
								clearTimeout(this.closeTask);
								this.closeTask = null;
							}
							if (delayed) {
								this.closeTask = setTimeout(function() { state._close.call(state); }, this.options.closeDelay);
							} else {
								this._close();
							}
						}
					}
				};

				// destroy handler (public)
				state.destroy = state.destroy || function() {
					this.close();
					this.input.unbind();
					this.menu.remove();
				};

				// update user-defined handlers
				var userHandlerWrapper = function(handler) {
					if (handler) {
						return function() {
							if (state.options.debug) {
								return handler.apply(state, arguments);
							}
							try { return handler.apply(state, arguments); } catch (e) { }
						}
					} else {
						return function() { };
					}
				};

				state.source	= userHandlerWrapper(state.options.source);
				state.render	= userHandlerWrapper(state.options.render);
				state.layout	= userHandlerWrapper(state.options.layout);
				state.scroll	= userHandlerWrapper(state.options.scroll);
				state.highlight = userHandlerWrapper(state.options.highlight);
				state.select	= userHandlerWrapper(state.options.select);
				state.search	= userHandlerWrapper(state.options.search);

				// private handlers
				state._key = state._key || function(event) {
					switch(event.keyCode) {
					case 9: // tab
						if (!this.active || !this.select('keyboard')) {
							return;
						}
						event.preventDefault();
						break;

					case 13:  // enter
						if (!this.active) {
							this.search(this.input.val());
						} else {
							this.select('keyboard');
						}
						event.preventDefault();
						break;

					case 27: // escape
						this.close(true);
						return;

					case 33: // page up
						this.open();
						this.scroll(-1);
						event.preventDefault();
						return;
					case 34: // page down
						this.open();
						this.scroll(1);
						event.preventDefault();
						return;

					case 38: // up
						this.open();
						this.highlight('keyboard', -1);
						event.preventDefault();
						return;
					case 40: // down
						this.open();
						this.highlight('keyboard', 1);
						event.preventDefault();
						return;
					}
					this.complete();
				};
				state._open = state._open || function() {
					if (!this.options.allowDefaultQuery && this.input.val().length > 0 && this.input.val() == this.defaultValue) {
						this.input.val('');
					}

					this.active = true;
					this.complete(true);
				};
				state._completeAsync = state._completeAsync || function(force) {
					this.searchTask = null;

					// check query
					var query = $.trim(this.input.val());

					if (query.length < this.options.minLength) {
						this._render(query);
						return;
					}
					if (!force && this.lastQuery == query) {
						this.highlight(null, -1, true);
						this.input.focus();
						return;
					}
					this.lastQuery = query;

					// cache lookup
					if (this.cache && this.cache[query.toLowerCase()]) {
						this._render(query, this.cache[query.toLowerCase()]);
						return;
					}

					// trigger source
					this.sourceTaskAbort = this.source(query, function(data) { state._completeCallback.call(state, query, data); } );
				}
				state._completeCallback = state._completeCallback || function(query, data) {
					this.sourceTaskAbort = null;
					if (this.cache) {
						this.cache[query.toLowerCase()] = data;
					}
					this._render(query, data);
				}
				state._render = state._render || function(query, data) {
					if (data && this.render(query, data)) {
						this.menu.fadeIn(this.options.fadeDelay, function() { state.layout(true); });
						this.layout(false);
						this.active = true;
						this.highlight('null', -1, true);
						this.input.focus();
					} else {
						this.close(true);
					}
				};
				state._close = state._close || function() {
					this.active = false;
					this.menu.fadeOut(this.options.fadeDelay);
				};

				// save state
				$(this).data('cemComplete.state', state)
			}
		);

		// wrap each widget
		var jq = $.sub();
		var self = this;

		jq.fn.cemSearch = function(query) {
			self.each(
				function() {
					var state = $(this).data('cemComplete.state');

					state.search(query ? query : state.input.val());
				}
			);
		};
		jq.fn.cemOpen = function(delayed) {
			self.each(
				function() {
					var state = $(this).data('cemComplete.state');

					state.open(delayed);
				}
			);
		};
		jq.fn.cemClose = function(force, delayed) {
			self.each(
				function() {
					var state = $(this).data('cemComplete.state');

					state.close(force, delayed);
				}
			);
		};
		jq.fn.cemDestroy = function() {
			self.each(
				function() {
					var state = $(this).data('cemComplete.state');

					state.destroy();
				}
			);
		};
		return jq(this);
	};
})(window.jQueryBoxalino || window.jQuery);

