/*
 * Glow JavaScript Library
 * Copyright (c) 2007 British Broadcasting Corporation
 */
if (window.glow) { throw new Error("glow Core module already included"); }
var glow = (function() {
	var moduleRegister = {glow: true};
	var ua = navigator.userAgent.toLowerCase();

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

		env: {
			rhino  : !!window.load,
			gecko  : !window.load && Number((/gecko\/(\d+)/.exec(ua) || [0, NaN])[1]),
			ie     : /opera/.test(ua) ? NaN : Number((/msie ([\d\.]+)/.exec(ua) || [0,NaN])[1]),
			opera  : Number((/opera[\s\/]([\d\.]+)/.exec(ua) || [0,NaN])[1]),
			webkit : Number((/applewebkit\/(\d+(?:\.\d+)?)/.exec(ua) || [0,NaN])[1]),
			standardsMode : document.compatMode == "CSS1Compat"
		},
		module: function(sName, sVer, oParams) {
			var i, namePartsLen;
			//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
			var nameParts = sName.split("."); //split module name into parts
			var objRef = window; //holds the parent object for the new module
			//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(){
			var d = document;
			if (/msie/.test(ua) && !(/opera/.test(ua))) {
				return function(c) {
					if (this.isReady) { c(); return; }
					// 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
						c();
					})();
				};
			} else if (
				typeof d.readyState != 'undefined'
				&& ! (/webkit/i.test(navigator.userAgent) && navigator.userAgent.match(/[\d.]+$/)[0] < 312)
			) {
				return function(c){
					if (this.isReady) { c(); return; }
					var f = function(){ /loaded|complete/.test(d.readyState) ? c() : setTimeout(f, 10); };
					f();
				};
			} else {
				return function(c){
					if (this.isReady) { c(); return; }
					var callback = function () {
						if (arguments.callee.fired) { return; }
						arguments.callee.fired = true;
						c();
					};
					d.addEventListener("DOMContentLoaded", callback, false);
					var oldOnload = window.onload;
					window.onload = function () {
						if (oldOnload) { oldOnload(); }
						callback();
					};
				};
			}
		})(),
		lang: {
			trim: function(sStr) {
				return sStr.replace(/^\s+|\s+$/g, "");
			},
			toArray: function(aArrayLike) {
				if (aArrayLike.constructor == Array) {
					return aArrayLike;
				}
				//use array.slice if not IE? Could be faster
				var r = [];
				for (var i = 0, len = aArrayLike.length; i < len; i++) {
					r[r.length] = 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;
				var res = new Array(len);
				var thisp = arguments[1] || arr;
				for (var i = 0; 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';
				var 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;
				for (var i in data) {
					r = r.replace(new RegExp("{" + i + "}", "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 () {};
				f.prototype = base.prototype;
				var p = new f();
				sub.prototype = p;
				p.constructor = sub;
				sub.base = base;
			}
		}
	};
})();

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.2.0", {
	require: [],
	implementation: function() {
		//private

		var 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: (glow.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/;

		function removeClassRegex (name) {
			return new RegExp("\\b" + name + "\\b");
		}

		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 = [];
			for (var i = 0; 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 i = 0, length = this.length; i < length; i++) {
				attributeSetter.call(
					this[i],
					typeof value == 'function' ?
						value.call(this[i], i) :
						value
				);
			}
			return this;
		}
		
		//return different function for IE & Opera to deal with their stupid bloody expandos. Pah.
		var append;
		if (document.all) {
			append = function(a, b) {
				var i, ai, length;
				if (typeof b.length == "number") {
					for (i = 0, ai = a.length, length = b.length; i < length; i++) {
						a[ai++] = b[i];
					}
				} else {
					for (i = 0, ai = a.length; b[i]; i++) {
						a[ai++] = b[i];
					}
				}
			};
		} else {
			append = function(a, b) {
				var i, ai;
				for (i = 0, ai = a.length; b[i]; i++) {
					a[ai++] = b[i];
				}
			};
		}

		function unique(aNodes) {
			//remove duplicates
			var r = [];
			var ri = 0;
			for (var i = 0; aNodes[i]; i++) {
				if (aNodes[i]._ucheck != ucheck && aNodes[i].nodeType == 1) {
					r[ri++] = aNodes[i];
				}
				aNodes[i]._ucheck = ucheck;
			}
			ucheck++;
			return r;
		}

		var getByTagName;
		if (document.all) { //go the long way around for IE (and Opera)
			getByTagName = function(tag, context) {
				var r = [];
				for (var i = 0; context[i]; i++) {
					if (tag == "*") { // 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 = [];
				for (var i = 0, len = context.length; i < len; i++) {
					append(r, context[i].getElementsByTagName(tag));
				}
				return r;
			};
		}

		function getElmSize(elm) {
			var r,
				oldVals = {},
				trbl = ["Top", "Right", "Bottom", "Left"];
			if (elm.window) {
				if (glow.env.webkit < 522.11) {
					r = {width:elm.innerWidth, height:elm.innerHeight};
				} else if (glow.env.webkit) {
					r = {width:elm.document.body.clientWidth, height:elm.innerHeight};
				} else if (glow.env.opera) {
					r = {width:elm.document.body.clientWidth, height:elm.document.body.clientHeight};
				} else {
					var docElm = window.document.compatMode == "CSS1Compat" ? window.document.documentElement : window.document.body;
					r = {width:docElm.clientWidth, height:docElm.clientHeight};
				}
			} else if (elm.getElementById) {
				r = {width: Math.max(elm.body.scrollWidth, elm.body.offsetWidth, elm.documentElement.offsetWidth),height: Math.max(elm.body.scrollHeight, elm.body.offsetHeight, elm.documentElement.offsetHeight)};
			} else {
				//set border and padding to 0, backing up old vals
				for (var i = 0, len = trbl.length; i < len; i++) {
					oldVals["padding" + trbl[i]] = elm.style["padding" + trbl[i]];
					oldVals["border" + trbl[i] + "Width"] = elm.style["border" + trbl[i] + "Width"];
					elm.style["padding" + trbl[i]] = "0";
					elm.style["border" + trbl[i] + "Width"] = "0";
				}

				//capture values
				r = {width: elm.offsetWidth, height: elm.offsetHeight};
				
				//reset old vals
				for (i = 0; i < len; i++) {
					elm.style["padding" + trbl[i]] = oldVals["padding" + trbl[i]];
					elm.style["border" + trbl[i] + "Width"] = oldVals["border" + trbl[i] + "Width"];
				}
			}
			return r;
		}

		function getElmPos(elm, pos) {
			var map = {t:"Top",l:"Left"},
				trbl = ["Top", "Right", "Bottom", "Left"],
				oldVals = {},
				compTo = glow.env.ie && document.compatMode == "CSS1Compat" ? getBodyElm(elm).parentNode : getBodyElm(elm),
				r;
			
			for (var i = 0, len = trbl.length; i < len; i++) {
				oldVals["margin" + trbl[i]] = elm.style["margin" + trbl[i]];
				elm.style["margin" + trbl[i]] = "0";
			}
			if (pos == "t" || pos == "l") {
				r = elm["offset" + map[pos]];
				if (glow.env.ie) {
					var offsetParentPos = elm.offsetParent.currentStyle["position"];
					if (offsetParentPos != "relative" && offsetParentPos != "absolute") {
						r += getElmPos(elm.offsetParent, pos);
					}
				}
			} else if (pos == "r") {
				r = getElmSize(elm.offsetParent == compTo ? window : elm.offsetParent).width - elm.offsetLeft - elm.offsetWidth;
			} else if (pos == "b") {
				r = getElmSize(elm.offsetParent == compTo ? window : elm.offsetParent).height - elm.offsetTop - elm.offsetHeight;
			}
			for (var i = 0; i < len; i++) {
				elm.style["margin" + trbl[i]] = oldVals["margin" + trbl[i]];
			}
			return r;
		}

		function getBodyElm(elm) {
			if (glow.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 glow.env.ie ? "styleFloat" : "cssFloat";
			}
			//have to do it like this due to lack of support for a function as .replace() 2nd param
			var matches;
			for (var i = 0; matches = /-(\w)/g.exec(prop); i++) {
				prop = prop.replace(matches[0], matches[1].toUpperCase());
			}
			return prop;
		}

		function tempBlock(elm, func) {
			var r, 
				oldDisp = elm.style.display,
				oldVis = elm.style.visibility,
				oldPos = elm.style.position;
			
			elm.style.visibility = "hidden";
			elm.style.position = "absolute";
			elm.style.display = "block";
			if (!isVisible(elm)) {
				elm.style.position = oldPos;
				r = tempBlock(elm.parentNode, func);
				elm.style.display = oldDisp;
				elm.style.visibility = oldVis;
			} else {
				r = func();
				elm.style.display = oldDisp;
				elm.style.position = oldPos;
				elm.style.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
			if (prop.push) { //multiple properties, add them up
				var total = 0;
				for (var i = 0, len = prop.length; i < len; i++) {
					total += parseInt(getCssValue(elm, prop[i]), 10) || 0;
				}
				return total + "px";
			}
			//had fun with opera including padding in width, this seems safer
			if (prop == "width" || prop == "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 (/^(top|bottom|left|right)$/.test(prop) && getCssValue(elm, "position") != "relative") {
				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 (document.defaultView && document.defaultView.getComputedStyle) { //W3 Method
				//this returns computed values
				var compStyle = document.defaultView.getComputedStyle(elm, null);
				if (!compStyle) {
					//safari 2 returns null for compStyle
					var 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 (!document.defaultView.getComputedStyle(elm, null)) {
								return "none";
							}
							elm.style.display = "block";
						}
						return getCssValue(elm, prop);
					});
				} else {
					r = compStyle.getPropertyValue(prop);
				}
			} else if (elm.currentStyle) { //IE method
				if (prop == "opacity") {
					var match = /alpha\(opacity=([^\)]+)\)/.exec(elm.currentStyle.filter);
					return match ? String(parseInt(match[1], 10) / 100) : "1";
				}
				//this returns cascaded values so needs fixing
				r = String(elm.currentStyle[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) {
			var match, //tmp regex match holder
				r, g, b, //final colour vals
				hex; //tmp hex holder
			if (match = /^rgb\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)/i.exec(val)) { //rgb() format, cater for percentages
				r = match[2] ? Math.round(((parseFloat(match[1]) / 100) * 255)) : parseInt(match[1]);
				g = match[4] ? Math.round(((parseFloat(match[3]) / 100) * 255)) : parseInt(match[3]);
				b = match[6] ? Math.round(((parseFloat(match[5]) / 100) * 255)) : parseInt(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 = parseInt(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;
		}
		
		//public
		var r = {}; //object to be returned

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

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

		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() {
				for (var i = 0, nArgLen = arguments.length; i < nArgLen; i++) {
					if (arguments[i].constructor == Array) { //is array
						Array.prototype.push.apply(this, arguments[i]);
					} else if (arguments[i].item && arguments[i][0]) { //is nodelist
						for (var n = 0, nNodeListLength = arguments[i].length; n < nNodeListLength; n++) {
							Array.prototype.push.call(this, arguments[i][n]);
						}
					} else if (arguments[i].getElementsByTagName || arguments[i].document) { //is Node
						Array.prototype.push.call(this, arguments[i]);
					}
				}
				return this;
			},

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

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

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

			attr: function (name
) {
				if (this.length === 0) {
					return arguments.length > 1 ? this : undefined;
				}
				if (typeof name == 'object') {
					for (var i in name) {
						if (glow.lang.hasOwnProperty(name, i)) {
							this.attr(i, name[i]);
						}
					}
					return this;
				}
				// TODO what if it an xml document
				if (glow.env.ie && dom0PropertyMapping[name]) {
					if (arguments.length > 1) {
						setAttribute.call(
							this,
							arguments[1],
							// in the callback this is the dom node
							function (val) { this[dom0PropertyMapping[name]] = val; }
						);
						return this;
					}
					var value = this[0][dom0PropertyMapping[name]];
					if (dom0BooleanAttribute[name]) {
						return value ? name : undefined;
					}
					else if (dom0AttributeMappings[name]) {
						return dom0AttributeMappings[name](value);
					}
					return value;
				}
				if (arguments.length > 1) {
					setAttribute.call(
						this,
						arguments[1],
						// in the callback this is the dom node
						function (val) { this.setAttribute(name, val); }
					);
					return this;
				}
				return this[0].getAttribute(name, 2);
			},

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

			hasAttr: function (name) {
				if (this[0].getAttributeNode) {
					var attr = this[0].getAttributeNode(name);
					return attr ? attr.specified : false;
				}
				return !! this.attr(name);
			},

			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);
				for (var i = 0, length = this.length; i < length; i++) {
					this[i].className = this[i].className.replace(re, "");
				}
				return this;
			},

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

			val: function () {

				function elementValue (el) {
					if (el.type == "radio") {

						return el.checked ? el.value : undefined;
					} else if (el.type == "checkbox") {
						return el.checked ? el.value : undefined;

					} else if (el.type == "select-one") {
						return el.selectedIndex > -1 ?
							el.options[el.selectedIndex].value :
							// don't do this because it will replace an empty value
							// || el.options[el.selectedIndex].innerText  :
							"";

					} else if (el.type == "select-multiple") {
						var vals = [];
						for (var i = 0, length = el.options.length; i < length; i++) {
							if (el.options[i].selected) {
								vals[vals.length] = el.options[i].value;
									// don't do this because it will replace an empty value
									// || el.options[i].innerText;
							}
						}
						return vals;
					} else {
						return el.value;
					}
				}

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

				function setFormValues (form, vals) {
					var prop, currentField,
						fields = {},
						storeType, i, n, len, foundOne;
					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 (i = 0; currentField[i]; i++) {
								if (currentField[i].type == "radio") {
									storeType = "radios";
								} else if (currentField[i].type == "select-one" || currentField[i].type == "checkbox") {
									storeType = "checkboxesSelects";
								} else if (currentField[i].type == "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, length, n, nlen;
					if (el.type == "select-one") {
						for (i = 0, 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++) {
							var optionVal = el.options[i].value;
							if (isArray) {
								el.options[i].selected = false;
								for (n = 0, nlen = val.length; n < nlen; n++) {
									if (optionVal == val[n]) {
										el.options[i].selected = true;
										val.splice(n, 1);
										break;
									}
								}
							} else {
								return el.options[i].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 (
) {
					if (arguments.length === 0) {
						return this[0].nodeName == 'FORM' ?
							formValues(this[0]) :
							elementValue(this[0]);
					}
					var val = arguments[0];
					if (this[0].nodeName == 'FORM') {
						if (! typeof val == 'object') {
							throw 'value for FORM must be object';
						}
						setFormValues(this[0], val);
					} else {
						for (var i = 0, length = this.length; i < length; i++) {
							setValue(this[i], val);
						}
					}
					return this;
				};
			}(),

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

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

			children: function() {
				var r = [];
				var ri = 0;
				var childTmp;
				for (var i = 0, length = this.length; i < length; i++) {
					childTmp = this[i].childNodes;
					for (var n = 0; childTmp[n]; n++) {
						if (childTmp[n].nodeType == 1 && childTmp[n].nodeName != "!") {
							r[ri++] = childTmp[n];
						}
					}
				}
				return glow.dom.get(r);
			},

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

			next: function() {
				var r = [];
				var ri = 0;
				var nextTmp;
				for (var i = 0, length = this.length; i < length; i++) {
					nextTmp = this[i];
					while (nextTmp = nextTmp.nextSibling) {
						if (nextTmp.nodeType == 1 && nextTmp.nodeName != "!") {
							r[ri++] = nextTmp;
							break;
						}
					}
				}
				return glow.dom.get(r);
			},

			prev: function() {
				var r = [];
				var ri = 0;
				var prevTmp;
				for (var i = 0, length = this.length; i < length; i++) {
					prevTmp = this[i];
					while (prevTmp = prevTmp.previousSibling) {
						if (prevTmp.nodeType == 1 && prevTmp.nodeName != "!") {
							r[ri++] = prevTmp;
							break;
						}
					}
				}
				return glow.dom.get(r);
			},

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

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

			empty: function () {
				for (var i = 0, length = this.length; i < length; i++) {
					this[i].innerHTML = "";
				}
				return this;
			},

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

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

			html: function (
) {
				if (arguments.length > 0) {
					for (var i = 0, length = this.length; i < length; i++) {
						this[i].innerHTML = arguments[0];
					}
					return this;
				}
				return this[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) {
				if (val != undefined) { //setting stuff
					prop = toStyleProp(prop);
					for (var i = 0, len = this.length; i < len; i++) {
						if (prop == "opacity" && glow.env.ie) {
							if (val === "") {
								this[i].style.filter = "";
							} else {
								this[i].style.filter = "alpha(opacity=" + Math.round(Number(val, 10) * 100) + ")";
							}
						} else {
							this[i].style[prop] = val;
						}
					}
					return this;
				} else { //getting stuff
					if (!this.length) { return; }
					return getCssValue(this[0], prop);
				}
			},

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

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

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

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

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

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

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

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

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

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

			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.2.0", {
	require: [],
	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");
			}
		};
	}
});
;

glow.module("glow.net", "0.2.0", {
	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.6.0','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(
				{
					load: function(){},
					error: 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.load(response);
						} else {
							opts.error(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.load(response);
				} else {
					opts.error(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.2.0", {
	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);
				}
			});
		}

		// for opera madness
		var previousKeyDownKeyCode;

		var operaResizeListener;
		function addDomListener (attachTo, name) {
			var wheelEventName;
			if (name.toLowerCase() == 'resize' && attachTo == window && glow.env.opera && ! operaResizeListener) {
				operaResizeListener = r.addListener(window.document.body, 'resize', function (e) { r.fire(window, 'resize', e); });
			}
			
			var callback = function (e) {
				if (! e) { e = window.event; }
				var event = new r.Event();
				event.nativeEvent = e;
				event.source = e.target || e.srcElement;
				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 (! 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)\+)*)(.+)$/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);
			if (! res) { throw 'key format not recognised'; }
			var mods = 0;
			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].toLowerCase() + ':' + type;
			//console.log('adding event key: ' + eventKey);
			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];
		};

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