text: /*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 LICENSE.
   *
   * @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-default',
               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 position = e.clientX - 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.options.from = values[0];
               this.options.to = values[1];
               this.interval = parseInt(values[1]) - parseInt(values[0]);
               if(value){
                   this.setValue(value);
               }else{
                   this.setValue(this.getValue());
               }
               if (this.options.showScale) {
                   var len = this.options.scale ? this.options.scale.length : 2;
                   var step = this.interval / (len - 1)
                   var scale = [];
                   for (var i = 0; i < len; i += 1) {
                       var value = i * step + parseInt(this.options.from);
                       scale.push(value);
                   }
                   this.options.scale = scale;
                   this.renderScale();
               }
           },
           updateRender: function(data) {
              return 0 !== this.inputNode.width() || this.options.width ? (this.options.width = this.options.width || this.inputNode.width(), this.domNode.width(this.options.width), this.inputNode.hide(), this.isSingle() && (this.lowPointer.hide(), this.lowLabel.hide()), this.options.showLabels || this.labels.hide(), this.attachEvents(), this.options.showScale && this.updateScale(data), void this.setValue(this.options.value)) : void console.log("jRange : no width found, returning")
           },
           updateScale: function(scale, from, to) {
               for(var t = scale || [from, to], i = Math.round(100 / (t.length - 1) * 10) / 10, s = "", o = 0; o < t.length; o++) s += '<span style="left: ' + o * i + '%">' + ("|" != t[o] ? "<ins>" + t[o] + "</ins>" : "") + "</span>";
                   this.scale.html(s), $("ins", this.scale).each(function() {
                       $(this).css({
                           marginLeft: -$(this).outerWidth() / 2
                       })
                   })
           }
       };
   
       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() {
                      // *** ADDITIONAL CODE TO SET WIDTH ***
                      var width = data.domNode.parent().width();
                      data.options.width = width;
                      data.render();
                      // *** ADDITIONAL CODE ENDS ***
                      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);