jQuery.fn.disableSelection = function() {
    return this.each(function() {           
        jQuery(this).attr('unselectable', 'on')
               .css({
                   '-moz-user-select':'none',
                   '-webkit-user-select':'none',
                   'user-select':'none'
               })
               .each(function() {
                   this.onselectstart = function() { return false; };
               });
    });
};

//var cached jquery selectors
var html = jQuery('html');

(function(jQuery){

	var JStyle = function(element, options){
		
		var getType = function(elem){
			return elem.is('select') ? (elem.is('[multiple]') ? 'mselect' : 'sselect') : 
					elem.is('textarea') ? 'textarea' : 
					elem.is('button') ? 'button' :
					elem.is('input') ? elem.attr('type') : '';
		}
		
		var change = function(elem, type){
			var t = getType(elem);
			if(t === 'checkbox' || t === 'radio'){
				if(elem.is(':checked')){
					elem.parent().addClass('checked');
				} else {
					elem.parent().removeClass('checked');
				}
				
				if(t === 'radio'){
					element.find('[name="'+elem.attr('name')+'"]').not(':checked').each(function(i){
						jQuery(this).parent().removeClass('checked');
					});
				}				
			} else if (t === 'sselect'){
				elem.next().find('.val').text(elem.find(':selected').text());
			}
		}
		
		var disenable = function(elem, method){
			if(method === 'enable'){
				//elem.removeAttr('disabled');
				elem.removeAttr('disabled');
				nonmethod = 'disable';
			} else {
				elem.attr('disabled', 'disabled');
				nonmethod = 'enable';
			}
			var type = elem.is('select') ? 'select' : 
			elem.is('option') ? 'option' : 
			elem.is('textarea') ? 'textarea' : 
			elem.is(':text') ? 'text' : 
			elem.is(':checkbox') ? 'checkbox' : 
			elem.is(':radio') ? 'radio' : 
			elem.is(':button') ? 'button' : 
			elem.is(':submit') ? 'button' : 
			elem.is(':reset') ? 'button' : '';
			
			if(/checkbox|radio|select/.test(type)){
				elem.parent().removeClass(nonmethod+'d').addClass(method+'d');	
			} else if (type === 'text' || type === 'textarea' || type === 'button'){
				elem.removeClass(nonmethod+'d').addClass(method+'d');	
			} else if (type === 'option'){
				elem.parents('select').next().find('ul li:eq('+elem.index()+')').removeClass(nonmethod+'d').addClass(method+'d');
			}
		}
		
		var check = function(elem, method){
			if(method){
				elem.attr('checked', 'checked').parent().addClass('checked');
			} else {
				elem.removeAttr('checked').parent().removeClass('checked');
			}
		}
		
		var select = function(elem, method){
			var select = elem.parents('select');
			if(method){
				if(!select.is('[multiple]')){
					select.children(':selected').each(function(i){
						var opt = jQuery(this);
						opt.removeAttr('selected');
						select.next().find('ul li:eq('+opt.index()+')').removeClass('selected');
					});
				} else {
					select.next().children('.value .val').text(elem.text());
				}
				elem.attr('selected', 'selected');
				select.next().find('ul li:eq('+elem.index()+')').addClass('selected');
			} else {
				elem.removeAttr('selected');
				select.next().find('ul li:eq('+elem.index()+')').removeClass('selected');
			}
		}
		
		var addInput = function(elem){
			var t = getType(elem);
			if(/checkbox|radio/.test(t)){
				elem.addClass('bform').wrap('<div class="bform check-rad '+t+' '+((elem.is(':enabled')) ? 'enabled' : 'disabled')+((elem.is(':checked')) ? ' checked' : '')+'"/>');
			} else if (/submit|reset|button|text|textarea/.test(t)) {
				elem.addClass('bform '+t+' '+((elem.is(':enabled')) ? 'enabled' : 'disabled'));
			} else if (/sselect|mselect/.test(t)) {
				elem.addClass('bform').wrap('<div class="bform select '+t+' '+((elem.is(':enabled')) ? 'enabled' : 'disabled')+'"/>');
				var selectWrap = elem.parent().append('<div class="select-wrapper"/>').children('div');
				if(t === 'sselect'){
					var selectVal = selectWrap.append('<div class="value"><div class="val">'+(elem.children(':selected').text())+'</div><div class="arrow"/></div>').find('.val');
				}
				var selectCont = selectWrap.append('<ul />').children('ul');
				elem.children().each(function(i){
					var opt = jQuery(this);
					selectCont.append('<li class="'+((opt.is(':selected')) ? 'selected ' : '')+((opt.is(':enabled')) ? 'enabled' : 'disabled')+'">'+opt.text()+'</li>');
				});
				selectCont.find('li').disableSelection();
				
				//function to enforce strict scrolling (scrolling will snap to each option)
				selectCont.scroll(function(ev){
					ev.preventDefault();
					var h = parseInt(selectCont.children('li:eq(1)').outerHeight(), 10);
					var cont = jQuery(ev.target);
					cont.scrollTop(Math.round(cont.scrollTop()/h)*h);
				});
				
				if(t === 'sselect'){
					
					//top most element that contains all elements pertaining to this <select>
					var holder = selectWrap.parent();
					
					selectWrap.click(function(ev){
					
						//unfocus other elements
						elem.trigger('unfocusall');
					
						ev.preventDefault();
						ev.stopPropagation();
						var target = jQuery(ev.target);
						
						if(target.is('li')){

							if(elem.hasClass('disabled') || target.hasClass('disabled')){
								return;
							}
							
							//select the element that was clicked
							var idx = target.index();
							elem.children(':eq('+idx+')').trigger('select');
							
							//update the pseudo select
							
							elem.trigger('change', {bubbles: false});
							
							//close the pseudo select, leaving focus on the element
							selectWrap.removeClass('focus');
						
						} else {
						
							if(holder.hasClass('disabled')){
								return;
							}
							
							var scroll = false;
						
							//give focus to the element, and open the list
							selectWrap.addClass('focus');
							holder.addClass('focus');
							
							//scrollto the selected option
							var ul = selectWrap.find('ul');
							var top = selectWrap.find('.selected').position().top;
							ul.scrollTop(Math.round(ul.scrollTop()+top));
							
							//listen for hovers on an element
							selectWrap.delegate('li.enabled', 'mouseenter mousemove click', function(ev){
								if(scroll){
									scroll = false;
									return;
								}
								var trg = jQuery(ev.target);
								if(!trg.is('.hover')){
									selectWrap.find('.hover').removeClass('hover');
									trg.addClass('hover');
								}
								ev.preventDefault();
								ev.stopPropagation();
							});
							
							//initialize keyboard listeners
							html.bind('keydown.sselect', function(ev){
							
								if(elem.parent().hasClass('disabled')){
									return;
								}
							
								if(/37|38|39|40/.test(ev.which)){
									//arrow key
									
									//if select box is closed, open it
									if(!selectWrap.hasClass('focus')){
										selectWrap.addClass('focus');
										top = selectWrap.find('.selected').position().top;
										ul.scrollTop(Math.round(ul.scrollTop()+top));
									}
									
									//get a list of options that are available to be selected (aka enabled)
									var en = selectWrap.find('li.enabled'),
										hov = en.filter('.hover'),
										idx = en.index(hov);
									if(idx === -1){
										idx = selectWrap.find('.selected').index();
									}
									if(/37|38/.test(ev.which)){
										//left or up arrow
										if(idx !== 0) {
											idx-=1;
										}
										
									} else {
										//right or down arrow
										if(idx+1 !== en.length) {
											idx+=1;
										}
										
									}
										
									hov.removeClass('hover');
									newEl = en.filter(':eq('+idx+')');
									if(!newEl.length){
										newEl = en.filter('.selected');
									}
									newEl.addClass('hover');
									var top = Math.round(newEl.position().top);
									if(top >= ul.height() || top < 0){
										ul.scrollTop(ul.scrollTop()+newEl.position().top);
										scroll = true;
									}
									
								} else if (ev.which === 13/* || ev.which === 32*/){
									//enter key or /*space bar*/
									
									if(selectWrap.hasClass('focus')){
										
										//select the hovered element and close the select box
										elem.children(':eq('+selectWrap.find('.hover').index()+')').trigger('select');
										elem.trigger('change', {bubbles: false});
									
										selectWrap.removeClass('focus');
										
									} else {
										//open the select box
										selectWrap.addClass('focus');
										top = selectWrap.find('.selected').position().top;
										ul.scrollTop(Math.round(ul.scrollTop()+top));
									}
								
								} else {
									return;
								}
								ev.preventDefault();
								
							});
							
							html.bind('click.sselect', function(ev){
							
								//close the list and unfocus the select box
								selectWrap.removeClass('focus');
								holder.removeClass('focus');
								//unbind the listeners
								html.unbind('click.sselect')
									.unbind('keydown.sselect');
							});
						
						}
					});
					
				} else if (t === 'mselect'){
				
					var holder = selectWrap.parent();
					
					var scroll = false;
					
					selectCont.delegate('li', 'mouseenter mousemove', function(ev){
						if(scroll){
							scroll = false;
							return;
						}
						var trg = jQuery(this);
						if(trg.hasClass('disabled')){
							return;
						}
						if(!trg.hasClass('hover')){
							
							selectCont.find('.hover').removeClass('hover');
							trg.addClass('hover');
							
						}
					});
						
					var lastSelected = selectCont.find('.selected');
				
					selectCont.click(function(ev){
					
						//unfocus other elements
						elem.trigger('unfocusall');

						ev.stopPropagation();
						//add focus to the pseudo select box
						selectWrap.addClass('focus');
						holder.addClass('focus');
						html.unbind('click.mselect')
							.bind('click.mselect', function(ev){
						
							//take focus away from the pseudo select box
							selectWrap.removeClass('focus');
							holder.removeClass('focus');
							
							html.unbind('click.mselect keydown.mselect');
							
						});
							
						html.bind('keydown.mselect', function(ev){
							
							if(elem.parent().hasClass('disabled')){
								return;
							}
							if (/37|38|39|40/.test(ev.which)){
								//calculate the new index to move to
								var en = selectCont.find('li.enabled'),
									hov = en.filter('.hover'),
									idx = en.index(hov);
									
								if(idx === -1){
									idx = lastSelected.index();
								}
								
								
								if(/37|38/.test(ev.which)){
									//left or up arrow
									if(idx !== 0){
										idx-=1;
									}
									
								} else {
									//right or down arrow
									if(idx+1 !== en.length){
										idx+=1;
									}
									
								}
								hov.removeClass('hover');
								var newEl = en.eq(idx).addClass('hover');
								
								var top = Math.round(newEl.position().top);
								if(top >= selectCont.height() || top < 0){
									selectCont.scrollTop(selectCont.scrollTop()+newEl.position().top);
									scroll = true;
								}
								
								if(ev.ctrlKey){
									//do nothing
								} else if (ev.shiftKey) {
									//select range
									var a = lastSelected.index();
									var b = newEl.index();
									
									elem.children().trigger('deselect').slice((a<b) ? a : b, (a>=b) ? a+1 : b+1).filter(':enabled').trigger('select');
									
								} else {
									//do nothing
									elem.children().trigger('deselect').filter(':eq('+newEl.index()+')').trigger('select');
								}
								
								elem.trigger('change');
								
							} else if (ev.which === 32){
								elem.children(':eq('+selectCont.children('.hover').index()+')').trigger('select');
							} else {
								return;
							}
							ev.preventDefault();
							ev.stopPropagation();
						});
						
					});
					
					selectCont.delegate('li', 'click', function(ev){
						if(elem.parent().hasClass('disabled')){
							return;
						}
						var opt = jQuery(this);
						var origOpt = elem.children('option:eq('+opt.index()+')');
						if(origOpt.is(':disabled')){
							return;
						}
						var thisSel = origOpt.is(':selected');
						if(ev.ctrlKey){
							if(thisSel){
								origOpt.trigger('deselect');
							} else {
								origOpt.trigger('select');
							}
							lastSelected = opt;
						} else if (ev.shiftKey){
							//select between this and last selected
							var a = lastSelected.index();
							var b = opt.index();
							
							elem.children().trigger('deselect').slice((a<b) ? a : b, (a>=b) ? a+1 : b+1).filter(':enabled').trigger('select');
						} else 	{
							//deselect all options except this one
							elem.find(':selected').trigger('deselect');
							//selectWrap.find('.selected').removeClass('selected');
							origOpt.trigger('select');
							lastSelected = opt;
						}
						elem.trigger('change');
					});
				}
			}
			
			/************************
			 * * * * * * * * * * * *
			 * * * BIND EVENTS * * *
			 * * * * * * * * * * * *
			************************/
			
			elem.bind('unfocusall', function(ev){
				//unfocus other elements
				jQuery('.bform.focus').not(elem.parent()).each(function(i){
					jQuery(this).removeClass('focus').find('.focus').removeClass('focus');
				});
			});
			
			elem.change(function(){
				change(elem);
			});
			
			elem.bind('disable enable', function(ev){
				//use ev.target instead of elem for option elements
				disenable(jQuery(ev.target), ev.type);
			});
			
			if(elem.is(':checkbox') || elem.is(':radio')){
				elem.bind('check uncheck', function(ev){
					check(elem, (ev.type === 'check') );
				});
			}
			
			if(elem.is('select')){
				elem.children().bind('select deselect', function(ev){
					select(jQuery(this), (ev.type === 'select') );
				});
			}
			
			if(elem.is(':text') || elem.is('textarea')){
				elem.focus(function(ev){
					//focusin
					elem.addClass('focus');
				}, function(ev){
					//focusout
					elem.removeClass('focus');					
				});
			}
		}
		
		var refresh = function(){
			//initialize each form element currently on the page
			element.find(':input').filter(':not(.bform)').each(function(i){
				addInput(jQuery(this));
			});
		}		
		
		this.refresh = function(){
			refresh();
		}
		
		//initialize each form element currently on the page
		refresh();
		
	}

	jQuery.fn.jStyle = function(options) {
		return this.each(function(){
			var element = jQuery(this);
			var elem = this;
			// Return early if this element already has a plugin instance
			if (element.data('jStyle')){
				//check for any new elements
				element.data('jStyle').refresh();
				return;
			}
			// Pass options to plugin constructor
			var jStyle = new JStyle(element, options);
			// Store plugin object in this element's data
			element.data('jStyle', jStyle);
		});
	};
})(jQuery);
