
Slider = createClass();
Slider.prototype.init = function(args) {
    
    this.dispose = function() {
        var slider = this;    
        DOM.removeEvent(this.track, "mousedown", this.startDrag);
        DOM.unRegister("mouseup", this);
        DOM.unRegister("mousemove", this);
        for(var i=0; i<this.handles.length; i++)
            DOM.removeEvent(this.handles[i], "mousedown", this.startDrag);
    };
 
    this.setDisabled = function() {
        this.disabled = true;
    };
    
    this.setEnabled = function() {
        this.disabled = false;
    };  
    
    this.getNearestValue = function(value) {
        if (this.allowedValues) {
            if (value >= this.allowedValues.max()) return(this.allowedValues.max());
            if (value <= this.allowedValues.min()) return(this.allowedValues.min());

            var offset = Math.abs(this.allowedValues[0] - value);
            var newValue = this.allowedValues[0];
            this.allowedValues.each( function(v) {
                var currentOffset = Math.abs(v - value);
                if (currentOffset <= offset) {
                    newValue = v;
                    offset = currentOffset;
                } 
            });
            return newValue;
        }
        if (value > this.range.end) return this.range.end;
        if (value < this.range.start) return this.range.start;
        return value;
    };
    
    this.setValue = function(sliderValue, handleIdx) {
        if (!this.active) {
            this.activeHandleIdx = handleIdx || 0;
            this.activeHandle = this.handles[this.activeHandleIdx];
            this.updateStyles();
        }
        handleIdx = handleIdx || this.activeHandleIdx || 0;
        if (this.initialized && this.restricted) {
            if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
                sliderValue = this.values[handleIdx-1] + this.restrictVal;
            if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
                sliderValue = this.values[handleIdx+1] - this.restrictVal;
        }
	    sliderValue = this.getNearestValue(sliderValue);
        this.values[handleIdx] = sliderValue;
        this.value = this.values[0]; // assure backwards compat
        this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = this.translateToPx(sliderValue);
        
        this.drawSpans();
        if (!this.dragging || !this.event) this.updateFinished();
    };
    
    this.setValueBy = function(delta, handleIdx) {
        this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, handleIdx || this.activeHandleIdx || 0);
    };
    
    this.translateToPx = function(value) {
        var i = (this.range.end-this.range.start);
        if(i == 0) i = 1;
        return Math.round(
            ((this.trackLength-this.handleLength)/i)
             * (value - this.range.start)) + "px";
    };
    
    this.translateToValue = function(offset) {
        return ((offset/(this.trackLength-this.handleLength)
         * (this.range.end-this.range.start)) + this.range.start);
    };
    
    this.getRange = function(range) {
        var v = this.values.sort(function(a, b) { return a-b; }); 
        range = range || 0;
        return { start: v[range], end: v[range+1] };
    };
    
    this.minimumOffset = function() {
        return(this.isVertical() ? this.alignY : this.alignX);
    };
    
    this.maximumOffset = function() {
        return(this.isVertical() ? 
            (this.track.offsetHeight != 0 ? this.track.offsetHeight :
            this.track.style.height.replace(/px$/,"")) - this.alignY : 
            (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
            this.track.style.width.replace(/px$/,"")) - this.alignX);
    };  
    
    this.isVertical = function() {
        return (this.axis == 'vertical');
    };
    
    this.drawSpans = function() {
        var slider = this;
        if (this.spans)
            for(i=0; i<this.spans.length-1; i++)
                slider.setSpan(slider.spans[r], slider.getRange(r));
        if (this.options.startSpan)
            this.setSpan(this.options.startSpan, { start: 0, end: this.values.length>1 ? this.getRange(0).min() : this.value });
        if (this.options.endSpan)
            this.setSpan(this.options.endSpan, { start: this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, end: this.maximum });
    };
    
    this.setSpan = function(span, range) {
        if (this.isVertical()) {
            span.style.top = this.translateToPx(range.start);
            span.style.height = this.translateToPx(range.end - range.start + this.range.start);
        } 
        else {
            span.style.left = this.translateToPx(range.start);
            span.style.width = this.translateToPx(range.end - range.start + this.range.start);
        }
    };
    
    this.updateStyles = function() {
        for(var i=0; i<this.handles.length; i++)
            DOM.removeClass(this.handles[i], 'selected');
        DOM.addClass(this.activeHandle, 'selected');
    };

    this.startDrag = function(event) {
        var slider = DOM.getTargetElement(event).slider;
	    if(!isDefined(slider))
            return;
	    if (event.type == "mousedown") {
            if (!slider.disabled) {
                slider.active = true;
            
                var handle = DOM.getTargetElement(event);
                var pointer  = [DOM.getPointer(event).x, DOM.getPointer(event).y];
                var track = handle;
                if (track==slider.track) {
                    var offsets  = DOM.getAbsolutePos(slider.track); 
                    slider.event = event;
                    slider.setValue(slider.translateToValue( 
                        (slider.isVertical() ? pointer[1]-offsets.y : pointer[0]-offsets.x)-(slider.handleLength/2)
                    ));

                    var offsets  = DOM.getAbsolutePos(slider.activeHandle);
                    slider.offsetX = (pointer[0] - offsets.x);
                    slider.offsetY = (pointer[1] - offsets.y);
                } 
                else {
                    // find the handle (prevents issues with Safari)
                    while((slider.indexOf(handle) == -1) && handle.parentNode) 
                        handle = handle.parentNode;
                
                    if (slider.indexOf(handle)!=-1) {
                        slider.activeHandle = handle;
                        slider.activeHandleIdx = slider.indexOf(slider.activeHandle);
                        slider.updateStyles();
              
                        var offsets  = DOM.getAbsolutePos(slider.activeHandle);
                        slider.offsetX = (pointer[0] - offsets.x);
                        slider.offsetY = (pointer[1] - offsets.y);
                    }
                }
            }
        }
        DOM.stopEvent(event);
    };

    this.update = function(event, slider) {
        if(!isDefined(slider))
		    return;
        if (slider.active) {
            if (!slider.dragging) slider.dragging = true;
                slider.draw(event);
            //if (Prototype.Browser.WebKit) window.scrollBy(0,0);
            DOM.stopEvent(event);
        }
    };
    
    this.draw = function(event) {
        var pointer = [DOM.getPointer(event).x, DOM.getPointer(event).y];
        var offsets = DOM.getAbsolutePos(this.track);
        pointer[0] -= this.offsetX + offsets.x;
        pointer[1] -= this.offsetY + offsets.y;
        this.event = event;
        this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
        if (this.initialized && this.options.onSlide)
            this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
    };
      
    this.endDrag = function(event, slider) {
	    if(!isDefined(slider))
		    return;
	    if (slider.active && slider.dragging) {
            slider.finishDrag(event, true);
            DOM.stopEvent(event);
        }
        slider.active = false;
        slider.dragging = false;
    };
    
    this.finishDrag = function(event, success) {
        this.active = false;
        this.dragging = false;
        this.updateFinished();
    };
    
    this.updateFinished = function() {
        if (this.initialized && this.options.onChange) 
            this.options.onChange(this.values.length>1 ? this.values : this.value, this);
        this.event = null;
    };
    
    this.indexOf = function(h) {
	    var pos = -1;
	    for(var i=0; i<this.handles.length; i++)
		    if(this.handles[i] == h) {
			    pos = i;
			    break;
		    }
	    return pos;
    };
    
    var handle = args.handle;
	var track = args.track;
	var options = args.options;

	var slider = this;
    if (!isArray(handle))
      handle = [handle];
    
	this.handles = [];
	var el;
	for(var i=0; i<handle.length; i++)
		this.handles.push($(handle[i]));

    this.track   = $(track);
    this.options = options || { };

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || { start: 0, end: 1 };
    
    this.value     = 0; // assure backwards compat
    this.values    = [];
	for(var i=0; i<this.handles.length; i++)
		this.values.push(0);
    this.spans     = this.options.spans ? [] : false;
	for (var span in this.options.spans) this.options.spans.push($(span));

    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;    

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    // Will be used to align the handle onto the track, if necessary
    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');
    
    this.trackLength = this.maximumOffset() - this.minimumOffset();

    this.handleLength = this.isVertical() ? 
      (this.handles[0].offsetHeight != 0 ? 
        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
        this.handles[0].style.width.replace(/px$/,""));

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if (this.options.disabled) this.setDisabled();

    // Allowed values array
    this.allowedValues = this.options.values ? this.options.values.sort(function(a, b) { return a-b; }) : false;
    if (this.allowedValues) {
        this.minimum = this.allowedValues.min();
        this.maximum = this.allowedValues.max();
    }

    // Initialize handles in reverse (make sure first handle is active)
    for(var i=this.handles.length-1; i>=0; i--) {
        slider.setValue(parseFloat(
            (isArray(slider.options.sliderValue) ? 
            slider.options.sliderValue[i] : slider.options.sliderValue) || 
            slider.range.start), i);

		el = this.handles[i];
		var pos = el.style.position;
		if (pos == 'static' || !pos) {
            el._madePositioned = true;
            el.style.position = 'absolute';
            // Opera returns the offset relative to the positioning context, when an
            // element is position relative but top and left have not been defined
            if (window.opera) {
                el.style.top = 0;
                el.style.left = 0;
            }
		}
		DOM.addEvent(el, "mousedown", this.startDrag);
		el.slider = this;
    }

    // change made to avoid overlap
    this.restrictVal = (this.range.end - this.range.start)/25;
	this.track.slider = this;
    DOM.addEvent(this.track, "mousedown", this.startDrag);
    DOM.register("mouseup", this, this.endDrag);
    DOM.register("mousemove", this, this.update);
        
    this.initialized = true;  
};
