// This code depends on prototype.js, common.js

// Base (abstract) class for slider controls
var SliderBase = Class.create(
  hasCallback('slideEnd'),
  hasCallback('change'),
  hasCallback('ready'),
  hasUniqueID,
{
  _defaultSliderLength: 126, // px
  _sliderLength: null,
  _min: null,
  _max: null,
  _bgElt: null,
  _stepSize: 1,
  _stepOffset: 0,
  
  initialize: function(parent, length) {
    this._sliderLength = length || this._defaultSliderLength;
  },
  setStep: function(stepSize, stepOffset) {
    this._stepSize = stepSize || this._stepSize;
    this._stepOffset = stepOffset || this._stepSize;
  },
  _afterInitialize: function() {
    this.setLimits(0,1);
    this._slider.subscribe('slideEnd', this._fireSlideEnd.bind(this));
    this._slider.subscribe('change', this._fireChange.bind(this));
  },
  // Sets upper and lower limits on the slider's value.  Feel free to use your own units; this doesn't have to be specified in pixels or anything like that.
  setLimits: function(min, max) {
    if(min > max || min == null || max == null) {
      throw new Error("Invalid range: ["+min+","+max+"]");
    }
    this._min = min;
    this._max = max;
    this._fireChange();
    this._fireSlideEnd();
  },
  getMin: function() {
    return this._min;
  },
  getMax: function() {
    return this._max;
  },
  addBackgroundClass: function(klass) {
    if(this._bgElt) {
      this._bgElt.addClassName(klass);
    } else {
      throw new Error('background element not present');
    }
  },
  _createBG: function(parent, prefix) {
    prefix = prefix || 'slider-bg-';
    var bgName = prefix + this._getUniqueID();
    var bg = newElt('div', bgName, 'slider-bg', parent);
    return { name: bgName, elt: bg };
  },
  _createThumb: function(parent, prefix) {
    prefix = prefix || "slider-thumb-";
    var thumbName = prefix + this._getUniqueID();
    var thumb = newElt('div', thumbName, 'slider-thumb', parent);
    return { name: thumbName, elt: thumb };
  },
  _toSliderValue: function(value) {
    value = this._roundDown(value);
    var delta = this._max - this._min;
    var normalizedValue = (delta > 0) ? (value - this._min) / delta : this._min;
    return normalizedValue * this._sliderLength;
  },
  _fromSliderValue: function(sValue) {
    var delta = this._max - this._min;
    var normalizedValue = sValue / this._sliderLength;
    var value = this._min + normalizedValue * delta;
    return this._roundDown(value);
  },
  _roundDown: function(value) {
    var offsetValue = value + this._stepOffset;
    return value - (offsetValue % this._stepSize);
  }
});

// A slider with a single draggable element
var SingleSlider = Class.create(SliderBase, {
  // Creates a new slider within the specified element.
  // $super is supplied automatically, so just pretend it isn't there.
  initialize: function($super, parent, length) {
    $super(parent, length);
    
    // Create req'd htmlements
    var bg = this._createBG(parent);
    this._bgElt = bg.elt;
    var thumb = this._createThumb(bg.elt);
    
    // Create the YUI slider control
    this._slider = YAHOO.widget.Slider.getHorizSlider(
      bg.name, thumb.name, 0, this._sliderLength);
    
    this._slider.onAvailable = this._fireReady.bind(this);
    
    this._afterInitialize();
  },
  // Gets the value corresponding to the current location of the slider.
  getValue: function() {
    // If the slider GUI elements aren't ready yet, YUI queues up changes in a field called deferredSetValue and returns the old value when getValue is called. We want the new value, so we'll break the slider's encapsulation to get it. I don't think YUI provides another way to do it.
    var deferredSetValue = this._slider.deferredSetValue;
    var sliderValue = deferredSetValue ?
      deferredSetValue[0] : this._slider.getValue();
      
    return this._fromSliderValue(sliderValue);
  },
  // Sets the value corresponding to the current location of the slider.
  setValue: function(value) {
    this._slider.setValue(this._toSliderValue(value), true);
  }
});

// A slider with two draggable elements. Together they specify a range.
var DoubleSlider = Class.create(SliderBase, {
  // Creates a new slider within the specified element.
  // $super is supplied automatically, so just pretend it isn't there.
  initialize: function($super, parent, length) {
    $super(parent, length);
    
    // Create req'd htmlements
    var bg = this._createBG(parent);
    this._bgElt = bg.elt;
    var leftThumb = this._createThumb(bg.elt, "slider-left-thumb");
    var rightThumb = this._createThumb(bg.elt, "slider-right-thumb");
    
    // Create the YUI slider control
    this._slider = YAHOO.widget.Slider.getHorizDualSlider(
      bg.name, leftThumb.name, rightThumb.name, this._sliderLength);
    
    this._slider.subscribe('ready', function() {
      this._isReady = true;
      
      if(this._deferredLowerValue) this.setLowerValue(this._deferredLowerValue);
      if(this._deferredUpperValue) this.setUpperValue(this._deferredUpperValue);
      this._deferredUpperValue = this._deferredLowerValue = null;
      
      this._fireReady();
    }.bind(this));
    
    this._afterInitialize();
  },
  getLowerValue: function() {
    return this._deferredLowerValue || 
      this._fromSliderValue(this._slider.minVal);
  },
  getUpperValue: function() {
    return this._deferredUpperValue || 
      this._fromSliderValue(this._slider.maxVal);
  },
  setValues: function(lower, upper) {
    // To avoid setting off extra events, we only reset changed values.
    if(lower != this.getLowerValue()) this.setLowerValue(lower);
    if(upper != this.getUpperValue()) this.setUpperValue(upper);
  },
  setLowerValue: function(value) {
    if(this._isReady) {
      this._slider.setMinValue(this._toSliderValue(value), true);
    } else {
      this._deferredLowerValue = value;
    }
  },
  setUpperValue: function(value) {
    if(this._isReady) {
      this._slider.setMaxValue(this._toSliderValue(value), true);
    } else {
      this._deferredUpperValue = value;
    }
  },
  
  _isReady: false,
  _deferredLowerValue: null,
  _deferredUpperValue: null
});
