/*!
 * # Fomantic-UI 2.8.8 - Slider
 * http://github.com/fomantic/Fomantic-UI/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

;(function ( $, window, document, undefined ) {

"use strict";

window = (typeof window != 'undefined' && window.Math == Math)
  ? window
  : (typeof self != 'undefined' && self.Math == Math)
    ? self
    : Function('return this')()
;

$.fn.slider = function(parameters) {

  var
    $allModules    = $(this),
    $window        = $(window),

    moduleSelector = $allModules.selector || '',

    time           = new Date().getTime(),
    performance    = [],

    query          = arguments[0],
    methodInvoked  = (typeof query == 'string'),
    queryArguments = [].slice.call(arguments, 1),

    alphabet       = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],

    SINGLE_STEP     = 1,
    BIG_STEP        = 2,
    NO_STEP         = 0,
    SINGLE_BACKSTEP = -1,
    BIG_BACKSTEP    = -2,

    // Used to manage document bound events.
    // Use this so that we can distinguish between which document events are bound to which range.
    currentRange    = 0,

    returnedValue
  ;

  $allModules
    .each(function() {

      var
        settings        = ( $.isPlainObject(parameters) )
          ? $.extend(true, {}, $.fn.slider.settings, parameters)
          : $.extend({}, $.fn.slider.settings),

        className       = settings.className,
        metadata        = settings.metadata,
        namespace       = settings.namespace,
        error           = settings.error,
        keys            = settings.keys,
        interpretLabel  = settings.interpretLabel,

        isHover         = false,
        eventNamespace  = '.' + namespace,
        moduleNamespace = 'module-' + namespace,

        $module         = $(this),
        $currThumb,
        $thumb,
        $secondThumb,
        $track,
        $trackFill,
        $labels,

        element         = this,
        instance        = $module.data(moduleNamespace),

        documentEventID,

        value,
        position,
        secondPos,
        offset,
        precision,
        isTouch,
        gapRatio = 1,
        previousValue,

        initialPosition,
        initialLoad,
        module
      ;

      module = {

        initialize: function() {
          module.debug('Initializing slider', settings);
          initialLoad = true;

          currentRange += 1;
          documentEventID = currentRange;

          isTouch = module.setup.testOutTouch();
          module.setup.layout();
          module.setup.labels();

          if(!module.is.disabled()) {
            module.bind.events();
          }

          module.read.metadata();
          module.read.settings();

          initialLoad = false;
          module.instantiate();
        },

        instantiate: function() {
          module.verbose('Storing instance of slider', module);
          instance = module;
          $module
            .data(moduleNamespace, module)
          ;
        },

        destroy: function() {
          module.verbose('Destroying previous slider for', $module);
          clearInterval(instance.interval);
          module.unbind.events();
          module.unbind.slidingEvents();
          $module.removeData(moduleNamespace);
          instance = undefined;
        },

        setup: {
          layout: function() {
            if( $module.attr('tabindex') === undefined) {
              $module.attr('tabindex', 0);
            }
            if($module.find('.inner').length == 0) {
              $module.append("<div class='inner'>"
                             + "<div class='track'></div>"
                             + "<div class='track-fill'></div>"
                             + "<div class='thumb'></div>"
                             + "</div>");
            }
            precision = module.get.precision();
            $thumb = $module.find('.thumb:not(.second)');
            $currThumb = $thumb;
            if(module.is.range()) {
              if($module.find('.thumb.second').length == 0) {
                $module.find('.inner').append("<div class='thumb second'></div>");
              }
              $secondThumb = $module.find('.thumb.second');
            }
            $track = $module.find('.track');
            $trackFill = $module.find('.track-fill');
            offset = $thumb.width() / 2;
          },
          labels: function() {
            if(module.is.labeled()) {
              $labels = $module.find('.labels:not(.auto)');
              if($labels.length != 0) {
                module.setup.customLabel();
              } else {
                module.setup.autoLabel();
              }

              if (settings.showLabelTicks) {
                $module.addClass(className.ticked)
              }
            }
          },
          testOutTouch: function() {
            try {
             document.createEvent('TouchEvent');
             return true;
            } catch (e) {
             return false;
            }
          },
          customLabel: function() {
            var
              $children   = $labels.find('.label'),
              numChildren = $children.length,
              min         = module.get.min(),
              max         = module.get.max(),
              ratio
            ;
            $children.each(function(index) {
              var
                $child = $(this),
                attrValue = $child.attr('data-value')
              ;
              if(attrValue) {
                attrValue = attrValue > max ? max : attrValue < min ? min : attrValue;
                ratio = (attrValue - min) / (max - min);
              } else {
                ratio = (index + 1) / (numChildren + 1);
              }
              module.update.labelPosition(ratio, $(this));
            });
          },
          autoLabel: function() {
            $labels = $module.find('.labels');
            if($labels.length != 0) {
              $labels.empty();
            }
            else {
              $labels = $module.append('<ul class="auto labels"></ul>').find('.labels');
            }
            for(var i = 0, len = module.get.numLabels(); i <= len; i++) {
              var
                labelText = module.get.label(i),
                $label = (labelText !== "")
                  ? !(i % module.get.gapRatio())
                    ? $('<li class="label">' + labelText + '</li>')
                    : $('<li class="halftick label"></li>')
                  : null,
                ratio  = i / len
              ;
              if($label) {
                module.update.labelPosition(ratio, $label);
                $labels.append($label);
              }
            }
          }
        },

        bind: {
          events: function() {
            module.bind.globalKeyboardEvents();
            module.bind.keyboardEvents();
            module.bind.mouseEvents();
            if(module.is.touch()) {
              module.bind.touchEvents();
            }
            if (settings.autoAdjustLabels) {
              module.bind.windowEvents();
            }
          },
          keyboardEvents: function() {
            module.verbose('Binding keyboard events');
            $module.on('keydown' + eventNamespace, module.event.keydown);
          },
          globalKeyboardEvents: function() {
            $(document).on('keydown' + eventNamespace + documentEventID, module.event.activateFocus);
          },
          mouseEvents: function() {
            module.verbose('Binding mouse events');
            $module.find('.track, .thumb, .inner').on('mousedown' + eventNamespace, function(event) {
              event.stopImmediatePropagation();
              event.preventDefault();
              module.event.down(event);
            });
            $module.on('mousedown' + eventNamespace, module.event.down);
            $module.on('mouseenter' + eventNamespace, function(event) {
              isHover = true;
            });
            $module.on('mouseleave' + eventNamespace, function(event) {
              isHover = false;
            });
          },
          touchEvents: function() {
            module.verbose('Binding touch events');
            $module.find('.track, .thumb, .inner').on('touchstart' + eventNamespace, function(event) {
              event.stopImmediatePropagation();
              event.preventDefault();
              module.event.down(event);
            });
            $module.on('touchstart' + eventNamespace, module.event.down);
          },
          slidingEvents: function() {
            // these don't need the identifier because we only ever want one of them to be registered with document
            module.verbose('Binding page wide events while handle is being draged');
            if(module.is.touch()) {
              $(document).on('touchmove' + eventNamespace, module.event.move);
              $(document).on('touchend' + eventNamespace, module.event.up);
            }
            else {
              $(document).on('mousemove' + eventNamespace, module.event.move);
              $(document).on('mouseup' + eventNamespace, module.event.up);
            }
          },
          windowEvents: function() {
            $window.on('resize' + eventNamespace, module.event.resize);
          }
        },

        unbind: {
          events: function() {
            $module.find('.track, .thumb, .inner').off('mousedown' + eventNamespace);
            $module.find('.track, .thumb, .inner').off('touchstart' + eventNamespace);
            $module.off('mousedown' + eventNamespace);
            $module.off('mouseenter' + eventNamespace);
            $module.off('mouseleave' + eventNamespace);
            $module.off('touchstart' + eventNamespace);
            $module.off('keydown' + eventNamespace);
            $module.off('focusout' + eventNamespace);
            $(document).off('keydown' + eventNamespace + documentEventID, module.event.activateFocus);
            $window.off('resize' + eventNamespace);
          },
          slidingEvents: function() {
            if(module.is.touch()) {
              $(document).off('touchmove' + eventNamespace);
              $(document).off('touchend' + eventNamespace);
            } else {
              $(document).off('mousemove' + eventNamespace);
              $(document).off('mouseup' + eventNamespace);
            }
          },
        },

        event: {
          down: function(event) {
            event.preventDefault();
            if(module.is.range()) {
              var
                eventPos = module.determine.eventPos(event),
                newPos = module.determine.pos(eventPos)
              ;
              // Special handling if range mode and both thumbs have the same value
              if(settings.preventCrossover && module.is.range() && module.thumbVal === module.secondThumbVal) {
                initialPosition = newPos;
                $currThumb = undefined;
              } else {
                $currThumb = module.determine.closestThumb(newPos);
              }
              if (previousValue === undefined) {
                previousValue = module.get.currentThumbValue();
              }
            } else if (previousValue === undefined) {
                previousValue = module.get.value();
            }

            if(!module.is.disabled()) {
              module.bind.slidingEvents();
            }
          },
          move: function(event) {
            event.preventDefault();
            var value = module.determine.valueFromEvent(event);
            if($currThumb === undefined) {
              var
                eventPos = module.determine.eventPos(event),
                newPos = module.determine.pos(eventPos)
              ;
              $currThumb = initialPosition > newPos ? $thumb : $secondThumb;
            }
            if(module.get.step() == 0 || module.is.smooth()) {
              var
                thumbVal = module.thumbVal,
                secondThumbVal = module.secondThumbVal,
                thumbSmoothVal = module.determine.smoothValueFromEvent(event)
              ;
              if(!$currThumb.hasClass('second')) {
                if(settings.preventCrossover && module.is.range()) {
                  value = Math.min(secondThumbVal, value);
                  thumbSmoothVal = Math.min(secondThumbVal, thumbSmoothVal);
                }
                thumbVal = value;
              } else {
                if(settings.preventCrossover && module.is.range()) {
                  value = Math.max(thumbVal, value);
                  thumbSmoothVal = Math.max(thumbVal, thumbSmoothVal);
                }
                secondThumbVal = value;
              }
              value = Math.abs(thumbVal - (secondThumbVal || 0));
              module.update.position(thumbSmoothVal);
              settings.onMove.call(element, value, thumbVal, secondThumbVal);
            } else {
              module.update.value(value, function(value, thumbVal, secondThumbVal) {
                settings.onMove.call(element, value, thumbVal, secondThumbVal);
              });
            }
          },
          up: function(event) {
            event.preventDefault();
            var value = module.determine.valueFromEvent(event);
            module.set.value(value);
            module.unbind.slidingEvents();
            if (previousValue !== undefined) {
              previousValue = undefined;
            }
          },
          keydown: function(event, first) {
            if(settings.preventCrossover && module.is.range() && module.thumbVal === module.secondThumbVal) {
              $currThumb = undefined;
            }
            if(module.is.focused()) {
              $(document).trigger(event);
            }
            if(first || module.is.focused()) {
              var step = module.determine.keyMovement(event);
              if(step != NO_STEP) {
                event.preventDefault();
                switch(step) {
                  case SINGLE_STEP:
                    module.takeStep();
                    break;
                  case BIG_STEP:
                    module.takeStep(module.get.multiplier());
                    break;
                  case SINGLE_BACKSTEP:
                    module.backStep();
                    break;
                  case BIG_BACKSTEP:
                    module.backStep(module.get.multiplier());
                    break;
                }
              }
            }
          },
          activateFocus: function(event) {
            if(!module.is.focused() && module.is.hover() && module.determine.keyMovement(event) != NO_STEP) {
              event.preventDefault();
              module.event.keydown(event, true);
              $module.focus();
            }
          },
          resize: function(_event) {
            // To avoid a useless performance cost, we only call the label refresh when its necessary
            if (gapRatio != module.get.gapRatio()) {
              module.setup.labels();
              gapRatio = module.get.gapRatio();
            }
          }
        },

        resync: function() {
          module.verbose('Resyncing thumb position based on value');
          if(module.is.range()) {
            module.update.position(module.secondThumbVal, $secondThumb);
          }
          module.update.position(module.thumbVal, $thumb);
          module.setup.labels();
        },
        takeStep: function(multiplier) {
          var
            multiplier = multiplier != undefined ? multiplier : 1,
            step = module.get.step(),
            currValue = module.get.currentThumbValue()
          ;
          module.verbose('Taking a step');
          if(step > 0) {
            module.set.value(currValue + step * multiplier);
          } else if (step == 0){
            var
              precision = module.get.precision(),
              newValue = currValue + (multiplier/precision)
            ;
            module.set.value(Math.round(newValue * precision) / precision);
          }
        },

        backStep: function(multiplier) {
          var
            multiplier = multiplier != undefined ? multiplier : 1,
            step = module.get.step(),
            currValue = module.get.currentThumbValue()
          ;
          module.verbose('Going back a step');
          if(step > 0) {
            module.set.value(currValue - step * multiplier);
          } else if (step == 0) {
            var
              precision = module.get.precision(),
              newValue = currValue - (multiplier/precision)
            ;
            module.set.value(Math.round(newValue * precision) / precision);
          }
        },

        is: {
          range: function() {
            return $module.hasClass(settings.className.range);
          },
          hover: function() {
            return isHover;
          },
          focused: function() {
            return $module.is(':focus');
          },
          disabled: function() {
            return $module.hasClass(settings.className.disabled);
          },
          labeled: function() {
            return $module.hasClass(settings.className.labeled);
          },
          reversed: function() {
            return $module.hasClass(settings.className.reversed);
          },
          vertical: function() {
            return $module.hasClass(settings.className.vertical);
          },
          smooth: function() {
            return settings.smooth || $module.hasClass(settings.className.smooth);
          },
          touch: function() {
            return isTouch;
          }
        },

        get: {
          trackOffset: function() {
            if (module.is.vertical()) {
              return $track.offset().top;
            } else {
              return $track.offset().left;
            }
          },
          trackLength: function() {
            if (module.is.vertical()) {
              return $track.height();
            } else {
              return $track.width();
            }
          },
          trackLeft: function() {
            if (module.is.vertical()) {
              return $track.position().top;
            } else {
              return $track.position().left;
            }
          },
          trackStartPos: function() {
            return module.is.reversed() ? module.get.trackLeft() + module.get.trackLength() : module.get.trackLeft();
          },
          trackEndPos: function() {
            return module.is.reversed() ? module.get.trackLeft() : module.get.trackLeft() + module.get.trackLength();
          },
          trackStartMargin: function () {
            var margin;
            if (module.is.vertical()) {
              margin = module.is.reversed() ? $module.css('padding-bottom') : $module.css('padding-top');
            } else {
              margin = module.is.reversed() ? $module.css('padding-right') : $module.css('padding-left');
            }
            return margin || '0px';
          },
          trackEndMargin: function () {
            var margin;
            if (module.is.vertical()) {
              margin = module.is.reversed() ? $module.css('padding-top') : $module.css('padding-bottom');
            } else {
              margin = module.is.reversed() ? $module.css('padding-left') : $module.css('padding-right');
            }
            return margin || '0px';
          },
          precision: function() {
            var
              decimalPlaces,
              step = module.get.step()
            ;
            if(step != 0) {
              var split = String(step).split('.');
              if(split.length == 2) {
                decimalPlaces = split[1].length;
              } else {
                decimalPlaces = 0;
              }
            } else {
              decimalPlaces = settings.decimalPlaces;
            }
            var precision = Math.pow(10, decimalPlaces);
            module.debug('Precision determined', precision);
            return precision;
          },
          min: function() {
            return settings.min;
          },
          max: function() {
            var step = module.get.step(),
                min = module.get.min(),
                quotient = step === 0 ? 0 : Math.floor((settings.max - min) / step),
                remainder = step === 0 ? 0 : (settings.max - min) % step;
            return remainder === 0 ? settings.max : min + quotient * step;
          },
          step: function() {
            return settings.step;
          },
          numLabels: function() {
            var value = Math.round((module.get.max() - module.get.min()) / (module.get.step() === 0 ? 1 : module.get.step()));
            module.debug('Determined that there should be ' + value + ' labels');
            return value;
          },
          labelType: function() {
            return settings.labelType;
          },
          label: function(value) {
            if(interpretLabel) {
              return interpretLabel(value);
            }

            switch (settings.labelType) {
              case settings.labelTypes.number:
                return Math.round(((value * (module.get.step() === 0 ? 1 : module.get.step())) + module.get.min()) * precision ) / precision;
              case settings.labelTypes.letter:
                return alphabet[(value) % 26];
              default:
                return value;
            }
          },
          value: function() {
            return value;
          },
          currentThumbValue: function() {
            return $currThumb !== undefined && $currThumb.hasClass('second') ? module.secondThumbVal : module.thumbVal;
          },
          thumbValue: function(which) {
            switch(which) {
              case 'second':
                if(module.is.range()) {
                  return module.secondThumbVal;
                }
                else {
                  module.error(error.notrange);
                  break;
                }
              case 'first':
              default:
                return module.thumbVal;
            }
          },
          multiplier: function() {
            return settings.pageMultiplier;
          },
          thumbPosition: function(which) {
            switch(which) {
              case 'second':
                if(module.is.range()) {
                  return secondPos;
                }
                else {
                  module.error(error.notrange);
                  break;
                }
              case 'first':
              default:
                return position;
            }
          },
          gapRatio: function() {
            var gapRatio = 1;
            
            if( settings.autoAdjustLabels ) {
              var 
                numLabels = module.get.numLabels(),
                trackLength = module.get.trackLength(),
                gapCounter = 1
              ;

              // While the distance between two labels is too short,
              // we divide the number of labels at each iteration
              // and apply only if the modulo of the operation is an odd number.
              if(trackLength>0){
                while ((trackLength / numLabels) * gapCounter < settings.labelDistance) {
                  if( !(numLabels % gapCounter) ) {
                    gapRatio = gapCounter;
                  }
                  gapCounter += 1;
                }
              }
            }
            return gapRatio;
          }
        },

        determine: {
          pos: function(pagePos) {
            return module.is.reversed()
              ?
              module.get.trackStartPos() - pagePos + module.get.trackOffset()
              :
              pagePos - module.get.trackOffset() - module.get.trackStartPos()
            ;
          },
          closestThumb: function(eventPos) {
            var
              thumbPos = parseFloat(module.determine.thumbPos($thumb)),
              thumbDelta = Math.abs(eventPos - thumbPos),
              secondThumbPos = parseFloat(module.determine.thumbPos($secondThumb)),
              secondThumbDelta = Math.abs(eventPos - secondThumbPos)
            ;
            if(thumbDelta === secondThumbDelta && module.get.thumbValue() === module.get.min()) {
              return $secondThumb;
            }
            return thumbDelta <= secondThumbDelta ? $thumb : $secondThumb;
          },
          closestThumbPos: function(eventPos) {
            var
              thumbPos = parseFloat(module.determine.thumbPos($thumb)),
              thumbDelta = Math.abs(eventPos - thumbPos),
              secondThumbPos = parseFloat(module.determine.thumbPos($secondThumb)),
              secondThumbDelta = Math.abs(eventPos - secondThumbPos)
            ;
            return thumbDelta <= secondThumbDelta ? thumbPos : secondThumbPos;
          },
          thumbPos: function($element) {
            var pos =
              module.is.vertical()
              ?
              module.is.reversed() ? $element.css('bottom') : $element.css('top')
              :
              module.is.reversed() ? $element.css('right') : $element.css('left')
            ;
            return pos;
          },
          positionFromValue: function(value) {
            var
              min = module.get.min(),
              max = module.get.max(),
              value = value > max ? max : value < min ? min : value,
              trackLength = module.get.trackLength(),
              ratio = (value - min) / (max - min),
              position = Math.round(ratio * trackLength)
            ;
            module.verbose('Determined position: ' + position + ' from value: ' + value);
            return position;
          },
          positionFromRatio: function(ratio) {
            var
              trackLength = module.get.trackLength(),
              step = module.get.step(),
              position = Math.round(ratio * trackLength),
              adjustedPos = (step == 0) ? position : Math.round(position / step) * step
            ;
            return adjustedPos;
          },
          valueFromEvent: function(event) {
            var
              eventPos = module.determine.eventPos(event),
              newPos = module.determine.pos(eventPos),
              value
            ;
            if(eventPos < module.get.trackOffset()) {
              value = module.is.reversed() ? module.get.max() : module.get.min();
            } else if(eventPos > module.get.trackOffset() + module.get.trackLength()) {
              value = module.is.reversed() ? module.get.min() : module.get.max();
            } else {
              value = module.determine.value(newPos);
            }
            return value;
          },
          smoothValueFromEvent: function(event) {
            var
              min = module.get.min(),
              max = module.get.max(),
              trackLength = module.get.trackLength(),
              eventPos = module.determine.eventPos(event),
              newPos = eventPos - module.get.trackOffset(),
              ratio,
              value
            ;
            newPos = newPos < 0 ? 0 : newPos > trackLength ? trackLength : newPos;
            ratio = newPos / trackLength;
            if (module.is.reversed()) {
              ratio = 1 - ratio;
            }
            value = ratio * (max - min) + min;
            return value;
          },
          eventPos: function(event) {
            if(module.is.touch()) {
              var
                touchEvent = event.changedTouches ? event : event.originalEvent,
                touches = touchEvent.changedTouches[0] ? touchEvent.changedTouches : touchEvent.touches,
                touchY = touches[0].pageY,
                touchX = touches[0].pageX
              ;
              return module.is.vertical() ? touchY : touchX;
            }
            var
              clickY = event.pageY || event.originalEvent.pageY,
              clickX = event.pageX || event.originalEvent.pageX
            ;
            return module.is.vertical() ? clickY : clickX;
          },
          value: function(position) {
            var
              startPos = module.is.reversed() ? module.get.trackEndPos() : module.get.trackStartPos(),
              endPos = module.is.reversed() ? module.get.trackStartPos() : module.get.trackEndPos(),
              ratio = (position - startPos) / (endPos - startPos),
              range = module.get.max() - module.get.min(),
              step = module.get.step(),
              value = (ratio * range),
              difference = (step == 0) ? value : Math.round(value / step) * step
            ;
            module.verbose('Determined value based upon position: ' + position + ' as: ' + value);
            if(value != difference) {
              module.verbose('Rounding value to closest step: ' + difference);
            }
            // Use precision to avoid ugly Javascript floating point rounding issues
            // (like 35 * .01 = 0.35000000000000003)
            module.verbose('Cutting off additional decimal places');
            return Math.round((difference + module.get.min()) * precision) / precision;
          },
          keyMovement: function(event) {
            var
              key = event.which,
              downArrow =
                module.is.vertical()
                ?
                module.is.reversed() ? keys.downArrow : keys.upArrow
                :
                keys.downArrow
              ,
              upArrow =
                module.is.vertical()
                ?
                module.is.reversed() ? keys.upArrow : keys.downArrow
                :
                keys.upArrow
              ,
              leftArrow =
                !module.is.vertical()
                ?
                module.is.reversed() ? keys.rightArrow : keys.leftArrow
                :
                keys.leftArrow
              ,
              rightArrow =
                !module.is.vertical()
                ?
                module.is.reversed() ? keys.leftArrow : keys.rightArrow
                :
                keys.rightArrow
            ;
            if(key == downArrow || key == leftArrow) {
              return SINGLE_BACKSTEP;
            } else if(key == upArrow || key == rightArrow) {
              return SINGLE_STEP;
            } else if (key == keys.pageDown) {
              return BIG_BACKSTEP;
            } else if (key == keys.pageUp) {
              return BIG_STEP;
            } else {
              return NO_STEP;
            }
          }
        },

        handleNewValuePosition: function(val) {
          var
            min = module.get.min(),
            max = module.get.max(),
            newPos
          ;
          if (val <= min) {
            val = min;
          } else if (val >= max) {
            val = max;
          }
          newPos = module.determine.positionFromValue(val);
          return newPos;
        },

        set: {
          value: function(newValue, fireChange) {
            fireChange = fireChange !== false;
            var toReset = previousValue === undefined;
            previousValue = previousValue === undefined ? module.get.value() : previousValue;
            module.update.value(newValue, function(value, thumbVal, secondThumbVal) {
              if ((!initialLoad || settings.fireOnInit) && fireChange){
                if (newValue !== previousValue) {
                  settings.onChange.call(element, value, thumbVal, secondThumbVal);
                }
                settings.onMove.call(element, value, thumbVal, secondThumbVal);
              }
              if (toReset) {
                previousValue = undefined;
              }
            });
          },
          rangeValue: function(first, second, fireChange) {
            fireChange = fireChange !== false;
            if(module.is.range()) {
              var
                min = module.get.min(),
                max = module.get.max(),
                toReset = previousValue === undefined
              ;
              previousValue = previousValue === undefined ? module.get.value() : previousValue;
              if (first <= min) {
                first = min;
              } else if(first >= max){
                first = max;
              }
              if (second <= min) {
                second = min;
              } else if(second >= max){
                second = max;
              }
              module.thumbVal = first;
              module.secondThumbVal = second;
              value = Math.abs(module.thumbVal - module.secondThumbVal);
              module.update.position(module.thumbVal, $thumb);
              module.update.position(module.secondThumbVal, $secondThumb);
              if ((!initialLoad || settings.fireOnInit) && fireChange) {
                if (value !== previousValue) {
                  settings.onChange.call(element, value, module.thumbVal, module.secondThumbVal);
                }
                settings.onMove.call(element, value, module.thumbVal, module.secondThumbVal);
              }
              if (toReset) {
                previousValue = undefined;
              }
            } else {
              module.error(error.notrange);
            }
          },
          position: function(position, which) {
            var thumbVal = module.determine.value(position);
            switch (which) {
              case 'second':
                module.secondThumbVal = thumbVal;
                module.update.position(thumbVal, $secondThumb);
                break;
              default:
                module.thumbVal = thumbVal;
                module.update.position(thumbVal, $thumb);
            }
            value = Math.abs(module.thumbVal - (module.secondThumbVal || 0));
            module.set.value(value);
          }
        },

        update: {
          value: function(newValue, callback) {
            var
              min = module.get.min(),
              max = module.get.max()
            ;
            if (newValue <= min) {
              newValue = min;
            } else if(newValue >= max){
              newValue = max;
            }
            if(!module.is.range()) {
              value = newValue;
              module.thumbVal = value;
            } else {
              if($currThumb === undefined) {
                $currThumb = newValue <= module.get.currentThumbValue() ? $thumb : $secondThumb;
              }
              if(!$currThumb.hasClass('second')) {
                if(settings.preventCrossover && module.is.range()) {
                  newValue = Math.min(module.secondThumbVal, newValue);
                }
                module.thumbVal = newValue;
              } else {
                if(settings.preventCrossover && module.is.range()) {
                  newValue = Math.max(module.thumbVal, newValue);
                }
                module.secondThumbVal = newValue;
              }
              value = Math.abs(module.thumbVal - module.secondThumbVal);
            }
            module.update.position(newValue);
            module.debug('Setting slider value to ' + value);
            if(typeof callback === 'function') {
              callback(value, module.thumbVal, module.secondThumbVal);
            }
          },
          position: function(newValue, $element) {
            var
              newPos = module.handleNewValuePosition(newValue),
              $targetThumb = $element != undefined ? $element : $currThumb,
              thumbVal = module.thumbVal || module.get.min(),
              secondThumbVal = module.secondThumbVal || module.get.min()
            ;
            if(module.is.range()) {
              if(!$targetThumb.hasClass('second')) {
                position = newPos;
                thumbVal = newValue;
              } else {
                secondPos = newPos;
                secondThumbVal = newValue;
              }
            } else {
              position = newPos;
              thumbVal = newValue;
            }
            var
              trackPosValue,
              thumbPosValue,
              min = module.get.min(),
              max = module.get.max(),
              thumbPosPercent = 100 * (newValue - min) / (max - min),
              trackStartPosPercent = 100 * (Math.min(thumbVal, secondThumbVal) - min) / (max - min),
              trackEndPosPercent = 100 * (1 - (Math.max(thumbVal, secondThumbVal) - min) / (max - min))
            ;
            if (module.is.vertical()) {
              if (module.is.reversed()) {
                thumbPosValue = {bottom: 'calc(' + thumbPosPercent + '% - ' + offset + 'px)', top: 'auto'};
                trackPosValue = {bottom: trackStartPosPercent + '%', top: trackEndPosPercent + '%'};
              }
              else {
                thumbPosValue = {top: 'calc(' + thumbPosPercent + '% - ' + offset + 'px)', bottom: 'auto'};
                trackPosValue = {top: trackStartPosPercent + '%', bottom: trackEndPosPercent + '%'};
              }
            } else {
              if (module.is.reversed()) {
                thumbPosValue = {right: 'calc(' + thumbPosPercent + '% - ' + offset + 'px)', left: 'auto'};
                trackPosValue = {right: trackStartPosPercent + '%', left: trackEndPosPercent + '%'};
              }
              else {
                thumbPosValue = {left: 'calc(' + thumbPosPercent + '% - ' + offset + 'px)', right: 'auto'};
                trackPosValue = {left: trackStartPosPercent + '%', right: trackEndPosPercent + '%'};
              }
            }
            $targetThumb.css(thumbPosValue);
            $trackFill.css(trackPosValue);
            module.debug('Setting slider position to ' + newPos);
          },
          labelPosition: function (ratio, $label) {
            var
              startMargin = module.get.trackStartMargin(),
              endMargin   = module.get.trackEndMargin(),
              posDir =
                module.is.vertical()
                ?
                module.is.reversed() ? 'bottom' : 'top'
                :
                  module.is.reversed() ? 'right' : 'left',
              startMarginMod = module.is.reversed() && !module.is.vertical() ? ' - ' : ' + '
            ;
            var position = '(100% - ' + startMargin + ' - ' + endMargin + ') * ' + ratio;
            $label.css(posDir, 'calc(' + position + startMarginMod + startMargin + ')');
          }
        },

        goto: {
          max: function() {
            module.set.value(module.get.max());
          },
          min: function() {
            module.set.value(module.get.min());
          },
        },

        read: {
          metadata: function() {
            var
              data = {
                thumbVal        : $module.data(metadata.thumbVal),
                secondThumbVal  : $module.data(metadata.secondThumbVal)
              }
            ;
            if(data.thumbVal) {
              if(module.is.range() && data.secondThumbVal) {
                module.debug('Current value set from metadata', data.thumbVal, data.secondThumbVal);
                module.set.rangeValue(data.thumbVal, data.secondThumbVal);
              } else {
                module.debug('Current value set from metadata', data.thumbVal);
                module.set.value(data.thumbVal);
              }
            }
          },
          settings: function() {
            if(settings.start !== false) {
              if(module.is.range()) {
                module.debug('Start position set from settings', settings.start, settings.end);
                module.set.rangeValue(settings.start, settings.end);
              } else {
                module.debug('Start position set from settings', settings.start);
                module.set.value(settings.start);
              }
            }
          }
        },

        setting: function(name, value) {
          module.debug('Changing setting', name, value);
          if( $.isPlainObject(name) ) {
            $.extend(true, settings, name);
          }
          else if(value !== undefined) {
            if($.isPlainObject(settings[name])) {
              $.extend(true, settings[name], value);
            }
            else {
              settings[name] = value;
            }
          }
          else {
            return settings[name];
          }
        },
        internal: function(name, value) {
          if( $.isPlainObject(name) ) {
            $.extend(true, module, name);
          }
          else if(value !== undefined) {
            module[name] = value;
          }
          else {
            return module[name];
          }
        },
        debug: function() {
          if(!settings.silent && settings.debug) {
            if(settings.performance) {
              module.performance.log(arguments);
            }
            else {
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
              module.debug.apply(console, arguments);
            }
          }
        },
        verbose: function() {
          if(!settings.silent && settings.verbose && settings.debug) {
            if(settings.performance) {
              module.performance.log(arguments);
            }
            else {
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
              module.verbose.apply(console, arguments);
            }
          }
        },
        error: function() {
          if(!settings.silent) {
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
            module.error.apply(console, arguments);
          }
        },

        performance: {
          log: function(message) {
            var
              currentTime,
              executionTime,
              previousTime
            ;
            if(settings.performance) {
              currentTime   = new Date().getTime();
              previousTime  = time || currentTime;
              executionTime = currentTime - previousTime;
              time          = currentTime;
              performance.push({
                'Name'           : message[0],
                'Arguments'      : [].slice.call(message, 1) || '',
                'Element'        : element,
                'Execution Time' : executionTime
              });
            }
            clearTimeout(module.performance.timer);
            module.performance.timer = setTimeout(module.performance.display, 500);
          },
          display: function() {
            var
              title = settings.name + ':',
              totalTime = 0
            ;
            time = false;
            clearTimeout(module.performance.timer);
            $.each(performance, function(index, data) {
              totalTime += data['Execution Time'];
            });
            title += ' ' + totalTime + 'ms';
            if(moduleSelector) {
              title += ' \'' + moduleSelector + '\'';
            }
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
              console.groupCollapsed(title);
              if(console.table) {
                console.table(performance);
              }
              else {
                $.each(performance, function(index, data) {
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
                });
              }
              console.groupEnd();
            }
            performance = [];
          }
        },

        invoke: function(query, passedArguments, context) {
          var
            object = instance,
            maxDepth,
            found,
            response
          ;
          passedArguments = passedArguments || queryArguments;
          context         = element         || context;
          if(typeof query == 'string' && object !== undefined) {
            query    = query.split(/[\. ]/);
            maxDepth = query.length - 1;
            $.each(query, function(depth, value) {
              var camelCaseValue = (depth != maxDepth)
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
                : query
              ;
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
                object = object[camelCaseValue];
              }
              else if( object[camelCaseValue] !== undefined ) {
                found = object[camelCaseValue];
                return false;
              }
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
                object = object[value];
              }
              else if( object[value] !== undefined ) {
                found = object[value];
                return false;
              }
              else {
                module.error(error.method, query);
                return false;
              }
            });
          }
          if ( $.isFunction( found ) ) {
            response = found.apply(context, passedArguments);
          }
          else if(found !== undefined) {
            response = found;
          }
          if($.isArray(returnedValue)) {
            returnedValue.push(response);
          }
          else if(returnedValue !== undefined) {
            returnedValue = [returnedValue, response];
          }
          else if(response !== undefined) {
            returnedValue = response;
          }
          return found;
        }
      };

      if(methodInvoked) {
        if(instance === undefined) {
          module.initialize();
        }
        module.invoke(query);
      }
      else {
        if(instance !== undefined) {
          instance.invoke('destroy');
        }
        module.initialize();
      }
    })
  ;

  return (returnedValue !== undefined)
    ? returnedValue
    : this
  ;

};

$.fn.slider.settings = {

  silent       : false,
  debug        : false,
  verbose      : false,
  performance  : true,

  name         : 'Slider',
  namespace    : 'slider',

  error    : {
    method    : 'The method you called is not defined.',
    notrange : 'This slider is not a range slider'
  },

  metadata: {
    thumbVal        : 'thumbVal',
    secondThumbVal  : 'secondThumbVal'
  },

  min              : 0,
  max              : 20,
  step             : 1,
  start            : 0,
  end              : 20,
  labelType        : 'number',
  showLabelTicks   : false,
  smooth           : false,
  autoAdjustLabels : true,
  labelDistance    : 100,
  preventCrossover : true,
  fireOnInit       : false,
  interpretLabel   : false,

  //the decimal place to round to if step is undefined
  decimalPlaces  : 2,

  // page up/down multiplier. How many more times the steps to take on page up/down press
  pageMultiplier : 2,

  selector: {

  },

  className     : {
    reversed : 'reversed',
    disabled : 'disabled',
    labeled  : 'labeled',
    ticked   : 'ticked',
    vertical : 'vertical',
    range    : 'range',
    smooth   : 'smooth'
  },

  keys : {
    pageUp     : 33,
    pageDown   : 34,
    leftArrow  : 37,
    upArrow    : 38,
    rightArrow : 39,
    downArrow  : 40
  },

  labelTypes    : {
    number  : 'number',
    letter  : 'letter'
  },

  onChange : function(value, thumbVal, secondThumbVal){},
  onMove   : function(value, thumbVal, secondThumbVal){},
};


})( jQuery, window, document );
