$.fn.equals = function (compareTo) {
	if (!compareTo || !compareTo.length || this.length != compareTo.length) {
		return false;
	}
	for (var i = 0; i < this.length; i++) {
		if (this[i] !== compareTo[i]) {
			return false;
		}
	}
	return true;
} 




/* ______________________________________________________________________________
 *
 * jQuery CaixaBox Plugin
 * 
 * 	@version		- 0.3.1 - 22.06.2010
 * 	@author 		- VOID (www.void.pt)
 *	@dependencies 	- jQuery
 *					- jqUtils.0.1.js
 *
 * ------------------------------------------------------------------------------
 *	OPTIONS
 * ------------------------------------------------------------------------------
 *		- style (string) 	- the main css class for the caixabox.
 *		- label (string) 	- default label text when no option is selected.
 *		- stretch (boolean) - defines if the caixabox should fit all text.
 *		- open (function) 	- open event callback function.
 *		- close (function) 	- close event callback function.
 *		- change (function) - change event callback function.
 * ------------------------------------------------------------------------------
 *	USAGE
 * ------------------------------------------------------------------------------
 *		$('#myselect').caixabox({ class: 'caixabox', label: 'select one', stretch: true, open: function () {}, close: function () {}, change: function () {} });
 *		$('#myselect').data('caixabox').method();
 * ------------------------------------------------------------------------------
 *	METHODS
 * ------------------------------------------------------------------------------
 *		- clearList ()
 *		- addOption (label, value, index)
 *		- removeOption (label)
 *		- removeOptionAt (index)
 *		- unselect ()
 *		- selectByIndex (index)
 *		- selectByLabel (label)
 *		- selectByValue (value)
 * ------------------------------------------------------------------------------
 *	TO-DO
 * ------------------------------------------------------------------------------
 *		- fix 2 hovers when keys and mouse are involved
 * ___________________________________________________________________________ */

if (jQuery) (function ($) {
	
	var CaixaBox = function (element, settings) {
		
		/* SCOPE VARS -------------------------------------------------------- */
		
		var elem = $(element);
		var self = this;
		
		/* OPTIONS ----------------------------------------------------------- */
		
		var options = $.extend({
			style: 'caixabox', 
			label: 'Select', 
			stretch: true, 
			open: function () {}, 
			close: function () {}, 
			change: function () {}
		}, settings || {});
		
		/* VARIABLES --------------------------------------------------------- */
		
		var _select;
		
		var _wrapper;
		var _cbox;
		var _button;
		var _label;
		var _drop;
		var _scroll;
		var _list;
		var _items;
		
		var _width;
		var _selected;
		var _state;
		
		/* -------------------------------------------------------------------
		 * build
		 * 	Builds the caixabox and hides the select element.
		 * ------------------------------------------------------------------- */
		
		var build = function () {
			_wrapper = $('<div><div class="' + options.style +'"><div class="caixabox-button"><input type="text" class="caixabox-label" readonly="readonly" value=""></div><div class="caixabox-drop"><div class="caixabox-scroll"><ul></ul></div></div></div></div>');
			
			_cbox 	= _wrapper.children('div.' + options.style);
			_button = _cbox.children('div.caixabox-button');
			_label 	= _button.children('input.caixabox-label');
			_drop 	= _cbox.children('div.caixabox-drop').hide();
			_scroll	= _drop.children('div.caixabox-scroll');
			_list 	= _scroll.children('ul');
			
			if (element.options[0].getAttribute('value') == null) {
				options.label = element.options[0].text;
				_select.children('option:eq(0)').text('');
			}
			
			_select.children('option').each(function (i) {
				var itm = '';
				itm += '<li class="';
				if ($(this).is(':selected')) itm += 'selected ';
				if ($(this).val() == '') itm += 'hide';
				itm += '">' + $(this).text() + '</li>';
				
				_list.append(itm);
			});
			
			_list.children('li:first').addClass('first');
			_list.children('li:last').addClass('last');
			_items = _list.children('li');
			_width = _select.css('width');
			
			_select.hide();
			_select.after(_cbox);
		};
		
		/* -------------------------------------------------------------------
		 * init
		 * 	Selects default option and shows in label, sets width.
		 * ------------------------------------------------------------------- */
		 
		var init = function () {
			_cbox.data('_state', 'closed');
			_selected = _select.find('option:selected');
			
			if (_width !== 'auto') {
				_width = 10+parseInt(_width.split('px').join(''), 10) + 'px';
				_cbox.css('width', _width);
				_scroll.css('width', _width);					
			} else if (options.stretch === true) {
				_label.val(getLongestText());
				_cbox.css('width', _cbox.width() + 'px');
				_scroll.css('width', _cbox.width() + 'px');                             
			}
			
                        
			if (_selected.length > 0 && _selected.text() != '') _label.val(_selected.text());
			else _label.val(options.label);
		}
		
		/* -------------------------------------------------------------------
		 * bindEvents
		 * 	Binds the events of the select, caixabox and options.
		 * ------------------------------------------------------------------- */
		 
		var bindEvents = function () {
			_select.change(onChange);
			_select.keyup(onKeyUp);
			
			_button.click(onButtonClick);
			
			_label.keydown(onKeyDown);
			
			_list.mouseover(onItemHover);
			_list.mouseout(onItemOut);
			_list.click(onItemClick);
		}
		
		/* -------------------------------------------------------------------
		 * open
		 * 	Opens the caixabox list.
		 * ------------------------------------------------------------------- */
		 
		var open = function () {
			_cbox.data('_state', 'open');
			
			closeAll();
			
			$('html').bind('mousedown', function (evt) {
				if ($(evt.target).parents('.caixabox').length === 0) close();
			});
				
			checkPosition();
			
			_cbox.addClass('open');
			_drop.show();

			options.open.call(this);
		}
		
		/* -------------------------------------------------------------------
		 * close
		 * 	Closes the caixabox list.
		 * ------------------------------------------------------------------- */
		 
		var close = function () {
			_cbox.data('_state', 'closed');
			_list.children('li').removeClass('hover');
			
			_cbox.each(function () {
				$(this).children('div.caixabox-drop').hide();
			});
			_cbox.removeClass('open');
			_cbox.removeClass('top');
			
			$('html').unbind('mousedown');
			
			options.close.call(this);
		}
		
		/* -------------------------------------------------------------------
		 * closeAll
		 * 	Closes all caixabox in the page.
		 * ------------------------------------------------------------------- */
		
		var closeAll = function () {
			$('.' + options.style).each(function () {
				var cb = $(this);
				if (!cb.equals(_cbox)) {
					cb.each(function () {
						cb.children('div.caixabox-drop').hide();
					});
					cb.removeClass('open');
					cb.removeClass('top');
					cb.data('_state', 'closed');
				}
			});
		}
		
		/* -------------------------------------------------------------------
		 * selectOption
		 * 	Sets the item as selected.
		 *
		 * 	itm (number) - item number.
		 * ------------------------------------------------------------------ */
		
		var selectOption = function (itm) {
			if (itm != undefined) {
				_items.removeClass('selected');
				$(itm).addClass('selected');
				
				_select[0].selectedIndex = _items.index(itm);
				_select.change();
			} else {
				_items.removeClass('selected');
				_select[0].selectedIndex = -1;
				_select.change();
			}
		}
		
		/* -------------------------------------------------------------------
		 * Event Handlers
		 * ------------------------------------------------------------------- */
		
		var onChange = function (evt) {
			_selected = $(':selected', _select);
			
			if (_selected.text() != '') {
				_label.val(_selected.text());
			} else {
				_items.removeClass('selected');
				_label.val(options.label);
			}
			
			options.change.call(this);
		}
		
		var onKeyUp = function (evt) {
			_select.change();
		}
		
		var onButtonClick = function (evt) {
			if (_cbox.data('_state') === 'closed') open();
			else if (_cbox.data('_state') === 'open') close();
			return false;
		}
		
		var onItemHover = function (evt) {
			if (evt.target.nodeName.toLowerCase() === 'li') {
				_list.children('li').removeClass('hover');
				$(evt.target).addClass('hover');
				return false;
			}
		}
		
		var onItemOut = function (evt) {
			if (evt.target.nodeName.toLowerCase() === 'li') {
				$(evt.target).removeClass('hover');
				return false;
			}
		}
		
		var onItemClick = function (evt) {
			if (evt.target.nodeName.toLowerCase() === 'li') {
				selectOption(evt.target);
				close();
				return false;
			}
		}
		
		var onKeyDown = function (evt) {
			if (_cbox.data('_state') === 'open') {
				// TAB
				if (evt.keyCode == 9) {
					_button.trigger('click');
					_label.focus().next(':input').focus();
					return true;
				}
				// ESC, LEFT, RIGHT
				if (evt.keyCode == 27 || evt.keyCode == 37 || evt.keyCode == 39 ) {
					_button.trigger('click');
				}
				// DOWN
				if (evt.keyCode == 40) {
					if (!_list.children('li').hasClass('hover')) {
						if (_list.children('li').hasClass('selected')) {
							_list.children('li.selected').next('li').trigger('mouseover');
						} else {
							if (_list.children('li:first').hasClass('hide')) {
								_list.children('li:first').next('li').trigger('mouseover');
							} else {
								_list.children('li:first').trigger('mouseover');
							}
						}
					} else {
						_list.children('li.hover').removeClass('hover').next('li').trigger('mouseover');
						if (!_list.children('li').hasClass('hover')) {
							if (_list.children('li:first').hasClass('hide')) {
								_list.children('li:first').next('li').trigger('mouseover');
							} else {
								_list.children('li:first').trigger('mouseover');
							}
						}
					}
					return false;
				}
				// UP
				if (evt.keyCode == 38) {
					if (!_list.children('li').hasClass('hover')) {
						if (_list.children('li').hasClass('selected')) {
							if (_list.children('li.selected').hasClass('hide')) {
								_list.children('li:last').trigger('mouseover');
							} else { 
								_list.children('li.selected').prev('li').trigger('mouseover');
							}
						} else {
							_list.children('li:last').trigger('mouseover');
						}
					} else {
						if (_list.children('li.hover').prev('li').hasClass('hide')) {
							_list.children('li.hover').removeClass('hover');
							_list.children('li:last').trigger('mouseover');
						} else {
							_list.children('li.hover').removeClass('hover').prev('li').trigger('mouseover');
						}
						if (!_list.children('li').hasClass('hover')) {
							_list.children('li:last').trigger('mouseover');
						}
					}
					return false;
				}
				// LETTERS
				if (evt.keyCode > 64 && evt.keyCode < 91) {
					var letter = String.fromCharCode(evt.keyCode);
					for (var i = 0; i < _list.children('li').length; i++) {
						var otxt = _list.children('li:eq(' + i + ')').text().toUpperCase();
						if (otxt.charAt(0) === letter) {
							_list.children('li:eq(' + i + ')').trigger('mouseover');
							return false;
						}
					}
				}
				// ENTER, SPACE
				if (evt.keyCode == 13 || evt.keyCode == 32) {
					if (!_list.children('li.hover').hasClass('selected')) {
						_list.children('li.hover').trigger('click');
					}
				}
				return false;
			} else {
				// UP, DOWN, ENTER, SPACE
				if (evt.keyCode == 38 || evt.keyCode == 40 || evt.keyCode == 13 || evt.keyCode == 32) {
					_button.trigger('click');
					_list.children('li:first').trigger('mouseover');
					return false;
				}
				// TAB
				if (evt.keyCode == 9) {
					_label.focus().next(':input').focus();
					return true;
				}
				// LETTERS
				if (evt.keyCode > 64 && evt.keyCode < 91) {
					var letter = String.fromCharCode(evt.keyCode);
					for (var i = 0; i < _list.children('li').length; i++) {
						var otxt = _list.children('li:eq(' + i + ')').text().toUpperCase();
						if (otxt.charAt(0) === letter) {
							_list.children('li:eq(' + i + ')').trigger('click');
							return false;
						}
					}
				}
			}
			// Prevent enter key from submitting form
			if (evt.keyCode == 13) return false;
		}
		
		/* -------------------------------------------------------------------
		 * getLongestText
		 * 	Finds and returns the longest option text.
		 * ------------------------------------------------------------------- */
		 
		var getLongestText = function () {
			var cur;
			var itm;
			
			itm = $(_items.get(0)).text();
			
			_items.each(function (i, val) {
				cur = $(val).text();
				if (cur.length > itm.length) itm = cur;
			});
			
			return itm;
		}
		
		/* -------------------------------------------------------------------
		 * checkPosition
		 * 	Sets the position of the list.
		 * ------------------------------------------------------------------- */
		 
		var checkPosition = function () {
			var yPos = _cbox.offset().top + _cbox.height();
			var hei = _drop.outerHeight();
			var bot = $(window).height() + getScroll()[1];
			var top = 0;
			
			_drop.css('top', '');
			
			if ((yPos + hei > bot) == true && (yPos - hei < 0) == false) {
				_drop.css('top', (hei * -1) + 'px');
				_cbox.addClass('top');
			} else {
				_cbox.removeClass('top');
			}
		}
		
		/* -------------------------------------------------------------------
		 * getScroll
		 * 	Finds the scroll position depending on the browser.
		 * ------------------------------------------------------------------- */
		
		var getScroll = function () {
			var scrX = 0;
			var scrY = 0;
			
			if (typeof(window.pageYOffset) === 'number') {
				scrY = window.pageYOffset;
				scrX = window.pageXOffset;
			} else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
				scrY = document.body.scrollTop;
				scrX = document.body.scrollLeft;
			} else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
				scrY = document.documentElement.scrollTop;
				scrX = document.documentElement.scrollLeft;
			}
			
			return [scrX, scrY];
		}
		
		/* -------------------------------------------------------------------
		 * clearList
		 * 	Removes all options from the caixabox.
		 * ------------------------------------------------------------------- */
		 
		this.clearList = function () {
			_select.children('option').remove();
			_list.children('li').remove();
			_items = _list.children('li');
		}
		
		/* -------------------------------------------------------------------
		 * addOption
		 * 	Adds a new option to the caixabox.
		 * ------------------------------------------------------------------- */
		 
		this.addOption = function (lbl, val, idx) {
			var num = _select.children('option').length;
			
			if (!idx || idx == undefined || idx == '' || num == 0) {
				_select.append('<option value="' + val + '">' + lbl + '</option>');
				_list.append('<li>' + lbl + '</li>');
				_items = _list.children('li');
			} else {
				var pos = idx;
				var pOpt = _select.children('option:eq(' + pos + ')');
				var pItm = _list.children('li:eq(' + pos + ')');
				
				if (pOpt.length == 1 && pItm.length == 1) {
					pOpt.before('<option value="' + val + '">' + lbl + '</option>');
					pItm.before('<li>' + lbl + '</li>');
					_items = _list.children('li');
				}
			}
		}
		
		/* -------------------------------------------------------------------
		 * removeOption
		 * 	Removes the option with the provided label.
		 * ------------------------------------------------------------------- */
		 
		this.removeOption = function (lbl) {
			var opt = _select.children('option:contains(' + lbl + ')');
			var itm = _list.children('li:contains(' + lbl + ')');
			var idx = opt.index();
			
			if (opt.length == 1 && itm.length == 1) {
				if (idx == _select[0].selectedIndex) unselect();
				
				opt.remove();
				itm.remove();
				
				_items = _list.children('li');
			}
		}
		
		/* -------------------------------------------------------------------
		 * removeOptionAt
		 * 	Removes the option at a given index position.
		 * ------------------------------------------------------------------- */
		 
		this.removeOptionAt = function (idx) {
			var opt = _select.children('option:eq(' + idx + ')');
			var itm = _list.children('li:eq(' + idx + ')');
			
			if (opt.length == 1 && itm.length == 1) {
				if (idx == _select[0].selectedIndex) unselect();
				
				opt.remove();
				itm.remove();
				
				_items = _list.children('li');
			}
		}
		
		/* -------------------------------------------------------------------
		 * unselect
		 * 	Unselects all options and shows default label.
		 * ------------------------------------------------------------------- */
		 
		this.unselect = function () {
			//_select.children('option').attr('selected', false);
			var itm = _list.children('li:eq(0)');
			if (itm.length == 1) selectOption();
		}
		
		/* -------------------------------------------------------------------
		 * selectByIndex
		 * 	Selects the option at the provided index position.
		 * ------------------------------------------------------------------- */
		 
		this.selectByIndex = function (idx) {
			var itm = _list.children('li:eq(' + idx + ')');
			if (itm.length == 1) selectOption(itm);
		}
		
		/* -------------------------------------------------------------------
		 * selectByLabel
		 * 	Selects the option that has the provided label.
		 * ------------------------------------------------------------------- */
		 
		this.selectByLabel = function (lbl) {
			var itm = _list.children('li:contains(' + lbl + ')');
			if (itm.length == 1) selectOption(itm);
		}
		
		/* -------------------------------------------------------------------
		 * selectByValue
		 * 	Selects the option that has the provided value.
		 * ------------------------------------------------------------------- */
		 
		this.selectByValue = function (val) {
			var sitm = _select.children('option[value=' + val + ']');
			if (sitm.length == 1) {
				var idx = sitm.index();
				var itm = _list.children('li:eq(' + idx + ')');
				if (itm.length == 1) selectOption(itm);
			}
		}
		
		/* INITIALIZATION ---------------------------------------------------- */
		
		_select = elem;
		
		build();
		init();
		bindEvents();
	};
	
	$.fn.caixabox = function (settings) {
		return this.each(function () {
			var element = $(this);
			
			if (element.data('caixabox')) return;
			
			var caixabox = new CaixaBox(this, settings);
			
			element.data('caixabox', caixabox);
		});
	};

}) (jQuery);
