if (!twodegrees) var twodegrees = {};

twodegrees.scrollbox = function() {

	var event = global.event,
		win = window,
		dom = global.dom,
		doc = document,

		draghandleractive,
		draghandlerlist = [],
		draghandlercount = 0,
		draghandlerendlist = [],
		draghandlerendcount = 0,

		scrollboxlist = ['imagelist','newslist'],
		initprocess = true,
		winonloadfired;

	function getmousepositiony(e) {

		return event.getmouseposition(e).y;
	}

	// removetexthighlight() unselects highlighted text on page, we call this method when the up/down/drag functions are used to clean up any selected text
	function removetexthighlight() {

		win.setTimeout(removetexthighlightcallback,0);
	}

	function removetexthighlightcallback() {

		if (win.getSelection) {
			// W3
			var sel = win.getSelection();
			if (sel && sel.removeAllRanges) sel.removeAllRanges();

		} else if (doc.selection && doc.selection.empty) {
			// IE
			doc.selection.empty();
		}
	}

	function handler() {

		var obj = {},

			// global handler node references
			scrollbarnode,
			panelnode,

			// scrollbar position, direction and running status
			scrollbarcurpos = 0,
			scrollbarcurdir,

			// scrollbar dimension, max throw, bar to panel ratio
			scrollbarheight,
			scrollbarmaxthrow,
			scrollbartopanelratio,

			// move type will either be 'shift' (buttons or mouse wheel) or 'frame' depending on what started the currently running movement
			// we use this to ensure 'shift' movements lock out running 'frame' tween movements and vice-versa
			scrollbarmovetype = '',

			// scrollbar end point, and shift distance, used specifically by the shift up/down (button/mouse wheel) system
			scrollbarendpoint,
			scrollbarshiftdistance,
			scrollbarmousewheelshiftdistance,

			// drag offset between the mouse pointer and the scrollbar, when -1 means dragging is not in progress used specifically by the scrollbar drag system
			scrollbardragoffset = -1,

			// top y pixel position of the scrollbar frame on the page, used by the scroll frame clicker and scrollbar dragger
			scrollbarframetop,

			tweenhandler = new dom.animation.tween(
				{
					handler: function(tween) { scrolltweenhander(tween.value); },
					finish: scrolltweenfinishhandler
				},
				0,10,0.5,
				'easeout'
			);

		function isscrollbardragging() {

			return (scrollbardragoffset >= 0);
		}

		function isscrollbarrunning() {

			return (scrollbarmovetype != '');
		}

		function scrolltweenfinishhandler() {

			// reset tween handler, so it's direction is again 'forward'
			tweenhandler.reset();

			// reset move type value
			scrollbarmovetype = '';
		}

		function scrolltweenhander(value) {

			// update scrollbar DOM position
			scrollbarnode.style.top = value + "px";

			// update scroll panel DOM position to move the actual content
			panelnode.style.top = Math.floor(value * scrollbartopanelratio * -1) + "px";

			// save current scrollbar position
			scrollbarcurpos = value;
		}

		function scrollbardraghandlerend() {

			if (!isscrollbardragging()) return;

			// reset scrollbardragoffset to denote end of drag process
			scrollbardragoffset = -1;
		}

		function scrollbardraghandler(e) {

			if (!isscrollbardragging()) return;

			// stops the mouse moving over text while dragging getting selected in IE
			event.preventdefault(e);

			// calculate the new drag position for the scrollbar
			var newdragpos = getmousepositiony(e) - scrollbarframetop - scrollbardragoffset;
			newdragpos = Math.max(0,newdragpos);
			newdragpos = Math.min(newdragpos,scrollbarmaxthrow);

			// update scrollbar and content panel in the DOM
			scrolltweenhander(newdragpos);
		}

		function scrollbardraghandlerstart(e) {

			// preventdefault to stop dragging of the scrollbar node on screen
			event.preventdefault(e);

			if (isscrollbarrunning()) {
				// scrollbar tween is currently running either via a 'shift' or 'frame' movement type, stop it now
				tweenhandler.reset();
				scrollbarmovetype = '';
			}

			// calculate the offset between the top of the scrollbar and the current mouse position
			scrollbardragoffset = getmousepositiony(e) - scrollbarframetop - scrollbarcurpos;
		}

		function scrollbarframehandler(e) {

			// dont do a frame movement if a button movement is running
			if (scrollbarmovetype == 'shift') return;

			removetexthighlight();

			var frameposy = getmousepositiony(e) - scrollbarframetop,
				newposition = -1;

			// check if frameposy is within a valid click area
			if (frameposy < scrollbarcurpos) newposition = frameposy;
			if (frameposy > (scrollbarcurpos + scrollbarheight)) newposition = frameposy - scrollbarheight;
			if (newposition < 0) return;

			// if scrollbar is currently running and direction will change reset the tween first and set its 'from' position
			if (isscrollbarrunning()) {
				if (
					((newposition > scrollbarcurpos) && (scrollbarcurdir < 0)) ||
					((newposition < scrollbarcurpos) && (scrollbarcurdir > 0))
				) {
					tweenhandler.reset();
					tweenhandler.setfrom(scrollbarcurpos);
					scrollbarmovetype = '';
				}

			} else {
				// scrollbar not running - set 'from' tween point to be the current scrollbar position
				tweenhandler.setfrom(scrollbarcurpos);
			}

			// set the tween 'to' end point and remember the current direction
			tweenhandler.setto(newposition);
			scrollbarcurdir = (newposition > scrollbarcurpos) ? 1 : -1;

			// if scrollbar not currently running, start the tween now
			if (!isscrollbarrunning()) tweenhandler.start();
			scrollbarmovetype = 'frame';
		}

		function scrollshifthandler(direction,shiftdistance) {

			// dont do a button movement if a frame movement is running
			if (scrollbarmovetype == 'frame') return;

			removetexthighlight();

			function resettweenfromcurpos() {

				tweenhandler.setfrom(scrollbarcurpos);
				scrollbarendpoint = scrollbarcurpos;
			}

			if (isscrollbarrunning()) {
				// if scrollbar is running and direction has changed, reset the tween for the direction change
				if (direction != scrollbarcurdir) {
					tweenhandler.reset();
					resettweenfromcurpos();
					scrollbarmovetype = '';
				}

			} else {
				// scrollbar not running - set 'from' tween point to be the current scrollbar position
				resettweenfromcurpos();
			}

			// calculate the end point for the scrollbar, and ensure it is within bounds
			scrollbarendpoint += (direction * shiftdistance);
			scrollbarendpoint = Math.min(scrollbarendpoint,scrollbarmaxthrow);
			scrollbarendpoint = Math.max(scrollbarendpoint,0);

			// if calulated end point equals the current scrollbar position, then no work to do
			if (scrollbarcurpos == scrollbarendpoint) return;

			// set the tween 'to' end point and remember the current direction
			tweenhandler.setto(scrollbarendpoint);
			scrollbarcurdir = direction;

			// if scrollbar not currently running, start the tween now
			if (!isscrollbarrunning()) tweenhandler.start();
			scrollbarmovetype = 'shift';
		}

		function calculatescrollbarmetrics(viewportheight,panelheight,scrollbarframeheight) {

			// calculate the height of the scrollbar (based on content amount), max throw for the scrollbar and scrollbar position to panel position ratio
			scrollbarheight = Math.floor(scrollbarframeheight * (viewportheight / panelheight));
			scrollbarmaxthrow = scrollbarframeheight - scrollbarheight;
			scrollbartopanelratio = (panelheight - viewportheight) / scrollbarmaxthrow;

			// calculate the shift distance to move when the up/down scrollshifthandler() method is called
			// 1/4 of the scrollbar max throw with button clicks and 1/6 with mouse wheel movements
			scrollbarshiftdistance = Math.ceil(scrollbarmaxthrow / 4);
			scrollbarmousewheelshiftdistance = Math.ceil(scrollbarmaxthrow / 6);
		}

		obj.init = function(el) {

			var viewport = el.getElementsByTagName('div')[0];
			panelnode = dom.node('div',{ 'class': 'panel' });

			// put all viewport child nodes into <div class="panel"> to be placed back inside the viewport, the panel <div> will then be moved up/down for scrolling
			while (viewport.firstChild) panelnode.appendChild(viewport.firstChild);
			viewport.appendChild(panelnode);

			// if height of panel is less than or equal to that of the viewport then don't build the scroll interface
			if (panelnode.offsetHeight <= viewport.offsetHeight) return;

			// add enabled class to scrollbox container
			el.className += ' scrollbox-enabled';

			// create scrollbar frame, scrollbar and up/down button nodes
			var scrollbarframenode = dom.node('div',{ 'class': 'scrollbarframe' }),
				scrollupbuttonnode = dom.node('div',{ 'class': 'scrollup' }),
				scrolldownbuttonnode = dom.node('div',{ 'class': 'scrolldown' });

			scrollbarnode = dom.node('div',{ 'class': 'scrollbar' });

			// insert scrollbar mechanics into the DOM
			dom.insertafter(scrollbarframenode,viewport);
			scrollbarframenode.appendChild(scrollbarnode);

			dom.insertafter(scrollupbuttonnode,viewport);
			dom.insertafter(scrolldownbuttonnode,viewport);

			// calculate various scrollbar metrics
			calculatescrollbarmetrics(viewport.offsetHeight,panelnode.offsetHeight,scrollbarframenode.offsetHeight);

			// attach mousedown handlers to scrollupbuttonnode/scrolldownbuttonnode & mousewheel handler to viewport node
			// don't bother if scrollbarshiftdistance or scrollbarmousewheelshiftdistance is zero
			if ((scrollbarshiftdistance != 0) && (scrollbarmousewheelshiftdistance != 0)) {
				event.add(scrollupbuttonnode,'mousedown',function() { scrollshifthandler(-1,scrollbarshiftdistance); });
				event.add(scrolldownbuttonnode,'mousedown',function() { scrollshifthandler(1,scrollbarshiftdistance); });

				event.add(el,'mousewheel',function(e) {

					// shift scrollbox based on spin direction of the mouse wheel
					scrollshifthandler(event.getmousewheeldir(e),scrollbarmousewheelshiftdistance);

					// stop scroll wheel scrolling the whole page if page height is greater than browser height
					event.preventdefault(e);
				});
			}

			// attach click handler to the scrollbar frame
			event.add(scrollbarframenode,'click',scrollbarframehandler);

			// attach mouse down/up handlers to the actual scrollbar and document mousemove/mouseup events to handle scrollbar dragging
			// these last two are attached to the proxy handlers draghandlerlist and draghandlerendlist so only a single event is attached to the document for each
			event.add(scrollbarnode,'mousedown',scrollbardraghandlerstart);
			draghandlerlist[draghandlercount++] = scrollbardraghandler;
			draghandlerendlist[draghandlerendcount++] = scrollbardraghandlerend;

			// save the y position on the page of the scrollbarframenode
			scrollbarframetop = dom.getnodeposition(scrollbarframenode).top;

			// set height of the scrollbar
			scrollbarnode.style.height = scrollbarheight + "px";
		};

		return obj;
	}

	function initscrollboxes() {

		if (!initprocess) {
			var initcount = 0;

			for (var i = 0;i < scrollboxlist.length;i++) {
				if (!scrollboxlist[i]) continue;
				initcount++;

				// check if scrollbox container is found and is complete in the DOM
				var scrollboxnode = $(scrollboxlist[i]);
				if (!dom.isnodecomplete(scrollboxnode)) continue;

				// scroll box is ready to init
				var x = new handler();
				x.init(scrollboxnode);
				scrollboxlist[i] = false;
			}

			// if initcount is zero then no more scrollboxes to setup
			if (!initcount) return;
		}

		// setup timeout to try again in a short while - unless window onload has already fired
		if (!winonloadfired) win.setTimeout(initscrollboxes,20);
		initprocess = false;
	}

	// setup system to quickly find scrollbox items in page
	initscrollboxes();

	// setup a shared document mousemove/mouseup hander - since the global.event handler for IE can't handle attaching
	// multiple identical function signatures to the same object with its event/this fixing system
	event.add(doc,'mousedown',function() { draghandleractive = true; });

	event.add(doc,'mousemove',function(e) {

		if (!draghandleractive || !draghandlercount) return;
		for (var i = 0;i < draghandlercount;i++) draghandlerlist[i](e);
	});

	event.add(doc,'mouseup',function() {

		draghandleractive = false;
		if (!draghandlerendcount) return;
		for (var i = 0;i < draghandlerendcount;i++) draghandlerendlist[i]();
	});

	event.add(win,'load',function() { winonloadfired = true; });

	// set class="js" on <html> tag
	global.addhtmlclass('js');
}();
