/**
 * LBi Slider module
 *
 * @module    slider
 * @version   1.11.101119
 * @requires  jQuery, LBi
 * @author    LBi Lost Boys
 */
(function($){

	var Event = LBi.Event;
	
	Event.SLIDER_UPDATED = 'sliderupdated';

	/**
	 * The Slider class binds a slider to an input element and updates the input's value when the 
	 * slider is used. Various settings may be used to change the slider's behaviour. See the 
	 * Slider.Defaults for available options.
	 *
	 * @class LBi.Slider
	 * @constructor
	 * @param {Node} element The input element the slider is created for.
	 * @param {Object} settings
	 * @return {Slider} slider
	 */
	var Slider = function(node, settings) {
		this.settings = $.extend({}, Slider.Defaults, settings);
		this.doc = $(document);
		this.origin = node;
		this.create();
	};

	Slider.prototype = {
		constructor: Slider,
		
		/**
		 * Adds the slider to the document, and binds the necessary mouse events. A "slider" property
		 * is added to the original input element using jQuery's data functionality. This property may
		 * be used to implement additional behaviour not coverred by the Slider class itself. The create
		 * method is called automatically.
		 * 
		 * @method create
		 */
		create:function() {
			var settings = this.settings;
				
			this._locked = false;
			this._down = this.mousedown.bind(this);
			this._up = this.mouseup.bind(this);
			this._move = this.mousemove.bind(this);
			this._change = this.onchange.bind(this);
			
			var template = new LBi.Template(settings.template);
			var html = $(template.parse(settings));
			
			var $origin = $(this.origin);
			$origin.addClass(settings.replacedClass);
			$origin.bind(Event.CHANGE, this._change);

			if(/before/i.test(settings.insertMode)){
				$origin.before(html);
			} else {
				$origin.after(html);
			}

			this.root = html;
			this.track = html.find('.' + settings.trackClass);
			this.thumb = html.find('.' + settings.thumbClass);

			this.thumb.bind(Event.MOUSEDOWN, this._down);

			this.findRange();
			this.setValue(this.origin.value || this._min);

			$.data(this.origin, 'slider', this);
		},

		/**
		 * Mousedown handler
		 * @private
		 */
		mousedown:function(e) {
			e.preventDefault();
			
			if(this.locked()) {
				return;
			}

			this.baseX = e.clientX;
			this.baseLeft = this.getPosition();
			
			this.doc.bind(Event.MOUSEMOVE, this._move);
			this.doc.bind(Event.MOUSEUP, this._up);
		},

		/**
		 * Mousemove handler, dynamically bound and unboud when the slider is used. When the value
		 * of the slider changes, a SLIDER_UPDATED event is fired to the Dispatcher.
		 * 
		 * @param {Event} e Mousemove event
		 */
		mousemove:function(e) {
			e.preventDefault();
			var settings = this.settings;
			var position = this.baseLeft + (e.clientX - this.baseX);
			var width = this.getWidth();
			
			position = limit(position, 0, width);
			
			this.setPosition(position);

			LBi.Dispatcher.trigger(Event.SLIDER_UPDATED, this.origin, {
				slider: this,
				value: this._value
			});
		},
		
		/**
		 * Mouseup handler, dynamically bound and unboud when the slider is used.
		 * @private
		 */
		mouseup:function(e) {
			e.preventDefault();
			this.doc.unbind(Event.MOUSEMOVE, this._move);
			this.doc.unbind(Event.MOUSEUP, this._up);
		},

		/**
		 * Onchange handler of the replaced input, sets the slider to the correct value.
		 * @private
		 */
		onchange:function(e) {
			var value = limit(parseFloat(this.origin.value), this._min, this._max);
			this.setValue(value);

			LBi.Dispatcher.trigger(Event.SLIDER_UPDATED, this.origin, {
				slider: this,
				value: this._value
			});
		},

		/**
		 * Returns the available track width of the slider (total minus thumb).
		 * @private
		 */
		getWidth:function() {
			return this.root[0].offsetWidth - this.thumb[0].offsetWidth;
		},
		
		/**
		 * Sets the value of the slider to the given value. The value is automatically limited to
		 * the slider's range.
		 * 
		 * @param {number} value
		 */
		setValue:function(value) {
			var position = this.valueToPosition(
				limit(value, this._min, this._max)
			);
			this.thumb.css({left: position});
			this.track.css({width: position});
			this.setInputValue(value);
		},
		
		/**
		 * Returns the rounded slider value, as passed to the related input.
		 * 
		 * @return {number} slider value
		 */
		getValue:function() {
			return parseInt(this.origin.value, 10);
		},

		/**
		 * Returns the float value of the slider, regardless of step size or rounding. 
		 *
		 * @return {number} slider value 
		 */
		getFloatValue:function(){
			return this._value;
		},

		/**
		 * Sets the thumb's position, and calculates the related value.
		 * @private
		 */
		setPosition:function(position) {
			var snapped = this.snapPosition(position);
			var value = this.positionToValue(position);
			this.thumb.css({left: snapped});
			this.track.css({width: snapped});
			this.setInputValue(value);
		},

		/**
		 * Returns the thumb's position on the track.
		 * @private
		 */
		getPosition:function() {
			return this.thumb[0].offsetLeft;
		},
		
		/**
		 * Returns a reference to the slider's input node.
		 * 
		 * @return {Node} input
		 */
		getInput:function() {
			return this.origin;
		},

		/**
		 * Passes the given value to the related input as an int, and stores the float.
		 * @private
		 */
		setInputValue:function(value) {
			this._value = parseFloat(value);
			this.origin.value = this._fixed?
				parseFloat(value).toFixed(this._fixed) : parseInt(value, 10);
		},

		/**
		 * Sets the min and max range of the slider
		 * 
		 */
		findRange:function() {
			var input = this.origin;
			var step = input.getAttribute('step');
			if(step && step.indexOf('.') > 0) {
				this._fixed = step.split('.')[1].length;
			}

			this._min = parseFloat(input.getAttribute('min') || 0);
			this._max = parseFloat(input.getAttribute('max') || 100);
			this._range = this._max - this._min;
			this._step = parseFloat(step || this._range / 100);
		},
		
		/**
		 * Locks or unlocks the slider, and toggles a class on the slider's outer node for 
		 * styling purposes. See Slider.Defaults.
		 * 
		 * @param {boolean} toggle
		 */
		lock:function(lock) {
			this._locked = lock;
			this.root.toggleClass(this.settings.lockedClass, lock);
		},
		
		/**
		 * Returns a boolean value indicating whether the slider is locked or not.
		 * 
		 * @return {boolean} locked
		 */
		locked:function() {
			return this._locked;
		},
		
		/**
		 * Calculates the thumb position for a given value
		 * @private
		 */
		valueToPosition:function(value) {
			var position = (parseFloat(value) / this._range) * this.getWidth();
			return Math.round(position);
		},

		/**
		 * Applies step size and optional snapping
		 * @private
		 */
		snapPosition:function(position) {
			var snapped = position;
			if(this.settings.snap) {
				var step = this.getWidth() / (this._range / this._step);
				snapped = (position/step) * step;
			}

			return Math.round(snapped);
		},

		/**
		 * Calculates the value for a given thumb position
		 * @private
		 */
		positionToValue:function(position) {
			var value = this._min + (position/this.getWidth() * this._range);
			var step = this._step;
			return Math.round(value/step) * step;
		}
	};

	LBi.namespace('Slider', Slider);

	var limit = function(value, min, max) {
		return Math.min(Math.max(value, min), max);
	};
	
	
	/**
	 * Default settings for slider instances. 
	 * 
	 * @static
	 * @class LBi.Slider.Defaults
	 */
	Slider.Defaults = {
		/**
		 * The template fragment of which the slider is constructed.
		 * @property template
		 * @type String
		 * @default '&lt;div class="$sliderClass">&lt;span class="$trackClass">&lt;/span>&lt;span class="$thumbClass">&lt;/span>&lt;/div>'
		 */
		template: '<div class="$sliderClass"><span class="$trackClass"></span><span class="$thumbClass"></span></div>',
		/**
		 * The CSS class of a slider .
		 * @property sliderClass
		 * @type String
		 * @default "slider"
		 */
		sliderClass: 'slider',
		/**
		 * The CSS class of a slider's thumb node.
		 * @property thumbClass
		 * @type String
		 * @default "slider-thumb"
		 */
		thumbClass: 'slider-thumb',
		/**
		 * The CSS class of a slider's track node.
		 * @property trackClass
		 * @type String
		 * @default "slider-track"
		 */
		trackClass: 'slider-track',
		/**
		 * The CSS class that is added to the related input of a slider.
		 * @property replacedClass
		 * @type String
		 * @default "slider-input"
		 */
		replacedClass: 'slider-input',
		/**
		 * The CSS class toggle on the slider when it is locked.
		 * @property lockedClass
		 * @type String
		 * @default "slider-locked"
		 */
		lockedClass: 'slider-locked',
		/**
		 * Specifies whether the slider html is inserted before or after the related input.
		 * @property insertMode
		 * @type String
		 * @default "before"
		 */
		insertMode: 'after',
		/**
		 * Defines whether the thumb snaps to the step size or not.
		 * @property snap
		 * @type boolean
		 * @default false
		 */
		snap: false
	};
	
	/**
	 * Creates a Slider instance for all elements in the jQuery result. The settings
	 * object may be used to customize the slider's behavior. See the Slider class for details.
	 * Since this call must maintain the jQuery chain, instances are added to the 
	 * LBi.Slider.instances array.
	 *
	 * @for jQuery.functions
	 * @method slider
	 * @param {Object} settings
	 * @return {jQuery} result
	 */
	Slider.instances = $.registerPlugin('slider', Slider);

})(jQuery);

