/*!
 * @author Dimitar Ivanov
 * @version 0.1
 * @last_modified 2014-03-14
 */
(function ($, undefined) {
	"use strict";
	var PROP_NAME = 'autosuggest',
		FALSE = false,
		TRUE = true,
		cache = {};

	function Autosuggest() {
		this._defaults = {
			url: 'search/', //String
			delay: 1000, //Number
			chars: 3, //Number
			param: "q", //String
			cache: true, //Boolean
			content: {
				items: []
			},
			// Callbacks
			onRender: null //Function
		};
		
		this.messages = {
			wrong_result: "Invalid response",
			empty_result: "No matches found"
		};
	}
	
	Autosuggest.prototype = {
		_attachAutosuggest: function (target, settings) {
			if (this._getInst(target)) {
				return FALSE;
			}
			var $target = $(target),
				self = this,
				inst = self._newInst($target),
				offset = $target.offset();
			
			$.extend(inst.settings, self._defaults, settings);
				
			inst.$results = $("<div>").addClass("pj-autosuggest-results").css({
				"display": "none",
				"position": "absolute",
				"top": (offset.top + $target.outerHeight() + 2) + "px",
				"left": (offset.left) + "px"
			}).on("click.ac", "a", function (e) {
				if (e && e.preventDefault) {
					e.preventDefault();
				}
				
				inst.$results.hide();
				$target.val($(this).html());
				
				return false;
			}).appendTo("body");
			
			$target.addClass('pj-autosuggest-input').on("focusin.ac keyup.ac", function (e) {
				var q = $(this).val();
				if (q.length >= inst.settings.chars) {
					window.clearTimeout(inst.timeoutID);
					inst.timeoutID = window.setTimeout(function () {
						self._loadAutosuggest.call(self, target, q);
					}, inst.settings.delay);
				}
			});

			inst.$preloader = $("<div>")
				.hide()
				.css({
					"position": "absolute",
					"top": (offset.top + ($target.outerHeight() - 16) / 2) + "px",
					"left": (offset.left + $target.outerWidth() - 16 - 3) + "px"
				})
				.addClass("pj-autosuggest-preloader")
				.appendTo("body");
			
			$(document).on("click.ac", "*:not(.pj-autosuggest-input)", function (e) {
				if (inst.$results.is(":visible")) {
					if (!$(e.target).closest(".pj-autosuggest-results").length) {
						inst.$results.hide();
					}
				}
			}).bind("ajaxStart.ac", function () {
				if (inst.loading === TRUE) {
					inst.$preloader.show();
				}
			}).bind("ajaxStop.ac", function () {
				inst.$preloader.hide();
			}).on("keyup.ac", "*", function (e) {
				var k = e.keyCode ? e.keyCode : e.which;
				if (k == 27) {
					inst.$results.hide();
				}
			});
			$.data(target, PROP_NAME, inst);
		},
		_loadAutosuggest: function (target, q) {
			var inst = this._getInst(target);
			if (!inst) {
				return FALSE;
			}
			var self = this,
				params = {};
			
			if (inst.settings.cache && q in cache) {
				inst.settings.content = cache[q];
				self._renderAutosuggest.call(self, target);
				return;
			}
			
			params[inst.settings.param] = q;
			inst.loading = TRUE;
			$.get(inst.settings.url, params).done(function (data) {
				inst.settings.content = data;
				cache[q] = data;
				inst.loading = FALSE;
				$.data(target, PROP_NAME, inst);
				self._renderAutosuggest.call(self, target);
			});
		},
		_renderAutosuggest: function (target) {
			var inst = this._getInst(target);
			if (!inst) {
				return FALSE;
			}
			var i, iCnt,
				$target = $(target),
				self = this,
				$li, $a, $ul; 
			
			inst.$results.hide().html("");
			$ul = $("<ul>");
			if (inst.settings.content.items === null || inst.settings.content.items === undefined) {
				$li = $("<li>").appendTo($ul);
				$a = $("<span>").addClass("pj-autosuggest-notice").text(self.messages.wrong_result).appendTo($li);
			} else {
				iCnt = inst.settings.content.items.length;
				if (iCnt > 0) {
					for (i = 0; i < iCnt; i++) {
						$li = $("<li>").appendTo($ul);
						$a = $('<a>', {
							"href": "#",
							"data-value": inst.settings.content.items[i].value
						}).html(inst.settings.content.items[i].label).appendTo($li);
					}
				} else {
					$li = $("<li>").appendTo($ul);
					$a = $("<span>").addClass("pj-autosuggest-notice").text(self.messages.empty_result).appendTo($li);
				}
			}
			
			$ul.appendTo(inst.$results);
			inst.$results.show();
			
			if (inst.settings.onRender !== null) {
				inst.settings.onRender.call(null);
			}
			
			$.data(target, PROP_NAME, inst);
		},
		_destroyAutosuggest: function (target) {
			var inst = this._getInst(target);
			if (!inst) {
				return FALSE;
			}

			$(target).removeClass("pj-autosuggest-input").off(".ac");
			$(document).off(".ac").unbind(".ac");
			inst.$results.off(".ac").remove();
			inst.$preloader.remove();
			
			$.data(target, PROP_NAME, FALSE);
		},
		_newInst: function(target) {
			var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1');
			return {
				id: id, 
				input: target, 
				uid: Math.floor(Math.random() * 99999999),
				
				$results: null,
				$preloader: null,
				timeoutID: null,
				loading: FALSE,
				
				settings: {}
			}; 
		},
		_getInst: function(target) {
			try {
				return $.data(target, PROP_NAME);
			}
			catch (err) {
				throw 'Missing instance data for this autosuggest';
			}
		}
	};

	$.fn.autosuggest = function (options) {
		
		var otherArgs = Array.prototype.slice.call(arguments, 1);
		if (typeof options == 'string' && options == 'isDisabled') {
			return $.autosuggest['_' + options + 'Autosuggest'].apply($.autosuggest, [this[0]].concat(otherArgs));
		}
		
		if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string') {
			return $.autosuggest['_' + options + 'Autosuggest'].apply($.autosuggest, [this[0]].concat(otherArgs));
		}
		
		return this.each(function() {
			typeof options == 'string' ?
				$.autosuggest['_' + options + 'Autosuggest'].apply($.autosuggest, [this].concat(otherArgs)) :
				$.autosuggest._attachAutosuggest(this, options);
		});
	};
	
	$.autosuggest = new Autosuggest(); // singleton instance
	$.autosuggest.version = "0.1";
})(jQuery);