/*
 * Glow JavaScript Library
 * Copyright (c) 2008 British Broadcasting Corporation
 */
if (window.glow) { throw new Error("glow Core module already included"); }
var glow = (function() {
	var moduleRegister = {glow: true},
		regexEscape = /([$^\\\/()|?+*\[\]{}.-])/g,
		ua = navigator.userAgent.toLowerCase();

	return {
		VERSION: "0.4.1",
		isReady: false,

		env: function(){
			var nanArray = [0, NaN],
				opera = Number((/opera[\s\/]([\d\.]+)/.exec(ua) || nanArray)[1]),
				ie = opera ? NaN : Number((/msie ([\d\.]+)/.exec(ua) || nanArray)[1]);
			return {
				rhino  : !!window.load,
				gecko  : !window.load && Number((/gecko\/(\d+)/.exec(ua) || nanArray)[1]),
				ie     : ie,
				opera  : opera,
				webkit : Number((/applewebkit\/(\d+(?:\.\d+)?)/.exec(ua) || nanArray)[1]),
				khtml  : Number((/khtml\/(\d+(?:\.\d+)?)/.exec(ua) || nanArray)[1]),
				standardsMode : document.compatMode != "BackCompat" && (!ie || ie >= 6)
			}
		}(),
		module: function(sName, sVer, oParams) {
			var i, namePartsLen, nameParts,
				objRef = window; //holds the parent object for the new module
			//check version number match core version
			if (sVer != this.VERSION) {
				throw new Error("Cannot register " + sName + ": Version mismatch");
			}
			
			//check dependencies loaded
			if (oParams.require) {
				if (typeof oParams.require == 'string') { oParams.require = [ oParams.require ]; }
				for (i = 0; oParams.require[i]; i++) {
					//check exists
					if (!moduleRegister[oParams.require[i]]) {
						//module is missing
						var missingModule = oParams.require[i];
						//check again ondomready to detect if modules are being included in wrong order
						this.ready(function() {
							if (moduleRegister[missingModule]) {
								throw new Error("Module " + missingModule + " is included after modules that depend on it, include it sooner.");
							}
						});
						throw new Error("Module " + missingModule + " required in " + sName);
					}
				}
			}
			
			//create module
			nameParts = sName.split("."); //split module name into parts
				
			//make empty objects for missing parts, except the last part which will be the module
			for (i = 0, namePartsLen = nameParts.length; i < namePartsLen - 1; i++) {
				if (!objRef[nameParts[i]]) {
					objRef[nameParts[i]] = {};
				}
				objRef = objRef[nameParts[i]];
			}
			//create module
			objRef[nameParts[i]] = oParams.implementation ? oParams.implementation() : {};
			moduleRegister[sName] = true;
			return this;
		},
		ready: function(callback) {
			if (glow.isSupported) { this.onDomReady(callback); }
			return this;
		},
		onDomReady: function(f) {
			//just run function if already ready
			if (this.isReady) {
				f();
			} else {
				var oldLoadFunc = this._lf;
				this._lf = function () {
					oldLoadFunc();
					f();
				};
			}
		},
		_lf: function() { },
		lang: {
			trim: function(sStr) {
				//this optimisation from http://blog.stevenlevithan.com/archives/faster-trim-javascript
				return sStr.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');
			},
			toArray: function(aArrayLike) {
				if (aArrayLike.constructor == Array) {
					return aArrayLike;
				}
				//use array.slice if not IE? Could be faster
				var r = [], i=0, len = aArrayLike.length;
				for (; i < len; i++) {
					r[i] = aArrayLike[i];
				}
				return r;
			},
			
			apply: function(destination, source) {
				for (var i in source) {
					destination[i] = source[i];
				}
				return destination;
			},
			map: function (arr, callback) {
				if (Array.prototype.map) { return Array.prototype.map.call(arr, callback, arguments[2]); }
				if (typeof callback != "function") { throw new TypeError(); }
				var len = arr.length,
					res = new Array(len),
					thisp = arguments[1] || arr,
					i = 0;
				for (; i < len; i++) {
					if (i in arr) {
						res[i] = callback.call(thisp, arr[i], i, arr);
					}
				}
				return res;				
			},
			replace: (function () {
				var replaceBroken = "g".replace(/g/, function () { return 'l'; }) != 'l',
					def = String.prototype.replace;
				return function (inputString, re, replaceWith) {
					var pos, match, last, buf;
					if (! replaceBroken || typeof(replaceWith) != 'function') {
						return def.call(inputString, re, replaceWith);
					}
					if (! (re instanceof RegExp)) {
						pos = inputString.indexOf(re);
						return pos == -1 ?
							inputString :
							def.call(inputString, re, replaceWith.call(null, re, pos, inputString));
					}
					buf = [];
					last = re.lastIndex = 0;
					while ((match = re.exec(inputString)) != null) {
						pos = match.index;
						buf[buf.length] = inputString.slice(last, pos);
						buf[buf.length] = replaceWith.apply(null, match);
						if (re.global) {
							last = re.lastIndex;
						} else {
							last = pos + match[0].length;
							break;
						}			
					}
					buf[buf.length] = inputString.slice(last);
					return buf.join("");
				};
			})(),
			interpolate: function(template, data) {
				var r = template, i;
				for (i in data) {
					r = r.replace(new RegExp("\\{" + i.replace(regexEscape, "\\$1") + "\\}", "g"), data[i]);
				}
				return r;
			},
			hasOwnProperty: {}.hasOwnProperty ? //not supported in Safari 1.3
				function(obj, prop) {
					return obj.hasOwnProperty(prop);
				} :
				function(obj, prop) {
					var propVal = obj[prop], //value of the property
						objProto = obj.__proto__, //prototype of obj
						protoVal = objProto ? objProto[prop] : {}; //prototype val
					if (propVal !== protoVal) {
						return true;
					}
					//try changing prototype and see if obj reacts
					var restoreProtoVal = glow.lang.hasOwnProperty(objProto, prop),
						tempObjProtoVal = objProto[prop] = {},
						hasOwn = (obj[prop] !== tempObjProtoVal);
					
					delete objProto[prop];
					if (restoreProtoVal) {
						objProto[name] = tempObjProtoVal;
					}
					return hasOwn;
				},
			extend: function(sub, base) {
				var f = function () {}, p;
				f.prototype = base.prototype;
				p = new f();
				sub.prototype = p;
				p.constructor = sub;
				sub.base = base;
			}
		}
	};
})();

//run queued ready functions when DOM is ready
(function(){
	var d = document, env = glow.env;
	if (env.ie) {
		// polling for no errors
		(function () {
			try {
				// throws errors until after ondocumentready
				d.documentElement.doScroll('left');
			} catch (e) {
				setTimeout(arguments.callee, 50);
				return;
			}
			// no errors, fire
			glow._lf();
		})();
	} else if (
		typeof d.readyState != 'undefined'
		&& ! (env.webkit < 312)
	) {
		var f = function(){ /loaded|complete/.test(d.readyState) ? glow._lf() : setTimeout(f, 10); };
		f();
	} else {
		var callback = function () {
			if (arguments.callee.fired) { return; }
			arguments.callee.fired = true;
			glow._lf();
		};
		d.addEventListener("DOMContentLoaded", callback, false);
		var oldOnload = window.onload;
		window.onload = function () {
			if (oldOnload) { oldOnload(); }
			callback();
		};
	}
})()

glow.onDomReady(function() { glow.isReady = true; });
glow.isSupported = /*@cc_on @if (@_jscript_version > 5.1)@*/!/*@end @*/!1;
;/*@cc_on @*//*@if (@_jscript_version > 5.1)@*/;

glow.module("glow.dom", "0.4.1", {
	require: [],
	implementation: function() {
		//private
		
		var env = glow.env,
			lang = glow.lang,

			cssRegex = {
				tagName: /^(\w+|\*)/,
				combinator: /^\s*([>]?)\s*/,
				//safari 1.3 is a bit dim when it comes to unicode stuff, only dot matches them (not even \S), so some negative lookalheads are needed
				classNameOrId: (env.webkit < 417) ? new RegExp("^([\\.#])((?:(?![\\.#\\[:\\s\\\\]).|\\\\.)+)") : /^([\.#])((?:[^\.#\[:\\\s]+|\\.)+)/
			},

			cssCache = {},

			dom0PropertyMapping = {
				checked    : "checked",
				"class"    : "className",
				"disabled" : "disabled",
				"for"      : "htmlFor",
				maxlength  : "maxLength"
			},

			dom0BooleanAttribute = {
				checked  : true,
				disabled : true
			},

			dom0AttributeMappings = {
				maxlength : function (val) { return val.toString() == "2147483647" ? undefined : val; }
			},

			ucheck = 1,

			htmlColorNames = {
				black: 0,
				silver: 0xc0c0c0,
				gray: 0x808080,
				white: 0xffffff,
				maroon: 0x800000,
				red: 0xff0000,
				purple: 0x800080,
				fuchsia: 0xff00ff,
				green: 0x8000,
				lime: 0xff00,
				olive: 0x808000,
				yellow: 0xffff00,
				navy: 128,
				blue: 255,
				teal: 0x8080,
				aqua: 0xffff,
				orange: 0xffa500
			},

			usesYAxis = /height|top/,
			colorRegex = /^rgb\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)/i,
			cssPropRegex = /^(?:(width|height)|(top|bottom|left|right)|(border-(top|bottom|left|right)-width))$/,
			//append gets set to a function below
			append,
			//unique gets set to a function below
			unique,
			trbl = ["Top", "Right", "Bottom", "Left"],
			trblLen = 4,
			paddingStr = "padding",
			marginStr = "margin",
			borderStr = "border",
			widthStr = "Width",
			//getByTagName gets get to a function below
			getByTagName,
			win = window,
			doc = document,
			docBody,
			docElm;
			
		glow.ready(function() {
			docBody = doc.body;
			docElm = doc.documentElement;
		})

		function removeClassRegex (name) {
			return new RegExp(["\\b", name, "\\b"].join(""));
		}

		function stringToNodes(str) {
			// TODO: need to change container for certain elements. Make a lookup table for
			// {element: container}, for exceptions to div container
			var div = document.createElement("div"),
				r = [], rLen = 0;
			div.innerHTML = str;
			while (div.childNodes[0]) {
				r[rLen++] = div.removeChild(div.childNodes[0]);
			}
			return r;
		}

		function nodelistToArray(nodelist) {
			var r = [], i = 0;
			for (; nodelist[i]; i++) {
				r[i] = nodelist[i];
			}
			return r;
		}

		// is marginal having this separated out as it is only used twice and call is almost as big
		// leaving it separate for now for once and only once, as well as in case attr does some more mutating stuff
        // could be merged back with attr later
		function setAttribute (value, attributeSetter) {
			for (var that = this, i = 0, length = that.length; i < length; i++) {
				attributeSetter.call(
					that[i],
					value.call ?
						value.call(that[i], i) :
						value
				);
			}
			return that;
		}
		
		//return different function for IE & Opera to deal with their stupid bloody expandos. Pah.
		if (document.all) {
			append = function(a, b) {
				var i = 0,
					ai = a.length,
					length = b.length;
				if (typeof b.length == "number") {
					for (; i < length; i++) {
						a[ai++] = b[i];
					}
				} else {
					for (; b[i]; i++) {
						a[ai++] = b[i];
					}
				}
			};
		} else {
			append = function(a, b) {
				var i = 0, ai = a.length;
				for (; b[i]; i++) {
					a[ai++] = b[i];
				}
			};
		}

		function isXml(node) {
			//test for nodes within xml element
			return  (node.ownerDocument && !node.body)||
					//test for xml document elements
					(node.documentElement && !node.body);
		}

		//worth checking if it's an XML document?
		if (env.ie) {
			unique = function(aNodes) {
				if (aNodes.length == 1) { return aNodes; }
				
				//remove duplicates
				var r = [],
					ri = 0,
					i = 0;
				
				for (; aNodes[i]; i++) {
					if (aNodes[i].getAttribute("_ucheck") != ucheck && aNodes[i].nodeType == 1) {
						r[ri++] = aNodes[i];
					}
					aNodes[i].setAttribute("_ucheck", ucheck);
				}
				for (i=0; aNodes[i]; i++) {
					aNodes[i].removeAttribute("_ucheck");
				}
				ucheck++;
				return r;
			}
		} else {
			unique = function(aNodes) {
				if (aNodes.length == 1) { return aNodes; }
				
				//remove duplicates
				var r = [],
					ri = 0,
					i = 0;
					
				for (; aNodes[i]; i++) {
					if (aNodes[i]._ucheck != ucheck && aNodes[i].nodeType == 1) {
						r[ri++] = aNodes[i];
					}
					aNodes[i]._ucheck = ucheck;
				}
				ucheck++;
				return r;
			}
		}

		if (document.all) { //go the long way around for IE (and Opera)
			getByTagName = function(tag, context) {
				var r = [], i = 0;
				for (; context[i]; i++) {
					//need to check .all incase data is XML
					//TODO: Drop IE5.5
					if (tag == "*" && context[i].all && !isXml(context[i])) { // IE 5.5 doesn't support getElementsByTagName("*")
						append(r, context[i].all);
					} else {
						append(r, context[i].getElementsByTagName(tag));
					}
				}
				return r;
			};
		} else {
			getByTagName = function(tag, context) {
				var r = [], i = 0, len = context.length;
				for (; i < len; i++) {
					append(r, context[i].getElementsByTagName(tag));
				}
				return r;
			};
		}

		function getElmSize(elm) {
			var r,
				oldVals = {},
				i = 0,
				docElmOrBody = env.standardsMode ? docElm : docBody,
				elmStyle = elm.style;
				
			if (elm.window) {
				r = (env.webkit < 522.11 && {width: elm.innerWidth, height: elm.innerHeight}) ||
					(env.webkit && 			{width: docBody.clientWidth, height: elm.innerHeight}) ||
					(env.opera && 			{width: docBody.clientWidth, height: docBody.clientHeight}) ||
				{width: docElmOrBody.clientWidth, height: docElmOrBody.clientHeight};
					
			} else if (elm.getElementById) {
				r = {
						width: Math.max(
							docBody.scrollWidth,
							docBody.offsetWidth,
							docElm.offsetWidth
						),
						height: Math.max(
							docBody.scrollHeight,
							docBody.offsetHeight,
							docElm.offsetHeight
						)
					};
			} else {
				//set border and padding to 0, backing up old vals
				for (; i < trblLen; i++) {
					oldVals[paddingStr + trbl[i]] = elmStyle[paddingStr + trbl[i]];
					oldVals[borderStr + trbl[i] + widthStr] = elmStyle[borderStr + trbl[i] + widthStr];
					elmStyle[paddingStr + trbl[i]] = "0";
					elmStyle[borderStr + trbl[i] + widthStr] = "0";
				}

				//capture values
				r = {width: elm.offsetWidth, height: elm.offsetHeight};
				
				//reset old vals
				for (i = 0; i < trblLen; i++) {
					elmStyle[paddingStr + trbl[i]] = oldVals[paddingStr + trbl[i]];
					elmStyle[borderStr + trbl[i] + widthStr] = oldVals[borderStr + trbl[i] + widthStr];
				}
			}
			return r;
		}

		function getElmPos(elm, pos) {
			// TODO - get Jake to sanity check this
			if (elm.nodeName == 'BODY' || elm.nodeName == 'HTML') return 0;
			var map = {t:"Top",l:"Left"},
				oldVals = {},
				elmBody = getBodyElm(elm),
				compTo = env.ie && env.standardsMode ? elmBody.parentNode : elmBody,
				r,
				elmOffsetParent = elm.offsetParent,
				i = 0,
				offsetParentPos,
				rbPositionContext = (elmOffsetParent == compTo ? win : elmOffsetParent);
			
			for (; i < trblLen; i++) {
				oldVals[marginStr + trbl[i]] = elm.style[marginStr + trbl[i]];
				if (elmOffsetParent) {
					oldVals[borderStr + trbl[i] + widthStr] = elmOffsetParent.style[borderStr + trbl[i] + widthStr];
					elm.style[marginStr + trbl[i]] = elmOffsetParent.style[borderStr + trbl[i] + widthStr] = "0";
				}
			}
			if (pos == "t" || pos == "l") {
				r = elm["offset" + map[pos]];
				if (env.ie) {
					offsetParentPos = elmOffsetParent.currentStyle["position"];
					if (elmOffsetParent.offsetParent && offsetParentPos != "relative" && offsetParentPos != "absolute") {
						r += getElmPos(elmOffsetParent, pos);
					}
				}
				
			} else if (pos == "r") {
				r = getElmSize(rbPositionContext).width - elm.offsetLeft - elm.offsetWidth;
			} else if (pos == "b") {
				r = getElmSize(rbPositionContext).height - elm.offsetTop - elm.offsetHeight;
			}
			for (i = 0; i < trblLen; i++) {
				elm.style[marginStr + trbl[i]] = oldVals[marginStr + trbl[i]];
				if (elmOffsetParent) {
					elmOffsetParent.style[borderStr + trbl[i] + widthStr] = oldVals[borderStr + trbl[i] + widthStr];
				}
			}
			return r;
		}

		function getBodyElm(elm) {
			if (env.ie < 6) {
				return elm.document.body;
			} else {
				return elm.ownerDocument.body;
			}
		}

		function setElmsSize(elms, val, type) {
			if (typeof val == "number" || /\d$/.test(val)) {
				val += "px";
			}
			for (var i = 0, len = elms.length; i < len; i++) {
				elms[i].style[type] = val;
			}
		}

		function toStyleProp(prop) {
			if (prop == "float") {
				return env.ie ? "styleFloat" : "cssFloat";
			}
			return lang.replace(prop, /-(\w)/g, function(match, p1) {
				return p1.toUpperCase();
			});
		}

		function tempBlock(elm, func) {
			var r,
				elmStyle = elm.style,
				oldDisp = elmStyle.display,
				oldVis = elmStyle.visibility,
				oldPos = elmStyle.position;
			
			elmStyle.visibility = "hidden";
			elmStyle.position = "absolute";
			elmStyle.display = "block";
			if (!isVisible(elm)) {
				elmStyle.position = oldPos;
				r = tempBlock(elm.parentNode, func);
				elmStyle.display = oldDisp;
				elmStyle.visibility = oldVis;
			} else {
				r = func();
				elmStyle.display = oldDisp;
				elmStyle.position = oldPos;
				elmStyle.visibility = oldVis;
			}
			return r;
		}

		function isVisible(elm) {
			//this is a bit of a guess, if there's a better way to do this I'm interested!
			return elm.offsetWidth ||
				elm.offsetHeight;
		}

		function getCssValue(elm, prop) {
			var r, //return value
				total = 0,
				i = 0,
				propLen = prop.length,
				compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(elm, null) || doc.defaultView.getComputedStyle),
				elmCurrentStyle = elm.currentStyle,
				oldDisplay,
				match,
				propTest = prop.push || cssPropRegex.exec(prop) || [];
			
			
			if (prop.push) { //multiple properties, add them up
				for (; i < propLen; i++) {
					total += parseInt(getCssValue(elm, prop[i]), 10) || 0;
				}
				return total + "px";
			}
			//had fun with opera including padding in width, this seems safer
			if (propTest[1]) { //is 'width' or 'height'
				if (!isVisible(elm)) { //element may be display: none
					return tempBlock(elm, function() {
						return getElmSize(elm)[prop] + "px";
					});
				} else {
					return getElmSize(elm)[prop] + "px";
				}
			} else if (propTest[2] && getCssValue(elm, "position") != "relative") { //is 'top' 'left' 'bottom' 'right'
				if (!isVisible(elm)) { //element may be display: none
					return tempBlock(elm, function() {
						return getElmPos(elm, prop.charAt(0)) + "px";
					});
				} else {
					return getElmPos(elm, prop.charAt(0)) + "px";
				}
            } else if (propTest[3] //is border-*-width
				&& glow.env.ie
                && getCssValue(elm, "border-" + propTest[4] + "-style") == "none"
            ) {
				return "0";
			} else if (compStyle) { //W3 Method
				//this returns computed values
				if (typeof compStyle == "function") {
					//safari returns null for compStyle
					
					oldDisplay = elm.style.display;
					r = tempBlock(elm, function() {
						if (prop == "display") { //get true value for display, since we've just fudged it
							elm.style.display = oldDisplay;
							if (!doc.defaultView.getComputedStyle(elm, null)) {
								return "none";
							}
							elm.style.display = "block";
						}
						return getCssValue(elm, prop);
					});
				} else {
					// assume equal horizontal margins in safari 3
					// TODO put max version on this when http://bugs.webkit.org/show_bug.cgi?id=13343 is fixed
					if (glow.env.webkit >= 522 && prop == 'margin-right') prop = 'margin-left';
					r = compStyle.getPropertyValue(prop);
				}
			} else if (elmCurrentStyle) { //IE method
				if (prop == "opacity") {
					match = /alpha\(opacity=([^\)]+)\)/.exec(elmCurrentStyle.filter);
					return match ? String(parseInt(match[1], 10) / 100) : "1";
				}
				//this returns cascaded values so needs fixing
				r = String(elmCurrentStyle[toStyleProp(prop)]);
				if (/^-?\d+[a-z%]+$/i.test(r) && prop != "font-size") {
					r = getPixelValue(elm, r, usesYAxis.test(prop)) + "px";
				}
			}
			//some results need post processing
			if (prop.indexOf("color") != -1) { //deal with colour values
				r = normaliseCssColor(r).toString();
			} else if (r.indexOf("url") == 0) { //some browsers put quotes around the url, get rid
				r = r.replace(/\"/g,"");
			}
			return r;
		}

		function getPixelValue(element, value, useYAxis) {
			if (/^-?\d+(px)?$/i.test(value)) { return parseInt(value); }
			var axisSetValue = useYAxis ? "top" : "left",
				axisGetValue = useYAxis ? "Top" : "Left",
				elmStyle = element.style,
				origWidth = elmStyle.left,
				origPos = elmStyle.overflow,
				origMargin = elmStyle.margin;
			elmStyle.position = "absolute";
			elmStyle.margin = "0";
			elmStyle[axisSetValue] = value || 0;
			value = element["offset" + axisGetValue];
			elmStyle.position = origPos;
			elmStyle[axisSetValue] = origWidth;
			elmStyle.margin = origMargin;
			return value;
		}

		
		function normaliseCssColor(val) {
			if (/^(transparent|rgba\(0, ?0, ?0, ?0\))$/.test(val)) { return 'transparent'; }
			var match, //tmp regex match holder
				r, g, b, //final colour vals
				hex, //tmp hex holder
				mathRound = Math.round,
				parseIntFunc = parseInt,
				parseFloatFunc = parseFloat;
				
			if (match = colorRegex.exec(val)) { //rgb() format, cater for percentages
				r = match[2] ? mathRound(((parseFloatFunc(match[1]) / 100) * 255)) : parseIntFunc(match[1]);
				g = match[4] ? mathRound(((parseFloatFunc(match[3]) / 100) * 255)) : parseIntFunc(match[3]);
				b = match[6] ? mathRound(((parseFloatFunc(match[5]) / 100) * 255)) : parseIntFunc(match[5]);
			} else {
				if (typeof val == "number") {
					hex = val;
				} else if (val.charAt(0) == "#") {
					if (val.length == "4") { //deal with #fff shortcut
						val = "#" + val.charAt(1) + val.charAt(1) + val.charAt(2) + val.charAt(2) + val.charAt(3) + val.charAt(3);
					}
					hex = parseIntFunc(val.slice(1), 16);
				} else {
					hex = htmlColorNames[val];
				}
				
				r = (hex) >> 16;
				g = (hex & 0x00ff00) >> 8;
				b = (hex & 0x0000ff);
			}
			
			val = new String("rgb(" + r + ", " + g + ", " + b + ")");
			val.r = r;
			val.g = g;
			val.b = b;
			return val;
		}

		function getTextNodeConcat(elm) {
			var r = "",
				nodes = elm.childNodes,
				i = 0,
				len = nodes.length;
			for (; i < len; i++) {
				if (nodes[i].nodeType == 3) { //text
					//this function is used in safari only, string concatination is faster
					r += nodes[i].nodeValue;
				} else if (nodes[i].nodeType == 1) { //element
					r += getTextNodeConcat(nodes[i]);
				}
			}
			return r;
		}

		function getNextOrPrev(nodelist, dir
) {
			var ret = [],
				ri = 0,
				nextTmp,
				i = 0,
				length = nodelist.length;
			
			for (; i < length; i++) {
				nextTmp = nodelist[i];
				while (nextTmp = nextTmp[dir + "Sibling"]) {
					if (nextTmp.nodeType == 1 && nextTmp.nodeName != "!") {
						ret[ri++] = nextTmp;
						break;
					}
				}
			}
			return r.get(ret);
		}
		
		//public
		var r = {}; //object to be returned

		
		r.get = function() {
			var r = new glow.dom.NodeList(),
				i = 0,
				args = arguments,
				argsLen = args.length;
			
			for (; i < argsLen; i++) {
				if (typeof args[i] == "string") {
					r.push(new glow.dom.NodeList().push(doc).get(args[i]));
				} else {
					r.push(args[i]);
				}
			}
			return r;
		};

		r.create = function(sHtml) {
			var toCheck = stringToNodes(sHtml),
				ret = [],
				i = 0,
				rLen = 0;
			for (; toCheck[i]; i++) {
				if (toCheck[i].nodeType == 1 && toCheck[i].nodeName != "!") {
					ret[rLen++] = toCheck[i];
				} else if (toCheck[i].nodeType == 3 && lang.trim(toCheck[i].nodeValue) !== "") {
					throw new Error("glow.dom.create - Text must be wrapped in an element");
				}
			}
			return new r.NodeList().push(ret);
		};

		r.parseCssColor = function(cssColor) {
			var normal = normaliseCssColor(cssColor);
			return {r: normal.r, g: normal.g, b: normal.b};
		}

		r.NodeList = function() {

			this.length = 0; //number of elements in NodeList
		};

		r.NodeList.prototype = {

			item: function (nIndex) {
				return this[nIndex];
			},

			push: function() {
				var args = arguments,
					argsLen = args.length,
					i = 0,
					n,
					nNodeListLength,
					that = this,
					arrayPush = Array.prototype.push;
				
				for (; i < argsLen; i++) {
					if (args[i].constructor == Array) { //is array
						arrayPush.apply(that, args[i]);
					} else if (args[i].item && args[i][0]) { //is nodelist
						for (n = 0, nNodeListLength = args[i].length; n < nNodeListLength; n++) {
							arrayPush.call(that, args[i][n]);
						}
					} else if (args[i].nodeType == 1 || args[i].nodeType == 9 || args[i].document) { //is Node
						arrayPush.call(that, args[i]);
					}
				}
				return that;
			},

			each: function (callback) {
				for (var i = 0, that = this, length = that.length; i < length; i++) {
					callback.call(that[i], i, that);
				}
				return that;
			},

			eq: function (nodelist) {
				var that = this, i = 0, length = that.length;
			
				if (! nodelist.push) { nodelist = [nodelist]; }
				if (nodelist.length != that.length) { return false; }
				for (; i < length; i++) {
					if (that[i] != nodelist[i]) { return false; }
				}
				return that;
			},

			isWithin: function (node) {
				if (node.push) { node = node[0]; }
				var that = this,
					i = 0,
					length = that.length,
					toTest; //node to test in manual method
					
				if (node.contains && env.webkit >= 521) { //proprietary method, safari 2 has a wonky implementation of this, avoid, avoid, avoid
					//loop through
					for (; i < length; i++) {
						// myNode.contains(myNode) returns true in most browsers
						if (!(node.contains(that[i]) && that[i] != node)) { return false; }
					}
				} else if (that[0].compareDocumentPosition) { //w3 method
					//loop through
					for (; i < length; i++) {
						//compare against bitmask
						if (!(that[i].compareDocumentPosition(node) & 8)) { return false; }
					}
				} else { //manual method
					for (; i < length; i++) {
						toTest = that[i];
						while (toTest = toTest.parentNode) {
							if (toTest == node) { break; }
						}
						if (!toTest) { return false; }
					}
				}
				return true;
			},

			attr: function (name
) {
				var that = this,
					args = arguments,
					argsLen = args.length,
					i,
					value;
			
				if (that.length === 0) {
					return argsLen > 1 ? that : undefined;
				}
				if (typeof name == 'object') {
					for (i in name) {
						if (lang.hasOwnProperty(name, i)) {
							that.attr(i, name[i]);
						}
					}
					return that;
				}
				if (env.ie && dom0PropertyMapping[name]) {
					if (argsLen > 1) {
						setAttribute.call(
							that,
							args[1],
							// in the callback this is the dom node
							function (val) { this[dom0PropertyMapping[name]] = val; }
						);
						return that;
					}
					value = that[0][dom0PropertyMapping[name]];
					if (dom0BooleanAttribute[name]) {
						return value ? name : undefined;
					}
					else if (dom0AttributeMappings[name]) {
						return dom0AttributeMappings[name](value);
					}
					return value;
				}
				if (argsLen > 1) {
					setAttribute.call(
						that,
						args[1],
						// in the callback this is the dom node
						function (val) { this.setAttribute(name, val); }
					);
					return that;
				}
				//2nd parameter makes IE behave, but errors for XML nodes (and isn't needed for xml nodes)
				return isXml(that[0]) ? that[0].getAttribute(name) : that[0].getAttribute(name, 2);
			},

			removeAttr: function (name) {
				var mapping = env.ie && dom0PropertyMapping[name],
					that = this,
					i = 0,
					length = that.length;
				
				for (; i < length; i++) {
					if (mapping) {
						that[i][mapping] = "";
					} else {
						that[i].removeAttribute(name);
					}
				}
				return that;
			},

			hasAttr: function (name) {
				var firstNode = this[0],
					attributes = firstNode.attributes;
					
				if (isXml(firstNode) && env.ie) { //getAttributeNode not supported for XML
					var attributes = firstNode.attributes,
						i = 0,
						len = attributes.length;
					
					//named node map doesn't seem to work properly, so need to go by index
					for (; i < len; i++) {
						if (attributes[i].nodeName == name) {
							return attributes[i].specified;
						}
					}
					return false;
				} else if (this[0].getAttributeNode) {
					var attr = this[0].getAttributeNode(name);
					return attr ? attr.specified : false;
				}
				
				return typeof attributes[attr] != "undefined";
			},

			hasClass: function (name) {
				for (var i = 0, length = this.length; i < length; i++) {
					if ((" " + this[i].className + " ").indexOf(" " + name + " ") != -1) {
						return true;
					}
				}
				return false;
			},

			addClass: function (name) {
				for (var i = 0, length = this.length; i < length; i++) {
					if ((" " + this[i].className + " ").indexOf(" " + name + " ") == -1) {
						this[i].className += " " + name;
					}
				}
				return this;
			},

			removeClass: function (name) {
				var re = removeClassRegex(name),
					that = this,
					i = 0,
					length = that.length;
				
				for (; i < length; i++) {
					that[i].className = that[i].className.replace(re, "");
				}
				return that;
			},

			toggleClass: function (name) {
				for (var that = this, i = 0, length = that.length; i < length; i++) {
					if ((" " + that[i].className + " ").indexOf(" " + name + " ") != -1) {
						that[i].className = that[i].className.replace(removeClassRegex(name), "");
					} else {
						that[i].className += " " + name;
					}
				}
				return that;
			},

			val: function () {

				function elementValue (el) {
					var elType = el.type,
						elChecked = el.checked,
						elValue = el.value,
						vals = [],
						i = 0;
					
					if (elType == "radio") {
						return elChecked ? elValue : undefined;
					} else if (elType == "checkbox") {
						return elChecked ? elValue : undefined;

					} else if (elType == "select-one") {
						return el.selectedIndex > -1 ?
							el.options[el.selectedIndex].value : "";

					} else if (elType == "select-multiple") {
						for (var length = el.options.length; i < length; i++) {
							if (el.options[i].selected) {
								vals[vals.length] = el.options[i].value;
							}
						}
						return vals;
					} else {
						return elValue;
					}
				}

				function formValues (form) {
					var vals = {},
						radios = {},
						formElements = form.elements,
						i = 0,
						length = formElements.length,
						name,
						formElement,
						j = 0,
						radio;
					
					for (; i < length; i++) {
						formElement = formElements[i];
						name = formElement.name;
						
						if (formElement.type == "checkbox" && ! formElement.checked) {
							if (! name in vals) {
								vals[name] = undefined;
							}
						} else if (formElement.type == "radio") {
							if (radios[name]) {
								radios[name][radios[name].length] = formElement;
							} else {
								radios[name] = [formElement];
							}
						} else {
							var value = elementValue(formElement);
							if (name in vals) {
								if (vals[name].push) {
									vals[name][vals[name].length] = value;
								} else {
									vals[name] = [vals[name], value];
								}
							} else {
								vals[name] = value;
							}
						}
					}
					for (i in radios) {
						for (length = radios[i].length; j < length; j++) {
							radio = radios[i][j];
							name = radio.name;
							if (radio.checked) {
								vals[radio.name] = radio.value;
								break;
							}
						}
						if (! name in vals) { vals[name] = undefined; }
					}
					return vals;
				}

				function setFormValues (form, vals) {
					var prop, currentField,
						fields = {},
						storeType, i = 0, n, len, foundOne, currentFieldType;
						
					for (prop in vals) {
						currentField = form[prop];
						if (currentField && currentField[0]) { // is array of fields
							//normalise values to array of vals
							vals[prop] = vals[prop] && vals[prop].push ? vals[prop] : [vals[prop]];
							//order the fields by types that matter
							fields.radios = [];
							fields.checkboxesSelects = [];
							fields.multiSelects = [];
							fields.other = [];
							
							for (; currentField[i]; i++) {
								currentFieldType = currentField[i].type;
								if (currentFieldType == "radio") {
									storeType = "radios";
								} else if (currentFieldType == "select-one" || currentFieldType == "checkbox") {
									storeType = "checkboxesSelects";
								} else if (currentFieldType == "select-multiple") {
									storeType = "multiSelects";
								} else {
									storeType = "other";
								}
								//add it to the correct array
								fields[storeType][fields[storeType].length] = currentField[i];
							}
							
							for (i = 0; fields.multiSelects[i]; i++) {
								vals[prop] = setValue(fields.multiSelects[i], vals[prop]);
							}
							for (i = 0; fields.checkboxesSelects[i]; i++) {
								setValue(fields.checkboxesSelects[i], "");
								for (n = 0, len = vals[prop].length; n < len; n++) {
									if (setValue(fields.checkboxesSelects[i], vals[prop][n])) {
										vals[prop].slice(n, 1);
										break;
									}
								}
							}
							for (i = 0; fields.radios[i]; i++) {
								fields.radios[i].checked = false;
								foundOne = false;
								for (n = 0, len = vals[prop].length; n < len; n++) {
									if (setValue(fields.radios[i], vals[prop][n])) {
										vals[prop].slice(n, 1);
										foundOne = true;
										break;
									}
									if (foundOne) { break; }
								}
							}
							for (i = 0; fields.other[i] && vals[prop][i] !== undefined; i++) {
								setValue(fields.other[i], vals[prop][i]);
							}
						} else if (currentField && currentField.nodeName) { // is single field, easy
							setValue(currentField, vals[prop]);
						}
					}
				}

				function setValue (el, val) {
					var i = 0,
						length,
						n = 0,
						nlen,
						elOption,
						optionVal;
					
					if (el.type == "select-one") {
						for (length = el.options.length; i < length; i++) {
							if (el.options[i].value == val) {
								el.selectedIndex = i;
								return true;
							}
						}
						return false;
					} else if (el.type == "select-multiple") {
						var isArray = !!val.push;
						for (i = 0, length = el.options.length; i < length; i++) {
							elOption = el.options[i];
							optionVal = elOption.value;
							if (isArray) {
								elOption.selected = false;
								for (nlen = val.length; n < nlen; n++) {
									if (optionVal == val[n]) {
										elOption.selected = true;
										val.splice(n, 1);
										break;
									}
								}
							} else {
								return elOption.selected = val == optionVal;
							}
						}
						return false;
					} else if (el.type == "radio" || el.type == "checkbox") {
						el.checked = val == el.value;
						return val == el.value;
					} else {
						el.value = val;
						return true;
					}
				}

				// toplevel implementation
				return function (
) {
					var args = arguments,
						val = args[0],
						that = this,
						i = 0,
						length = that.length;
					
					if (args.length === 0) {
						return that[0].nodeName == 'FORM' ?
							formValues(that[0]) :
							elementValue(that[0]);
					}
					if (that[0].nodeName == 'FORM') {
						if (! typeof val == 'object') {
							throw 'value for FORM must be object';
						}
						setFormValues(that[0], val);
					} else {
						for (; i < length; i++) {
							setValue(that[i], val);
						}
					}
					return that;
				};
			}(),

			slice: function (
) {
				return new r.NodeList().push(Array.prototype.slice.apply(this, arguments));
			},
			
			sort: function(func) {
				var that = this, i=0, aNodes;
				
				if (!that.length) { return that; }
				if (!func) {
					if (typeof that[0].sourceIndex == "number") {
						// sourceIndex is IE proprietary (but Opera supports)
						func = function(a, b) {
							return a.sourceIndex - b.sourceIndex;
						};
					} else if (that[0].compareDocumentPosition) {
						// DOM3 method
						func = function(a, b) {
							return 3 - (a.compareDocumentPosition(b) & 6);
						};
					} else {
						// js emulation of sourceIndex
						aNodes = getByTagName("*", [doc]);
						for (; aNodes[i]; i++) {
							aNodes[i]._sourceIndex = i;
						}
						func = function(a, b) {
							return a._sourceIndex - b._sourceIndex;
						};
					}
				}
				
				return r.get([].sort.call(that, func));
			},

			filter: function(callback) {
				var ret = [], //result
					ri = 0,
					i = 0,
					length = this.length;
				for (; i < length; i++) {
					if (callback.apply(this[i], [i])) {
						ret[ri++] = this[i];
					}
				}
				return r.get(ret);
			},

			children: function() {
				var ret = [],
					ri = 0,
					i = 0,
					n = 0,
					length = this.length,
					childTmp;

				for (; i < length; i++) {
					childTmp = this[i].childNodes;
					for (; childTmp[n]; n++) {
						if (childTmp[n].nodeType == 1 && childTmp[n].nodeName != "!") {
							ret[ri++] = childTmp[n];
						}
					}
				}
				return r.get(ret);
			},

			parent: function() {
				var ret = [],
					ri = 0,
					i = 0,
					length = this.length;
				
				for (; i < length; i++) {
					ret[ri++] = this[i].parentNode;
				}
				return r.get(unique(ret));
			},

			next: function() {
				return getNextOrPrev(this, "next");
			},

			prev: function() {
				return getNextOrPrev(this, "previous");
			},

			is: function (selector) {
				// TODO - this implementation needs to be optimized
				var nodes = glow.dom.get(selector),
					i = 0,
					iLen = this.length,
					j,
					jLen;
				
				node:
				for (; i < iLen; i++) {
					for (j = 0, jLen = nodes.length; j < jLen; j++) {
						if (this[i] == nodes[j]) {
							continue node;
						}
					}
					return false;
				}
				return true;
			},

			text: function (
) {
				var args = arguments,
					i = 0,
					that = this,
					length = that.length;
				
				if (args.length > 0) {
					for (; i < length; i++) {
						that[i].innerHTML = "";
						that[i].appendChild(doc.createTextNode(args[0]));
					}
					return that;
				}
				//innerText (empty) and textContent (undefined) don't work in safari 2 for hidden elements
				return that[0].innerText || that[0].textContent == undefined ? getTextNodeConcat(that[0]) : that[0].textContent;
			},

			empty: function () {
				for (var i = 0, length = this.length, child; i < length; i++) {
					while (child = this[i].firstChild) {
						this[i].removeChild(child);
					}
				}
				return this;
			},

			remove: function () {
				for (var that = this, i = 0, length = that.length, parentNode; i < length; i++) {
					if (parentNode = that[i].parentNode) {
						parentNode.removeChild(that[i]);
					}
				}
				return that;	
			},

			clone: function () {
				// TODO - clone events
				var ret = [],
					i = 0,
					length = this.length;
					
				for (; i < length; i++) {
					ret[i] = this[i].cloneNode(true);
				}
				return r.get(ret);
			},

			html: function (
) {
				var args = arguments,
					that = this,
					i = 0,
					length = that.length;
			
				if (args.length > 0) {
					for (; i < length; i++) {
						that[i].innerHTML = args[0];
					}
					return that;
				}
				return that[0].innerHTML;
			},

			width: function(width) {
				if (width == undefined) {
					return getElmSize(this[0]).width;
				}
				setElmsSize(this, width, "width");
				return this;
			},

			height: function(height) {
				if (height == undefined) {
					return getElmSize(this[0]).height;
				}
				setElmsSize(this, height, "height");
				return this;
			},

			css: function(prop, val) {
				var that = this,
					thisStyle,
					i = 0,
					len = that.length;
				
				if (val != undefined) { //setting stuff
					prop = toStyleProp(prop);
					for (; i < len; i++) {
						thisStyle = that[i].style;
						if (prop == "opacity" && env.ie) {
							if (val === "") {
								thisStyle.filter = "";
							} else {
								thisStyle.filter = "alpha(opacity=" + Math.round(Number(val, 10) * 100) + ")";
							}
						} else {
							thisStyle[prop] = val;
						}
					}
					return that;
				} else { //getting stuff
					if (!that.length) { return; }
					return getCssValue(that[0], prop);
				}
			},

			offset: function (ignoreScrolling) {
				var node = this[0],
					x = 0,
					y = 0,
					first = true;
				
				if (! node) { return undefined; }
				do {
					x += node.offsetLeft;
					y += node.offsetTop;
					if (!ignoreScrolling && !first && node != docElm && node != docBody) {
						x -= node.scrollLeft;
						y -= node.scrollTop;
					}
					first = false;
				} while (node = node.offsetParent);
				return {x:x, y:y};
			},

			append: function (nodeSpec) {
				var that = this,
					j = 0,
					i = 1,
					length = that.length,
					nodes;
			
				if (length == 0) { return that; }
				nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) :
						nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec);
				
				for (; nodes[j]; j++) {
					that[0].appendChild(nodes[j]);
				}
				for (; i < length; i++) {
					for (j = 0; nodes[j]; j++) {
						that[i].appendChild(nodes[j].cloneNode(true));
					}
				}
				return that;
			},

			prepend: function (nodeSpec) {
				var that = this,
					j = 0,
					i = 1,
					length = that.length,
					nodes,
					first;
			
				if (length == 0) { return that; }
				
				nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) :
						nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec);
				
				first = that[0].firstChild;
				
				for (; nodes[j]; j++) {
					that[0].insertBefore(nodes[j], first);
				}
				
				for (; i < length; i++) {
					first = that[i].firstChild;
					for (j = 0; nodes[j]; j++) {
						that[i].insertBefore(nodes[j].cloneNode(true), first);
					}
				}
				return that;
			},

			appendTo: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.append(this);
				return this;
			},

			prependTo: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.prepend(this);
				return this;
			},

			after: function (nodeSpec) {
				var that = this,
					length = that.length,
					nodes,
					nodesLen,
					j,
					i = 1,
					cloned;
				
				if (length == 0) { return that; }
				
				nodes = typeof nodeSpec == "string" ? r.create(nodeSpec) :
					nodeSpec instanceof r.NodeList ? nodeSpec :
					r.get(nodeSpec);
					
				nodesLen = nodes.length;
				
				for (j = nodesLen - 1; j >= 0; j--) {
					that[0].parentNode.insertBefore(nodes[j], that[0].nextSibling);
				}
				for (; i < length; i++) {
					cloned = nodes.clone();
					
					for (j = nodesLen - 1; j >= 0; j--) {
						that[i].parentNode.insertBefore(cloned[j], that[i].nextSibling);
					}
				}
				return that;
			},

			before: function (nodeSpec) {
				var that = this,
					length = that.length,
					j = 0,
					i = 1,
					nodes,
					nodesLen,
					cloned;
			
				if (length == 0) { return that; }
				
				nodes = typeof nodeSpec == "string" ? r.create(nodeSpec) :
					nodeSpec instanceof r.NodeList ? nodeSpec :
					r.get(nodeSpec);
				
				nodesLen = nodes.length;
				
				for (; j < nodesLen; j++) {
					that[0].parentNode.insertBefore(nodes[j], that[0]);
				}
				for (; i < length; i++) {
					cloned = nodes.clone();
					for (j = 0; j < nodesLen; j++) {
						that[i].parentNode.insertBefore(cloned[j], that[i]);
					}
				}
				return that;
			},

			insertAfter: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.after(this);
				return this;
			},

			insertBefore: function (nodes) {
				if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
				nodes.before(this);
				return this;
			},

			replaceWith: function (nodeSpec) {
				var that = this,
					length = that.length,
					nodes,
					nodesLen,
					j = 0,
					i = 1,
					cloned;
			
				if (length == 0) { return that; }
				
				nodes =
					typeof nodeSpec == "string" ? r.create(nodeSpec) :
					nodeSpec instanceof r.NodeList ? nodeSpec :
					r.get(nodeSpec);
				
				that[0].innerHTML = "";
				
				nodesLen = nodes.length;
				
				for (; j < nodesLen; j++) {
					that[0].appendChild(nodes[j]);
				}
				for (; i < length; i++) {
					that[i].innerHTML = "";
					cloned = nodes.clone();
					for (j = 0; j < nodesLen; j++) {
						that[i].appendChild(cloned[j]);
					}
				}
				return that;
			},

			get: function() {

				function compileSelector(sSelector) {
					//return from cache if possible
					if (cssCache[sSelector]) {
						return cssCache[sSelector];
					}
					
					var r = [], //array of result objects
						ri = 0, //results index
						comb, //current combinator
						tagTmp,
						idTmp,
						aRx, //temp regex result
						matchedCondition, //have we matched a condition?
						sLastSelector, //holds last copy of selector to prevent infinite loop
						firstLoop = true;
					
					while (sSelector && sSelector != sLastSelector) {
						tagTmp = "";
						idTmp = "";
						//protect us from infinite loop
						sLastSelector = sSelector;
						
						//start by getting the scope (combinator)
						if (aRx = cssRegex.combinator.exec(sSelector)) {
							comb = aRx[1];
							sSelector = sSelector.slice(aRx[0].length);
						}
						//look for optimal id & tag searching
						if (aRx = cssRegex.tagName.exec(sSelector)) {
							tagTmp = aRx[1];
							sSelector = sSelector.slice(aRx[0].length);
						}
						if (aRx = cssRegex.classNameOrId.exec(sSelector)) {
							if (aRx[1] == "#") {
								idTmp = aRx[2];
								sSelector = sSelector.slice(aRx[0].length);
							}
						}
						if (!comb) { //use native stuff
							if (idTmp && firstLoop) {
								r[ri++] = [getByIdQuick, [idTmp.replace(/\\/g, ""), tagTmp || "*", null]];
							} else {
								r[ri++] = [getByTagName, [tagTmp || "*", null]];
								if (idTmp) {
									r[ri++] = [hasId, [idTmp.replace(/\\/g, ""), null]];
								}
							}
						} else if (comb == ">") {
							r[ri++] = [getChildren, [null]];
							if (idTmp) {
								r[ri++] = [hasId, [idTmp.replace(/\\/g, ""), null]];
							}
							if (tagTmp && tagTmp != "*") { //uses tag
								r[ri++] = [isTag, [tagTmp, null]];
							}
						}

						//other conditions can appear in any order, so here we go:
						matchedCondition = true;
						while (matchedCondition) {
							//look for class or ID
							if (sSelector.charAt(0) == "#" || sSelector.charAt(0) == ".") {
								if (aRx = cssRegex.classNameOrId.exec(sSelector)) {
									if (sSelector.charAt(0) == "#") { //is ID
										//define ID, remove escape chars
										r[ri++] = [hasId, [aRx[2].replace(/\\/g, ""), null]];
									} else { //is class
										r[ri++] = [hasClassName, [aRx[2].replace(/\\/g, ""), null]];
									}
									sSelector = sSelector.slice(aRx[0].length);
								} else {
									//make this more user friendly?
									throw new Error("Invalid Selector");
								}
							} else {
								matchedCondition = false;
							}
						}
						
						firstLoop = false;
					}
					
					if (sSelector !== "") {
						//make this more user friendly?
						throw new Error("Invalid Selector");
					}
					
					//add to cache and return
					return cssCache[sSelector] = r;
				}

				function fetchElements(a, initialContext) {
					var context = initialContext; //elements to look within
					
					for (var i = 0, al = a.length; i < al; i++) {
						a[i][1][a[i][1].length - 1] = context;
						context = a[i][0].apply(this, a[i][1]);
					}
					return context;
				}

				function getByIdQuick(id, tagName, context) {
					var r = [], ri = 0, notQuick = [], notQuicki = 0, tmpNode;
					for (var i = 0, length = context.length; i < length; i++) {
						if (context[i].getElementById) {
							tmpNode = context[i].getElementById(id);
							if (tmpNode && (tmpNode.tagName == tagName.toUpperCase() || tagName == "*" || tmpNode.tagName == tagName)) {
								r[ri++] = tmpNode;
							}
						} else {
							notQuick[notQuicki++] = context[i];
						}
					}
					//deal with the ones we couldn't do quick
					if (notQuick[0]) {
						notQuick = getByTagName(tagName, notQuick);
						notQuick = hasId(id, notQuick);
					}
					return r.concat(notQuick);
				}
				
				function getChildren(context) {
					var r = [];
					for (var i = 0, length = context.length; i < length; i++) {
						append(r, context[i].childNodes);
					}
					return r;
				}
				
				function hasId(id, context) {
					for (var i = 0, length = context.length; i < length; i++) {
						if (context[i].id == id) {
							//is this a safe optimisation?
							return [context[i]];
						}
					}
					return [];
				}

				function isTag(tagName, context) {
					var r = [], ri = 0;
					for (var i = 0, length = context.length; i < length; i++) {
						if (context[i].tagName == tagName.toUpperCase() || context[i].tagName == tagName) {
							r[ri++] = context[i];
						}
					}
					return r;
				}

				function hasClassName(className, context) {
					var r = [], ri = 0;
					for (var i = 0, length = context.length; i < length; i++) {
						if ((" " + context[i].className + " ").indexOf(" " + className + " ") != -1) {
							r[ri++] = context[i];
						}
					}
					return r;
				}

				function getBySelector(sSelector, context) {
					var aCompiledCSS; // holds current compiled css statement
					var r = [];
					//split multiple selectors up
					var aSelectors = sSelector.split(",");
					//process each
					for (var i = 0, nSelLen = aSelectors.length; i < nSelLen; i++) {
						aCompiledCSS = compileSelector(glow.lang.trim(aSelectors[i]));
						//get elements from DOM
						r = r.concat(fetchElements(aCompiledCSS, context));
					}
					return r;
				}

				function getIfWithinContext(nodes, context) {
					nodes = nodes.length ? nodes : [nodes];
					var r = []; //to return
					var nl; 
					
					//loop through nodes
					for (var i = 0; nodes[i]; i++) {
						nl = glow.dom.get(nodes[i]);
						//loop through context nodes
						for (var n = 0; context[n]; n++) {
							if (nl.isWithin(context[n])) {
								r[r.length] = nl[0];
								break;
							}
						}
					}
					return r;
				}
				
				// main implementation
				return function(sSelector) {
					// no point trying if there's no current context
					if (!this.length) { return this; }
					var r = []; //nodes to return
					// decide what to do by arg
					for (var i = 0, argLen = arguments.length; i < argLen; i++) {
						if (typeof arguments[i] == "string") { // css selector string
							r = r.concat(getBySelector(arguments[i], this));
							// append(r, getBySelector(arguments[i], this));
						} else { // nodelist, array, node
							r = r.concat(getIfWithinContext(arguments[i], this));
							// append(r, getIfWithinContext(arguments[i], this));
						}
					}
					
					// strip out duplicates, wrap in nodelist
					return glow.dom.get(unique(r));
				};
			}()
		};
		return r;
	}
});
;
;

glow.module("glow.data", "0.4.1", {
	require: ["glow.dom"],
	implementation: function() {
		//private

		var TYPES = {
			UNDEFINED : "undefined",
			OBJECT    : "object",
			NUMBER    : "number",
			BOOLEAN   : "boolean",
			STRING    : "string",
			ARRAY     : "array",
			FUNCTION  : "function",
			NULL      : "null"
		}

		var TEXT = {
			AT    : "@",
			EQ    : "=",
			DOT   : ".",
			EMPTY : "",
			AND   : "&",
			OPEN  : "(",
			CLOSE : ")"
		}

		var JSON = {
			HASH : {
				START     : "{",
				END       : "}",
				SHOW_KEYS : true
			},

			ARRAY : {
				START     : "[",
				END       : "]",
				SHOW_KEYS : false
			},

			DATA_SEPARATOR   : ",",
			KEY_SEPARATOR    : ":",
			KEY_DELIMITER    : "\"",
			STRING_DELIMITER : "\"",

			SAFE_PT1 : /^[\],:{}\s]*$/,
			SAFE_PT2 : /\\./g,
			SAFE_PT3 : /\"[^\"\\\n\r]*\"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g,
			SAFE_PT4 : /(?:^|:|,)(?:\s*\[)+/g
		}

		var SLASHES = {
			TEST : /[\b\n\r\t\\\f\"]/g,
			B : {PLAIN : "\b", ESC : "\\b"},
			N : {PLAIN : "\n", ESC : "\\n"},
			R : {PLAIN : "\r", ESC : "\\r"},
			T : {PLAIN : "\t", ESC : "\\t"},
			F : {PLAIN : "\f", ESC : "\\f"},
			SL : {PLAIN : "\\", ESC : "\\\\"},
			QU : {PLAIN : "\"", ESC : "\\\""}
		}

		function _replaceSlashes(s) {
			switch (s) {
				case SLASHES.B.PLAIN: return SLASHES.B.ESC;
				case SLASHES.N.PLAIN: return SLASHES.N.ESC;
				case SLASHES.R.PLAIN: return SLASHES.R.ESC;
				case SLASHES.T.PLAIN: return SLASHES.T.ESC;
				case SLASHES.F.PLAIN: return SLASHES.F.ESC;
				case SLASHES.SL.PLAIN: return SLASHES.SL.ESC;
				case SLASHES.QU.PLAIN: return SLASHES.QU.ESC;
				default: return s;
			}
		}

		function _getType(object) {
			if((typeof object) == TYPES.OBJECT) {
				if (object == null) {
					return TYPES.NULL;
				} else {
					return (object instanceof Array)?TYPES.ARRAY:TYPES.OBJECT;
				}
			} else {
				return (typeof object);
			}
		}

		//public
		return {

			encodeUrl : function (object) {
				var objectType = _getType(object);
				var paramsList = [];
				var listLength = 0;

				if (objectType != TYPES.OBJECT) {
					throw new Error("glow.data.encodeUrl: cannot encode item");
				} else {
					for (var key in object) {
						switch(_getType(object[key])) {
							case TYPES.FUNCTION:
							case TYPES.OBJECT:
								throw new Error("glow.data.encodeUrl: cannot encode item");
								break;
							case TYPES.ARRAY:
								for(var i = 0, l = object[key].length; i < l; i++) {
									switch(_getType(object[key])[i]) {
										case TYPES.FUNCTION:
										case TYPES.OBJECT:
										case TYPES.ARRAY:
											throw new Error("glow.data.encodeUrl: cannot encode item");
											break;
										default:
											paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key][i]);
									}
								}
								break;
							default:
								paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key]);
						}
					}

					return paramsList.join(TEXT.AND);
				}
			},

			decodeUrl : function (text) {
				if(_getType(text) != TYPES.STRING) {
					throw new Error("glow.data.decodeUrl: cannot decode item");
				} else if (text === "") {
					return {};
				}

				var result = {};
				var keyValues = text.split(TEXT.AND);

				var thisPair, key, value;

				for(var i = 0, l = keyValues.length; i < l; i++) {
					thisPair = keyValues[i].split(TEXT.EQ);
					if(thisPair.length != 2) {
						throw new Error("glow.data.decodeUrl: cannot decode item");
					} else {
						key = decodeURIComponent(thisPair[0]);
						value = decodeURIComponent(thisPair[1]);

						switch (_getType(result[key])) {
							case TYPES.ARRAY:
								result[key][result[key].length] = value;
								break;
							case TYPES.UNDEFINED:
								result[key] = value;
								break;
							default:
								result[key] = [result[key], value];
						}
					}
				}

				return result;
			},

			encodeJson : function (object, options) {
				function _encode(object, options)
				{
					if(_getType(object) == TYPES.ARRAY) {
						var type = JSON.ARRAY;
					} else {
						var type = JSON.HASH;
					}

					var serial = [type.START];
					var len = 1;
					var dataType;
					var notFirst = false;

					for(var key in object) {
						dataType = _getType(object[key]);

						if(dataType != TYPES.UNDEFINED) {

							if(notFirst) {
								serial[len++] = JSON.DATA_SEPARATOR;
							}
							notFirst = true;

							if(type.SHOW_KEYS) {
								serial[len++] = JSON.KEY_DELIMITER;
								serial[len++] = key;
								serial[len++] = JSON.KEY_DELIMITER;
								serial[len++] = JSON.KEY_SEPARATOR;
							}

							switch(dataType) {
								case TYPES.FUNCTION:
									throw new Error("glow.data.encodeJson: cannot encode item");
									break;
								case TYPES.STRING:
								default:
									serial[len++] = JSON.STRING_DELIMITER;
									serial[len++] = glow.lang.replace(object[key], SLASHES.TEST, _replaceSlashes);
									serial[len++] = JSON.STRING_DELIMITER;
									break;
								case TYPES.NUMBER:
								case TYPES.BOOLEAN:
									serial[len++] = object[key];
									break;
								case TYPES.OBJECT:
								case TYPES.ARRAY:
									serial[len++] = _encode(object[key], options);
									break;
								case TYPES.NULL:
									serial[len++] = TYPES.NULL;
									break;
							}
						}
					}
					serial[len++] = type.END;

					return serial.join(TEXT.EMPTY);
				}

				options = options || {};
				var type = _getType(object);

				if((type == TYPES.OBJECT) || (type == TYPES.ARRAY)) {
					return _encode(object, options);
				} else {
					throw new Error("glow.data.encodeJson: cannot encode item");
				}
			},

			decodeJson : function (text, options) {
				if(_getType(text) != TYPES.STRING) {
					throw new Error("glow.data.decodeJson: cannot decode item");
				}

				options = options || {};
				options.safeMode = options.safeMode || false;

				var canEval = true;

				if(options.safeMode) {
					canEval = (JSON.SAFE_PT1.test(text.replace(JSON.SAFE_PT2, TEXT.AT).replace(JSON.SAFE_PT3, JSON.ARRAY.END).replace(JSON.SAFE_PT4, TEXT.EMPTY)));
				}

				if(canEval) {
					try {
						return eval(TEXT.OPEN + text + TEXT.CLOSE);
					}
					catch(e) {
}
				}

				throw new Error("glow.data.decodeJson: cannot decode item");
			},
	
			escapeHTML : function (html) {
				return glow.dom.create('<div></div>').text(html).html();
			}		   
		};
	}
});
;

glow.module("glow.net", "0.4.1", {
	require: ["glow.data"],
	implementation: function() {
		//private
		
		var STR = {
			XML_ERR:"Cannot get response as XML, check the mime type of the data",
			POST_DEFAULT_CONTENT_TYPE:'application/x-www-form-urlencoded;'
		},

			requests = [];

		function xmlHTTPRequest() {
			if (window.XMLHttpRequest) {
				return (xmlHTTPRequest = function() { return new XMLHttpRequest(); })();
			} else if (glow.env.ie < 7) {
				var progIds = ["Msxml2.XMLHTTP"];
				for (var i = 0, len = progIds.length; i < len; i++) {
					try {
						new ActiveXObject(progIds[i]);
						return (xmlHTTPRequest = function() { return new ActiveXObject(progIds[i]); })();
					} catch(e) { }
				}
			}
			return null;
		}

		function populateOptions(opts) {

			opts.load && (opts.onLoad = opts.load);
			opts.error && (opts.onError = opts.error);

			return glow.lang.apply(
				{
					onLoad: function(){},
					onError: function(){},
					addToHistory: false,
					headers: {},
					async: true,
					useCache: false,
					data: null
				},
				opts
			);
		}

		function noCacheUrl(url) {
			return [url, (/\?/.test(url) ? "&" : "?"), "a", new Date().getTime(), parseInt(Math.random()*100000)].join("");
		}

		function request(method, url, opts) {
			var reqId, //ID of this request
				req = xmlHTTPRequest(), //request object
				data = opts.data && (typeof opts.data == "string" ? opts.data : glow.data.encodeUrl(opts.data));
			
			if (! opts.useCache) {
				url = noCacheUrl(url);
			}
			
			req.open(method, url, opts.async);
			
			//add custom headers
			for (var i in opts.headers) {
				req.setRequestHeader(i, opts.headers[i]); 
			}
			
			if (opts.async) {
				req.onreadystatechange = function() {
					if (req.readyState == 4) {
						var response = new Response(req);
						if (req.status == 200 || (req.status == 0 && req.responseText)) {
							opts.onLoad(response);
						} else {
							opts.onError(response);
						}
					}
				};
				requests[reqId = requests.length] = req;
				req.send(data);
				return reqId;
			} else {
				req.send(data);
				var response = new Response(req);
				if (req.status == 200 || (req.status == 0 && req.responseText)) {
					opts.onLoad(response);
				} else {
					opts.onError(response);
				}
				return response;
			}
		}
		
		//public
		var r = {}; //the module

		r._jsonCbs = {len:0};

		r.get = function(url, o) {
			o = populateOptions(o);
			return request('GET', url, o);
		};

		r.post = function(url, data, o) {
			o = populateOptions(o);
			o.data = data;
			if (!o.headers["Content-Type"]) {
				o.headers["Content-Type"] = STR.POST_DEFAULT_CONTENT_TYPE;
			}
			return request('POST', url, o);
		};

		r.abort = function(id) {
			if (requests[id]) {
				requests[id].onreadystatechange = function(){};
				requests[id].abort();
			}
			return glow.net;
		}

		r.loadScript = function(url, callback) {
			if (callback) {
				var callbackName = "c" + r._jsonCbs.len++;
				r._jsonCbs[callbackName] = callback;
				url = glow.lang.interpolate(url, {callback: "glow.net._jsonCbs." + callbackName});
			}
			var script = document.createElement("script");
			script.src = noCacheUrl(url);
			glow.ready(function() {
				document.body.appendChild(script);
			});
		}


		function Response(nativeResponse) {

			this.nativeResponse = nativeResponse;

			this.status = nativeResponse.status;
		}
		Response.prototype = {

			text: function() {
				return this.nativeResponse.responseText;
			},

			xml: function() {
				//TODO: wrap this in debug stuff
				if (! this.nativeResponse.responseXML) {
					throw new Error(STR.XML_ERR);
				}
				return this.nativeResponse.responseXML;
			},

			json: function(safe) {
				return glow.data.decodeJson(this.text(), {safeMode:safe});
			},

			header: function(name) {
				return this.nativeResponse.getResponseHeader(name);
			},

			statusText: function() {
				return this.nativeResponse.statusText;
			}
		};
		
		return r;
	}
});

;
glow.module("glow.events", "0.4.1", {
	require: [],
	implementation: function() {

		var r = {};
		var eventid = 1;
		var objid = 1;
		// object (keyed by obj id), containing object (keyed by event name), containing arrays of listeners
		var listenersByObjId = {};
		// object (keyed by ident) containing listeners
		var listenersByEventId = {};
		var domListeners = {};
		var psuedoPrivateEventKey = '__intGlowEventId' + Math.floor(Math.random() * 1337);
		var psuedoPreventDefaultKey = psuedoPrivateEventKey + 'PreventDefault';
		var psuedoStopPropagationKey = psuedoPrivateEventKey + 'StopPropagation';

		var topKeyListeners = {};
		var keyListenerId = 1;
		var keyListeners = {};
		var keyTypes = {};

		var CTRL = 1;
		var ALT = 2;
		var SHIFT = 4;

		var specialPrintables = {
			TAB      : '\t',
			SPACE    : ' ',
			ENTER    : '\n',
			BACKTICK : '`'
		};

		var keyNameAliases = {
			'96' : 223
		};

		var keyNameToCode = {
			CAPSLOCK : 20, NUMLOCK : 144, SCROLLLOCK : 145, BREAK : 19,
			BACKTICK : 223, BACKSPACE : 8, PRINTSCREEN : 44, MENU : 93, SPACE : 32,
			SHIFT : 16,  CTRL : 17,  ALT : 18,
			ESC   : 27,  TAB  : 9,   META     : 91,  RIGHTMETA : 92, ENTER : 13,
			F1    : 112, F2   : 113, F3       : 114, F4        : 115,
			F5    : 116, F6   : 117, F7       : 118, F8        : 119,
			F9    : 120, F10  : 121, F11      : 122, F12       : 123,
			INS   : 45,  HOME : 36,  PAGEUP   : 33,
			DEL   : 46,  END  : 35,  PAGEDOWN : 34,
			LEFT  : 37,  UP   : 38,  RIGHT    : 39,  DOWN      : 40
		};
		var codeToKeyName = {};
		for (var i in keyNameToCode) {
			codeToKeyName['' + keyNameToCode[i]] = i;
		}

		var operaBrokenChars = '0123456789=;\'\\\/#,.-';
		function removeKeyListener (ident) {
			var keyType = keyTypes[ident];
			if (! keyType) { return false; }
			var listeners = keyListeners[keyType];
			if (! listeners) { return false; }
			for (var i = 0, len = listeners.length; i < len; i++) {
				if (listeners[i][0] == ident) {
					listeners.splice(i, 1);
					return true;
				}
			}
			return false;
		}
		function initTopKeyListener (type) {
			topKeyListeners[type] = r.addListener(document, 'key' + type, function (e) {
				var mods = 0;
				if (e.ctrlKey) { mods += CTRL; }
				if (e.altKey) { mods += ALT; }
				if (e.shiftKey) { mods += SHIFT; }
				var keyType = e.chr ? e.chr.toLowerCase() : e.key ? e.key.toLowerCase() : e.keyCode;
				var eventType = mods + ':' + keyType + ':' + type;
				var listeners = keyListeners[eventType] ? keyListeners[eventType].slice(0) : [];
				// if the user pressed shift, but event didn't specify that, include it
				if (e.shiftKey) { // upper-case letter, should match regardless of shift
					var shiftEventType = (mods & ~ SHIFT) + ':' + keyType + ':' + type;
					if (keyListeners[shiftEventType]) {
						for (var i = 0, len = keyListeners[shiftEventType].length; i < len; i++) {
							listeners[listeners.length] = keyListeners[shiftEventType][i];
						}
					}
				}
				if (! listeners) { return; }
				for (var i = 0, len = listeners.length; i < len; i++) {
					listeners[i][2].call(listeners[i][3] || this, e);
				}
			});
		}
		function clearEvents() {
			var ident;
			for (ident in listenersByEventId) {
				r.removeListener(ident);
			}
		}

		// for opera madness
		var previousKeyDownKeyCode;

		var operaResizeListener,
			operaDocScrollListener;
		function addDomListener (attachTo, name) {
			var wheelEventName;
			
			if (glow.env.opera) {
				if (name.toLowerCase() == 'resize' && !operaResizeListener && attachTo == window) {
					operaResizeListener = r.addListener(window.document.body, 'resize', function (e) { r.fire(window, 'resize', e); });
				} else if (name.toLowerCase() == 'scroll' && !operaDocScrollListener && attachTo == window) {
					operaDocScrollListener = r.addListener(window.document, 'scroll', function (e) { r.fire(window, 'scroll', e); });
				}
			}
			
			var callback = function (e) {
				if (! e) { e = window.event; }
				var event = new r.Event();
				event.nativeEvent = e;
				event.source = e.target || e.srcElement;
				//safari 1.3 registers clicks on text nodes, get the parent node
				if (event.source && event.source.nodeType != 1) {
					event.source = event.source.parentNode;
				}
				event.relatedTarget = e.relatedTarget || (name.toLowerCase() == "mouseover" ? e.fromElement : e.toElement);
			    event.button = glow.env.ie ? (e.button & 1 ? 0 : e.button & 2 ? 2 : 1) : e.button;
				if (e.pageX || e.pageY) {
					event.pageX = e.pageX;
					event.pageY = e.pageY;
				} else if (e.clientX || e.clientY) 	{
					event.pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
					event.pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
				}
				if (name.toLowerCase() == 'mousewheel') {
					// this works in latest opera, but have read that it needs to be switched in direction
					// if there was an opera bug, I can't find which version it was fixed in
					event.wheelDelta =
						e.wheelDelta ? e.wheelDelta / 120 :
						e.detail ? - e.detail / 3 :
							0;
					if (event.wheelDelta == 0) { return; }
				}
				if (name.toLowerCase().indexOf("key") != -1) {
					event.altKey = !! e.altKey;
					event.ctrlKey = !! e.ctrlKey;
					event.shiftKey = !! e.shiftKey;

					if (name == 'keydown') {
						previousKeyDownKeyCode = e.keyCode;
					}

					event.charCode = e.keyCode && e.charCode !== 0 ? undefined : e.charCode;

					if (name.toLowerCase() == 'keypress') {
						if (typeof(event.charCode) == 'undefined') {
							event.charCode = e.keyCode;
						}

						if (glow.env.opera && event.charCode && event.charCode == previousKeyDownKeyCode &&
							operaBrokenChars.indexOf(String.fromCharCode(event.charCode)) == -1
						) {
							event.charCode = undefined;
							event.keyCode = previousKeyDownKeyCode;
						}
					}
					
					// make things a little more sane in opera
					if (event.charCode && event.charCode <= 31) { event.charCode = undefined; }

					if (event.charCode) {
						event.chr = String.fromCharCode(event.charCode);
					}
					else if (e.keyCode) {
						event.charCode = undefined;
						event.keyCode = keyNameAliases[e.keyCode.toString()] || e.keyCode;
						event.key = codeToKeyName[event.keyCode];
						if (specialPrintables[event.key]) {
							event.chr = specialPrintables[event.key];
							event.charCode = event.chr.charCodeAt(0);
						}
					}

					if (event.chr) {
						event.capsLock =
							event.chr.toUpperCase() != event.chr ? // is lower case
								event.shiftKey :
							event.chr.toLowerCase() != event.chr ? // is upper case
								! event.shiftKey :
								undefined; // can only tell for keys with case
					}
				}

				r.fire(this, name, event);
				if (event.defaultPrevented()) { return false; }
			};
			
			if (attachTo.addEventListener && (!glow.env.webkit || glow.env.webkit > 418)) {
				attachTo.addEventListener(name.toLowerCase() == 'mousewheel' && glow.env.gecko ? 'DOMMouseScroll' : name, callback, false);
			} else {
				var onName = 'on' + name;
				var existing = attachTo[onName];
				if (existing) {
					attachTo[onName] = function () {
						existing.apply(this, arguments);
						callback.apply(this, arguments);
					};
				} else {
					attachTo[onName] = callback;
				}
			}
			attachTo = null;
		}
		r.addListener = function (attachTo, name, callback, context) {
			if (! attachTo) { throw 'no attachTo paramter passed to addListener'; }

			if (typeof attachTo == 'string') {
				if (! glow.dom) { throw "glow.dom must be loaded to use a selector as the first argument to glow.events.addListener"; }
				if (! (attachTo = glow.dom.get(attachTo)[0])) { return undefined; }
				
			}
			else if (glow.dom && attachTo instanceof glow.dom.NodeList) {
				if (attachTo.length == 0) { return undefined; }
				attachTo = attachTo[0];
			}
			
			var objIdent;
			if (! (objIdent = attachTo[psuedoPrivateEventKey])) {
				objIdent = attachTo[psuedoPrivateEventKey] = objid++;
			}
			var ident = eventid++;
			var listener = [ objIdent, name, callback, context ];
			listenersByEventId[ident] = listener;

			var objListeners = listenersByObjId[objIdent];
			if (! objListeners) { objListeners = listenersByObjId[objIdent] = {}; }
			var objEventListeners = objListeners[name];
			if (! objEventListeners) { objEventListeners = objListeners[name] = []; }
			objEventListeners[objEventListeners.length] = listener;

			if ((attachTo.addEventListener || attachTo.attachEvent) && ! domListeners[objIdent + ':' + name]) {
				addDomListener(attachTo, name);
				domListeners[objIdent + ':' + name] = true;
			}
			return ident;
		};
		r.removeListener = function (ident) {
			if (ident && ident.toString().indexOf('k:') != -1) {
				return removeKeyListener(ident);
			}
			var listener = listenersByEventId[ident];
			if (! listener) { return false; }
			delete listenersByEventId[ident];
			var listeners = listenersByObjId[listener[0]][listener[1]];
			for (var i = 0, len = listeners.length; i < len; i++) {
				if (listeners[i] == listener) {
					listeners.splice(i, 1);
					break;
				}
			}
			if (! listeners.length) {
				delete listenersByObjId[listener[0]][listener[1]];
			}
			var listenersLeft = false;
			for (var i in listenersByObjId[listener[0]]) { listenersLeft = true; break;	}
			if (! listenersLeft) {
				delete listenersByObjId[listener[0]];
			}
			return true;
		};
		r.fire = function (attachedTo, name, e) {
   			if (! attachedTo) throw 'glow.events.fire: required parameter attachedTo not passed (name: ' + name + ')';
   			if (! name) throw 'glow.events.fire: required parameter name not passed';
			if (! e) { e = new r.Event(); }
			e.type = name;
			e.attachedTo = attachedTo;
			if (! e.source) { e.source = attachedTo; }

			var objIdent = attachedTo[psuedoPrivateEventKey],
				objListeners = objIdent && listenersByObjId[objIdent],
				objEventListeners = objListeners && objListeners[name];
			
			if (! objEventListeners) { return e; }
			
			var listener, res;

			// we make a copy of the listeners before calling them, as the event handlers may
			// remove themselves (took me a while to track this one down)
			var listeners = objEventListeners.slice(0);
			for (var i = 0, len = listeners.length; i < len; i++) {
				listener = listeners[i];
				res = listener[2].call(listener[3] || attachedTo, e);
				if (typeof res == 'boolean' && ! res) {
					e.preventDefault();
				}
			}
			return e;
		};
		var keyRegex = /^((?:(?:ctrl|alt|shift)\+)*)(?:(\w+|.)|[\n\r])$/i;
		r.addKeyListener = function (key, type, callback, context) {
			type.replace(/^key/i, "");
			type = type.toLowerCase();
			if (! (type == 'press' || type == 'down' || type == 'up')) {
				throw 'event type must be press, down or up';
			}
			if (! topKeyListeners[type]) { initTopKeyListener(type); }
			var res = key.match(keyRegex),
				mods = 0,
				charCode;
			if (! res) { throw 'key format not recognised'; }
			if (res[1].toLowerCase().indexOf('ctrl') != -1)  { mods += CTRL;  }
			if (res[1].toLowerCase().indexOf('alt') != -1)   { mods += ALT;   }
			if (res[1].toLowerCase().indexOf('shift') != -1) { mods += SHIFT; }
			var eventKey = mods + ':' + (res[2] ? res[2].toLowerCase() : '\n') + ':' + type;
			var ident = 'k:' + keyListenerId++;
			keyTypes[ident] = eventKey;
			var listeners = keyListeners[eventKey];
			if (! listeners) { listeners = keyListeners[eventKey] = []; }
			listeners[listeners.length] = [ident, type, callback, context];
			return ident;
		};
		r.Event = function () {};
		r.Event.prototype.preventDefault = function () {
			if (this[psuedoPreventDefaultKey]) { return; }
			this[psuedoPreventDefaultKey] = true;
			if (this.nativeEvent && this.nativeEvent.preventDefault) {
				this.nativeEvent.preventDefault();
				this.nativeEvent.returnValue = false;
			}
		};
		r.Event.prototype.defaultPrevented = function () {
			return !! this[psuedoPreventDefaultKey];
		};
		r.Event.prototype.stopPropagation = function () {
			if (this[psuedoStopPropagationKey]) { return; }
			this[psuedoStopPropagationKey] = true;
			var e = this.nativeEvent;
			if (e) {
				e.cancelBubble = true;
				if (e.stopPropagation) { e.stopPropagation(); }
			}
		};
		r.Event.prototype.propagationStopped = function () {
			return !! this[psuedoStopPropagationKey];
		};
		
		//cleanup to avoid mem leaks in IE
		r.addListener(window, "unload", clearEvents);

		return r;
	}
});
/*@end @*/