/** Incremental search for complex arrays
 *	@author danyastuff
 *	@version 1.0 (since 28 aug 2009)
 */

var ComplexSearch = function (settings) {
	var self = this,
		defaults = {
			filter		: null,	// Input with filter chars
			area		: null,	// Complex array for searching in
			banFields	: [],
			minChars	: 1,
			minDigits	: 1,
			maxToShow	: 99,
			maxHeight	: '400px',
			onMatch		: function (text, elem, index) { }, // Returns string to show
			onResult	: function (li) { }
		}
	this.src = $.extend(defaults, settings);
	this.upgradeJS();
	this.bindSelectFunc();
	
	this.list = this.listInit();
	
	var filter = this.src.filter;
		filter.keyup(function (e) {
			if ( $(this).attr('value').length > 0 && $.keyCode.ctrlKeys.indexOf(e.keyCode) == -1 )
				self.matchFilter() 
		})
		filter.blur (function (e) {
			if (self.list.mouseOut) self.list.hide() 
		})
}

ComplexSearch.prototype = {
// Show area's elems wich match filter
	matchFilter : function () {
		var row, node,
			fVal = this.src.filter.attr('value'), 
			fReg = new RegExp ('(^|\\s|-)' + fVal, ['i']),
			lim	 = isNaN(parseInt(fVal, 10)) ? this.src.minChars : this.src.minDigits;
		
		this.list.clear().appear();
		if (fVal.length >= lim)
			for (i in this.src.area) {
				row = this.src.area[i];
				for (var j in row) {
					if (this.src.banFields.indexOf(j) == -1)
						if ( row[j].toString().search(fReg) >= 0 ) {
							node = this.src.onMatch(
								this.makeMatchBold(row[j].toString(), fVal),
								row, j
							)
							this.list.attach(node);
						//	break
						}
				} //row
				if (this.list.numChildren > this.src.maxToShow) break;
			} //area
		this.list.moveFirst().bind4LI();
	},
	bindSelectFunc : function () {
		var self = this;
		this.src.filter.bind('keydown', function (e) {
			if (e.keyCode == $.keyCode.DOWN) {
				self.list.moveUp();
				return;
			}
			if (e.keyCode == $.keyCode.UP) {
				self.list.moveDown();
				return;
			}
			if (e.keyCode == $.keyCode.ENTER) {
				self.list.onResult();
				return;
			}
			
		})
	},
// utils
	upgradeJS : function () {
		Array.prototype.indexOf = function (obj) {
			for (var i=0, l=this.length; i < l; i++)
				if (this[i] == obj) return i;
			return -1;
		}
		//TODO: remove ctrlKeys by any method
		$.keyCode = {
			ctrlKeys : [40, 38, 37, 39, 13, 27, 36, 35],
			DOWN  : 40,
			UP	  : 38,
			LEFT  : 37,
			RIGHT : 39,
			HOME  : 36,
			END	  : 35,
			ENTER : 13,
			ESC	  : 27
		}
	},
	listInit : function () {
		var list = $('<DIV><UL></UL></DIV>'),
			self = this;
		
		list.addClass('complex-search');
		list.css({
			position : 'absolute',
			overflow : 'auto',
			zIndex   : 10,
			maxHeight: self.src.maxHeight
		})
		list.ul = list.find('UL');
		list.ul.bind('mousedown', function (e) {
			list.onResult()
		})
		list.elems = list.ul.find('LI');
	// methods
		list.numChildren = 0;
		list.attach = function (node) { 
			var li = $(document.createElement('LI'))
				li.append(node);
			this.ul.append(li);
			this.numChildren++;
			return this;
		}
		list.clear  = function () { 
			this.ul.html('');
			this.numChildren = 0;
			return this;
		}
		list.appear = function () {
			var off = self.src.filter.offset();
			this.css({
				left: off.left,
				top : off.top + self.src.filter.get(0).offsetHeight,
				width : self.src.filter.get(0).offsetWidth
			}).show()
			return this;
		}
		list.moveUp = function () {
			if (this.selected.next().length) {
				this.selected.removeClass('selected').next().addClass('selected'), this.scrollSelect();
				this.prev = this.selected; 
				this.selected = this.selected.next(); 
			}
			return this;
		}
		list.moveDown = function () {
			if (this.selected.prev().length) {
				this.selected.removeClass('selected').prev().addClass('selected'), this.scrollSelect();
				this.prev = this.selected;
				this.selected = this.selected.prev(); 
			}
			return this;
		}
		list.moveFirst = function () {
			this.prev = this.selected = this.ul.find('LI:first');  
			this.prev.addClass('selected');
			this.get(0).scrollTop = 0;
			return this;
		}
		list.scrollSelect = function () {
			var slLI  = this.ul.find('LI.selected'),
				lOff = list.offset(),
				lDOM = list.get(0), sDOM = slLI.get(0),
				slHeight = slLI.offset().top - lOff.top + sDOM.offsetHeight,
				maxHeight = lDOM.offsetHeight;  
			if (slHeight > maxHeight) lDOM.scrollTop += sDOM.offsetHeight;
			if (slHeight < sDOM.offsetHeight) lDOM.scrollTop -= sDOM.offsetHeight;
		}
		list.onResult = function () {
			self.src.onResult(this.selected);
			self.src.filter.attr('value', this.selected.text());
			this.ul.find('LI').unbind();
			this.hide();
			return this;
		}
		list.bind4LI = function () {
			var LIs = this.ul.find('LI'); 
			LIs.bind('mousemove', function () {
				list.prev.removeClass('selected');
				list.selected.removeClass('selected');
				list.selected = $(this);
				list.selected.addClass('selected');
				list.prev = list.selected; 
			})
			return this;
		}
		list.mouseOut = true;
		list.bind('mouseover', function (e) { this.mouseOut = false	})
		list.bind('mouseout', function (e) { this.mouseOut = true })
	// complete
		$('BODY').append(list);
		list.hide();
		return list;
	},
	makeMatchBold : function (text, match) {
		mReg = new RegExp ('(.*)(^|\\s|-)(' + match + ')(.*)', ['i']);
		return text.replace(mReg, '$1$2<b>$3</b>$4');
	}
}