global.dom = function() {

	var obj = {},
		win = window,
		doc = document;

	obj.getprevioussibling = function(el,tagname) { return getsiblingbytagname(el,tagname,true); };
	obj.getnextsibling = function(el,tagname) { return getsiblingbytagname(el,tagname); };

	function getsiblingbytagname(el,tagname,previous) {

		while (el = ((previous) ? el.previousSibling : el.nextSibling)) {
			if (el.nodeName.toLowerCase() == tagname) return el;
		}

		return null;
	}

	obj.getnodeposition = function(el) {

		var left = 0,
			top = 0;

		while (el.offsetParent) {
			left += el.offsetLeft;
			top += el.offsetTop;
			el = el.offsetParent;
		}

		return {
			left: left,
			top: top
		};
	};

	obj.insertbefore = function(el,ref) { ref.parentNode.insertBefore(el,ref); };
	obj.insertafter = function(el,ref) { ref.parentNode.insertBefore(el,ref.nextSibling); };

	obj.removechildren = function(el) { while (el.firstChild) el.removeChild(el.firstChild); };

	obj.isnodecomplete = function(el) {

		while (el && !el.nextSibling) el = el.parentNode;
		return (el) ? true : false;
	};

	// creates a new dom node with attributes and optional child nodes
	obj.node = function(name,attrib,child) {

		var el = doc.createElement(name);
		if (attrib) {
			for (var i in attrib) {
				var v = attrib[i];

				if (i == 'opacity') { obj.setopacity(el,v); continue; }
				if (i == 'class') i = 'className';
				if (i == 'colspan') i = 'colSpan';
				if (i == 'for') i = 'htmlFor';

				el[i] = v;
			}
		}

		if (child) {
			for (var i = 0,j = child.length;i < j;i++) {
				var item = child[i];
				el.appendChild((typeof item == 'string') ? doc.createTextNode(item) : item);
			}
		}

		return el;
	};

	// allows easy update of multiple node css properties
	obj.setnodecss = function(el,attrib) {

		for (var i in attrib) {
			var v = attrib[i];
			if (i == 'opacity') { obj.setopacity(el,v); continue; }
			el.style[i] = v;
		}
	};

	// sets element opacity where opacity is between 0 and 1
	obj.setopacity = function(el,opacity) {

		// ensure opacity is between 0 and 1, 3 decimal places max
		if (opacity > 1) opacity = 1;
		else if (opacity < 0) opacity = 0;
		else opacity = Math.round(opacity * 1000) / 1000;

		var style = el.style;
		style.filter = 'alpha(opacity=' + Math.floor(opacity * 100) + ')';
		style.KhtmlOpacity = opacity;
		if (opacity == 1) opacity = 0.9999999;
		style.opacity = opacity;
		style.MozOpacity = opacity;
	};

	obj.getviewportdim = function() {

		var docel = doc.documentElement || null,
			docclientwidth = (docel && docel.clientWidth) ? docel.clientWidth : null,
			width = 0,
			height = 0;

		// w3 mode, IE standards compliant, IE quirks, fail
		if (win.innerWidth) {
			width = docclientwidth || win.innerWidth;
			height = win.innerHeight;

		} else if (docclientwidth) {
			width = docclientwidth;
			height = docel.clientHeight;

		} else if (doc.body) {
			width = doc.body.clientWidth;
			height = doc.body.clientHeight;
		}

		return { width: width,height: height };
	};

	obj.getpagedim = function() {

		var docbody = doc.body || null,
			width = 0,
			height = 0;

		// w3 mode, IE standards compliant, IE older/quirks, fail
		if (docbody) {
			if (win.innerHeight && win.scrollMaxY) {
				width = docbody.scrollWidth;
				height = win.innerHeight + win.scrollMaxY;

			} else if (docbody.scrollHeight > docbody.offsetHeight) {
				width = docbody.scrollWidth;
				height = docbody.scrollHeight;

			} else {
				width = docbody.offsetWidth;
				height = docbody.offsetHeight;
			}
		}

		return { width: width,height: height };
	};

	obj.getpagescroll = function() {

		// w3 mode, IE standards compliant, older IE, fail
		if (win.pageYOffset) return win.pageYOffset;

		var docel = doc.documentElement || null;
		if (docel && docel.scrollTop) return docel.scrollTop;

		if (doc.body) return doc.body.scrollTop;

		return 0;
	};

	return obj;
}();


global.dom.animation = function() {

	var obj = {},
		bgregexp = /^(\d+)([^ ]*) (\d+)(.*)$/,
		backinoutvalue = 1.70158,
		tweeninterval = null,
		activetweenlist = [],

		// c = current frame, t = total frames, s = start, d = delta
		easingfunc = {
			none: function(c,t,s,d) { return ((d * c) / t) + s; },
			easein: function(c,t,s,d) { return (d * (c /= t) * c) + s; },
			easeout: function(c,t,s,d) { return (-d * (c /= t) * (c - 2)) + s; },
			backin: function(c,t,s,d) { return (d * (c /= t) * c * ((backinoutvalue + 1) * c - backinoutvalue)) + s;	},
			backout: function(c,t,s,d) { return d * ((c = c / t - 1) * c * ((backinoutvalue + 1) * c + backinoutvalue) + 1) + s;	},
			bouncein: function(c,t,s,d) { return d - easingfunc.bounceout(t - c,t,0,d) + s; },
			bounceout: function(c,t,s,d) {

				var y = 2.75,
					z = 7.5625;

				if ((c /= t) < (1 / y)) return d * (z * c * c) + s;
				if (c < (2 / y)) return d * (z * (c -= (1.5 / y)) * c + 0.75) + s;
				if (c < (2.5 / y)) return d * (z * (c -= (2.25 / y)) * c + 0.9375) + s;
				return d * (z * (c -= (2.625 / y)) * c + 0.984375) + s;
			}
		};

	function animatetweenlist() {

		var updatetween = 0,
			endtween = 0,
			tweenfinishlist = [];

		var index = activetweenlist.length;
		while (index--) {
			// get next tween, and check if one at this index - if not next loop
			var tween = activetweenlist[index];
			if (!tween) continue;
			updatetween++;

			var el = tween.el,
				css = tween.css,
				current = tween.current;

			// get current element value & round result (unless we are modifying element opacity)
			var value = tween.easing(tween.frame,current.framecount,current.from,current.to - current.from);
			value = (css != 'opacity') ? Math.floor(value) : value;
			tween.value = value;

			if (el && css) {
				// update element css
				if (css == 'bgx') updbgpos(el,value,'*');
				else if (css == 'bgy') updbgpos(el,'*',value);
				else if (css == 'opacity') global.dom.setopacity(el,value);
				else el.style[css] = value + 'px';
			}

			// if tween has a handler() callback function defined, call it now, passing tween object
			if (tween.handler) tween.handler(tween);

			if (tween.frame++ >= current.framecount) {
				// end of tween - remove from activetweenlist array
				removeactivetween(tween);
				endtween++;

				// if tween has a finish() callback function defined, add to tweenfinishlist array
				// important to add finish() methods to tweenfinishlist since finish() methods might add new tweens into the activetweenlist array
				// so they need to be added outside of the current animation loop we are running in
				if (tween.finish) tweenfinishlist[tweenfinishlist.length] = tween;
			}
		}

		if (updatetween == endtween) {
			// all tweens ended
			window.clearInterval(tweeninterval);
			tweeninterval = null;
		}

		// if tweens in tweenfinishlist array call each finish() function
		var index = tweenfinishlist.length;
		while (index--) {
			var tween = tweenfinishlist[index];
			tween.finish(tween);
		}
	}

	function removeactivetween(tween) {

		// if tween index is less than zero then it's not in the active tween list, so exit
		if (tween.index < 0) return;

		tween.running = false;
		activetweenlist[tween.index] = null;
		tween.index = -1;
	}

	function updbgpos(el,x,y) {

		var style = el.style,
			match;

		if (!(match = bgregexp.exec(style.backgroundPosition))) {
			// no bgposition match - set defaults
			match = [,0,'px',0,'px'];
		}

		if (x != '*') { match[1] = x; match[2] = 'px'; }
		if (y != '*') { match[3] = y; match[4] = 'px'; }
		style.backgroundPosition = match[1] + match[2] + ' ' + match[3] + match[4];
	}

	// skewtweenframe() takes a running tween and adjusts the frame to place it as close to the tweens current value as possible
	function skewtweenframe(tween) {

		var newframe = -1,
			current = tween.current,
			framecount = current.framecount,
			forward = (current.to > current.from);

		while (newframe++ < framecount) {
			// calculate easing value for newframe
			var value = tween.easing(newframe,framecount,current.from,current.to - current.from);

			if (
				(forward && (value > tween.value)) ||
				(!forward && (value < tween.value))
			) {
				// found frame calculating the next value after last rendered value, set tween to this new frame and exit
				tween.frame = newframe;
				return;
			}
		}

		// can't find a frame to use, set tween frame back to zero - animation will then restart
		tween.frame = 0;
	}

	obj.tween = function(data,fromvalue,tovalue,duration,easing) {

		// note: 'id' isn't used by global.dom.animation, but is a handy tween unique identifier for handler/finish callback functions
		var datalist = ['el','css','handler','finish','id'],
			datanum = datalist.length;

		while (datanum--) {
			var key = datalist[datanum];
			this[key] = data[key] || null;
		}

		this.frame = 0;
		this.index = -1;
		this.value = fromvalue;
		this.easing = easingfunc[easing || 'none'];

		// framecount will be the number of frames a tween will take based on desired second duration and the 30ms interval delay
		this.base = {
			from: fromvalue,
			to: tovalue,
			framecount: Math.floor((duration * 1000) / 30)
		};
	};

	obj.tween.prototype = {

		start: function() {

			var base = this.base,
				baseframecount = base.framecount;

			function setcurrent(self,fromvalue,tovalue,framecount) {

				self.current = {
					from: fromvalue,
					to: tovalue,
					framecount: framecount
				};
			}

			// set/reverse tween direction
			this.forward = !this.forward;

			if (this.running) {
				// note: not a 100% method of reverse when called in quick succession
				// updated framecount will never be less than 60% of the base framecount

				// update current from/to/framecount
				setcurrent(
					this,
					this.value,
					(this.forward) ? base.to : base.from,
					Math.floor(Math.max(
						(this.frame / this.current.framecount) * baseframecount,
						baseframecount * 0.6
					))
				);

			} else {
				// add tween to activetweenlist
				var index = activetweenlist.length;
				for (var i = 0,j = activetweenlist.length;i < j;i++) {
					if (!activetweenlist[i]) {
						// found unused activetweenlist slot
						index = i;
						break;
					}
				}

				// add tween to activetweenlist array and save index against tween object
				activetweenlist[index] = this;
				this.index = index;

				// update current from/to/framecount
				setcurrent(
					this,
					(this.forward) ? base.from : base.to,
					(this.forward) ? base.to : base.from,
					baseframecount
				);
			}

			this.running = true;
			this.frame = 0;

			// start tweening process
			if (!tweeninterval) tweeninterval = window.setInterval(animatetweenlist,30);
		},

		reset: function() {

			this.running = false;
			this.frame = 0;
			this.value = this.base.from;
			this.forward = false; // will be set 'true' when the start() tween method is next called

			// if tween has an assigned activetweenlist index, remove it from activetweenlist array now
			removeactivetween(this);
		},

		setfrom: function(fromvalue) {

			this.base.from = fromvalue;
			if (!this.running) return;

			this.current.from = fromvalue;
			skewtweenframe(this);
		},

		setto: function(tovalue) {

			this.base.to = tovalue;
			if (!this.running) return;

			this.current.to = tovalue;
			skewtweenframe(this);
		}
	};

	return obj;
}();
