/**
 * Slider with 2 sliding Items to set a price scale
 * @author Thomas Kosel
 * © ePromoOffice bv 
 */

// Create user extensions namespace (Ext.ux)
Ext.namespace('Ext.ux');

/**
 * @class Ext.Slider
 * @extends Ext.BoxComponent
 * Slider which supports vertical or horizontal orientation, keyboard adjustments, 
 * configurable snapping, axis clicking and animation. Can be added as an item to
 * any container. Example usage:
<pre><code>
new Ext.Slider({
	renderTo: Ext.getBody(),
	width: 200,
	value: 50,
	increment: 10,
	minValue: 0,
	maxValue: 100
});
</code></pre>
 */
Ext.ux.DoubleSlider = Ext.extend(Ext.Slider, {
	// private
	clickRange: [5,15],
	/**
	 * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
	 */
	clickToChange : true,
	/**
	 * @cfg {Boolean} animate Turn on or off animation. Defaults to true
	 */
	animate: true,

	// private override
	initComponent : function(){
		if(this.value === undefined){
			this.value = {left: this.minValue, right: this.maxValue};
		}
		Ext.Slider.superclass.initComponent.call(this);
		this.keyIncrement = Math.max(this.increment, this.keyIncrement); 
		this.addEvents(
			/**
			 * @event beforechange
			 * Fires before the slider value is changed. By returning false from an event handler, 
			 * you can cancel the event and prevent the slider from changing.
			 * @param {Ext.Slider} slider The slider
			 * @param {Number} newValue The new value which the slider is being changed to.
			 * @param {Number} oldValue The old value which the slider was previously.
			 */		
			'beforechange', 
			/**
			 * @event change
			 * Fires when the slider value is changed.
			 * @param {Ext.Slider} slider The slider
			 * @param {Number} newValue The new value which the slider has been changed to.
			 */
			'change', 
			/**
			 * @event dragstart
			 * Fires after a drag operation has started.
			 * @param {Ext.Slider} slider The slider
			 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
			 */
			'dragstart', 
			/**
			 * @event drag
			 * Fires continuously during the drag operation while the mouse is moving.
			 * @param {Ext.Slider} slider The slider
			 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
			 */
			'drag', 
			/**
			 * @event dragend
			 * Fires after the drag operation has completed.
			 * @param {Ext.Slider} slider The slider
			 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
			 */
			'dragend'
		);

		if(this.vertical){
			Ext.apply(this, Ext.Slider.Vertical);
		}
	},

	// private override
	onRender : function(){
		this.autoEl = {
			cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
			cn:{cls:'x-slider-end',cn:{cls:'x-slider-inner',cn:[{cls:'x-slider-thumb'},{cls:'x-slider-thumb'},{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]}}
		};
		Ext.Slider.superclass.onRender.apply(this, arguments);
		this.endEl = this.el.first();
		this.innerEl = this.endEl.first();
		this.thumb1 = this.innerEl.first();
		this.thumb2 = this.innerEl.down("div:last");
		this.halfThumb = (this.vertical ? this.thumb1.getHeight() : this.thumb1.getWidth())/2;
		this.focusEl = this.thumb2.next();
		this.initEvents();
	},

	// private override
	initEvents : function(){
		this.thumb1.addClassOnOver('x-slider-thumb-over');
		this.thumb2.addClassOnOver('x-slider-thumb-over');
		this.mon(this.el, 'mousedown', this.onMouseDown, this);
		this.mon(this.el, 'keydown', this.onKeyDown, this);

		this.tracker1 = new Ext.dd.DragTracker({
			onBeforeStart: this.onBeforeDragStart.createDelegate(this),
			onStart: this.onDragStart.createDelegate(this, [this.thumb1]),
			onDrag: this.onDrag.createDelegate(this, ['tracker1']),
			onEnd: this.onDragEnd.createDelegate(this, [this.thumb1]),
			tolerance: 3,
			autoStart: 300
		});
		this.tracker2 = new Ext.dd.DragTracker({
			onBeforeStart: this.onBeforeDragStart.createDelegate(this),
			onStart: this.onDragStart.createDelegate(this, [this.thumb2]),
			onDrag: this.onDrag.createDelegate(this, ['tracker2']),
			onEnd: this.onDragEnd.createDelegate(this, [this.thumb2]),
			tolerance: 3,
			autoStart: 300
		});
		this.tracker1.initEl(this.thumb1);
		this.tracker2.initEl(this.thumb2);
		this.on('beforedestroy', this.tracker1.destroy, this.tracker1);
		this.on('beforedestroy', this.tracker2.destroy, this.tracker2);
	},

	// private override
	onMouseDown : function(e){
		if(this.disabled) {return;}
		if(this.clickToChange && (e.target != this.thumb1.dom && e.target != this.thumb2.dom)){
			var local = this.innerEl.translatePoints(e.getXY());
			this.onClickChange(local);
		}
		this.focus();
	},

	// private
	onClickChange : function(local){
		if(local.top > this.clickRange[0] && local.top < this.clickRange[1]){
			this.setValue(Math.round(local.left/this.getRatio()));
		}
	},
	
	// private
	onKeyDown : function(e){
		if(this.disabled){e.preventDefault();return;}
		var k = e.getKey();
		switch(k){
			case e.UP:
			case e.RIGHT:
				e.stopEvent();
				if(e.ctrlKey){
					this.setValue(this.maxValue);
				}else{
					this.setValue(this.value+this.keyIncrement);
				}
			break;
			case e.DOWN:
			case e.LEFT:
				e.stopEvent();
				if(e.ctrlKey){
					this.setValue(this.minValue);
				}else{
					this.setValue(this.value-this.keyIncrement);
				}
			break;
			default:
				e.preventDefault();
		}
	},
	
	// private
	doSnap : function(value){
		if(!this.increment || this.increment == 1 || !value) {
			return value;
		}
		var newValue = value, inc = this.increment;
		var m = value % inc;
		if(m > 0){
			if(m > (inc/2)){
				newValue = value + (inc-m);
			}else{
				newValue = value - m;
			}
		}
		return newValue.constrain(this.minValue,  this.maxValue);
	},
	
	// private
	afterRender : function(){
		Ext.Slider.superclass.afterRender.apply(this, arguments);
		if(this.value !== undefined){
			var v = this.normalizeValue(this.value);
			if(v !== this.value){
				delete this.value;
				this.setValue(v, false);
			}else{
				this.moveThumb(this.translateValue(v), false);
			}
		}
	},

	// private
	getRatio : function(){
		var w = this.innerEl.getWidth();
		var v = this.maxValue - this.minValue;
		return w/v;
	},

	// private
	normalizeValue : function(v){
		//recognize type
		switch(Ext.type(v))
		{
			case 'object':
				if(!Ext.type(v.left))
					v.left = this.value.left;
				if(!Ext.type(v.right))
					v.right = this.value.right;
				break;
			case 'string':
				v = Ext.decode(v);
				break;
			case 'array':
				v = {left:v[0], right:v[1]};
				break;
			case 'number':
				var middle = (this.value.left + this.value.right) / 2
				v = {left: (v < middle ? v : this.value.left),
					right: (v < middle ? this.value.right : v)};
				break;
			
			default:
				return v;
				break;
		}
	   if(typeof v.left != 'number'){
			v.left = parseInt(v.left);
		}
	   if(typeof v.right != 'number'){
			v.right = parseInt(v.right);
		}
		v.left  = Math.round(v.left);
		v.right = Math.round(v.right);
		//left and right are not allowed to sit on the same value
		if(v.left == v.right)
		{
			v.right++;
			v.left--;
		}
		v.left  = this.doSnap(v.left);
		v.right = this.doSnap(v.right);
		v.left  = v.left.constrain(this.minValue, (v.right-1 < this.maxValue ? v.right : this.maxValue));
		v.left  = v.left.constrain(this.minValue, this.maxValue);
		v.right = v.right.constrain(v.left+1, this.maxValue);
		v.right = v.right.constrain(this.minValue, this.maxValue);
		return v;
	},

	/**
	 * Programmatically sets the value of the Slider. Ensures that the value is constrained within
	 * the minValue and maxValue.
	 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
	 * @param {Boolean} animate Turn on or off animation, defaults to true
	 */
	setValue : function(v, animate){
		v = this.normalizeValue(v);
		if(v !== this.value && this.fireEvent('beforechange', this, v, this.value) !== false){
			this.value = v;
			this.moveThumb(this.translateValue(v), animate !== false);
			this.fireEvent('change', this, v);
		}
	},

	// private
	translateValue : function(v){
		return {left:(v.left * this.getRatio())-this.halfThumb,
			right:(v.right * this.getRatio())-this.halfThumb};
	},

	// private
	moveThumb: function(v, animate){
		if(!animate || this.animate === false){
			this.thumb1.setLeft(v.left);
			this.thumb2.setLeft(v.right);
		}else{
			this.thumb1.shift({left: v.left, stopFx: true, duration:.35});
			this.thumb2.shift({left: v.right, stopFx: true, duration:.35});
		}
	},

	// private
	focus : function(){
		this.focusEl.focus(10);
	},

	// private
	onBeforeDragStart : function(e){
		return !this.disabled;
	},

	// private
	onDragStart: function(thumb, e){
		try{thumb.addClass('x-slider-thumb-drag');}catch(e){}
		this.fireEvent('dragstart', this, e);
	},

	// private
	onDrag: function(tracker, e){
		var pos = this.innerEl.translatePoints(this[tracker].getXY());
		if(tracker == 'tracker1')
			this.setValue({left: Math.round(pos.left/this.getRatio()), right: this.value.right}, false);
		else
			this.setValue({left: this.value.left, right: Math.round(pos.left/this.getRatio())}, false);
		this.fireEvent('drag', this, e);
	},
	
	// private
	onDragEnd: function(thumb, e){
		try{thumb.removeClass('x-slider-thumb-drag');}catch(e){}
		this.fireEvent('dragend', this, e);
	},

	// private
	onResize : function(w, h){
		this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
	},
	
	/**
	 * Returns the current value of the slider
	 * @return {Number} The current value of the slider
	 */
	getValue : function(){
		return this.value;
	}
});
Ext.reg('doubleslider', Ext.ux.DoubleSlider);

