/*!
 * @author Vlad Yakovlev (red.scorpix@gmail.com)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 * @link www.scorpix.ru
 * @version 0.1.5
 * @date 2010-05-24
 * @requires jQuery
 * @requires jCommon
 */

/**
 * Инкрементальный поиск для элемента.
 * @param {String|Element|jQuery} fieldEl Элемент поля, которому нужен инкрементальный поиск.
 * @param {String|Element|jQuery} popupEl Элемент выпадающего списка.
 * @param {String|Object} findedData Адрес на сервере для Ajax-запроса или массив объектов (id, name).
 * @param {Object} [params] Параметры.
 * @option {String} containerClass = 'container'
 * @option {String} itemClass = 'item'
 * @option {Number} itemCount = 5
 * @option {Number} requestTimeout = 500
 * @option {String} queryParam = 'query'
 *
 */
function suggest(fieldEl, popupEl, findedData, params) {

	fieldEl = $(fieldEl);
	popupEl = $(popupEl);
	params = $.extend({}, {
		containerClass: 'container',
		itemClass: 'item',
		itemCount: 5,
		requestTimeout: 500,
		queryParam: 'query'
	}, params || {});

	var
		/** @type {jQuery} */
		itemEl = popupEl.find('.' + params.itemClass).remove(),
		curText = '',
		maxResultCount = 0,
		queries = {},
		cache = {},
		ids = {},
		timeoutId;

	// Навешиваем события только на активацию проверки. Остальные события в showPopup() и hidePopup().
	fieldEl.bind('focus click keyup', showVariants).attr('autocomplete', 'off');

	if ('string' != typeof findedData) {
		// Заполняем массив идентификаторов данными.
		$.each(findedData, function() {
			ids[this.name.toLowerCase()] = this.id;
		});
	}

	function onKeyDown(evt) {
		switch (evt.keyCode) {
			case $c.keyCode('up'):
				var
					item = popupEl.find('.' + params.itemClass + '.selected'),
					pos = item.length ? item.eq(0).prevAll().length - 1 : 0;

				setPos(pos);
				break;

			case $c.keyCode('down'):
				var
					item = popupEl.find('.' + params.itemClass + '.selected'),
					pos = item.length ? item.eq(0).prevAll().length + 1 : 0;

				setPos(pos);
				break;

			case $c.keyCode('enter'):
				var item = popupEl.find('.' + params.itemClass + '.selected');

				if (item.length) {
					evt.preventDefault();
					evt.stopPropagation();
					fieldEl.val(item.eq(0).text());
					hidePopup();
				}

				break;

			case $c.keyCode('tab'):
				var item = popupEl.find('.' + params.itemClass + '.selected');

				if (item.length) {
					fieldEl.val(item.eq(0).text());
					hidePopup();
				}

				break;

			case $c.keyCode('escape'):
				evt.stopPropagation();
				hidePopup();

				break;
		}
	}

	function onSelectItem(evt) {
		evt.stopPropagation();
		evt.preventDefault();

		var item = getItem(evt);

		if (false !== item) {
			fieldEl.val($(evt.target).text());
			hidePopup();
		}
	}

	function onMouseMove(evt) {
		var item = getItem(evt);
		false === item ? clearPos() : setPos($(item).prevAll().size());
	}

	function showVariants(evt) {
		if (evt) {
			switch (evt.keyCode) {
				case $c.keyCode('enter'):
				case $c.keyCode('escape'):
				case $c.keyCode('end'):
				case $c.keyCode('home'):
				case $c.keyCode('left'):
				case $c.keyCode('up'):
				case $c.keyCode('right'):
				case $c.keyCode('down'):
					break;
				default:
					updateContent(fieldEl.val(), false);
					showPopup();
					break;
			}
		} else {
			updateContent(fieldEl.val(), true);
			showPopup();
		}
	}

	function showPopup() {
		if (!popupEl.hasClass('hidden')) return;
		if (!popupEl.find('.' + params.itemClass).length) return;

		fieldEl
			.keydown(onKeyDown)
			.blur(hidePopup);
		popupEl
			.removeClass('hidden')
			.mousedown(onSelectItem)
			.mousemove(onMouseMove);
	}

	function hidePopup() {
		if (popupEl.hasClass('hidden')) return;

		fieldEl
			.unbind('blur', hidePopup)
			.unbind('keydown', onKeyDown);
		popupEl
			.addClass('hidden')
			.unbind('mousedown', onSelectItem)
			.unbind('mousemove', onMouseMove);
	}

	function updateContent(text, required) {
		if (!required && text == curText) return;

		if (text.length && undefined === cache[text]) {

			if ('string' == typeof findedData) {
				// Если берем данные с сервера.

				var halfText = text.substr(0, text.length - 1);

				while (undefined === cache[halfText] && halfText.length) {
					halfText = halfText.substr(0, halfText.length - 1);
				}

				if (halfText.length && cache[halfText].length < maxResultCount) {
					// Если в более общем запросе уже было ограниченное количество элементов результата,
					// то берем данные из него.
					cache[text] = [];
					var textLower = text.toLowerCase();

					for (var i = 0; i < cache[halfText].length; i++) {
						if (-1 < cache[halfText][i].name.toLowerCase().indexOf(textLower)) {
							cache[text].push(cache[halfText][i]);
						}
					}
				} else if (!queries[text]) {
					// Запрашиваем сервер, если еще не запрашивали по этой поисковой фразе.
					clearTimeout(timeoutId);
						timeoutId = setTimeout(function() {
							load(text);
						}, params.requestTimeout);
				}
			} else {
				// Если данные пришли во входном параметре.
				cache[text] = [];
				var textLower = text.toLowerCase();

				for (var i = 0; i < findedData.length; i++) {
					if (0 == findedData[i].name.toLowerCase().indexOf(textLower)) {
						cache[text].push(findedData[i]);
					}
				}

				for (var i = 0; i < findedData.length; i++) {
					if (0 < findedData[i].name.toLowerCase().indexOf(textLower)) {
						cache[text].push(findedData[i]);
					}
				}
			}
		}

		/** Содержимое попапа. */
		popupEl.find('.' + params.itemClass).remove();

		if (undefined !== cache[text] && cache[text].length) {
			var count = params.itemCount < cache[text].length ? params.itemCount : cache[text].length;

			if (1 < count || cache[text][0].name != text) {
				for (var i = 0; i < count; i++) {
					var el = itemEl.clone().text(cache[text][i].name);
					popupEl.find('.' + params.containerClass).append(el);
				}
			}
		} else {
			hidePopup();
		}

		curText = text;
	}

	function load(text) {
		var onLoad = function(data) {
			var
				xmlData = $('result', $c.xmlObject(data)),
				count = xmlData.find('item').length;

			if (count > maxResultCount) {
				maxResultCount = count;
			}

			cache[text] = [];

			xmlData.find('item').each(function() {
				var
					id = $(this).attr('id'),
					name = $(this).text();

				cache[text].push({
					id: id,
					name: name
				});
				ids[name.toLowerCase()] = id;
			});
			showVariants();
		}

		queries[text] = true;
		var dataParams = {};
		dataParams[params.queryParam] = text;

		$.ajax({
			url: findedData,
			dataType: 'text',
			data: dataParams,
			type: 'GET',
			timeout: 1000,
			success: onLoad
		});
	}

	/**
	 * Устанавливает текущую выделенную позицию результатов поиска.
	 * @param {int} index
	 */
	function setPos(index) {

		var items = popupEl.find('.' + params.itemClass);

		if (0 > index) {
			index = items.length - 1;
		} else if (index >= items.length) {
			index = 0;
		}

		items.removeClass('selected');
		items.eq(index).addClass('selected');
	}

	/**
	 * Сбрасывает текущую выделенную позицию результатов поиска.
	 */
	function clearPos() {
		popupEl.find('.' + params.itemClass + '.selected').removeClass('selected');
	}

	function getItem(evt) {
		var el = evt.target;

		while ('BODY' != el.nodeName && !$(el).hasClass(params.containerClass)) {
			if ($(el).hasClass(params.itemClass)) return el;
			el = el.parentNode;
		}

		return false;
	}

	function getId() {
		return ids[fieldEl.val().toLowerCase()] || undefined;
	}

	return {
		getId: getId
	};
}
