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.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 j = i,v = attrib[i];
				if (i == 'opacity') { obj.setopacity(el,v); continue; }
				if (j == 'class') j = 'className';
				if (j == 'for') j = 'htmlFor';
				el[j] = 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)
			else 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 s = el.style;
		s.filter = 'alpha(opacity=' + Math.floor(opacity * 100) + ')';
		s.KhtmlOpacity = opacity;
		if (opacity == 1) opacity = 0.9999999;
		s.opacity = opacity;
		s.MozOpacity = opacity;
	};

	obj.getviewportdim = function() {

		var docel = doc.documentElement || null,
			width = 0,
			height = 0;

		// w3 mode, IE standards compliant, older IE, fail
		if (win.innerWidth) {
			width = (docel && docel.clientWidth) ? docel.clientWidth : win.innerWidth;
			height = win.innerHeight;

		} else if (docel && docel.clientWidth) {
			width = docel.clientWidth;
			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, older IE, fail
		if (win.innerHeight && win.scrollMaxY) {
			width = docbody.scrollWidth;
			height = win.innerHeight + win.scrollMaxY;

		} else if (docbody) {
			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 = {},
		intervaldelay = 30,
		bgregexp = /^(\d+)([^ ]*) (\d+)(.*)$/,
		tweeninterval = null,
		activetween = [];

	// c = current frame, t = total frames, s = start, d = delta
	var 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) { var o = 1.70158; return (d * (c /= t) * c * ((o + 1) * c - o)) + s;	},
		backout: function(c,t,s,d) { var o = 1.70158; return d * ((c = c / t - 1) * c * ((o + 1) * c + o) + 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 animatetweens() {

		var updatetween = 0,
			endtween = 0,
			endlist = [];

		for (var index = 0,j = activetween.length;index < j;index++) {
			// get next tween, and check if one at this index - if not next loop
			var tween = activetween[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.handler) {
				// call handler function, passing position and element (element could be null)
				tween.handler(value,el);
			}

			if (tween.frame++ >= current.framecount) {
				// end of tween - remove from tween array
				removeactivetween(index);
				endtween++;

				// if tween has a finish() callback function defined, add to endlist array
				if (tween.finish) endlist[endlist.length] = tween;
			}
		}

		if (updatetween == endtween) {
			// all tweens ended
			window.clearInterval(tweeninterval);
			tweeninterval = null;
		}

		// if tweens in endlist array call finish() callback
		for (var i = 0,j = endlist.length;i < j;i++) {
			endlist[i].finish(endlist[i]);
		}
	}

	function removeactivetween(index) {

		var tween = activetween[index];
		tween.running = false;
		tween.index = -1;
		activetween[index] = false;
	}

	function updbgpos(el,x,y) {

		var s = el.style,match;
		if (!(match = bgregexp.exec(s.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'; }
		s.backgroundPosition = match[1] + match[2] + ' ' + match[3] + match[4];
	}

	// calcframecount() returns the number of frames a tween should take based on desired duration and the intervaldelay set
	function calcframecount(duration) { return Math.floor((duration * 1000) / intervaldelay); }

	obj.tween = function(data,from,to,duration,easing) {

		this.el = data.el || null;
		if (data.css) this.css = data.css;
		if (data.handler) this.handler = data.handler;
		if (data.finish) this.finish = data.finish;

		this.value = from;
		this.easing = easingfunc[easing];
		this.base = { from: from, to: to, framecount: calcframecount(duration) };
	};

	obj.tween.prototype = {

		start: function() {

			var base = this.base,
				baseframecount = base.framecount;

			if (this.running) {
				// note: not a 100% method of reverse when called in quick succession
				// minframe is the threshold if adjusted framecount is less than 60% of the base framecount
				var adjframe = Math.floor((this.frame / this.current.framecount) * baseframecount),
					minframe = Math.floor(baseframecount * 0.6);

				// update current from/to/framecount and update forward boolean
				this.current = {
					from: this.value,
					to: (this.forward = !this.forward) ? base.to : base.from,
					framecount: (adjframe < minframe) ? minframe : adjframe
				};

			} else {
				// add tween to activetween list
				var index = activetween.length;
				for (var i = 0,j = activetween.length;i < j;i++) {
					if (!activetween[i]) {
						// found unused activetween slot
						index = i;
						break;
					}
				}

				// add tween to activetween array and save index against tween object
				activetween[index] = this;
				this.index = index;

				// update current from/to/framecount and update forward boolean
				this.current = {
					from: (this.forward = !this.forward) ? base.from : base.to,
					to: (this.forward) ? base.to : base.from,
					framecount: baseframecount
				};
			}

			this.running = true;
			this.frame = 0;

			// start tweening process
			if (!tweeninterval) tweeninterval = window.setInterval(animatetweens,intervaldelay);
		},

		reset: function() {

			this.running = false;
			this.frame = 0;
			this.value = this.base.from;
			this.forward = false; // will be set 'true' when start() method is next called

			// if tween has an assigned activetween index, remove it from activetween array now
			if (this.index > -1) removeactivetween(this.index);
		}
	};

	return obj;
}();