/*	FreeSlider, version 0.1
 *	(c) 2007-2009 Savva Mikhalevski (smikhalevski@gmail.com, connect@apxe.org)
 *
 *	FreeSlider exploits Prototype javascript framework which is property of Sam Stephenson.
 *	FreeSlider is freely distributable under the terms of an MIT-style license.
 *	Please, feel free to contact me on any questions or suggestions on how to modify this lib.
 *
 *	Supported events:
 *	handle:jump(handle,value) - before jump start.
 *  handle:move(handle,value) - on handle move.
 *  handle:mousedown(handle,jumping)
 *  handle:mouseup(handle,jumping)
 */




(function() {
	if (!window.Transform)
		return setTimeout(arguments.callee,1000);
	var man = new Utils;
	Handle = Class.create({
		initialize:function(slider,handle) {
			var myself = this,sys = {events:[]},
				handle = $(handle);
			this.getElement = function() {
				return handle;
			};
			this.observe = function(event,fnc) {
				var fncs = sys.events[event] || [];
				fncs.push(man.toFunction(fnc));
				sys.events[event] = fncs;
				return myself;
			};
			this.stopObserving = function(event,fnc) {
				var events = sys.events[event],
					index = $A(events).indexOf(fnc);
				if (index > -1) events.splice(index,1);
				return myself;
			};
			this.fire = function(event) {
				var out = [],args = $A(arguments).slice(1),
					event = $A(sys.events[event]);
				for (var fnc,i = 0; fnc = event[i]; i++) {
					var reply = man.execArray([fnc].concat(args));
					if (Object.isNumber(reply) || reply === false) out.push(reply);
				} if (out.member(false)) return false;
				return $A(out).detect(function(obj) {
					return Object.isNumber(obj);
				});
			};
		}
	});
	window.Slider = Class.create({
		initialize:function(track,vertical,defSmooth) {
			var myself = this,sys = {handles:new Collection};
			this.setRange = function(solid,values) {
				if (Object.isUndefined(solid)) solid = true;
				if (!man.isEnumerable(values)) values = [];
				for (i = 0; i < values.length; i++)
					values[i] = new Number(values[i]).valueOf() || 0;
				values = values.uniq().sort();
				if (values.length < 2) values = [0,1];
				Object.extend(values,{solid:solid,min:values.min(),max:values.max()});
				values.width = Math.abs(values.max-values.min);
				return (sys.range = values) && myself;
			};
			this.attachHandle = function(handle,rigid,value) {
				if (!Object.isElement(handle = $(handle))) return null;
				with (sys.handles) if (keys().member(handle)) return get(handle).ref;
				function browserRound(value) {
					with (Prototype.Browser) return (Opera || WebKit)?Math.round(value):value;
				} var axis = vertical?'height':'width',
					pixel = getSize(handle),rails = new Element('div'),
					size = rigid?pixel:100*pixel/(getSize(handle.parentNode)-pixel);
				rails = handle.wrap(rails).remove();
				rails.appendChild(document.createComment(' '));
				[rails,handle].invoke('setStyle',{position:'absolute'});
				rails.setStyle({top:0,left:0});
				track.insert(rails,{position:'top'});
				handle.style[axis] = browserRound(size)+(rigid?'px':'%');
				var pad = browserRound(100-100/(1+100/size));
				pad = rigid?pixel+'px':pad+'%';
				var expr = vertical?{width:0,top:0,bottom:pad}:{height:0,left:0,right:pad};
				if (Prototype.Browser.IE && man.getBrowserVersion() < 7)
					with (new Template('#{a}:expression(parentNode.#{b}-#{c})'))
						expr = evaluate({a:axis,b:'client'+axis.capitalize(),c:pixel});
				if (rigid) rails.setStyle(expr);
				else rails.setStyle(vertical?{width:0,height:pad}:{width:pad,height:0});
				if (Object.isUndefined(value)) value = sys.range.min;
				var obj = {ref:new Handle(myself,handle),obj:handle};
				sys.handles.set(handle,obj);
				myself.setValue(value,false,handle);
				obj.fader = handle.attachFader(null,10,false,removeLock);
				return obj.ref;
			};
			this.setValue = function(value,smooth,handle) {
				var rec = getHandle(handle),obj = rec.obj;
				if (Object.isElement(obj)) with (sys.range) {
					function quickStyle(value,validate) {
						if (validate) value = getNearest(value);
						with (sys.range) value = 100*(value-min)/width+'%';
						return obj.setStyle(vertical?{top:value}:{left:value});
					} var jumping = $(handle) == track,
						axis = vertical?'top':'left',
						callback = jumping?'handle:jump':'handle:move';
					if (Prototype.Browser.IE) document.selection.empty();
					value = isNaN(value)?min:getNearest(value);
					var reply = rec.ref.fire(callback,obj,value);
					if (reply === false) return false;
					if (Object.isNumber(reply)) return quickStyle(reply,true) && false;
					if (Object.isUndefined(smooth)?defSmooth:smooth) {
						with (new Template('*::#{axis}(#{from}>#{to}:#{count} trend{1>5:log})')) {
							var a = parseFloat(obj.style[axis]),b = 100*(value-min)/width,
								token = {axis:axis,from:obj.style[axis],to:b+'%'};
							with (Math) token.count = round(abs(abs(a)-abs(b))*2);
							var schema = evaluate(token);
						} var timer = setInterval(function() {
							value = parseFloat(obj.getStyle(axis));
							with (sys.range) value = value*width/100+min;
							reply = rec.ref.fire('handle:move',obj,value);
							if (reply === false || Object.isNumber(reply)?quickStyle(reply,true):false)
								rec.fader.immediateStop(removeLock(clearTimeout(timer)));
						},rec.fader.immediateStop().getTransform().setSchema(schema,obj).getDelay());
						sys.hardLock = man.toBoolean(rec.fader.rewind().playForward(function() {
							removeLock(clearTimeout(timer));
						}));
					} else quickStyle(value);
				} return true;
			};
			this.setEnabled = function(value) {
				sys.enabled = man.toBoolean(value);
				return myself;
			};
			this.getEnabled = function() {
				return sys.enabled;
			};
			var getHandle = function(obj) {
				with (sys.handles) if (size()) {
					if (obj instanceof Handle) obj = indexOf(obj);
					return get($(obj)) || get(sys.current) || get(keys().first());
				} return {};
			};
			var getNearest = function(value) {
				var result,offset = sys.range.width;
				if (!isNaN(result = value = man.toNumber(value))) with (sys.range) {
					if (new String(value).endsWith('%'))
						value = man.toNumber(value)*width/100+min;
					if (solid) return (value < min)?min:(value > max)?max:result;
					else void(result = min) || sys.range.each(function(cur,buf) {
						offset = Math.min(offset,buf = Math.abs(cur-value));
						if (buf == offset) result = cur;
					});
				} return result || null;
			};
			var getSize = function(obj) {
				if (!Object.isElement(obj = $(obj))) return null;
				return vertical?obj.getHeight():obj.getWidth();
			};
			var repaint = function(point,smooth,element) {
				with (track.cumulativeOffset())
					var pos = (vertical?point.y-top:point.x-left)-(sys.offset || 0);
				with (sys.range) pos = pos*width/getSize(getHandle().obj.parentNode)+min;
				return myself.setValue(pos,smooth,element);
			};
			var removeLock = function() {
				sys.hardLock = false;
			};
			var dragStart = function(evt,src) {
				if (!sys.enabled) return;
				with (Event) var origin = src = element(evt),
					scope = src == track || src.ancestors().member(track),
					point = {x:pointerX(evt),y:pointerY(evt)},
					keys = sys.handles.keys(),block;
				if (!scope) return;
				if (origin == track) block = false;
				else while (!(block = keys.member(src)) && (src = src.parentNode));
				sys.offset = getSize(getHandle().obj)/2;
				if (block) {
					removeLock(sys.current = src);
					getHandle(src).fader.immediateStop();
					if (sys.range.solid) with (src.cumulativeOffset())
						sys.offset = vertical?point.y-top:point.x-left;
				} else origin = track;
				src = getHandle().obj;
				var disable = false;
				sys.handles.each(function(pair) {
					if (pair.key == src) {
						src.parentNode.setStyle({zIndex:2});
						var jump = man.toBoolean(sys.jump = !block);
						var reply = pair.value.ref.fire('handle:mousedown',src,jump);
						disable = reply === false || Object.isNumber(reply);
					} else pair.key.parentNode.setStyle({zIndex:1});
				});
				Event.stop(evt);
				if (sys.active = !disable)
					repaint(point,(origin == track)?defSmooth:false,origin);
			};
			var dragUpdate = function(evt) {
				if (!sys.hardLock && sys.active && sys.enabled) {
					with (Event) repaint({x:pointerX(evt),y:pointerY(evt)},false);
					sys.dragging = true;
				} else dragStop(evt);
			};
			var dragStop = function(evt,rec) {
				if (sys.active || sys.dragging) if (rec = getHandle())
					rec.ref.fire('handle:mouseup',rec.obj,man.toBoolean(sys.jump));
				sys.active = sys.dragging = false;
				delete sys.offset;
			};
			this.attachHover = function(str,delay,subnodes,fncIn,fncOut) {
				if (!track.attachHover) return null;
				var fader = track.attachHover(str,delay,subnodes,fncIn,fncOut);
				var stop = function(evt) {
					var elem = Event.element(evt);
					var tr = fader.getTransform();
					if (tr.getKey(elem) != tr.getKey(myself.track))
						fader.playBackward();
					fader.enableObserve();
					Event.stopObserving(document,'mousemove',move);
					Event.stopObserving(document,'mouseup',stop);
				};
				var start = function() {
					fader.playForward();
					fader.disableObserve();
					Event.observe(document,'mousemove',move);
					Event.observe(document,'mouseup',stop);
				};
				var move = function(evt) {
					if (!Event.isLeftClick(evt)) stop(evt);
				};
				Event.observe(track,'mousedown',start);
				sys.handles.each(function(pair) {
					Event.observe(pair.value.obj,'mousedown',start);
				});
				return fader.enableObserve();
			};
			document.observe('mousemove',dragUpdate.bindAsEventListener(this));
			document.observe('mouseup',dragStop.bindAsEventListener(this));
			track = ($(track) || $$('html').shift()).makePositioned();
			track.observe('mousedown',dragStart.bindAsEventListener(this));
			this.setEnabled(true);
			this.setRange();
		}
	});
})();