/**
 * @author trixta
 */
(function($){

	$.support.waiAria = (!$.browser.msie || parseInt($.browser.version, 10) > 7);
	
    $.widget('ui.replaceSelect', {
		_init: function(){
			if(!this.element.is('select')){return;}
			var classNames = this.element.attr('class');
			
			this.inactiveTabindex = ($.support.waiAria) ? '0' : '-1';
			
			this.label = $('label[for='+ this.element.attr('id') +']');
						
			this.dataElem = $('<div class="datalist" role="listbox"><div class="datalist-box" role="presentation"><ul role="presentation"></ul></div></div>')
					.addClass(classNames)
					.css({visibility: 'hidden'})
					.attr({'aria-hidden': 'true'})
					.labelWith(this.label)
					.appendTo('body')
			;

			this.selectElem = $('<div class="combobox"><input readonly="readonly" /></div>')
				.attr({tabindex: '-1'})
				.addClass(classNames)
				.ownsThis(this.dataElem)
				.insertAfter(this.element)
			;
			
			this.inputElem = $('input', this.selectElem)
				.attr({
					role: 'combobox',
					'aria-readonly': 'false',
					'aria-expanded': 'false',
					tabindex: this.inactiveTabindex,
					unselectable: 'on'
				})
				.labelWith(this.label)
			;
			
			this._generateOptions();
			
			this.items = $('li', this.dataElem.find('ul')[0]).attr({tabindex: '-1'});
			
			this.reflectUI();
			this._addEvents();
			this._trigger('init', {type: 'init'}, this._ui());
		},
		_generateOptions: function(){
			var optList = '';
			$('option', this.element).each(function(){
				var option = $(this);
				optList += '<li role="option" data-value="'+ option.val() +'"><span role="presentation">'+ option.text() +'</span></li>';
			});
			$('ul', this.dataElem).html(optList);
		},
		reflectUI: function(){
			this.checkedIndex = this.element[0].selectedIndex;
			var text = $('li', this.dataElem)
				.removeClass('js-checked')
				.filter(':eq('+ this.checkedIndex +')')
				.addClass('js-checked')
				.text()
			;
			this.inputElem
				.val(text)
				.attr({'aria-valuetext': text})
			;
		},
		_addEvents: function(){
			var that = this,
				closeTimer, mousedown
			;
			
			function activateElem(elem){
				setTimeout(function(){
					that.inputElem.activateThis(elem);
				}, 0);
			}
			this.label.bind('click', function(){
				that.selectElem.setFocus();
				return false;
			});
			
			this.element.bind('change', function(e){
				that.reflectUI();
			});
			
			this.selectElem
				.bind('mousedown', function(e){
					that.selectElem.setFocus();
					that.toggleExpand(e);
					e.preventDefault();
				})
				.bind('focusin', function(e){
					clearTimeout(closeTimer);
					that.selectElem.addClass('combobox-active');
					that.inputElem.attr({tabindex: '-1'});
				})
				.bind('focusout', function(e){
					if(mousedown){return;}
					clearTimeout(closeTimer);
					closeTimer = setTimeout(function(){
						that.selectElem.removeClass('combobox-active');
						that.inputElem.attr({tabindex: that.inactiveTabindex});
						that.collapse(e);
					}, 2);
				})
				.bind('keypress keydown', function(e){
					var activeID 	= that.inputElem.attr('aria-activedescendant'),
						index 		= (activeID) ? 
											that.items.index($('#'+ activeID)) :
											that.checkedIndex,
						charCode 	= e.charCode || e.which || e.charCode,
						foundIndex, key
					;
					if(e.type === 'keydown'){
						if(e.altKey && ($.ui.keyCode.DOWN === e.keyCode || e.keyCode === $.ui.keyCode.UP)){
							
							that.toggleExpand(e);
							if(that.isExpanded){
								activateElem(that.items.filter(':eq('+ that.checkedIndex +')'));
							}
							return;
						}
						if(e.keyCode === $.ui.keyCode.ESCAPE){
							that.collapse(e);
							return;
						}
						
						if(e.keyCode === $.ui.keyCode.RIGHT || e.keyCode === $.ui.keyCode.DOWN){
							foundIndex = index + 1;
						} else if(e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.UP){
							foundIndex = index - 1;
						} else if(e.keyCode === $.ui.keyCode.END || e.keyCode === $.ui.keyCode.PAGE_DOWN){
							foundIndex = that.items.length -1;
						} else if(e.keyCode === $.ui.keyCode.HOME || e.keyCode === $.ui.keyCode.PAGE_UP){
							foundIndex = 0;
						} else if(e.keyCode === $.ui.keyCode.ENTER){
							that.select(index, e);
							that.collapse(e);
							return false;
						}
					}
					
					if(e.type === 'keypress' && charCode >= 32){
						key = String.fromCharCode(charCode).toUpperCase();
						that.items.each(function(i){
							var item = $(this),
								text = $(this).text().toUpperCase()
							;
							if(text.indexOf(key) === 0 && (foundIndex === undefined || i > index)){
								foundIndex = i;
								if(i > index){
									return false;
								}
							}
						});
					}
					
					if(foundIndex !== undefined){
						if(foundIndex < 0 || foundIndex >= that.items.length){
							foundIndex = index;
						}
						that.select(foundIndex, e);
						that.expand(e);
						activateElem(that.items.filter(':eq('+ foundIndex +')'));
						return false;
					}
				})
			;
			
			this.dataElem
				.bind('mousedown', function(e){
					mousedown = true; // IE workaround
					setTimeout(function(){
						clearInterval(closeTimer);
						mousedown = false;
					}, 1);
					return false;
				});
			
			this.items
				.bind('click', function(e){
					that.select(this, e);
					that.selectElem.setFocus();
					that.collapse(e);
				})
				.bind('mouseenter', function(e){
					that.items.removeClass('js-selected');
					that.inputElem.activateThis(
						$(this).addClass('js-selected')
					);
				})
			;
			
			if($.support.waiAria){
				this.element.hide();
			} else {
				this.element
					.addClass('aural')
					.one('focus', function(){
						that.element.removeClass('aural');
						that.selectElem.hide();
					});
			}
		},
		toggleExpand: function(e){
			this[(this.isExpanded) ? 'collapse' : 'expand'](e);
		},
		getPosition: function(){
			var offset = this.selectElem.offset({});
			offset.top += this.selectElem.outerHeight({margin: true});
			return offset;
		},
		expand: function(e){
			if(this.isExpanded){return;}
			e = e || {type: 'expand'};
			var that = this;
			this.isExpanded = true;
			this.beforeOpenIndex = this.checkedIndex;
			this.options.showCallback(this._ui(), this.getPosition());
			this._trigger('expand', e, this._ui());
			this.selectElem.addClass('js-datalist-visible');
			this.dataElem.attr({'aria-hidden': 'false'});
			setTimeout(function(){
				that.inputElem.attr({'aria-expanded': 'true'});
			}, 1);
		},
		collapse: function(e){
			if(!this.isExpanded){return;}
			e = e || {type: 'collapse'};
			var that = this;
			this.isExpanded = false;
			
			this.inputElem
				.attr({'aria-expanded': 'false'})
				.removeAttr('aria-activedescendant')
			;
			this.options.hideCallback(this._ui());
			if(this.beforeOpenIndex !== this.checkedIndex){
				this._trigger('change', e, this._ui());
				this.element.trigger('change');
			}
			this.beforeOpenIndex = undefined;
			this._trigger('collapse', e, this._ui());
			this.dataElem.queue(function(){
				that.items.removeClass('js-selected');
				that.selectElem.removeClass('js-datalist-visible');
				that.dataElem.attr({'aria-hidden': 'true'}).dequeue();
			});
		},
		select: function(sel, e){
			var oldIndex = this.checkedIndex,
				context
			;
			e = e || {type: 'select'};
			
			if(isFinite(sel)){
				this.element[0].selectedIndex = sel;
			} else if(typeof sel === 'string'){
				this.element.val(sel);
			} else if(typeof sel === 'object'){
				sel = $(sel);
				context = (sel.is('option')) ? 'element' : 'dataElem';
				this.element[0].selectedIndex = $(sel[0].tagName.toLowerCase(), this[context]).index(sel);
			}
			this.reflectUI();
			if(oldIndex !== this.checkedIndex){
				this._trigger('select', e, this._ui({oldCheckedIndex: oldIndex}));
			}
		},
		_ui: function(obj){
			return $.extend({
				instance: this,
				element: this.element,
				selectElem: this.selectElem,
				inputElem: this.inputElem,
				dataList: this.dataElem,
				items: this.items,
				options: this.options,
				checkedIndex: this.checkedIndex
			}, obj);
		}
	});
	
	$.ui.replaceSelect.defaults = {
		showCallback: function(ui, css){
			ui.dataList.css($.extend({}, css, {visibility: 'visible'}));
		},
		hideCallback: function(ui){
			ui.dataList.css({visibility: 'hidden'});
		}
	};
	
})(jQuery);
