(function($) {
	function VerticalScroller(obj, opts) {
		var defaults = {
			sels: {
				scrolled: ".vertical-scrolled",
				scroll:   ".vertical-scroll",
				dragged:  ".dragged",
				dragger:  ".dragger",
				up:       ".up-button",
				down:     ".down-button"
			},
			dragger_min_height: 20,
			up_step_height:     100,
			down_step_height:   100,
			wheel_speed_height: 20
		};
		this.obj = obj;
		this.opts = $.extend(defaults, opts);
		this.init();
	}

	VerticalScroller.prototype.extend = function(opts) {
		this.opts = $.extend(this.opts, opts);
	}

	VerticalScroller.prototype.init = function() {
		var instance = this;

		this.scrolled = $(this.obj).find(this.opts.sels.scrolled);
		this.scroll = $(this.obj).find(this.opts.sels.scroll);
		this.dragger = $(this.obj).find(this.opts.sels.dragger);
		this.dragged = $(this.obj).find(this.opts.sels.dragged);
		this.up = $(this.obj).find(this.opts.sels.up);
		this.down = $(this.obj).find(this.opts.sels.down);
		
		this.scroll.css({
			left: this.scrolled.position().left + this.scrolled.width() - this.scroll.width() + "px",
			top: this.scrolled.position().top,
			height: this.scrolled.height() + "px"
		})
		.show();

		var dragged_height = this.scrolled.height() -
		this.up.height() -
		this.down.height();
	
		this.dragged.height(dragged_height);

		var hole_height = 0;
		this.scrolled.children().each(
			function() {
				hole_height += $(this).outerHeight();
			});

		var percent = this.scrolled.height() / hole_height;
		var dragger_height = Math.round(percent * dragged_height);
		if(dragger_height < this.opts.dragger_min_height) {
			dragger_height = this.opts.dragger_min_height;
		}

		this.content_draggable_height = hole_height - this.scrolled.height();
		this.draggable_height = dragged_height - dragger_height;

		percent = this.scrolled.scrollTop() / this.content_draggable_height;
		this.dragger.css("top", Math.round(percent * this.draggable_height));

		this.dragger.height(dragger_height)
		.draggable({
			axis: "y",
			containment: "parent",
			drag: function(event, ui) {
				var percent = ui.position.top / instance.draggable_height;
				instance.scrolled.scrollTop(Math.round(percent * instance.content_draggable_height));
			}
		});

		this.up.unbind("click");
		this.up.click(function() {
			instance.up_click();
		});
		this.down.unbind("click");
		this.down.click(function() {
			instance.down_click();
		});

		$(this.obj).unbind("mousewheel");
		$(this.obj).bind(
			"mousewheel",
			function(event, delta) {
				return instance.mousewheel(event, delta);
			});
	}

	VerticalScroller.prototype.mousewheel = function(event, delta) {
		var percent = (this.scrolled.scrollTop() - delta * this.opts.wheel_speed_height) / this.content_draggable_height;
		var min_step = Math.abs(delta * this.opts.wheel_speed_height / this.content_draggable_height);
		if(percent < 0  || percent - min_step < 0) {
			percent = 0;
		} else if(percent > 1 || percent + min_step > 1) {
			percent = 1;
		}
		this.set_percent(percent);
		return false;
	}

	VerticalScroller.prototype.up_click = function() {
		var percent = (this.scrolled.scrollTop() - this.opts.up_step_height) / this.content_draggable_height;
		if(percent < 0) {
			percent = 0;
		}
		this.set_percent(percent);
	}

	VerticalScroller.prototype.down_click = function() {
		var percent = (this.scrolled.scrollTop() + this.opts.down_step_height) / this.content_draggable_height;
		if(percent > 1) {
			percent = 1;
		}
		this.set_percent(percent);
	}

	VerticalScroller.prototype.set_percent = function(percent) {
		var event = $.Event("VerticalScrollerPercent");
		event.percent = percent;
		$(this.obj).trigger(event);
		this.scrolled.scrollTop(Math.round(percent * this.content_draggable_height));
		this.dragger.css("top", Math.round(percent * this.draggable_height));
	}

	$.fn.extend({
		VerticalScroller: function(opts) {
			return this.each(
				function() {
					this.sc = new VerticalScroller(this, opts);
				});
		},
		VerticalScrollerExtend: function(opts) {
			return this.each(
				function() {
					if(!this.sc) {
						return;
					}
					this.sc.extend(opts);
				});
		}
	});
})(jQuery);
