(function(){
	var
	Y = YAHOO,
	CustomEvent = Y.util.CustomEvent,
	Animation = Y.util.Anim,
	Easing = Y.util.Easing,
	Event = Y.util.Event,
	Dom = Y.util.Dom,
	isIE = Y.env.ua.ie,
	log = Math.log,
	pow = Math.pow,
	round = Math.round,
	pre;
	
	Y.namespace('ln');
	
	Y.ln.Slider = function(el, min, max) {
		this.el = Dom.get(el);
		
		this.init(min, max);
	};
	
	Y.ln.Slider.prototype = {
		min: 0,
		max: 1000,
		dragging: null,
		start: null,
		init: function(min, max) {
			this.red = {
				el: Dom.getFirstChild(this.el),
				field: Dom.get(min),
				oldValue: null,
				onChange: new CustomEvent('change', this)
			};
			
			this.red.number = Dom.getFirstChild(this.red.el);
			this.red.lid = Dom.getNextSibling(this.red.number);
			this.red.proxy = this.red.el.cloneNode(true);
			this.red.region = Dom.getRegion(this.red.el);
			this.red.value = parseInt(this.red.field.value) || 0;
			
			this.blue = {
				el: Dom.getLastChild(this.el),
				field: Dom.get(max),
				oldValue: null,
				onChange: new CustomEvent('change', this)
			};
			
			this.blue.number = Dom.getFirstChild(this.blue.el);
			this.blue.lid = Dom.getNextSibling(this.blue.number);
			this.blue.proxy = this.blue.el.cloneNode(true);
			this.blue.region = Dom.getRegion(this.blue.el);
			this.blue.value = parseInt(this.blue.field.value) || 0;
			
			Event.on(this.red.field, 'change', this._fireChangeEvent, this.red, this);
			Event.on(this.blue.field, 'change', this._fireChangeEvent, this.blue, this);
			
			Dom.setStyle([this.red.proxy, this.blue.proxy], 'display', 'none');
			Dom.setStyle([this.red.proxy, this.blue.proxy], 'opacity', 0.5);
			
			this.el.appendChild(this.red.proxy);
			this.el.appendChild(this.blue.proxy);
			
			if(isIE) {
				Event.on(document, 'selectstart', this._ieNonSelect, null, this);
				Event.on([this.red.lid, this.blue.lid], 'dragstart', this._ieNonDrag, null, this);
			}
			
			Event.on([this.red.lid, this.red.number], 'mousedown', this.startDrag, this.red, this);
			Event.on([this.blue.lid, this.blue.number], 'mousedown', this.startDrag, this.blue, this);
			
			Event.on(document, 'mouseup', this.stopDrag, null, this);
			
			Event.on(this.red.field, 'keypress', this.onChange, this.red, this);
			Event.on(this.blue.field, 'keypress', this.onChange, this.blue, this);
			
			this.red.animation = new Animation(this.red.el, { height: { to: 18 } }, 1, Easing.bounceOut);
			this.blue.animation = new Animation(this.blue.el, { height: { to: 18 } }, 1, Easing.bounceOut);
			
			this.red.animation.onTween.subscribe(this.onTween, this, this.red);
			this.blue.animation.onTween.subscribe(this.onTween, this, this.blue);
			
			this.red.animation.onComplete.subscribe(this.onComplete, this, this.red);
			this.blue.animation.onComplete.subscribe(this.onComplete, this, this.blue);
		},
		_fireChangeEvent: function(e, pile) {
			pile.onChange.fire();
		},
		_ieNonDrag: function(e) {
			Event.stopEvent(e);
			
			return false;
		},
		_ieNonSelect: function(e) {
			if(Dom.isAncestor(this.el, Event.getTarget(e))) {
				Event.stopEvent(e);
				
				return false;
			}
		},
		startDrag: function(e, pile) {
			Event.stopEvent(e);
			
			pile.animation.stop();
			pile.region = Dom.getRegion(pile.el);
			
			Dom.getFirstChild(pile.proxy).innerHTML = pile.number.innerHTML;
			
			Dom.setStyle(pile.el, 'opacity', 0.5);
			
			Dom.setStyle(pile.proxy, 'height', Dom.getStyle(pile.el, 'height'));
			Dom.setStyle(pile.proxy, 'display', 'block');
			
			pile.oldValue = pile.value;
			
			this.dragging = pile;
			
			Event.on(document, 'mousemove', this.onDrag, pile, this);
			
			return false;
		},
		stopDrag: function(e) {
			if(this.dragging) {
				Event.purgeElement(document, false, 'mousemove');
				
				Dom.setStyle(this.dragging.proxy, 'display', 'none');
				
				Dom.setStyle(this.dragging.el, 'opacity', 1);
				
				this.resize();
				
				this.dragging = null;
			}
		},
		_change: function(pile) {
			var num = parseInt(pile.field.value) || 0;
			
			if(num > this.max) {
				num = this.max;
			}
			
			pile.field.value = num;
			pile.value = num;
			
			this.resize(pile);
		},
		onChange: function(e, pile) {
			var code = Event.getCharCode(e), character = String.fromCharCode(code);
			
			if(!/\d/.test(character)) {
				switch(code) {
					case 8: case 9: case 46: break;
					default:
						return;
				}
			}
			
			if(pile.ticker) {
				clearTimeout(pile.ticker);
			}
			
			pile.ticker = setTimeout(bind(this, this._change, [pile]), 1000);
		},
		onTween: function(type, args, me) {
			this.number.innerHTML = me._calc(this.animation.getAttribute('height') - 34);
		},
		onComplete: function() {
			this.number.innerHTML = this.value;
		},
		onDrag: function(e, pile) {
			var position = Event.getXY(e), distance = pile.region.bottom - position[1], value;
			
			if(pre && pre[0] == position[0] && pre[1] == position[1]) {
				Event.stopEvent(e);
				
				return false;
			};
			
			if(distance < 0) {
				distance = 0;
			} else if(distance > 160) {
				distance = 160;
			}
			
			value = this._calc(distance);
			
			pile.value = value;
			pile.number.innerHTML = value;
			
			Dom.setStyle(pile.el, 'zIndex', pile.value < pile.oldValue ? 100 : 10000);
			Dom.setStyle(pile.el, 'height', distance + 34 + 'px');
			
			pre = position;
		},
		_calc: function(pixel) {
			return pixel ? Math.round(this.max * pow( pow(160, pixel / 160) / 160, 2 - 160 / (this.max > 160 ? this.max : 160))) : 0;
		},
		resize: function(pile) {
			pile = pile || this.dragging;
			
			pile.value = pile.value > this.max || !isFinite(pile.value) ? this.max : pile.value;
			
			value = (160 * log(160 * pow(pile.value / this.max, 1 / (2 - 160 / (this.max > 160 ? this.max : 160))))) / log(160) + 34;
			
			value = value > 34 ? value : 34;
			
			pile.field.value = pile.value;
			pile.onChange.fire();
			pile.animation.stop();
			pile.animation.attributes.height.from = pile.region.height;
			pile.animation.attributes.height.to = isFinite(value) ? value : 34;
			pile.animation.method = pile.animation.attributes.height.from > pile.animation.attributes.height.to ? Easing.bounceOut : Easing.backOut;
			pile.animation.animate();
			
			if(this.red.value > this.blue.value || this.blue.value < this.red.value) {
				var opposite = pile == this.red ? this.blue : this.red;
				
				opposite.animation.stop();
				
				opposite.region = Dom.getRegion(opposite.el);
				opposite.oldValue = opposite.value;
				opposite.value = pile.value;
				
				this.dragging = opposite;
				
				this.resize();
			}
		},
		setMax: function(max) {
			if(this.max != max) {
				this.max = max;
			
				this.red.region = Dom.getRegion(this.red.el);
				this.blue.region = Dom.getRegion(this.blue.el);
				this.blue.value = max;
				
				this.resize(this.red);
				this.resize(this.blue);
			}
		}
	};
	
	function bind(scope, fn, args) {
		return function() {
			return fn.apply(scope, args || []);
		};
	}
})();
