(function($) {

$.widget("ui.rangeslider", $.extend({}, $.ui.mouse, {

  _init: function() {

    var self = this, o = this.options;
    this._keySliding = false;
    this._handleIndex = null;
    this._detectOrientation();
    this._mouseInit();

    this.element
      .addClass("ui-slider"
        + " ui-slider-" + this.orientation
        + " ui-widget"
        + " ui-widget-content"
        + " ui-corner-all");

    this.range = $('<div></div>');
    this.range.appendTo(this.element).addClass("ui-slider-range");
    this.range.addClass("ui-widget-header");

    if ($(".ui-slider-handle", this.element).length == 0)
      $('<a href="#"></a>')
        .appendTo(this.element)
        .addClass("ui-slider-handle");

    this.handles = $(".ui-slider-handle", this.element)
      .addClass("ui-state-default"
        + " ui-corner-all");

    this.handle = this.handles.eq(0);

    this.handles.add(this.range).filter("a")
      .click(function(event) { event.preventDefault(); })
      .hover(function() { $(this).addClass('ui-state-hover'); }, function() { $(this).removeClass('ui-state-hover'); })
      .focus(function() { $(".ui-slider .ui-state-focus").removeClass('ui-state-focus'); $(this).addClass('ui-state-focus'); })
      .blur(function() { $(this).removeClass('ui-state-focus'); });

    this.handles.each(function(i) {
      $(this).data("index.ui-slider-handle", i);
    });

    this.handles.keydown(function(event) {

      var ret = true;

      var index = $(this).data("index.ui-slider-handle");

      if (self.options.disabled)
        return;

      switch (event.keyCode) {
        case $.ui.keyCode.HOME:
        case $.ui.keyCode.END:
        case $.ui.keyCode.UP:
        case $.ui.keyCode.RIGHT:
        case $.ui.keyCode.DOWN:
        case $.ui.keyCode.LEFT:
          ret = false;
          if (!self._keySliding) {
            self._keySliding = true;
            $(this).addClass("ui-state-active");
            self._start(event, index);
          }
          break;
      }

      var curVal, newVal, step = self._step();
      if (self.options.values && self.options.values.length) {
        curVal = newVal = self.values(index);
      } else {
        curVal = newVal = self.value();
      }

      switch (event.keyCode) {
        case $.ui.keyCode.HOME:
          newVal = self._valueMin();
          break;
        case $.ui.keyCode.END:
          newVal = self._valueMax();
          break;
        case $.ui.keyCode.UP:
        case $.ui.keyCode.RIGHT:
          if(curVal == self._valueMax()) return;
          newVal = curVal + step;
          break;
        case $.ui.keyCode.DOWN:
        case $.ui.keyCode.LEFT:
          if(curVal == self._valueMin()) return;
          newVal = curVal - step;
          break;
      }

      self._slide(event, index, newVal);

      return ret;

    }).keyup(function(event) {

      var index = $(this).data("index.ui-slider-handle");

      if (self._keySliding) {
        self._stop(event, index);
        self._change(event, index);
        self._keySliding = false;
        $(this).removeClass("ui-state-active");
      }

    });

    this._refreshValue();
    this._refreshRange();

  },

  destroy: function() {

    this.handles.remove();
    this.range.remove();

    this.element
      .removeClass("ui-slider"
        + " ui-slider-horizontal"
        + " ui-slider-vertical"
        + " ui-slider-disabled"
        + " ui-widget"
        + " ui-widget-content"
        + " ui-corner-all")
      .removeData("slider")
      .unbind(".slider");

    this._mouseDestroy();

  },

  _mouseCapture: function(event) {

    var o = this.options;

    if (o.disabled)
      return false;

    this.elementSize = {
      width: this.element.outerWidth(),
      height: this.element.outerHeight()
    };
    this.elementOffset = this.element.offset();

    var position = { x: event.pageX, y: event.pageY };
    var normValue = this._normValueFromMouse(position);

    var distance = this._valueMax() - this._valueMin() + 1, closestHandle;
    var self = this, index;
    this.handles.each(function(i) {
      var thisDistance = Math.abs(normValue - self.values(i));
      if (distance > thisDistance) {
        distance = thisDistance;
        closestHandle = $(this);
        index = i;
      }
    });

    this._start(event, index);

    self._handleIndex = index;

    closestHandle
      .addClass("ui-state-active")
      .focus();

    var offset = closestHandle.offset();
    var mouseOverHandle = !$(event.target).parents().andSelf().is('.ui-slider-handle');
    this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
      left: event.pageX - offset.left - (closestHandle.width() / 2),
      top: event.pageY - offset.top
        - (closestHandle.height() / 2)
        - (parseInt(closestHandle.css('borderTopWidth'),10) || 0)
        - (parseInt(closestHandle.css('borderBottomWidth'),10) || 0)
        + (parseInt(closestHandle.css('marginTop'),10) || 0)
    };

    normValue = this._normValueFromMouse(position);
    this._slide(event, index, normValue);
    return true;

  },

  _mouseStart: function(event) {
    return true;
  },

  _mouseDrag: function(event) {

    var position = { x: event.pageX, y: event.pageY };
    var normValue = this._normValueFromMouse(position);

    this._slide(event, this._handleIndex, normValue);

    return false;

  },

  _mouseStop: function(event) {

    this.handles.removeClass("ui-state-active");
    this._stop(event, this._handleIndex);
    this._change(event, this._handleIndex);
    this._handleIndex = null;
    this._clickOffset = null;

    return false;

  },

  _detectOrientation: function() {
    this.orientation = this.options.orientation == 'vertical' ? 'vertical' : 'horizontal';
  },

  _normValueFromMouse: function(position) {

    var pixelTotal, pixelMouse;
    if ('horizontal' == this.orientation) {
      pixelTotal = this.elementSize.width;
      pixelMouse = position.x - this.elementOffset.left - (this._clickOffset ? this._clickOffset.left : 0);
    } else {
      pixelTotal = this.elementSize.height;
      pixelMouse = position.y - this.elementOffset.top - (this._clickOffset ? this._clickOffset.top : 0);
    }

    var percentMouse = (pixelMouse / pixelTotal);
    if (percentMouse > 1) percentMouse = 1;
    if (percentMouse < 0) percentMouse = 0;
    if ('vertical' == this.orientation)
      percentMouse = 1 - percentMouse;

    var valueTotal = this._valueMax() - this._valueMin(),
      valueMouse = percentMouse * valueTotal,
      valueMouseModStep = valueMouse % this.options.step,
      normValue = this._valueMin() + valueMouse - valueMouseModStep;

    if (valueMouseModStep > (this.options.step / 2))
      normValue += this.options.step;

    if (normValue < this._rangeMin()) normValue = this._rangeMin();
    if (normValue > this._rangeMax()) normValue = this._rangeMax();

    // Since JavaScript has problems with large floats, round
    // the final value to 5 digits after the decimal point (see #4124)
    return parseFloat(normValue.toFixed(5));

  },

  _start: function(event, index) {
    var uiHash = {
      handle: this.handles[index],
      value: this.value()
    };
    if (this.options.values && this.options.values.length) {
      uiHash.value = this.values(index)
      uiHash.values = this.values()
    }
    this._trigger("start", event, uiHash);
  },

  _slide: function(event, index, newVal) {

    var handle = this.handles[index];

    if (newVal != this.value()) {
      // A slide can be canceled by returning false from the slide callback
      var allowed = this._trigger("slide", event, {
        handle: this.handles[index],
        value: newVal
      });
      if (allowed !== false) {
        this._setData('value', newVal, ( event.type == 'mousedown' && this.options.animate ));
      }
    }
  },

  _stop: function(event, index) {
    var uiHash = {
      handle: this.handles[index],
      value: this.value()
    };
    if (this.options.values && this.options.values.length) {
      uiHash.value = this.values(index)
      uiHash.values = this.values()
    }
    this._trigger("stop", event, uiHash);
  },

  _change: function(event, index) {
    var uiHash = {
      handle: this.handles[index],
      value: this.value()
    };
    this._trigger("change", event, uiHash);
  },

  value: function(newValue) {
    if (arguments.length) {
      if (newValue < this._rangeMin()) newValue = this._rangeMin();
      if (newValue > this._rangeMax()) newValue = this._rangeMax();

      this._setData("value", newValue);
      this._change(null, 0);
    }

    return this._value();
  },

  values: function(index, newValue, animated, noPropagation) {

    if (arguments.length > 1) {
      this.options.values[index] = newValue;
      this._refreshValue(animated);
      if(!noPropagation) this._change(null, index);
    }

    if (arguments.length) {
      if (this.options.values && this.options.values.length) {
        return this._values(index);
      } else {
        return this.value();
      }
    } else {
      return this._values();
    }

  },

  _setData: function(key, value, animated) {

    $.widget.prototype._setData.apply(this, arguments);
    var refresh = false;

    switch (key) {
      case 'orientation':
        this._detectOrientation();
        this.element
          .removeClass("ui-slider-horizontal ui-slider-vertical")
          .addClass("ui-slider-" + this.orientation);
        refresh = true;
        break;
      case 'value':
      case 'min':
      case 'max':
      case 'rangemin':
      case 'rangemax':
        if (this.options.rangemin < this.options.min) this.options.rangemin = this.options.min;
        if (this.options.rangemax > this.options.max) this.options.rangemax = this.options.max;
        if (this.options.rangemax < this.options.rangemin) this.options.rangemax = this.options.rangemin;
        if (this.options.rangemin > this.options.value) this.options.value = this.options.rangemin;
        if (this.options.rangemin > this.options.rangemax) this.options.rangemin = this.options.rangemax;
        if (this.options.rangemax < this.options.value) this.options.value = this.options.rangemax;
        this._change(null, 0);
        refresh = true;
        break;
    }

    if (refresh)
    {
      this._refreshValue(animated);
      this._refreshRange();
    }

  },

  _step: function() {
    var step = this.options.step;
    return step;
  },

  _value: function() {

    var val = this.options.value;
    if (val < this._rangeMin()) val = this._rangeMin();
    if (val > this._rangeMax()) val = this._rangeMax();

    return val;

  },

  _valueMin: function() {
    var valueMin = this.options.min;
    return valueMin;
  },

  _valueMax: function() {
    var valueMax = this.options.max;
    return valueMax;
  },

  _rangeMin: function() {
    var rangeMin = this.options.rangemin;
    return rangeMin;
  },

  _rangeMax: function() {
    var rangeMax = this.options.rangemax;
    return rangeMax;
  },

  _refreshRange: function() {
    var rangeOffset = (this._rangeMin() - this._valueMin())  * 100 / (this._valueMax() - this._valueMin());
    var rangeWidth = (this._rangeMax() - this._rangeMin()) * 100 / (this._valueMax() - this._valueMin());
    this.range['css']({ left: rangeOffset + '%' });
    this.range['css']({ width: rangeWidth + '%' });
  },

  _refreshValue: function(animate) {
    var oRange = this.options.range, o = this.options, self = this;
    var value = this.value(),
      valueMin = this._valueMin(),
      valueMax = this._valueMax(),
      valPercent = valueMax != valueMin
        ? (value - valueMin) / (valueMax - valueMin) * 100
        : 0;
    var _set = {}; _set[self.orientation == 'horizontal' ? 'left' : 'bottom'] = valPercent + '%';
    this.handle.stop(1,1)[animate ? 'animate' : 'css'](_set, o.animate);

    (oRange == "min") && (this.orientation == "horizontal") && this.range.stop(1,1)[animate ? 'animate' : 'css']({ width: valPercent + '%' }, o.animate);
    (oRange == "max") && (this.orientation == "horizontal") && this.range[animate ? 'animate' : 'css']({ width: (100 - valPercent) + '%' }, { queue: false, duration: o.animate });
    (oRange == "min") && (this.orientation == "vertical") && this.range.stop(1,1)[animate ? 'animate' : 'css']({ height: valPercent + '%' }, o.animate);
    (oRange == "max") && (this.orientation == "vertical") && this.range[animate ? 'animate' : 'css']({ height: (100 - valPercent) + '%' }, { queue: false, duration: o.animate });
  }

}));

$.extend($.ui.rangeslider, {
  getter: "value values",
  version: "1.7.1",
  eventPrefix: "slide",
  defaults: {
    animate: false,
    delay: 0,
    distance: 0,
    max: 100,
    min: 0,
    rangemax: 100,
    rangemin: 0,
    orientation: 'horizontal',
    step: 1,
    value: 0,
  }
});

})(jQuery);
