﻿/*
 * Author : Rebrov Boris, ziper at medde.ru
 */
(function (window, $) {
	function sliderControl(options, jContainer) {
		var oSliderControl = this, aMode = ['from'], flSelectors, keyTimeout, oOldSliderControl;
		this.optValues = $.extend(true, {}, this.defOptions, options);
		if (!jContainer || !jContainer.length) {
			jContainer = $(this.optValues.cssSelectors.Line).parent();
		}
		this.jContainer = jContainer;
		if ((oOldSliderControl = this.jContainer.data('sliderCObj')) instanceof sliderControl) {
			return oOldSliderControl;
		}
		this.jContainer.data('sliderCObj', this);
		this.jLine = jContainer.find(this.optValues.cssSelectors.Line);
		if (!this.jLine.length) {
			throw new Error('Неверный селектор "Line"');
		}
		if (this.optValues.type == 'range') {
			aMode.push('to');
		}
		this.jResult = this.jContainer.find(this.optValues.cssSelectors.Result);
		flSelectors = this.optValues.cssSelectors.Fields;
		this.Sliders = {};
		this.Fields = {};
		this.supportTouches = ('ontouchstart' in window);
		this.oEventNames = {
			"click": this.supportTouches ? "touchstart" : "click",
			"mousedown": this.supportTouches ? "touchstart" : "mousedown",
			"mousemove": this.supportTouches ? "touchmove" : "mousemove",
			"mouseup"  : this.supportTouches ? "touchend" : "mouseup"
		};
		this.jLine.bind(this.oEventNames['click'] ,function (e) {
			oSliderControl.sliderClick(e.pageX);
		});
		for (var elNo = 0; elNo < aMode.length; elNo++) {
			this.Sliders[aMode[elNo]] = jContainer.find(this.optValues.cssSelectors[aMode[elNo] + 'Slider']);
			if (!this.Sliders[aMode[elNo]].length) {
				throw new Error('Неверный селектор "' + aMode[elNo] + 'Slider"');
			}
			if (flSelectors && flSelectors[aMode[elNo]].length) {
				this.Fields[aMode[elNo]] = $(flSelectors[aMode[elNo]]);
			}
			if (!this.Fields[aMode[elNo]] || !this.Fields[aMode[elNo]].length) {
				this.Fields[aMode[elNo]] = $('<input />').attr({type: 'hidden', name: this.optValues.fieldName}).appendTo(this.jLine);
			}
			(function (Slider, oEvents) {
				var jField = oSliderControl.Fields[Slider];
				if (jField.attr('type') != 'hidden') {
					jField.bind('keyup.sliderControl', function () {
						if (keyTimeout) {
							clearTimeout(keyTimeout);
							keyTimeout = false;
						}
						keyTimeout = setTimeout(function () {
							oSliderControl.setValues({
								from : oSliderControl.Fields.from.val(),
								to : oSliderControl.Fields.to.val()
							}, true, false);
						}, 500);
					});
				}
				oSliderControl.Sliders[Slider].bind(oEvents['mousedown'] + '.sliderControl', function (e) {
					e.stopPropagation();
					e.preventDefault();
					oSliderControl.dragSlider = Slider;
					oSliderControl.sliderDrag({top: e.pageY, left: e.pageX});
					$('body').bind(oEvents['mousemove'] + '.sliderControl', function (ev) {
						ev.stopPropagation();
						ev.preventDefault();
						if (oSliderControl.drawTimer) {
							return;
						}
						oSliderControl.drawTimer = setTimeout(function () {
							var oCoords;
							if (ev.targetTouches && ev.targetTouches[0]) {
								oCoords = {top : ev.targetTouches[0].pageY, left: ev.targetTouches[0].pageX};
							} else {
								oCoords = {top: ev.pageY, left: ev.pageX };
							}
							oSliderControl.sliderDrag(oCoords);
							clearTimeout(oSliderControl.drawTimer);
							oSliderControl.drawTimer = false;
						}, 1);
					}).bind(oEvents['mouseup'] + '.sliderControl', function () {
						oSliderControl.stopSliderDrag();
					});
				});
			})(aMode[elNo], this.oEventNames);
		}
		this.jRangeLine = jContainer.find(this.optValues.cssSelectors.rangeLine);
		if (!this.jRangeLine.length) {
			throw new Error('Неверный селектор "rangeLine"');
		}
		$(window).resize(function () {
			oSliderControl.onResize();
		});
		if (typeof window.SliderControlHandlers[this.optValues.valType] == 'object') {
			for (var Handler in window.SliderControlHandlers[this.optValues.valType]) {
				if (typeof this.optValues[Handler] !== 'undefined') {
					this.optValues[Handler] = window.SliderControlHandlers[this.optValues.valType][Handler];
				}
			}
		}
		this.getSize();
		this.setDefaults();
	}
	
	sliderControl.prototype = {
		defOptions : {
			fieldName : 'range',
			cssSelectors : {
				Line : 'div.line',
				fromSlider : 'i.l',
				toSlider : 'i.r',
				rangeLine : 'div.my_line',
				Fields : {
					from : '',
					to : ''
				},
				Result : 'span.js_result'
			},
			type : 'range',
			valType : 'Number',
			minVal : 0,
			maxVal : 108,
			stepSize : 12,
			slideByStep : true,
			calculateResult : false,
			drawResult : function () {
				if (this.jResult && this.jResult.length) {
					var result = [];
					for (var valName in this.returnValue) {
						result.push(this.returnValue[valName]);
					}
					this.jResult.html(result.join(' &ndash; '));
				}
			},
			defVal: {},
			drawHandler : false,
			slideHandler : false,
			changeHandler : false
		},
		percentToValue : function (prc) {
			return this.optValues.minVal + prc * (this.optValues.maxVal - this.optValues.minVal);
		},
		valueToPercent : function (val) {
			return val / (this.optValues.maxVal - this.optValues.minVal);
		},
		stopSliderDrag : function () {
			clearTimeout(this.drawTimer);
			this.drawTimer = false;
			delete this.dragSlider;
			delete this.dragPosition;
			$('body').unbind('.sliderControl');
		},
		sliderDrag : function (co) {
			var curPos = 0, way, inc, halfSlider = this.sliderSize / 2;
			if (this.dragSlider && this.dragPosition) {
				curPos = co.left;
				if (curPos > (this.lineRealStart + this.lineRealSize + halfSlider)
					|| curPos < this.lineRealStart - halfSlider
				) {
					return;
				}
				if (!(way = curPos - this.dragPosition.left)) {
					return;
				}
				inc = this.percentToValue(way / this.lineSize);
				this.setValue(this.Values[this.dragSlider] + inc);
			}
			this.dragPosition = co;
		},
		sliderClick : function (co) {
			var halfSlider = this.sliderSize / 2, 
				leftPos = parseInt(this.jRangeLine.css('left'), 10) + halfSlider,
				rightPos = this.lineRealSize - parseInt(this.jRangeLine.css('right'), 10) - halfSlider,
				clickPos = co - this.lineRealStart,
				isMin = clickPos < leftPos || (clickPos < rightPos && (rightPos - clickPos > clickPos - leftPos));
			this.setValue(this.percentToValue(clickPos / this.lineRealSize), isMin ? 'from' : 'to');
		},
		setValue : function (val, valType, checkVals, roundVals) {
			valType = valType || this.dragSlider || 'from';
			if (typeof checkVals == 'undefined') {
				checkVals = true;
			}
			if (typeof roundVals == 'undefined') {
				roundVals = true;
			}
			var rVal, isMin = valType == 'from';
			val = val * 1;
			if (isNaN(val)) {
				return;
			}
			rVal = roundVals ? Math.round(val / this.optValues.stepSize) * this.optValues.stepSize : val;
			if (checkVals) {
				if (isMin) {
					if (rVal >= this.rValues.to) {
						rVal = this.rValues.to - this.optValues.stepSize;
					}
					if (rVal < this.optValues.minVal) {
						rVal = this.optValues.minVal;
					}
				} else {
					if (rVal <= this.rValues.from) {
						rVal = this.rValues.from + this.optValues.stepSize;
					}
					if (rVal > this.optValues.maxVal) {
						rVal = this.optValues.maxVal;
					}
				}
			}
			this.Values[valType] = val;
			this.rValues[valType] = rVal;
			if (typeof this.optValues.changeHandler == 'function') {
				this.optValues.changeHandler.call(this);
			}
			this.drawValues(this[this.optValues.slideByStep ? 'rValues' : 'Values']);
		},
		setValues : function (oVals, checkVals, roundVals) {
			for (var valType in oVals) {
				this.setValue(oVals[valType], valType, checkVals, roundVals);
			}
		},
		drawValue : function (val, isMin) {
			var rangeLineCss = {};
			rangeLineCss[isMin ? 'left' : 'right'] = Math.abs((isMin ? 0 : this.lineRealSize) - Math.round(this.lineRealSize * this.valueToPercent(val)));
			this.jRangeLine.css(rangeLineCss);
			if (typeof this.optValues.drawHandler == 'function') {
				this.optValues.drawHandler.call(this, isMin, rangeLineCss[isMin ? 'left' : 'right']);
			}
		},
		drawValues : function (oVals) {
			for (var valType in oVals) {
				this.drawValue(oVals[valType], valType == 'from');
			}
			this.returnValues();
		},
		getSize : function () {
			var halfSlider;
			this.sliderSize = this.Sliders.from.outerWidth(true);
			halfSlider = this.sliderSize / 2;
			this.lineRealStart = this.jLine.offset().left;
			this.lineStart = this.lineRealStart + halfSlider;
			this.lineRealSize = this.jLine.outerWidth();
			this.lineSize = this.lineRealSize - halfSlider;
			this.pxPerUnit = this.lineSize / (this.optValues.maxVal - this.optValues.minVal);
		},
		setDefaults : function () {
			if (!this.Values) {
				this.Values = {};
			}
			if (!this.rValues) {
				this.rValues = {};
			}
			this.setValues({
				from : this.Fields.from.val() || this.optValues.defVal.from || this.optValues.minVal,
				to : this.Fields.to.val() || this.optValues.defVal.to || this.optValues.maxVal
			}, true, false);
			/*$.extend(this.Values, {
				from : this.Fields.from.val() || this.optValues.minVal,
				to : this.Fields.to.val() || this.optValues.maxVal
			});
			$.extend(this.rValues, this.Values);
			$.extend(this.returnValue, this.Values);*/
			this.drawValues(this.rValues);
		},
		returnValues : function () {
			var returnValue = {};
			for (var valType in this.rValues) {
				if (typeof this.optValues.calculateResult == 'function') {
					returnValue[valType] = this.optValues.calculateResult.call(this, this.rValues[valType]);
				} else {
					returnValue[valType] = this.rValues[valType];
				}
				this.Fields[valType].val(returnValue[valType]);
			}
			this.returnValue = returnValue;
			if (typeof this.optValues.drawResult == 'function') {
				this.optValues.drawResult.call(this);
			}
		},
		onResize: function () {
			this.getSize();
			this.setValues(this.rValues);
		}
	};
	
	window.SliderControlHandlers = {};
	
	jQuery.fn.goSliderControl = function (options) {
		new sliderControl(options, $(this));
	};
})(self, jQuery);
