// some prototype-extensions and stuff... Array first

Array.prototype.clean = function() { //removes null/undefined-values, sorts and removes duplicates

	this.sort();
	this.sort(function(a,b) { return a > b; });

	for(var i=0; i < this.length;) { //since length is due to change it's not stored
		if(!isSet(this[i]) || (isSet(this[i+1]) && this[i] === this[i+1])) this.splice(i,1);
		else i++;
	}

	return this;
}

Array.prototype.clone = function() {

	var copy = [];
	for(var i = 0, len = this.length; i < len; i++) {
		if(isArray(this[i])) copy[i] = this[i].clone();
		else if(isObject(this[i])) ;//inte klar
		else copy[i] = this[i];
	}
	return copy;
}

// filter borde kanske alltid returnera en array, även om det bara är första som ska tillbaka, yeah!
Array.prototype.filter = function(callback, referenceObject, returnFirstFind) { // mozilla has an own implementation but this is better, at least for me

	if(typeof callback != "function") throw new Error("callback must be a function");

	var len = this.length;
	var arr = [];

	referenceObject = referenceObject || this;

	for(var i = 0; i < len; i++) {

		var value = this[i];
		if(callback.call(referenceObject, value, i, this)) {
			arr.push(value);
			if(returnFirstFind) return arr; // no need to loop if this is true
		}
	}

	return returnFirstFind ? null : arr;
}

Array.prototype.last = function() { // returns the last element in an array, or null

	var len = this.length;
	return len == 0 ? null : this[len - 1];
}

// extend non-mozilla

if(!Array.prototype.forEach) {
	Array.prototype.forEach = function(callback, referenceObject) {

		if(typeof callback != "function") throw new Error("callback must be a function");

		var len = this.length;
		referenceObject = referenceObject || this;

		for(var i = 0; i < len; i++) callback.call(referenceObject, this[i], i, this);
	}
}

if(!Array.prototype.indexOf) { //fixes IE error... really shouldn't need to do this
	Array.prototype.indexOf = function(element, i) {
		i || (i = 0);
		var length = this.length;
		if(i < 0) i = length + i;
		for(; i < length; i++) if(this[i] === element) return i;
		return -1;
	};
}

// some additions to String.prototype

String.prototype.trimText = function() { //removes text from beginning and end of a string until it encounters a numeric character
	var t = this.match(/[^0-9]*([0-9]*)[^0-9]*/)[1];
	return !t ? "" : t;
}

String.prototype.contains = function( /* arguments separated by , */ ) {
	
	if(arguments.lenght == 0) return false;

	for(var i = 0, len = arguments.length; i < len; i++)
		if(this.indexOf(arguments[i]) != -1) return true;
	
	return false;
}

String.prototype.trimWord = function(word) {
	return this.ltrimWord(word).rtrimWord(word);
}

String.prototype.ltrimWord = function(word) {
	
	if(!word) return this;

	var tmp = this;

	if(tmp.indexOf(word) == 0) tmp = tmp.substring(0, word.length - 1);
	
	return tmp;
}

String.prototype.rtrimWord = function(word) {

	if(!word) return this;

	var askedPos = this.length - word.length;

	var tmp = this;

	if(tmp.indexOf(word) === askedPos) tmp = tmp.substring(0, askedPos);
	
	return tmp;
}

String.prototype.countChars = function(character) {

	var count = 0;

	for(var i = 0, len = this.length; i < len; i++) if(this.charAt(i) == character) count++;

	return count;
}

String.prototype.capitalize = function(all) {

	if(all) {
		var tokens = this.split(" ");
		for(var i = 0, length = tokens.length; i < length; i++)
			if(tokens[i]) tokens[i] = tokens[i].capitalize();
		return tokens.join(" ");
	}

	var tmp = this.charAt(0).toUpperCase();
	tmp += this.substring(1,this.length);
	return tmp;
}

String.prototype.matchTag = function(tag) { return (this.match(new RegExp("<" + tag + "(>| )","gi")).length === this.match(new RegExp("<\/" + tag + ">","gi")).length); } //funkar inte hundraprocentigt
String.prototype.stripTags = function() { return this.replace(/<[^>]*>/gi,""); }
String.prototype.ln2br = function() { return this.replace(/\n/gi, '<br/>'); }
String.prototype.br2ln = function() { return this.replace(/<br>|<br\/>/gi, '\n'); }
String.prototype.trim = function(chars) {  return this.ltrim(chars).rtrim(chars); } //overwrite default, this is better =)
String.prototype.ltrim = function(chars) { return this.replace(new RegExp("^[" + (chars === undefined ? ' ' : chars) +  "]+","g"),""); } // loopa ist?
String.prototype.rtrim = function(chars) { return this.replace(new RegExp("[" + (chars === undefined ? ' ' : chars) + "]+$", "g"), ""); }
String.prototype.lastChar = function() { return this.charAt(this.length -1); }
String.prototype.qoute = function() { return "\"" + this + "\""; }


// standalone-functions, start with cookie-stuff

function setCookie(cookieName, cookieContents, secondsToLive) { //this function overwrites old cookie with same name so concatenation should be done externally and passed along

	if(!secondsToLive && secondsToLive !== 0) secondsToLive = 90*24*60*60; //default is 90 days
	var cookieExpires="";

	if(secondsToLive) {
		if(navigator.appName.indexOf("Microsoft") != -1) { // well, don't know any better way of checking this
			var d = new Date();
			d.setSeconds(d.getSeconds() + secondsToLive);
			cookieExpires = ";expires=" + d.toGMTString();
		} else cookieExpires = ";max-age=" + secondsToLive;
	}

	document.cookie = cookieName + "=" + encodeURIComponent(cookieContents) + cookieExpires + ";path=/";
}

function getCookie(cookieName) { //returns an object of cookie-values or a string if it's only one value

	var currentCookies = decodeURIComponent(document.cookie).split(";");
	var cookie = null;

	for(var i=0, len = currentCookies.length; i < len; i++){

		var tempName = currentCookies[i].trim().split("=");

		if(tempName[0] == cookieName) {

			var cookieContents = tempName[1];

			if(cookieContents.indexOf("|") != -1) {

				cookie = {};
				var tmp;

				cookieContents = cookieContents.split("|");

				for(var u=0, len = cookieContents.length; u < len; u++) {
					if(cookieContents[u].indexOf(":") != -1) {
						tmp = cookieContents[u].split(":");
						cookie[tmp[0]] = tmp[1];
					}
				}
			} else cookie = cookieContents;
		}
	}
	return cookie;
}

function hasCookie(cookieName) { return (!getCookie(cookieName)) ? false : true; }

function removeCookie(cookieName) { deleteCookie(cookieName); } // since I never learn...
function deleteCookie(cookieName){ setCookie(cookieName, "", -1); }

// functions that handle classes or css in some way

function changeClassRules(className, rule, value) { // updates a class-value. Only in memory ofc, no ajax

	if(!className || !rule || !value) return false;

	var rules = getCssRules(className);
	
	if(rules != null && rules[rule]) {
		rules[rule] = value;
		return true;
	}

	// the class wasn't found so we try to add it

	var styles = document.styleSheets;
	var styleLength = styles.length;
	if(styleLength == 0) return false;

	var style = styles[styleLength - 1];

	var newRulePart = rule + ':' + value+ ';';

	if(style.insertRule) style.insertRule(className + ' { ' + newRulePart + ' }', 0);
	else if(style.addRule) style.addRule(className, newRulePart);
}

function getCssRules(selector) { // gets either a class or an id, use . or # at the beginning plz, else both are checked

	if(!selector) return null;

	selector = selector.toLowerCase();
	var checkBoth = (selector[0] != "#" && selector[0] != ".") ? true : false;

	var tr, stylesheets = document.styleSheets;
	var styleLength = stylesheets.length;

	if(styleLength == 0) { //felhantering
		throw new Error('The document has no css-information which leaves this function little else to do than cancel...');
		return null;
	}

	var comp = stylesheets[0]['cssRules'] ? 'cssRules' : 'rules'; //fucking okompabilitet

	for(var i = styleLength - 1; i >= 0; i--) {

		if(tr = _attributefinder(stylesheets[i][comp], selector)) return tr; //allt utom @import i explorer

		if(stylesheets[i].imports && stylesheets[i].imports.length != 0) //explorers @import-hantering...
			for(var u = 0, len = stylesheets[i].imports.length; u < len; u++) if(tr = _attributefinder(stylesheets[i].imports[u].rules, selector)) return tr;
	}

	function _attributefinder(rules, selector) { //inre funktion för att spara kod och köra rekursivt á la mozilla-style

		for(var i = rules.length - 1; i >= 0; i--) {

			if(rules[i].type == 3) { //mozilla-specifikt, @import

				if(tr = _attributefinder(rules[i].styleSheet[comp], selector)) return tr;

			} else if(rules[i].selectorText) {

				var sel = rules[i].selectorText.toLowerCase();
				if(checkBoth && (sel == "#" + selector || sel == "." + selector) || sel == selector) // found what we looked for
					return rules[i].style;

			}
		}
		return null;
	}

	return null;
}

function getAttributeBySelector(selector, attribute) {

	//returns class-attribute from inline or imported stylesheet. it needs real selectors,
	//if it's an ID you want you have to start with #, if it's a class you want you have to
	//start with ., if you don't provide either . or # it checks for both.

	if(!selector || !attribute) return false;

	if(attribute.contains("-")) { // får väl se om det här funkar i explorer
		var tmp = attribute.split("-");
		for(var i = 1, len = tmp.length; i < len; i++) tmp[i] = tmp[i].capitalize();
		attribute = tmp.join("");
	}

	selector = selector.toLowerCase();
	//var checkBoth = (selector[0] != "#" && selector[0] != ".") ? true : false;

	var rules = getCssRules(selector);
	if(!rules) return null;

	var attr = rules[attribute];
	return attr != "" ? attr : null;

	/*var tr, stylesheets = document.styleSheets;
	var styleLength = stylesheets.length;

	if(styleLength == 0) { //felhantering
		throw new Error('The document has no css-information which leaves this function little else to do than cancel...');
		return false;
	}

	var comp = stylesheets[0]['cssRules'] ? 'cssRules' : 'rules'; //fucking okompabilitet

	for(var i = styleLength - 1; i >= 0; i--) {

		if(tr = _attributefinder(stylesheets[i][comp], selector, attribute)) return tr; //allt utom @import i explorer

		if(stylesheets[i].imports && stylesheets[i].imports.length != 0) //explorers @import-hantering...
			for(var u = 0, len = stylesheets[i].imports.length; u < len; u++) if(tr = _attributefinder(stylesheets[i].imports[u].rules, selector, attribute)) return tr;
	}

	function _attributefinder(rules, selector, attribute) { //inre funktion för att spara kod och köra rekursivt á la mozilla-style

		for(var i = rules.length - 1; i >= 0; i--) {

			if(rules[i].type == 3) { //mozilla-specifikt, @import

				if(tr = _attributefinder(rules[i].styleSheet[comp], selector, attribute)) return tr;

			} else if(rules[i].selectorText) {

				var sel = rules[i].selectorText.toLowerCase();
				if((checkBoth && (sel == "#" + selector || sel == "." + selector) || sel == selector) && rules[i].style[attribute]) return rules[i].style[attribute];

			}
		}
		return false;
	}

	return false;*/
}

function elementsByClassName(className, tagName, root, onlyFirst) {

	if(!className) return false;
	tagName = !tagName ? "*" : tagName.toLowerCase();
	
	var arrayFlag = isArray(root) ? true : false;

	if(!arrayFlag) root = id(root);
	if(!root) root = document;

	var elements;

	if(document.getElementsByClassName && !arrayFlag) {

		elements = nodeListToArray(root.getElementsByClassName(className));
		if(elements && tagName != "*") elements = elements.filter(function(value) { return (isTag(value, tagName)) ? true : false; }, null, onlyFirst);

	} else {
		
		var potentialMatches =  nodeListToArray(arrayFlag ? root : root.getElementsByTagName(tagName));
		elements = potentialMatches.filter(function(value) { return (isMember(value, className)) ? true : false; }, null, onlyFirst);

	}

	if(onlyFirst) return ((elements && elements.length != 0) ? elements[0] : null);
	else return elements;
}

function isMember(obj, className) {

	obj = id(obj);
	if(!obj || !obj.className || typeof obj.className != "string" || !className) return false;

	return !(obj.className.search(new RegExp("\\b" + className + "\\b")) == -1);
}

function changeClass(obj, classFrom, classTo) {

	removeClass(obj, classFrom);
	addClass(obj, classTo);
}

function addClass(obj, className) {
	
	obj = id(obj);
	if(!obj || !className) return false;

	if(isMember(obj, className)) return true;
	else if(obj.className == "") obj.className = className;
	else obj.className += " " + className;

	return true;
}

function removeClass(obj, className) {

	obj = id(obj);
	if(!obj || !className) return false;

	if(isMember(obj, className)) obj.className = obj.className.replace(new RegExp("\\b" + className + "\\b"), "").trim();

	return true;
}

function toggleClass(obj, className) {

	if(isMember(obj, className)) removeClass(obj, className);
	else addClass(obj, className);
}

function currentStyle(obj, style, root, includeMargins, relativeToParent, forceComputed) {

	if(!root) root = document;
	if(typeof obj == "string") obj = id(obj, root);
	if(!obj || !style) return false;

	var isFirst = 0;
	var tmp = false;

	if(!forceComputed) {

		var computableObject = obj; // since the computable object is changed in a loop

		switch(style) { // styles that need to be counted

			case "top":

				tmp = computableObject.offsetTop || 0;
				if(relativeToParent) break;

				while(computableObject = computableObject.offsetParent) 
					tmp += computableObject.offsetTop + ((isFirst++ == 0) ? currentStyle(computableObject, "border-top-width") : 0);

				break;

			case "right":

				tmp = (windowDimensions(true).width - (computableObject.offsetWidth + computableObject.offsetLeft)) || 0;
				if(relativeToParent) break;

				while(computableObject = computableObject.offsetParent) 
					tmp -= computableObject.offsetWidth + ((isFirst++ == 0) ? currentStyle(computableObject, "border-right-width") : 0);

				break;

			case "bottom": // returns position from bottom of screen as a positive number

				tmp = (windowDimensions(true).height - (computableObject.offsetHeight + computableObject.offsetTop)) || 0;
				if(relativeToParent) break;

				while(computableObject = computableObject.offsetParent) 
					tmp -= computableObject.offsetTop + ((isFirst++ == 0) ? currentStyle(computableObject, "border-top-width") : 0);

				break;

			case "left":

				tmp = computableObject.offsetLeft || 0;
				if(relativeToParent) break;

				while(computableObject = computableObject.offsetParent)
					tmp += computableObject.offsetLeft + ((isFirst++ == 0) ? currentStyle(computableObject, "border-left-width") : 0);

				break;

			case "height":

				tmp = computableObject.offsetHeight || 0;
				if(includeMargins) tmp += currentStyle(computableObject, "padding-top") + currentStyle(computableObject, "padding-bottom");

				break;

			case "width":

				tmp = computableObject.offsetWidth || 0;
				if(includeMargins) tmp += currentStyle(computableObject, "padding-left") + currentStyle(computableObject, "padding-right");

				break;

			case "display": // this returns what the user sees, not the actual value, since this function always tries to return computed styles.

				do {

					var currStyle = currentStyle(computableObject, "display", root, false, false, true);
					if(!currStyle || currStyle == "none") {
						tmp = currStyle || "none";
						break;
					}

				} while((computableObject = computableObject.parentNode) && !isTag(computableObject, "body"));
		}
	}

	if(!tmp && tmp !== 0) {

		if(document.defaultView) {
			
			try {
				tmp = root.defaultView.getComputedStyle(obj, "").getPropertyValue(style);
			} catch(error) {
				alert(obj + " : " + style);
			}

		} else if(obj.currentStyle) {

			var explorerFix = style.split("-");
			if(explorerFix.length > 0) { // must format for explorer. i.e remove -
				for(var i = 1, len = explorerFix.length; i < len; i++) explorerFix[i] = explorerFix[i].capitalize();
				style = explorerFix.join("");
			}
	
			tmp = obj.currentStyle[style];

			if(style.indexOf("border") != -1) tmp = tmp.trimText() || 0; // explorer is stupid... and this list will probably grow

		}
	}

	if(typeof tmp == "string" && !isNaN(tmp.charAt(0))) return parseInt(tmp.trimText());
	else return tmp;
}

function addCss(obj, css) {

	obj = id(obj);
	if(!obj || !css) return false;

	if(typeof css == "string") { // fix input

		css = css.split(";");
		var newCss = {};

		for(var i = 0, len = css.length; i < len; i++) {

			var tmp = css[i].split(":");
			if(!tmp || tmp.length != 2) continue;

			newCss[tmp[0].trim()] = tmp[1].trim();
		}

		css = newCss;	
	}

	for(var key in css) {
		obj.style[key] = css[key].trim(";");
	}

	return true;
}

function removeCss(obj, css) {

	obj = id(obj);
	if(!obj || !css) return false;

	var css = css.split(",");
	
	for(var i = 0, len = css.length; i < len; i++) obj.style[css[i].trim()] = "";

	return true;
}

// functions that have some connection to events...

function focus(obj, classFrom, classTo, noblur, keepValue) {

	obj = id(obj);
	if(!obj) return;

	if(!classFrom && !classTo) return;

	if(!classTo) { //to support the shortest form oFocus(obj, 'class_to')
		classTo = classFrom;
		classFrom = '';
	}

	if(!keepValue) { //clear the obj.value, doesn't work in explorer though due to their buggy implementation of getAttribute. might fix someday
		var tagValue = obj.getAttribute("value");
		var realValue = obj.value;
		if(tagValue == realValue) obj.value = "";
	}

	if(isMember(obj, classTo)) return;

	changeClass(obj, classFrom, classTo);

	if(!noblur) { //use the built-in onblur

		var oldOnblur = obj.onblur;

		obj.onblur = function() {
			changeClass(obj, classTo, classFrom);
			obj.onblur = oldOnblur;
		}
	}
}

function hover(obj, mouseOver, noMouseOut) {

	obj = id(obj);

	if(!obj || !mouseOver) return;

	if(typeof mouseOver == "string") addClass(obj, mouseOver);
	else if(typeof mouseOver == "function") mouseOver(obj);

	if(noMouseOut) return;

	addEvent(obj, "mouseout", function() {
		if(typeof mouseOver == "string") removeClass(obj, mouseOver);
	});
}

function click(obj, classFrom, classTo) {
	
	obj = id(obj);
	if(!obj || !classFrom) return;

	if(!classTo) {
		classTo = classFrom;
		classFrom = '';
	}

	if(!isMember(obj, classTo)) changeClass(obj, classFrom, classTo);
}

function inObject(obj, value) { // returns the array key where value is present
	
	for(var currentValue in obj) {
		if(obj[currentValue] === value) return currentValue;
	}
	
	return null;
}

// pure event-functions

// sparar gamla ett tag för bugg-testning

/*
function addEvent(o,e,f,b){if(o!=document&&o!=window&&typeof o=='string')o=id(o);if(document.addEventListener){if(!b)b=false;o.addEventListener(e,f,b);}else if(document.attachEvent){o.attachEvent(eval("\"on"+e+"\""),f);}else{eval("o."+"on"+e+"="+f);}}
function removeEvent(o,e,f,b){if(o!=document)o=id(o);if(document.removeEventListener){if(!b)b=false;o.removeEventListener(e,eval(f),b);}else if(document.detachEvent)o.detachEvent(eval("\"on"+e+"\""),eval(f));}
/**/

function addEvent(obj, eventName, eventFunction, bubble) { // add events to an object through an event-buffer, eventFunction can be a function or an array of functions

	if(typeof obj == "string") obj = id(obj);
	if(!obj) return false;

	var eventIdentifier = true;
	
	eventName = eventName.ltrim("on");

	//some initialization
	if(!obj.eventHandler) obj.eventHandler = {};
	if(!obj.eventHandler[eventName]) obj.eventHandler[eventName] = [];

	//storage
	var events = obj.eventHandler[eventName];

	if(isArray(eventFunction)) {

		for(var i = 0, len = eventFunction.length; i < len; i++) {

			var currentValue = eventFunction[i];

			if(!events.filter(function(value) { if((value + "") === (currentValue + "")) return true; }, false, true)) { // check for presence in quite a strange way
				events[events.length] = currentValue;
			}
		}

	} else {

		if(!events.filter(function(value) { if((value + "") === (eventFunction + "")) return true; }, false, true)) { // check for presence in quite a strange way
			
			eventIdentifier = events.length;
			events[eventIdentifier] = eventFunction;
		}

	}

	// om ett objekt redan har events så ska de nya ignoreras i första köret. nä.
	// om ett event redan är kastat ska nya events i samma grej inte addas förräns det eventet är klart.

	var dispatchName = eventName + "dispatcher";

	if(!obj.eventHandler[dispatchName]) {

		obj.eventHandler[dispatchName] = function(e) {
			_eventDispatcher(e, obj, eventName);
		};

		_eventHelper(true, obj, eventName, bubble);
	}

	return [eventName, eventIdentifier];
}

function removeEvent(obj, eventIdentifier, bubble, removeAll) { // remove one or all events

	if(!isArray(eventIdentifier)) { // this is to support old notation. lets work to remove
		eventIdentifier = [eventIdentifier, bubble];
		bubble = removeAll;
		removeAll = arguments[4];
	}

	// eventIdentifier; 0 == eventName, 1 == eventIdentifier (number or function)

	if(typeof obj == "string") obj = id(obj);
	if(!obj || !obj.eventHandler) return false;

	if(removeAll || (!eventIdentifier[1])) { // assume remove all

		var events = obj.eventHandler;
		for(var eventName in events) if(eventName.indexOf("dispatcher") == -1) _eventHelper(false, obj, eventName, bubble);

		try {
			delete obj.eventHandler; //bug for ie < 8
		} catch(e) {
			obj.eventHandler = null;
		}

	} else {

		if(!obj.eventHandler[eventIdentifier[0]]) return;

		if(removeAll) {

			delete obj.eventHandler[eventIdentifier[0]];
			_eventHelper(false, obj, eventIdentifier[0], bubble);

		} else {

			
			var eventIndex = null;

			if(typeof eventIdentifier[1] != "function") {
				if(!isNaN(eventIdentifier[1])) eventIndex = eventIdentifier[1];
				else return false;
			}

			var events = obj.eventHandler[eventIdentifier[0]];
			
			for(var i = 0, len = events.length; i < len; i++) {

				if(eventIndex !== null && eventIndex == i || (events[i] + "").qoute() === (eventIdentifier[1] + "").qoute()) {
				
					if(i == len - 1) { // can only splice from the end, messes up indexes otherwise
					
						var counter = 0;
						
						for(var j = len - 2; j >= 0; j--) {
						
							if(events[j] == null) counter++;
							else break;
						}
						
						events.splice(i - counter, 1 + counter);
						
					} else {
						events[i] = null;
					}

					break;
				}
			}
		}

		if(events.length == 0) _eventHelper(false, obj, eventIdentifier[0], bubble);
	}
}/**/

function dispatchEvent(obj, eventName, bubbles) {

	if(typeof obj == "string") obj = id(obj);
	if(!obj || !eventName) return;

	var event = null;
	eventName = eventName.trim("on");

	if(document.createEvent) { // mozilla nd stuff

		event = document.createEvent("Event");
		event.initEvent(eventName, true, true);
		obj.dispatchEvent(event);

	} else if (document.createEventObject) { //explorer

		event = document.createEventObject();
		obj.fireEvent ("on" + eventName, event);
	}

	if(!event) return false;	
}

function stopEvent(e) {

	if(!e) return false;

	e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
	if(e.preventDefault) e.preventDefault();
}

function setEvents(obj, events, eventName, bubble) { // to set multiple events, or to set saved events

	if(typeof obj == "string") obj = id(obj);
	if(!obj || !events) return false;

	if(isArray(events)) {

		if(!eventName) return false;
		eventName = eventName.ltrim("on");

		addEvent(obj, eventName, events, bubble);
	} else if(isObject(events)) {

		for(var event in events) {
			if(event.indexOf("dispatcher") == -1) addEvent(obj, event, events[event], bubble);
		}
	}
}

function getEvents(obj, eventName) { // to save events from an object

	if(typeof obj == "string") obj = id(obj);
	if(!obj || !obj.eventHandler) return null;

	var eventClone = cloneObject(obj.eventHandler);
	
	//remove tracks and nicify for iterations
	for(var event in eventClone) if(event.indexOf("dispatcher") != -1) delete eventClone[event];

	return !eventName ? eventClone : eventClone[eventName.ltrim("on")];
}

function _eventDispatcher(e, obj, eventName) { // just a helper to save some space
	
	var currentEvents = obj.eventHandler[eventName];

	for(var i = 0, len = currentEvents.length; i < len; i++) {

		if(currentEvents[i] && typeof currentEvents[i] == "function") {
			currentEvents[i](e);
		}
	}
}

function _eventHelper(flagForAdd, obj, eventName, bubble) { // just a private helper-function. no use calling directly

	var dispatchName = eventName + "dispatcher";
	var dispatch = obj.eventHandler[dispatchName];
	if(!dispatch) return;

	if(document.addEventListener) { //mozilla
		flagForAdd ? obj.addEventListener(eventName, dispatch, bubble) : obj.removeEventListener(eventName, dispatch, bubble);
	} else if(document.attachEvent) { //explorer
		flagForAdd ? obj.attachEvent("on" + eventName, dispatch) : obj.detachEvent("on" + eventName, dispatch);
	} else { //traditional
		flagForAdd ? eval("obj."+"on" + eventName + "=" + dispatch) : eval("obj."+"on" + eventName + " = null");
	}

	if(!flagForAdd) obj.eventHandler[dispatchName] = null;
}

// node-functions

function replaceNode(nodeToReplace, nodeToReplaceWith) {

	nodeToReplace = id(nodeToReplace);
	nodeToReplaceWith = id(nodeToReplaceWith);

	if(!nodeToReplace || !nodeToReplaceWith) return false;

	var parentNode = nodeToReplace.parentNode;
	if(!parentNode) return false;
	
	try { //this seems to throw errors sometimes but this should be boolean so catch is needed.
		parentNode.replaceChild(nodeToReplaceWith, nodeToReplace);
		return true;
	} catch (e) {
		return false;
	}
}

function insertChildNodes(obj, nodeList) { //can handle multiple node-attributes...

	//doesn't handle iframes very well in firefox, drops events and contents. known bug since 2004... but might be fixable now with the new eventhandler

	obj = id(obj);
	if(!obj || !nodeList) return false;

	if(arguments.length > 2) nodeList = _collectNodeArguments(arguments, 1); //create an array of the arguments
	else nodeList = nodeListToArray(nodeList); //let's keep to arrays...

	for(var i = 0, len = nodeList.length; i < len; i++) obj.appendChild(nodeList[i]);

	return true;
}

function replaceChildNodes(obj, nodeList) { //can handle multiple node-attributes...

	obj = id(obj);
	if(!obj || !nodeList) return false;

	if(arguments.length > 2) nodeList = _collectNodeArguments(arguments, 1); //create an array of the arguments
	else nodeList = nodeListToArray(nodeList); //let's keep to arrays...

	removeChildNodes(obj);
	insertChildNodes(obj, nodeList);

	return true;
}

function _collectNodeArguments(arguments, startWith) { // fixa fixa fixa, kan spara mycket kod kanske

	var list = [];

	for(var i = startWith, len = arguments.length; i < len; i++)
		list.push( (typeof arguments[i] == 'string') ? id(arguments[i]) : arguments[i] );

	return list;
}

function copyNodes(obj) { //doesn't copy dynamic events. and that's not a bug in this code... fixable but needs heavy logic compared to the usage

	var nodes = [];

	var objNodes = obj.childNodes;

	for(var i = 0, len = objNodes.length; i < len; i++) nodes.unshift(objNodes[i].cloneNode(true));

	return nodes;
}

function removeNode(node) { //just simplifies...

	node = id(node);
	if(!node) return;

	var parentNode = node.parentNode;
	if(!parentNode) parentNode = document.body;

	return parentNode.removeChild(node);
}

function removeChildNodes(obj) {

	obj = id(obj);
	if(!obj) return false;

	while(obj.firstChild) obj.removeChild(obj.firstChild); //remove nodes

	return true;
}

function insertChildNodeFirst(node, nodeToInsert) {

	node = id(node);
	if(!node || !nodeToInsert) return;

	var childNodes = node.childNodes;

	if(childNodes.length == 0) node.appendChild(nodeToInsert);
	else node.insertBefore(nodeToInsert, childNodes[0]);
}

function insertAfterNode(node, nodeToInsert) {

	if(!node || !nodeToInsert || !node.parentNode) return false;

	if(node.nextSibling) node.nextSibling.parentNode.insertBefore(nodeToInsert, node.nextSibling);
	else node.parentNode.appendChild(nodeToInsert);

	return true;
}

function getLastChildNode(parentNode, tag) {

	parentNode = id(parentNode);

	var nodeList = parentNode.getElementsByTagName(tag ? tag : "*");
	var len = nodeList.length;

	if(len == 0) return false;
	else return nodeList[len - 1];
}

function isNodeList(obj) {

	if(!obj) return false;
	obj = id(obj);

	return ((obj.toString && obj.toString().contains("NodeList")) || (typeof obj == "object" && typeof obj.item != 'undefined' && typeof obj.length != 'undefined'));
}

function nodeListToArray(nodeList) {

	if(isNodeList(nodeList)) {

		var arrayOfNodes = [];

		for(var i = 0, len = nodeList.length; i < len; i++) arrayOfNodes.push(nodeList[i]);

		return arrayOfNodes;
	}

	return nodeList;
}

function isParentNode(parentNode, childSelectorOrNode, tagName) {
	return (getChildNodes(parentNode, childSelectorOrNode, tagName, true) !== null);
}

function hasChildNode(parentNode, childSelectorOrNode, tagName) { //same as above, different name
	return isParentNode(parentNode, childSelectorOrNode, tagName);
}

function getChildNodes(parentNode, childSelectorOrNode, tagName, onlyGetFirst) {

	parentNode = id(parentNode);
	if(!parentNode) return null;

	if(!tagName) tagName = "*";

	var nodes = [];

	if(childSelectorOrNode) { //else just tagName

		if(typeof childSelectorOrNode == "string" && childSelectorOrNode.charAt(0) == ".") {

			return elementsByClassName(childSelectorOrNode.trim("."), tagName, parentNode, onlyGetFirst);

		} else {

			var childNode;
			if(typeof childSelectorOrNode == "string") childSelectorOrNode = childSelectorOrNode.trim("#");

			if(isDomNode(parentNode)) {
				var tmpNode = id(childSelectorOrNode);
				if(isChildNode(tmpNode, parentNode)) childNode = tmpNode;
			} else {

				nodes = parentNode.getElementsByTagName("*");

				for(var i = 0, len = childNodes.length; i < len; i++) {

					if((typeof childSelectorOrNode == "string" && nodes[i].id == childSelectorOrNode) || nodes[i] === childSelectorOrNode) {
						childNode = nodes[i];
						break;
					}
				}
			}

			return (isTag(childNode, tagName)) ? childNode : null; 

		}
	} else {
		nodes = parentNode.getElementsByTagName(tagName);
	}

	if(onlyGetFirst) return !nodes[0] ? null : nodes[0];
	else return nodeListToArray(nodes);
}

function isChildNode(childNode, parentSelectorOrNode, tagName) { //checks if childNode is a childNode of a node matching parentSelector. Start with a period (.) to match by classname
	return (getParentNodes(childNode, parentSelectorOrNode, tagName, true) !== null);
}

function hasParentNode(childNode, parentSelectorOrNode, tagName) {
	return isChildNode(childNode, parentSelectorOrNode, tagName);
}

function getParentNodes(childNode, parentSelectorOrNode, tagName, onlyGetFirst) {
	
	if(!tagName) tagName = "*";
	childNode = id(childNode);

	var cl = false;

	if(parentSelectorOrNode == "contents") var flag = true;

	if(typeof parentSelectorOrNode == "string") {

		if(parentSelectorOrNode.charAt(0) == ".") {
			cl = true;
			parentSelectorOrNode = parentSelectorOrNode.trim(".");
		} else {
			parentSelectorOrNode = id(parentSelectorOrNode.trim("#"));
		}
	}

	var nodes = [];

	while(childNode && (childNode = childNode.parentNode)) {

		if(cl) {
			if(isMember(childNode, parentSelectorOrNode) && isTag(childNode, tagName)) {
				nodes.push(childNode);
				if(onlyGetFirst) break;
			}
		} else {
			if(childNode === parentSelectorOrNode) return childNode;
		}
	}

	if(onlyGetFirst) return !nodes[0] ? null : nodes[0];
	else return nodeListToArray(nodes);
}

// DOM-functions

function isDomNode(node) {

	node = id(node);
	if(!node) return false;

	while(node = node.parentNode) if(isTag(node, "html")) return true;

	return false;
}
	
function switchId(obj_1, obj_2) {

	if(typeof obj_1 == "string") obj_1 = id(obj_1);
	if(typeof obj_2 == "string") obj_2 = id(obj_2);

	var tmp = obj_1.id;
	obj_1.id = obj_2.id;
	obj_2.id = tmp;
}

function id(x, root){ //shorter than document.getElement... and also it has the ability to check for id inside a node exclusively

	if(!root && typeof x != "string") return x;

	if(root && root != document) {

		if(typeof root == "string") root = id(root);
		if(!root) return null;

		var nodes = root.getElementsByTagName("*");
		for(var i = 0, len = nodes.length; i < len; i++) if(nodes[i].id === x) return nodes[i];

		return null;

	} else return document.getElementById(x);
}

// gör om så den kan hantera klassnamn med, vet inte om push funkar på arrayer
function $() { // even shorter than id and can take arbitrary number of ids to put in an array
	
	var elements = [];
	
	for(var i = 0, len = arguments.length; i < len; i++) {

		var arg = arguments[i];
		
		var elem = arg.charAt(0) == "." ? elementsByClassName(arg) : id(arg);
		if(!elem) continue;

		if(len == 1) return elem;

		elements.push(elem);
	}

	return elements;
}

function frameWindow(frame) { //returns a reference to an iframes window-object

	frame = id(frame);
	return (!frame || !isTag(frame, "iframe")) ? false : frame.contentWindow;
}

// position and size-related functions

function objIsOver(obj, objToCheck) {

	var top = currentStyle(obj, "top");
	var bottom = top + currentStyle(obj, "height");
	var left = currentStyle(obj, "left");
	var right = left + currentStyle(obj, "width");

	if(isArray(objToCheck)) {

		for(var i = 0, len = objToCheck.length; i < len; i++) {

			var objLeft = currentStyle(objToCheck[i], "left");
			var objTop = getFixedPosition(objToCheck[i], "top");
			var objBottom = objTop + currentStyle(objToCheck[i], "height");
			var objLeft = getFixedPosition(objToCheck[i], "left");
			var objRight = objLeft + currentStyle(objToCheck[i], "width");

			if(((top <= objBottom && top >= objTop) || (bottom <= objBottom && bottom >= objTop)) && ((left >= objLeft && left <= objRight) || (right >= objLeft && right <= objRight))) return true;
			//if(top <= objBottom && top >= objTop && left >= objLeft && left <= objRight) return true;
		}

	} else {

		var objLeft = currentStyle(objToCheck, "left");
		var objTop = getFixedPosition(objToCheck, "top");
		var objBottom = objTop + currentStyle(objToCheck, "height");
		var objLeft = getFixedPosition(objToCheck, "left");
		var objRight = objLeft + currentStyle(objToCheck, "width");

		return (((top <= objBottom && top >= objTop) || (bottom <= objBottom && bottom >= objTop)) && ((left >= objLeft && left <= objRight) || (right >= objLeft && right <= objRight)));
	}

	return false;
}

function mouseIsOver(e, obj) { //returns true if the mouse is over this object. Better than checking target because this can check multiple levels.

	obj = id(obj);

	var top = e.clientY, left = e.clientX;

	var objTop = getFixedPosition(obj, "top");
	var objBottom = objTop + currentStyle(obj, "height");
	var objLeft = getFixedPosition(obj, "left");
	var objRight = objLeft + currentStyle(obj, "width");

	return (top <= objBottom && top >= objTop && left >= objLeft && left <= objRight);
}

function getFixedPosition(target, posFlag) { //returns the position target should have had if position: fixed, i.e relative to window, not document

	target = id(target);
	obj = target;

	var scrollTop = 0;
	var scrollLeft = 0;

	while(obj = obj.parentNode) {
		scrollTop += obj.scrollTop || 0;
		scrollLeft += obj.scrollLeft || 0;
	}

	if(posFlag == "top") return (currentStyle(target, "top") - scrollTop);
	else if(posFlag == "left") return (currentStyle(target, "left") - scrollLeft);
}

function windowDimensions(innerSize) { //returns window height and width, doesn't work well in quirks mode

	var obj = {};

	if(innerSize) {
		if(document.body.clientHeight) obj.width = document.body.clientWidth, obj.height = document.documentElement.clientHeight;
	} else {
		if(window.innerWidth) obj.width = window.innerWidth, obj.height = window.innerHeight;
		else if(document.documentElement.clientWidth) obj.width = document.documentElement.clientWidth, obj.height = document.documentElement.clientHeight;
	}

	return obj.width ? obj : null;
}

function setScrollPosition(obj) { // ke?

	obj = id(obj);
	if(!obj) return;

	//
}

// boolean control-functions and functions that doesn't fit in any other category

function getTarget(e) { // easy to use if the event only is used to get target
	e = e || window.event;
	return e.target || e.srcElement;
}

function cloneObject(obj) {

	if(!obj) return null;
	var copy = {};

	for(var value in obj) {

		var currentValue = obj[value];

		if(isArray(currentValue)) copy[value] = currentValue.clone();
		else if(isObject(currentValue)) copy[value] = cloneObject(currentValue);
		else  copy[value] = currentValue;
	}

	return copy;
}

function isSet(variable) { //returs true if a variable is set, and false otherwise. empty strings and false are true (of course)
	return !(variable === undefined || variable === null);
}

function isTag(obj, tag) {

	obj = id(obj);

	if(!obj || !tag || !obj.tagName) return false;

	if(tag == "*") {
		if(obj.nodeType == 1) return true;
		else return false;
	}
	
	return (obj.tagName.toLowerCase() == tag.toLowerCase()); 
}

function isOdd(number) {
	return number % 2 != 0;
}

function isNull() {

	if(arguments.length == 0) return true;

	for(var i = 0, len = arguments.length; i < len; i++) {
		if(arguments[i] == null) return true;
	}

	return false;
}

function isObject(obj) {
	return (typeof obj == "object");
}

function isArray(obj) { //doesn't work reliable for older browsers, probably not possible to check for array in older
	
	if(!obj) return false;
	if(!obj.constructor) { //well, silently making it usable for older browsers... 
		if(!isNaN(obj.length)) return true;
		return false;
	}

	return(obj.constructor.toString().match(/array/i) !== null);
}

//parser-functions

function toJson(input, readable) {
	
	//can take in objects and arrays and creates a json-string suitable for ajax-calls etc.
	//if readable is true some tabs and linebreaks are added for readability. the result won't be perfect but better than nothing...
	
	var json;

	var isarray = isArray(input) ? true : false;
	var isobject = (isObject(input) && !isarray) ? true : false;

	if(typeof input == "string") {

		input = [ input ];
		isarray = true;
	}

	if(!isarray && !isobject) return "";

	//use native if supported and no readability needed
	if(!readable && JSON.stringify) return JSON.stringify(input);

	var pre = "", end = "", space = "", linebreak = "";
	var split = 1;

	var _lvl = arguments[2] || 0; //internal flag controlling depth of indentation if readable

	if(readable) {
		
		space = " ";
		linebreak = "\n";
		split = 2;

		pre = "\t";
		end = linebreak;

		var tabs = "";

		for(var u = 0; u < _lvl; u++)
			tabs += "\t";

		pre += tabs;
		end += tabs;
	}

	if(isarray) {
		json = "[";
		end += "]";
	} else if(isobject) {
		json = "{";
		end += "}";
	}

	json += linebreak;

	if(isarray) { //array is handled one way

		input.forEach(function(value) {
			
			json += pre;

			if(value !== null && isObject(value)) json += toJson(value, readable, _lvl + 1);
			else json += ((typeof value == "string") ? "\"" + value + "\"" : value);
			
			json += "," + linebreak;
		});
	} else if(isobject) { //objects are handled another way, since they have no length-attribute

		for(var value in input) {

			json += pre + "\'" + value + "\'" + space + ":" + space;

			if(input[i] !== null && isObject(input[value])) json += toJson(input[value], readable, _lvl + 1);
			else json += ((typeof input[value] == "string") ? "\"" + input[value] + "\"" : input[value]);

			json += "," + linebreak;
		}
	}
	
	return (json.substring(0, json.length - split) + end);
}

function fromJson(input, trusted) {

	if(!input) return false;

	input = _jsoninputcontrol(input);
	
	var jsonObj;

	if(trusted) jsonObj = eval(input); //if all sources are trusted there's no need to check for security breaches and actually we can allow more to be stored as json
	else if(JSON.parse) jsonObj = JSON.parse(input);
	else jsonObj = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(input.replace(/"(\.|[^\"\\])*"/g, ''))) && eval('(' + input + ')'); //stolen from wikipedia

	return jsonObj;
}

function encodeJson(input) {

	if(typeof input != "string") return input;

	return input.replace(/\"/g, '\\"').replace(/\'/g, '\\\'');
}

function _jsoninputcontrol(input) {

	var c1 = input.charAt(0);
	var c2 = (c1 == "[") ? "]" : "}";

	if(!c2) return input;

	if(input.lastChar() == ",") input = input.rtrim(",");

	if(input.countChars(c1) > input.countChars(c2)) input += c2;

	return input;
}

// testa

function elementNextToMouse(e, parent, where) { //returns the object in parent who's center-position is closest to the mousepointer based on where (up, down, left or right)

	e = e || window.event;

	var bestMatch = null;

	parent = id(parent);
	if(!parent) return null;

	//calculate the total scrolltop
	var scrollTop = parent.scrollTop;

	var tmpParent = parent;
	while(tmpParent = tmpParent.parentNode) scrollTop += tmpParent.scrollTop || 0;

	//this is the value to compare to
	var top = e.clientY;
	var left = e.clientX;

	//check each node in the parent
	for(var i = 0, nodeList = parent.childNodes, len = nodeList.length; i < len; i++) {

		var nTop = currentStyle(nodeList[i], "top") - scrollTop;
		var nHeight = currentStyle(nodeList[i], "height");

		var nLeft = currentStyle(nodeList[i], "left");
		var nWidth = currentStyle(nodeList[i], "width");

		if(where == "up" || where == "down") {

			if(left < nLeft || left > (nLeft + nWidth)) continue;

			var nMiddle = nTop + nHeight / 2;

			if(where == "up") {
				if(nMiddle < top) bestMatch = nodeList[i];
				else break;
			} else if(where == "down") {
				if(nMiddle > top) {
					bestMatch = nodeList[i];
					break;
				}
			}
		} else {

			if(top < nTop || top > (nTop + nHeight)) continue;

			var nCenter = nLeft + nWidth / 2;

			if(where == "left") {
				if(nCenter < left) bestMatch = nodeList[i];
				else break;
			} else if(where == "right") {
				if(nCenter > left) {
					bestMatch = nodeList[i];
					break;
				}
			}
		}
	}
	return bestMatch;
}


//just some book-keeping

if(!window.modules) window.modules = {};
window.modules.util = true;




function getScrollPosition(obj) {

	obj = id(obj);
	if(!obj) return false;

	return obj.scrollTop;
}
