(function($){
	
	var CalculateBarHeight = function(c, u) {
		//container: the height of the content area
		//user: the user defined height of the srollbar. can be percentage or pixel
		if (typeof(u) == "string") {
			c = (eval(u.substr(0, u.length - 1)) / 100) * c;
		} else {
			c = u;
		}
		return c;
	};
	
	var List = function() {
		var _list = [];
		
		this.add = function(value) {
			_list[_list.length] = value;
		};
		
		this.join = function(separator) {
			return _list.join(separator);
		};
	};
	
	var ScrollBar = function(p) {
		var me = this;
		var height = {
			bar: 0,
			track: 0,
			inner: 0,
			limit: 0,
			outer: 0
		};
		
		var $html = $("html");
		var $document = $(document);
		
		//mouse y properties on bar
		var mouseOnBar = {
			before: 0,
			now: 0
		};
		
		//hotspot
		var limit = {
			top: 0,
			bottom: 0
		};
		
		//smooth scroll vars
		var ss = {
			delay: 0,
			direction: -1 //up: 0, down: 1
		};
		
		//scroll increments
		var movement = {
			arrow: 5,
			scroll: 10,
			track: 10,
			select: 5 //changes depending on the location of the mouse
		};
		
		//timeout for hiding the scrollbar
		var hide = {
			timeout: 0,
			visible: false,
			overBar: false
		};
		
		//used for track scrolling
		var mouse = {
			overBar: false,
			pageY: 0
		};
		
		
		var initialize = function() {
			p.$bar.bind("mousedown", function(e) {
				e.stopPropagation();
				
				//IE and Opera don't fully support :active
				p.$bar.addClass("venscrollbar-bar-active");
				
				var lastmove = 0;
				
				var oldCursor = $html.css("cursor");
				$html.css({"cursor":p.$bar.css("cursor")});
				
				mouseOnBar.before = 0;
				
				$document.bind("mousemove.venscrollbar-bar", function(e) {
					mouseOnBar.now = e.pageY - p.$bar.offsetParent().offset().top;
					if (mouseOnBar.before == 0) {
						mouseOnBar.before = mouseOnBar.now;
					}
					
					var distance = e.pageY - lastmove;
					lastmove = e.pageY;
					moveByMouse(distance);
				})
				.bind("mouseup.venscrollbar-bar", function() {
					//IE and Opera don't fully support :active
					p.$bar.removeClass("venscrollbar-bar-active");
					
					$html.css({"cursor":oldCursor});
					
					$document.unbind("mousemove.venscrollbar-bar")
					.unbind("mouseup.venscrollbar-bar");
				});
				
				
			});
			
			disableSelection(p.$bar[0]);
			
			// Apple mobile support
			var iOS_Support = function() {
				
				var lastmove = -1;
				mouseOnBar.before = 0;
				
				p.$bar[0].addEventListener("touchstart", function() {
					
					p.$bar.addClass("venscrollbar-bar-active");
					
					lastmove = -1;
					mouseOnBar.before = 0;
					
				}, false);
				
				p.$bar[0].addEventListener("touchend", function() {
					
					p.$bar.removeClass("venscrollbar-bar-active");
					
				}, false);
				
				p.$bar[0].addEventListener("touchmove", function(e){
					
					e.preventDefault();
					
					if (lastmove == -1) {
						lastmove = e.targetTouches[0].pageY;
					}
					
					mouseOnBar.now = e.targetTouches[0].pageY - p.$bar.offsetParent().offset().top;
					
					if (mouseOnBar.before == 0) {
						mouseOnBar.before = mouseOnBar.now;
					}
					
					var distance = e.targetTouches[0].pageY - lastmove;
					lastmove = e.targetTouches[0].pageY;
					moveByMouse(distance);
					
				}, false);
				
				
				p.$inner[0].addEventListener("touchstart", function() {
					
					lastmove = -1;
					
				}, false);
				
				p.$inner[0].addEventListener("touchmove", function(e) {
					
					e.preventDefault();
					
					if (lastmove == -1) {
						lastmove = e.targetTouches[0].pageY;
					}
					
					var distance = e.targetTouches[0].pageY - lastmove;
					lastmove = e.targetTouches[0].pageY;
					moveByFinger(distance);
					
				}, false);
				
			};
			if (navigator.userAgent.match(/(iPad|iPod|iPhone)/i) != null) iOS_Support();
			
			
			p.$inner.bind("mousedown", function() {
				var interval = 0;
				var currentDirection = 0; //0: inactive, 1: up, 2: down
				
				$document.bind("mousemove.venscrollbar-select", function(e) {
					//only clear interval and set new one if the direction changes
					if (e.pageY > limit.bottom) {
						
						//scroll speed slows down the closer the mouse is to the frame
						if (e.pageY < limit.bottom + 10) {
							movement.select = 1;
						} else if (e.pageY < limit.bottom + 20) {
							movement.select = 2;
						} else if (e.pageY < limit.bottom + 30) {
							movement.select = 3;
						} else if (e.pageY < limit.bottom + 50) {
							movement.select = 5;
						} else {
							movement.select = 7;
						}
						
						if (currentDirection != 2) {
							clearInterval(interval);
							currentDirection = 2;
							interval = setInterval(function() {
								moveByIncrement(movement.select, true);
							}, 30);
						}

					} else if (e.pageY < limit.top) {
						
						//scroll speed slows down the closer the mouse is to the frame
						if (e.pageY > limit.top - 10) {
							movement.select = 1;
						} else if (e.pageY > limit.top - 20) {
							movement.select = 2;
						} else if (e.pageY > limit.top - 30) {
							movement.select = 3;
						} else if (e.pageY > limit.top - 50) {
							movement.select = 5;
						} else {
							movement.select = 7;
						}
						
						if (currentDirection != 1) {
							clearInterval(interval);
							currentDirection = 1;
							interval = setInterval(function() {
								moveByIncrement(-movement.select, true);
							}, 30);
						}
					} else {
						clearInterval(interval);
						currentDirection = -1;
					}
				})
				.bind("mouseup.venscrollbar-select", function() {
					clearInterval(interval);
					currentDirection = -1;
					
					$document.unbind("mousemove.venscrollbar-select")
					.unbind("mouseup.venscrollbar-select");
				});
			});
			
			p.$track.offsetTop = p.$track.offset().top;
			p.$track.bind("mousedown", function(e) {
				var $this = $(this);
				$this.addClass("venscrollbar-track-active");
				
				$document.bind("mouseup.venscrollbar-track", function() {
					$this.removeClass("venscrollbar-track-active");
					
					$document.unbind("mouseup.venscrollbar-track");
				});
				
				if (e.pageY - p.$track.offsetTop < p.$bar.top) {
					mouse.pageY = e.pageY;
					mouse.overBar = false;
					track_mousedown(-15);
				} else if (e.pageY - p.$track.offsetTop > p.$bar.top + height.bar) {
					mouse.pageY = e.pageY;
					mouse.overBar = false;
					track_mousedown(15);
				}

			});
			disableSelection(p.$track[0]);
			
			
			if (p.options.autoHide) {
				p.$scrollArea.css({"opacity":"0"})
				.bind("mouseenter", function() {
					hide.overBar = true;
					clearTimeout(hide.timeout);
				})
				.bind("mouseleave", function() {
					hide.overBar = false;
				});
				
				p.$outer.bind("mousemove", function() {
					if (hide.overBar) {return true;}
					
					clearTimeout(hide.timeout);
					hide.timeout = setTimeout(function() {
						p.$scrollArea.animate({opacity:0}, 500, function() {
							hide.visible = false;
						});
						
					}, 3500);
					
					if (!hide.visible) {
						hide.visible = true;
						p.$scrollArea.animate({opacity:1}, 500);
					}
					
				});
			}
			
			setProperties();
			enableMouseWheel();
		};
		
		
		//methods
		var move = function(distance, smooth) {
			p.$bar.top += distance;
			var top = (height.inner / height.track) * p.$bar.top;
			
			if (smooth) {
				//prevent user from changing direction while timeout is still running
				if (ss.direction == -1) {
					//if direction hasn't been set since last move, set it now
					if (distance > 0) {
						ss.direction = 1; //down
					} else if (distance < 0) {
						ss.direction = 0; //up
					}
				} else {
					if ((ss.direction == 0 && distance > 0) || (ss.direction == 1 && distance < 0)) {
						p.$bar.top -= distance;
						return false;
					}
				}
				
				p.$bar.css({"top":p.$bar.top + "px"});
				
				//allow for scrolling really fast
				clearTimeout(ss.delay);
				ss.delay = setTimeout(function() {
					ss.direction = -1;

					p.$inner.stop().animate({top:-top}, 160);
				}, 25);
				
			} else {
				p.$bar.css({"top":p.$bar.top + "px"});
				
				p.$inner.css({"top":-top});
			}
		};
		
		var moveByMouse = function(distance) {
			if (distance == 0) {
				return false;
			}
			//exit if the bar is at its limits
			if (p.$bar.top + distance < 0 || p.$bar.top + distance + height.bar > height.track) {
				return false;
			}
			//exit if the mouse isn't at or beyond the position it was at when the mouse was clicked
			if (distance < 0 && mouseOnBar.now >= mouseOnBar.before || distance > 0 && mouseOnBar.now <= mouseOnBar.before) {
				return false;
			}
			
			mouseOnBar.before += distance; //update starting position on bar when it moves
			
			move(distance, false);
		};
		
		var moveByFinger = function(distance) {
			if (distance == 0) {
				return false;
			}
			//convert distance
			distance = (distance * (height.bar / height.inner)) * -1;
			
			//exit if the inner is at its limits
			if (p.$bar.top + distance < 0 || p.$bar.top + distance + height.bar > height.track) {
				return false;
			}
			
			move(distance, false);
		};
		
		var moveByIncrement = function(distance, forceRegular) {
			if ((p.$bar.top == 0 && distance < 0) || (p.$bar.top + height.bar == height.track && distance > 0)) {
				return false;
			}
			
			if (p.$bar.top + distance < 0) {
				distance = -p.$bar.top;
			} else if (p.$bar.top + distance + height.bar > height.track) {
				distance = height.track - height.bar - p.$bar.top;
			}
			
			if (p.options.smoothScroll && !forceRegular) {
				move(distance, true);
			} else {
				move(distance, false);
			}
			return true;
		};
		
		var arrow_mousedown = function(increment) {
			moveByIncrement(increment, false);
				
			var interval = 0;
			var timeout = setTimeout(function() {
				interval = setInterval(function() {
					moveByIncrement(increment, true);
				}, 30);
			}, 410);
			$document.bind("mouseup.venscrollbar-arrow", function() {
				$document.unbind("mouseup.venscrollbar-arrow");
				clearTimeout(timeout);
				clearInterval(interval);
			});
		};
		
		var track_mousedown = function(increment) {
			moveByIncrement(increment, false);
			
			//check if mouse is over bar and update mouse position
			$document.bind("mousemove.venscrollbar-track-scroll", function(e) {
				mouse.pageY = e.pageY;
				if (mouse.pageY - p.$track.offsetTop > p.$track.top && mouse.pageY - p.$track.offsetTop < p.$track.top + height.track) {
					mouse.overBar = true;
				}
			});
			
			var interval = 0;
			var timeout = setTimeout(function() {
				interval = setInterval(function() {
					if (mouse.overBar) {
						clearInterval(interval);
						$document.unbind("mousemove.venscrollbar-track-scroll");
						return false;
					}
					moveByIncrement(increment, true);
					
					//check if mouse is over bar
					if (mouse.pageY - p.$track.offsetTop > p.$bar.top && mouse.pageY - p.$track.offsetTop < p.$bar.top + height.bar) {
						mouse.overBar = true;
					}
				}, 30);
			}, 410);
			$document.bind("mouseup.venscrollbar-track-scroll", function() {
				$document.unbind("mouseup.venscrollbar-track-scroll");
				clearTimeout(timeout);
				clearInterval(interval);
				$document.unbind("mousemove.venscrollbar-track-scroll");
			});
		};
		
		var setProperties = function() {
			var setHeights = function() {
				height.inner = p.$inner.height();
				height.outer = p.$outer.height();
				height.limit = CalculateBarHeight(height.outer, p.options.barHeight);
				height.track = height.limit - p.options.tArrowHeight - p.options.bArrowHeight;
			};
			setHeights();
			
			//set bar height
			var setBarHeight = function() {
				var h = (height.track * height.outer) / height.inner;
				p.$bar.css({"height": h + "px"});
				height.bar = p.$bar.height();
				
				h = h - p.options.tBarHeight - p.options.bBarHeight;
				p.$barMiddle.css({"height": h + "px"});
				return h;
			};
			setBarHeight();
			
			//center scrollbar vertically
			var offsetToCenter = (p.$outer.height() - height.limit) / 2;
			$("div.venscrollbar-scrollarea", p.$outer).css({"top":offsetToCenter + "px"});
			
			//set other variables
			p.$bar.top = p.$bar.position().top;
			limit.top = p.$outer.offset().top + offsetToCenter;
			limit.bottom = limit.top + offsetToCenter + height.limit;
			
			//hide bar if it is not needed
			if (height.bar >= height.limit) {
				p.$bar.css({"display":"none"});
				p.$window.css({"width":p.$outer.width() + "px"});
				if (p.$up != undefined) {p.$up.css({"display":"none"});}
				if (p.$down != undefined) {p.$down.css({"display":"none"});}
			} else {
				p.$bar.css({"display":"block"});
				
				if (p.options.push) {
					p.$window.css({"width":(p.$outer.width() - p.$bar.width()) + "px"});
				} else {
					p.$window.css({"width":p.$outer.width() + "px"});
				}
				
				if (p.$up != undefined) {p.$up.css({"display":"block"});}
				if (p.$down != undefined) {p.$down.css({"display":"block"});}
				
				//refresh heights
				setHeights();
				
				//set bar height again
				var h = setBarHeight();
				
				//position decoration
				var t = 0;
				if (p.options.decorationHeight > p.$barMiddle.height()) {
					t = (h - p.$barMiddle.height()) / 2;
					p.$barDecor.css({"top":t + "px", "height":p.$barMiddle.height() + "px"});
				} else {
					t = (h - p.options.decorationHeight) / 2;
					p.$barDecor.css({"top":t + "px", "height":p.options.decorationHeight + "px"});
				}
			}
		};
		
		var disableSelection = function(element){
			if (typeof(element.onselectstart) != "undefined") {
				element.onselectstart = function() {return false;}
			} else if (typeof(element.style.MozUserSelect) != "undefined") {
				element.style.MozUserSelect = "none";
			} else {
				element.onmousedown = function() {return false;}
			}
		};
		
		var enableMouseWheel = function() {
			var handler = function(e) {
				e = e || window.event;
				
				
				var delta = 0;
				if (e.wheelDelta) {delta = e.wheelDelta/120;}
    			if (e.detail) {delta = -e.detail/3;}
    			
				if (moveByIncrement(-delta * movement.scroll, false) || p.options.lockWheel){
					
					//Prevent event from bubbling up
					if(e.stopPropagation) e.stopPropagation();
					if(e.preventDefault) e.preventDefault();
					e.cancelBubble = true;
					e.cancel = true;
					e.returnValue = false;
				}
			};
			
			if (p.$outer[0].addEventListener) {
				p.$outer[0].addEventListener("mousewheel", handler, false);
				p.$outer[0].addEventListener("DOMMouseScroll", handler, false); //for mozilla
			} else {
				//IE
				p.$outer[0].onmousewheel = handler;
			}
		};
		
		this.linkArrows = function($up, $down) {
			p.$up = $up;
			p.$down = $down;
			
			$up.bind("mousedown", function() {
				arrow_mousedown(-movement.arrow);
			});
			disableSelection($up[0]);
			
			$down.bind("mousedown", function() {
				arrow_mousedown(movement.arrow);
			});
			disableSelection($down[0]);
		};
		
		this.updateProperties = function() {
			p.$inner = $("div.venscrollbar-inner", p.$outer);
			
			setProperties();
			
			p.$inner.css({"top":0});
			p.$bar.css({"top":0});
		};
		
		//constructor. last to be called
		initialize();
	};
	
	$.fn.VenScrollBar = function(options) {
		var defaults = {
			push: true,
			withArrows: true,
			smoothScroll: false,
			autoHide: false,
			arrowsOnly: false,
			barWidth: 0,
			barHeight: "100%",
			tArrowHeight: 0,
			bArrowHeight: 0,
			tTrackHeight: 0,
			bTrackHeight: 0,
			tBarHeight: 0,
			bBarHeight: 0,
			decorationHeight: 0,
			arrowsBelow: false,
			lockWheel: false
		};
		defaults = $.extend(defaults, $.fn.VenScrollBar.GlobalDefaults);
		options = $.extend(defaults, options);
		
		if (!options.withArrows) {
			options.tArrowHeight = 0;
			options.bArrowHeight = 0;
		}
		
		return this.each(function(){
			
			var $this = $(this);
			
			var height = CalculateBarHeight($this.height(), options.barHeight);
			$this.TrackHeight = height - options.tArrowHeight - options.bArrowHeight;

			$this.css({"overflow":"visible"});
			if ($this.css("position") != "absolute" || $this.css("position") != "relative") {
				$this.css({"position":"relative"});
			}
			
			$this.HTML = $this.html(); //copy html
			$this.empty(); //delete html
			
			var setup = new List();
			setup.add(	"<div class='venscrollbar-window' style='overflow:hidden;position:absolute;width:");
			setup.add(		$this.width() - options.barWidth);
			setup.add(		"px;height:100%;'><div class='venscrollbar-inner' style='position:absolute;width:100%;'></div></div>");
			$this.append(setup.join(""));
			
			$this.Inner = $("div.venscrollbar-inner", this);
			$this.Inner.append($this.HTML); //paste html
			
			//inject scrollbar
			var barHTML = new List();
			barHTML.add("<div class='venscrollbar-scrollarea' style='position:absolute;width:");
			barHTML.add(		options.barWidth);
			barHTML.add(		"px;height:");
			barHTML.add(			(typeof(options.barHeight) == "number") ? options.barHeight + "px" : options.barHeight);
			barHTML.add(			";'>");
			if (options.withArrows) {
			barHTML.add(	"<div class='venscrollbar-arrowup' style='position:absolute;");
			if (options.arrowsBelow) {
			barHTML.add(		"bottom:");
			barHTML.add(		options.tArrowHeight);
			barHTML.add(		"px;height:");
			} else {
			barHTML.add(		"top:0;height:");
			}
			barHTML.add(		options.tArrowHeight);
			barHTML.add(		"px;'></div>");
			}
			barHTML.add(	"<div class='venscrollbar-track' style='position:absolute;");
			if (options.arrowsOnly) {
			barHTML.add(		"visibility:hidden;");
			}
			barHTML.add(		"height:");
			barHTML.add(		$this.TrackHeight);
			barHTML.add(		"px;top:");
			barHTML.add(		(options.arrowsBelow) ? 0 : options.tArrowHeight);
			barHTML.add(		"px;'>");
			barHTML.add( 		"<div class='venscrollbar-track-top' style='position:absolute;top:0;height:");
			barHTML.add(			options.tTrackHeight);
			barHTML.add(			"px;'></div>");
			barHTML.add( 		"<div class='venscrollbar-track-middle' style='position:absolute;height:");
			barHTML.add(			$this.TrackHeight - options.tTrackHeight - options.bTrackHeight);
			barHTML.add(			"px;top:");
			barHTML.add(			options.tTrackHeight);
			barHTML.add(			"px;'></div>");
			barHTML.add( 		"<div class='venscrollbar-track-bottom' style='position:absolute;bottom:0;height:");
			barHTML.add(			options.bTrackHeight);
			barHTML.add(			"px;'></div>");
			barHTML.add(		"<div class='venscrollbar-bar' style='position:absolute;top:0;'>");
			barHTML.add(			"<div class='venscrollbar-bar-top' style='position:absolute;top:0;height:");
			barHTML.add(				options.tBarHeight);
			barHTML.add(				"px;'></div>");
			barHTML.add(			"<div class='venscrollbar-bar-middle' style='position:absolute;top:");
			barHTML.add(				options.tBarHeight);
			barHTML.add(				"px;'>");
			barHTML.add(				"<div class='venscrollbar-bar-decoration' style='position:absolute;height:");
			barHTML.add(					options.decorationHeight);
			barHTML.add(					"px;'></div>");
			barHTML.add(			"</div>");
			barHTML.add(			"<div class='venscrollbar-bar-bottom' style='position:absolute;bottom:0;height:");
			barHTML.add(				options.bBarHeight);
			barHTML.add(				"px;'></div>");
			barHTML.add(		"</div>");
			barHTML.add(	"</div>");
			if (options.withArrows) {
			barHTML.add(	"<div class='venscrollbar-arrowdown' style='position:absolute;bottom:0;height:");
			barHTML.add(		options.bArrowHeight);
			barHTML.add(		"px;'></div>");
			}
			barHTML.add("</div>");
			$this.append(barHTML.join(""));
			
			$this.Inner.Height = $this.Inner.height();
			$this.Bar = $("div.venscrollbar-bar", this);
			$this.Bar.Middle = $("div.venscrollbar-bar-middle", $this.Bar);
			$this.Bar.Decoration = $("div.venscrollbar-bar-decoration", $this.Bar.Middle);
			
			$this.Bar.ScrollBar = new ScrollBar({
				$bar: $this.Bar,
				$inner: $this.Inner,
				$window: $("div.venscrollbar-window", $this),
				$outer: $this,
				$barMiddle: $this.Bar.Middle,
				$barDecor: $this.Bar.Decoration,
				$track: $("div.venscrollbar-track", $this),
				$scrollArea: $("div.venscrollbar-scrollarea", $this),
				options: options
			});
			if (options.withArrows) {
				$this.Bar.ScrollBar.linkArrows($("div.venscrollbar-scrollarea > div.venscrollbar-arrowup", $this), $("div.venscrollbar-scrollarea > div.venscrollbar-arrowdown", $this));
			}
			
			//store data in dom element so that we can update it later if need be
			$this.data("venscrollbar-data", $this.Bar.ScrollBar);
		});
	};
	
	$.fn.VenScrollBar.GlobalDefaults = {};
	
	$.fn.VenScrollBar.Refresh = function(selector) {
		var $inner = $(selector);
		if ($inner.length == 0) {return false;}
		
		$inner.each(function() {
			var data = $(this).data("venscrollbar-data");
			if (!data) {return false;}

			data.updateProperties();
		});
	};
})(jQuery);
