/**
 * infoSlider plugin for jQuery
 * @copyright 2009 info.nl
 * @author Arno Wolkers
 * @version 1.1
 * 
 * Attach a slider to an input or select field
 *
 *
 * ----------------------------------------
 * HOW TO USE
 * ----------------------------------------
 * $('input').slider();
 * 
 *
 * ----------------------------------------
 * OPTIONS
 * ----------------------------------------
 * See at the bottom of this page for public settings
 * Use the following to set the default value for all the sliders at once:
 * $.fn.infoSlider.defaults.maxVal = 20000;
 *  
 * 
 */
var sliderCollection = new Array();

(function($){
	$.fn.infoSlider = function(options) {		
		var settings = $.extend({}, $.fn.infoSlider.defaults, options);
		
		var private_settings = {
			slider_html:	'<div class="' + settings.className + '"><div class="track"></div><button class="button"></button></div>'
		};
		
		var settings = $.extend({}, settings, private_settings);
	
		return this.each(function(i, elem) {			
			// if no target is set the input field itself is the target
			if (!settings.sliderTarget) settings.sliderTarget = elem;
			
			var htmlSlider = '<div id="' + elem.id + '_sliderTrack" class="' + settings.className + '"><div class="track"></div><button class="button"></button></div>';
			
			//alert(sliderCollection.length);
			//var foundItem = false;			
			var sliderTrack = document.getElementById(elem.id + '_sliderTrack');	 
			//Ealert(sliderTrack);
			if(!sliderTrack)	
			{
				// place the slider
				switch (settings.InsertMethod) {
					case 'before': // insert before the target
						//alert('insert before');
						elem.slider = $(htmlSlider).insertBefore(settings.sliderTarget);
						break;
					case 'after': //insert after the target
						//alert('insert after');
						//alert('html');
						elem.slider = $(htmlSlider).insertAfter(settings.sliderTarget);
						break;
					case 'append': // insert at the end inside the target
						//alert('insert append');
						
						elem.slider = $(htmlSlider).appendTo(settings.sliderTarget);
						break;
					case 'prepend': // insert at the beginning inside the target
						//alert('insert prepend');
						elem.slider = $(htmlSlider).prependTo(settings.sliderTarget);
						break;
				}	
				//alert(elem.id);
				//sliderCollection.push(elem.id);
			}
						
			elem.drag		= false;
			elem.form_type	= $(elem).is('select') ? 'select' : 'input';
			elem.track		= $(elem.slider).find('.track');
			elem.btn		= $(elem.slider).find('.button');
			elem.btn_width	= parseFloat(elem.btn.css('width'));
			elem.max_pos	= parseFloat($(elem.slider).css('width')); // the max position of the slider.
			elem.min_pos	= 0;	
			
			
			switch (elem.form_type) {
				case 'select':
					elem.step_size	= elem.max_pos / ($(elem).find('option').length-1); // slider width / number of option tags in the select element
					elem.start_pos 	= elem.step_size * selectIndex(elem); //calculates the start position by the selected option
					break;
				case 'input':
					elem.step_size	= settings.stepSize ? (elem.max_pos / ((settings.maxVal-settings.minVal)/settings.stepSize)) : false; // if stepSize option is set calculates and sets the steps in pixels
					elem.start_pos 	= valueToPosition(elem, $(elem).val()); //calculates the start position by the given start value
					break;
			}

			$(elem.slider).
				mousedown(function(e) { // moves the position to the point clicked
					elem.drag = true;
					updateSliderPosition(elem, e.pageX - $(this).offset().left);
				}).
				mousemove(function(e) { // while dragging moves the position to the cursors position
					if (elem.drag) {
						updateSliderPosition(elem, e.pageX - $(this).offset().left);
					}
				});
			
			// deactivates the drag of the slider
			$(elem.slider).mouseup(function() {			
				elem.drag = false;
				$(elem).trigger('infoSliderUpdated');				
			});
			
			
			$(elem.btn).click(function(){ // deactivate default button behavior
				return false;
			}).keydown(function(e){ 
				// when the button is focussed and a key is pressed, updates the position of the slider
				var keyCode = e.charCode || e.keyCode || 0;
				switch (keyCode) {
					case 37: // left arrow
						updateSliderPosition(elem, elem.cur_pos - (elem.step_size ? elem.step_size : 1), (elem.step_size ? 1 : 0)); // default moves 1 pixel to the left else steps equals given steps
						return false;
						break;
					case 39: //right arrow
						updateSliderPosition(elem, elem.cur_pos + (elem.step_size ? elem.step_size : 1), (elem.step_size ? 1 : 0)); // default moves 1 pixel to the right else steps equals given steps
						return false;
						break;
					case 33: // page up
						updateSliderPosition(elem, elem.cur_pos + (elem.step_size ? elem.step_size : Math.round(elem.max_pos/10)), 1); // default moves 10% of the slider width to the left else steps equals given steps
						return false;
						break;
					case 34: // page down
						updateSliderPosition(elem, elem.cur_pos - (elem.step_size ? elem.step_size : Math.round(elem.max_pos/10)), 1); // default moves 10% of th slider width to the right else steps equals given steps
						return false;
						break;
					case 36: // home
						updateSliderPosition(elem, elem.min_pos, 1); // moves to the beginning of the slider
						return false;
						break;
					case 35: // end
						updateSliderPosition(elem, elem.max_pos, 1); // moves to the end of the slider
						return false;
						break;
					case 27: // escape
						$(this).blur(); // releases the focus on the button
					break;
				}
			}).			
			attr('aria-valuetext', '0').
			attr('aria-valuenow', '0'). 
			attr('aria-valuemax', settings.maxVal). 
			attr('aria-valuemin', settings.minVal).
			attr('role', 'slider');
			
			
			switch (elem.form_type) {
				case 'select':
					$(elem).bind('change keyup', function(){
						updateSliderPosition(elem, selectIndex(elem) * elem.step_size, 0);						   
					});
					break;
				case 'input':
					$(elem).bind('keyup', function(){
						var val = $.trim($(this).val());
						if (parseInt(val) < settings.minVal || parseInt(val) > settings.maxVal || isNaN(val)) {
							$(this).addClass('error');
						} else {
							$(this).removeClass('error');						
						}				   
					}).bind('keydown', function(e){
						var keyCode = e.charCode || e.keyCode || 0;
						if (keyCode == 13) { // is enter key hit
							$(this).removeClass('error');
							updateSliderPosition(elem, valueToPosition(elem, $.trim($(elem).val())), 0, 1);
						}
					}).bind('blur', function(){ // change the value of the slider if the form field is blured or select field is changed
						$(this).removeClass('error');
						updateSliderPosition(elem, valueToPosition(elem, $.trim($(elem).val())), 0, 1);
					});
					break;
			}
				
			// set the start position of the slider
			updateSliderPosition(elem, elem.start_pos, 0, 1);	
		});		
		
		function selectIndex(elem) {
			return $(elem).find('option').index($(elem).find('option:selected'));
		}
		
		/**
		 * Calculates the position on the slider by the given value
		 * 
		 * @param {object} elem The slider object
		 * @param {integer} val The given value
		 * @return [integer] The position on the slider in pixels
		 */
		function valueToPosition(elem, val) {
			return Math.round((val-settings.minVal)/(settings.maxVal-settings.minVal) * elem.max_pos);
		}
		
		/**
		 * Calculates the value on the slider by its position
		 * 
		 * @param {object} elem The slider object
		 * @return [integer] The value
		 */
		function positionToValue(elem) {
			return (elem.cur_pos/elem.max_pos * (settings.maxVal-settings.minVal))+settings.minVal;
		}
		
		/**
		 * Rounds the val to the given decimals, negative decimals Rounds 10, 100, 1000, etc
		 * 
		 * @param {integer} val The given value
		 * @param {integer} dec The number of caracters to round
		 * @return [integer] The formated value
		 */
		function roundValue(val, dec) {
			var new_val = Math.round(val * Math.pow(10,dec)) / Math.pow(10, dec);
			return new_val.toFixed(settings.decShown);
		}
		
		
		/**
		 * Updates the position of the active part and the button of the given slider
		 * 
		 * @param {object} elem The slider object
		 * @param {integer} new_pos The new position in pixels
		 */
		function updateSliderPosition(elem, new_pos, animate, keep_ori_val) {			
			var old_pos = elem.cur_pos;
			var old_val = elem.cur_val;
			
			// if the slider uses steps then recalculate the new position
			if (elem.step_size) {
				var pos_in_step = new_pos % elem.step_size;
				if (pos_in_step != 0) {
					if (pos_in_step <= elem.step_size/2) { // jump to prev step
						new_pos -= pos_in_step;
					} else { // jump to next step
						new_pos += (elem.step_size-pos_in_step);
					}
				}
			}
			
			// calculates the new position of the slider, must be within the slider
			elem.cur_pos = Math.min(elem.max_pos, Math.max(elem.min_pos, new_pos));
			
			switch (elem.form_type) {
				case 'select':
					elem.cur_val = $(elem).find('option:eq(' + Math.round(elem.cur_pos/elem.step_size) + ')').text(); 
					break;
				case 'input':
					if (keep_ori_val) {
						elem.cur_val = Math.min(settings.maxVal, Math.max(settings.minVal, $(elem).val()));
						elem.cur_val = elem.cur_val.toFixed(settings.decShown);
						if (settings.definedSteps) {
							// zoekt de dichtbijzijnde waarde op en neemt zijn positie aan
							$.each(settings.definedSteps, function(i){
								if (settings.definedSteps[i] >= elem.cur_val) {
									var a = (elem.cur_val - settings.definedSteps[i]) * -1;
									var b = elem.cur_val - settings.definedSteps[i-1];
									if (isNaN(b) || a==0 || a >= b) {
										elem.cur_pos = i;
									} else {
										elem.cur_pos = i-1;
									}

									return false;
								}
							})
							
						}
					} else {
						if (settings.definedSteps) {
							elem.cur_val = settings.definedSteps[Math.round(elem.cur_pos)];
							} else {
							elem.cur_val = roundValue(positionToValue(elem), settings.roundVal); // calculates the current value	
						}
					}
					break;
			}
			
			
			// calculates the positive difference between old en new position
			var diff = old_pos - elem.cur_pos;
			if (diff < 0) {
				diff = diff * -1;
			}
			// if step_size is set then the minimal required differce is two times a step_size and else it is minimal 20 pixels or 20% of the slider width 
			var min_diff = (elem.step_size ? elem.step_size*2 : Math.max(20, elem.max_pos*.2));
			
			// if the difference is more the minimal required differce then animate
			if (diff >= min_diff) {
				animate = true;
			} else {
				animate = false;	
			}
			
			// set wai-aria values and title to the button
			$(elem.btn).attr('title', elem.cur_val).attr('aria-valuetext', elem.cur_val).attr('aria-valuenow', elem.cur_val);
			
			// update the value of me
			$(elem).val(elem.cur_val);
			
			// trigger if not on start 
			if (elem.start_pos == false) {
				//$(elem).trigger('infoSliderUpdated');
			} else {
				elem.start_pos = false;	
			}
			
			// calculate the new position of the button
			var pos_btn = elem.cur_pos - elem.btn_width/2;
			
			if (!animate) {
				$(elem.track).stop().width(elem.cur_pos); // sets the new track width
				$(elem.btn).stop().css({left: pos_btn}); // set the new button position
			} else {
				$(elem.track).stop().animate({width:elem.cur_pos}, 300, settings.easing); // animate to new track width
				$(elem.btn).stop().animate({left: pos_btn}, 300, settings.easing); // animate to new button position
			}			
		}
				   
	};

	// plugin defaults 
	$.fn.infoSlider.defaults = {
		className: 		'slider',	// className of the slider
		stepSize:		false,		// the size of one step as measured in value
		roundVal:		0,			// On which caracter should the slider round the value. use zero value(default) to round on single number, positive value to round on decimals, negative value to round on 10, 100, 1000, etc
		decShown:		0,			// the number of decimals that has to be shown at all times
		minVal:			0,			// the minimum value of the slider
		maxVal:			10000,		// the maximum value of the slider
		easing:			'linear',	// default animation easing type
		sliderTarget:	false,		// selector of target where to insert the slider
		InsertMethod:	'after',	// insertion method for the slider in relatition to the target (before/after/append/prepend)
		definedSteps:	false,
		sliderRendered: false
	};

})(jQuery);

