/*jshint multistr:true, curly: false */
/*global jQuery:false, define: false */
/**
 * jRange - Awesome range control
 *
 * Written by
 * ----------
 * Nitin Hayaran (nitinhayaran@gmail.com)
 *
 * Licensed under the MIT (MIT-LICENSE.txt).
 *
 * @author Nitin Hayaran
 * @version 0.1-RELEASE
 *
 * Dependencies
 * ------------
 * jQuery (http://jquery.com)
 *
 **/
;
(function ($, window, document, undefined) {
	'use strict';

	var jRange = function () {
		return this.init.apply(this, arguments);
	};
	jRange.prototype = {
		defaults: {
			onstatechange: function () { },
			ondragend: function () { },
			onbarclicked: function () { },
			isRange: false,
			showLabels: true,
			showScale: true,
			step: 1,
			format: '%s',
			theme: 'theme-green',
			width: 300,
			disable: false,
			snap: false
		},
		template: '<div class="slider-container">\
			<div class="back-bar">\
                <div class="selected-bar"></div>\
                <div class="pointer low"></div><div class="pointer-label low">123456</div>\
                <div class="pointer high"></div><div class="pointer-label high">456789</div>\
                <div class="clickable-dummy"></div>\
            </div>\
            <div class="scale"></div>\
		</div>',
		init: function (node, options) {
			this.options = $.extend({}, this.defaults, options);
			this.inputNode = $(node);
			this.options.value = this.inputNode.val() || (this.options.isRange ? this.options.from + ',' + this.options.from : '' + this.options.from);
			this.domNode = $(this.template);
			this.domNode.addClass(this.options.theme);
			this.inputNode.after(this.domNode);
			this.domNode.on('change', this.onChange);
			this.pointers = $('.pointer', this.domNode);
			this.lowPointer = this.pointers.first();
			this.highPointer = this.pointers.last();
			this.labels = $('.pointer-label', this.domNode);
			this.lowLabel = this.labels.first();
			this.highLabel = this.labels.last();
			this.scale = $('.scale', this.domNode);
			this.bar = $('.selected-bar', this.domNode);
			this.clickableBar = this.domNode.find('.clickable-dummy');
			this.interval = this.options.to - this.options.from;
			this.render();
		},
		render: function () {
			// Check if inputNode is visible, and have some width, so that we can set slider width accordingly.
			if (this.inputNode.width() === 0 && !this.options.width) {
				console.log('jRange : no width found, returning');
				return;
			} else {
				this.options.width = this.options.width || this.inputNode.width();
				this.domNode.width(this.options.width);
				this.inputNode.hide();
			}

			if (this.isSingle()) {
				this.lowPointer.hide();
				this.lowLabel.hide();
			}
			if (!this.options.showLabels) {
				this.labels.hide();
			}
			this.attachEvents();
			if (this.options.showScale) {
				this.renderScale();
			}
			this.setValue(this.options.value);
		},
		isSingle: function () {
			if (typeof (this.options.value) === 'number') {
				return true;
			}
			return (this.options.value.indexOf(',') !== -1 || this.options.isRange) ?
				false : true;
		},
		attachEvents: function () {
			this.clickableBar.click($.proxy(this.barClicked, this));
			this.pointers.on('mousedown touchstart', $.proxy(this.onDragStart, this));
			this.pointers.bind('dragstart', function (event) {
				event.preventDefault();
			});
		},
		onDragStart: function (e) {
			if (this.options.disable || (e.type === 'mousedown' && e.which !== 1)) {
				return;
			}
			e.stopPropagation();
			e.preventDefault();
			var pointer = $(e.target);
			this.pointers.removeClass('last-active');
			pointer.addClass('focused last-active');
			this[(pointer.hasClass('low') ? 'low' : 'high') + 'Label'].addClass('focused');
			$(document).on('mousemove.slider touchmove.slider', $.proxy(this.onDrag, this, pointer));
			$(document).on('mouseup.slider touchend.slider touchcancel.slider', $.proxy(this.onDragEnd, this));
		},
		onDrag: function (pointer, e) {
			e.stopPropagation();
			e.preventDefault();

			if (e.originalEvent.touches && e.originalEvent.touches.length) {
				e = e.originalEvent.touches[0];
			} else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
				e = e.originalEvent.changedTouches[0];
			}

			var zoomFactor = (window.sqAppZoom != undefined) ? window.sqAppZoom : 1;
			var position = (e.clientX / zoomFactor) - this.domNode.offset().left;
			this.domNode.trigger('change', [this, pointer, position]);
		},
		onDragEnd: function (e) {
			this.pointers.removeClass('focused')
				.trigger('rangeslideend');
			this.labels.removeClass('focused');
			$(document).off('.slider');
			this.options.ondragend.call(this, this.options.value);
		},
		barClicked: function (e) {
			if (this.options.disable) return;
			var x = e.pageX - this.clickableBar.offset().left;
			if (this.isSingle())
				this.setPosition(this.pointers.last(), x, true, true);
			else {
				var firstLeft = Math.abs(parseFloat(this.pointers.first().css('left'), 10)),
					firstHalfWidth = this.pointers.first().width() / 2,
					lastLeft = Math.abs(parseFloat(this.pointers.last().css('left'), 10)),
					lastHalfWidth = this.pointers.first().width() / 2,
					leftSide = Math.abs(firstLeft - x + firstHalfWidth),
					rightSide = Math.abs(lastLeft - x + lastHalfWidth),
					pointer;

				if (leftSide == rightSide) {
					pointer = x < firstLeft ? this.pointers.first() : this.pointers.last();
				} else {
					pointer = leftSide < rightSide ? this.pointers.first() : this.pointers.last();
				}
				this.setPosition(pointer, x, true, true);
			}
			this.options.onbarclicked.call(this, this.options.value);
		},
		onChange: function (e, self, pointer, position) {
			var min, max;
			min = 0;
			max = self.domNode.width();

			if (!self.isSingle()) {
				min = pointer.hasClass('high') ? parseFloat(self.lowPointer.css("left")) + (self.lowPointer.width() / 2) : 0;
				max = pointer.hasClass('low') ? parseFloat(self.highPointer.css("left")) + (self.highPointer.width() / 2) : self.domNode.width();
			}

			var value = Math.min(Math.max(position, min), max);
			self.setPosition(pointer, value, true);
		},
		setPosition: function (pointer, position, isPx, animate) {
			var leftPos, rightPos,
				lowPos = parseFloat(this.lowPointer.css("left")),
				highPos = parseFloat(this.highPointer.css("left")) || 0,
				circleWidth = this.highPointer.width() / 2;
			if (!isPx) {
				position = this.prcToPx(position);
			}
			if (this.options.snap) {
				var expPos = this.correctPositionForSnap(position);
				if (expPos === -1) {
					return;
				} else {
					position = expPos;
				}
			}
			if (pointer[0] === this.highPointer[0]) {
				highPos = Math.round(position - circleWidth);
			} else {
				lowPos = Math.round(position - circleWidth);
			}
			pointer[animate ? 'animate' : 'css']({
				'left': Math.round(position - circleWidth)
			});
			if (this.isSingle()) {
				leftPos = 0;
			} else {
				leftPos = lowPos + circleWidth;
				rightPos = highPos + circleWidth;
			}
			var w = Math.round(highPos + circleWidth - leftPos);
			this.bar[animate ? 'animate' : 'css']({
				'width': Math.abs(w),
				'left': (w > 0) ? leftPos : leftPos + w
			});
			this.showPointerValue(pointer, position, animate);
			this.isReadonly();
		},
		correctPositionForSnap: function (position) {
			var currentValue = this.positionToValue(position) - this.options.from;
			var diff = this.options.width / (this.interval / this.options.step),
				expectedPosition = (currentValue / this.options.step) * diff;
			if (position <= expectedPosition + diff / 2 && position >= expectedPosition - diff / 2) {
				return expectedPosition;
			} else {
				return -1;
			}
		},
		// will be called from outside
		setValue: function (value) {
			var values = value.toString().split(',');
			values[0] = Math.min(Math.max(values[0], this.options.from), this.options.to) + '';
			if (values.length > 1) {
				values[1] = Math.min(Math.max(values[1], this.options.from), this.options.to) + '';
			}
			this.options.value = value;
			var prc = this.valuesToPrc(values.length === 2 ? values : [0, values[0]]);
			if (this.isSingle()) {
				this.setPosition(this.highPointer, prc[1]);
			} else {
				this.setPosition(this.lowPointer, prc[0]);
				this.setPosition(this.highPointer, prc[1]);
			}
		},
		renderScale: function () {
			var s = this.options.scale || [this.options.from, this.options.to];
			var prc = Math.round((100 / (s.length - 1)) * 10) / 10;
			var str = '';
			for (var i = 0; i < s.length; i++) {
				str += '<span style="left: ' + i * prc + '%">' + (s[i] != '|' ? '<ins>' + s[i] + '</ins>' : '') + '</span>';
			}
			this.scale.html(str);

			$('ins', this.scale).each(function () {
				$(this).css({
					marginLeft: -$(this).outerWidth() / 2
				});
			});
		},
		getBarWidth: function () {
			var values = this.options.value.split(',');
			if (values.length > 1) {
				return parseFloat(values[1]) - parseFloat(values[0]);
			} else {
				return parseFloat(values[0]);
			}
		},
		showPointerValue: function (pointer, position, animate) {
			var label = $('.pointer-label', this.domNode)[pointer.hasClass('low') ? 'first' : 'last']();
			var text;
			var value = this.positionToValue(position);
			// Is it higer or lower than it should be?

			if ($.isFunction(this.options.format)) {
				var type = this.isSingle() ? undefined : (pointer.hasClass('low') ? 'low' : 'high');
				text = this.options.format(value, type);
			} else {
				text = this.options.format.replace('%s', value);
			}

			var width = label.html(text).width(),
				left = position - width / 2;
			left = Math.min(Math.max(left, 0), this.options.width - width);
			label[animate ? 'animate' : 'css']({
				left: left
			});
			this.setInputValue(pointer, value);
		},
		valuesToPrc: function (values) {
			var lowPrc = ((parseFloat(values[0]) - parseFloat(this.options.from)) * 100 / this.interval),
				highPrc = ((parseFloat(values[1]) - parseFloat(this.options.from)) * 100 / this.interval);
			return [lowPrc, highPrc];
		},
		prcToPx: function (prc) {
			return (this.domNode.width() * prc) / 100;
		},
		isDecimal: function () {
			return ((this.options.value + this.options.from + this.options.to).indexOf(".") === -1) ? false : true;
		},
		positionToValue: function (pos) {
			var value = (pos / this.domNode.width()) * this.interval;
			value = parseFloat(value, 10) + parseFloat(this.options.from, 10);
			if (this.isDecimal()) {
				var final = Math.round(Math.round(value / this.options.step) * this.options.step * 100) / 100;
				if (final !== 0.0) {
					final = '' + final;
					if (final.indexOf(".") === -1) {
						final = final + ".";
					}
					while (final.length - final.indexOf('.') < 3) {
						final = final + "0";
					}
				} else {
					final = "0.00";
				}
				return final;
			} else {
				return Math.round(value / this.options.step) * this.options.step;
			}
		},
		setInputValue: function (pointer, v) {
			// if(!isChanged) return;
			if (this.isSingle()) {
				this.options.value = v.toString();
			} else {
				var values = this.options.value.split(',');
				if (pointer.hasClass('low')) {
					this.options.value = v + ',' + values[1];
				} else {
					this.options.value = values[0] + ',' + v;
				}
			}
			if (this.inputNode.val() !== this.options.value) {
				this.inputNode.val(this.options.value)
					.trigger('change');
				this.options.onstatechange.call(this, this.options.value);
			}
		},
		getValue: function () {
			return this.options.value;
		},
		getOptions: function () {
			return this.options;
		},
		getRange: function () {
			return this.options.from + "," + this.options.to;
		},
		isReadonly: function () {
			this.domNode.toggleClass('slider-readonly', this.options.disable);
		},
		disable: function () {
			this.options.disable = true;
			this.isReadonly();
		},
		enable: function () {
			this.options.disable = false;
			this.isReadonly();
		},
		toggleDisable: function () {
			this.options.disable = !this.options.disable;
			this.isReadonly();
		},
		updateRange: function (range, value) {
			var values = range.toString().split(',');
			this.interval = parseInt(values[1]) - parseInt(values[0]);
			if (value) {
				this.setValue(value);
			} else {
				this.setValue(this.getValue());
			}
		}
	};

	var pluginName = 'jRange';
	// A really lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[pluginName] = function (option) {
		var args = arguments,
			result;

		this.each(function () {
			var $this = $(this),
				data = $.data(this, 'plugin_' + pluginName),
				options = typeof option === 'object' && option;
			if (!data) {
				$this.data('plugin_' + pluginName, (data = new jRange(this, options)));
				$(window).resize(function () {
					data.setValue(data.getValue());
				}); // Update slider position when window is resized to keep it in sync with scale
			}
			// if first argument is a string, call silimarly named function
			// this gives flexibility to call functions of the plugin e.g.
			//   - $('.dial').plugin('destroy');
			//   - $('.dial').plugin('render', $('.new-child'));
			if (typeof option === 'string') {
				result = data[option].apply(data, Array.prototype.slice.call(args, 1));
			}
		});

		// To enable plugin returns values
		return result || this;
	};

})(jQuery, window, document);
