/*
	Copyright Code Computerlove Ltd 2009-2010
	Build: 1.0.0.107
	Date: 07/01/2010 09:46:12
*/

/*
* Header: Code.js
* Documentation for the Code.js library.
*/
(function() {

	/*
	* Class: Code 
	* This is a singleton class.
	*/
	Code = {

		_init: function() {

			Code.QueryString._init();
			Code.UserAgent._init();

			// Remove css image flicker on IE<7
			if (Code.UserAgent.isIElt7) {
				try {
					document.execCommand("BackgroundImageCache", false, true);
				}
				catch (e) { }
			}

		},

		/*
		* string: websiteRoot
		* Current website's root folder. Defaults to '/'.
		* 
		* See Also:
		* <Code.resolveUrl>
		*/
		websiteRoot: '/',

		/*
		* Function: breakOutOfFrames
		* Breaks current document out of all frames.
		*/
		breakOutOfFrames: function() {
			if (top.location != self.location) {
				top.location = self.location.href;
			}
		},


		/*
		* Function: clone
		* Returns a clone of an object.
		* 
		* Parameters:
		* 	object: - obj
		*/
		clone: function(obj) {
			return Code.merge({}, obj);
		},

		/*
		* Function: coalesce
		* Takes any number of arguments and returns the first non Null / Undefined argument.
		* 
		* Parameters:
		* 	objects: Any number of arguments
		*/
		coalesce: function() {
			for (var i = 0; i < arguments.length; i++) {
				if (!Code.Type.isNothing(arguments[i])) {
					return arguments[i];
				}
			}
			return null;
		},

		/*
		* Function: mergeObjects
		* Copies all the properties from sourceObj to destObj.
		* 
		* By default, if the destObj already contains a property in 
		* sourceObj, the value in destObj will be overwritten.
		* Setting overwriteProperties = false will prevent this.
		* 
		* Parameters:
		* 	object: - destObj
		* 	object: - sourceObj
		* 	boolean: - overwriteProperties
		*/
		mergeObjects: function(destObj, sourceObj, overwriteProperties) {
			if (Code.Type.isNothing(overwriteProperties)) {
				overwriteProperties = true;
			}
			if (destObj && sourceObj && Code.Type.isObject(sourceObj)) {
				for (var prop in sourceObj) {
					if (overwriteProperties) {
						destObj[prop] = sourceObj[prop];
					}
					else {
						if (typeof destObj[prop] == "undefined") {
							destObj[prop] = sourceObj[prop];
						}
					}
				}
			}
			return destObj;
		},

		/*
		* Function: redirect
		* Redirects browser to a url.
		* 
		* Optional delay will redirect after a number of milliseconds
		* 
		* Parameters:
		* 	string: - url - Url to redirect to
		* 	number: - delay - How long to delay before redirecting
		*/
		redirect: function(url, delay) {

			if (Code.Type.isNumber(delay)) {
				setTimeout(function() {
					window.location = url;
				}, delay);
			}
			else {
				window.location = url;
			}

		},


		/*
		* Function: registerNamespace
		* Creates namespaces to be used for scoping variables and classes so that they are not global.
		* 
		* Parameters:
		* 	strings: - Namespaces to register
		* 
		* Usage:
		* (start code)
		* Code.registerNamespace('Website.Pages');
		* Website.Pages.Index = {...};
		* (end code)
		*/
		registerNamespace: function() {
			var args = arguments, obj = null, i, j;
			for (i = 0; i < args.length; ++i) {
				var ns = args[i];
				var nsParts = ns.split(".");
				var root = nsParts[0];
				eval('if (typeof ' + root + ' == "undefined"){' + root + ' = {};} obj = ' + root + ';');
				for (j = 1; j < nsParts.length; ++j) {
					obj[nsParts[j]] = obj[nsParts[j]] || {};
					obj = obj[nsParts[j]];
				}
			}
		},

		/*
		* Function: resolveUrl
		* Works similar to ASP.NET resolve url. 
		* Given a url in the format '~/products/index.htm', 
		* this method will return the url with '~/' replaced by Code.websiteRoot.
		* 
		* Useful as often we don't know whether a website will sit at the root level
		* on a server or within a virtual folder etc.
		* 
		* Parameters:
		* 	string: - url 
		*  
		* See Also:
		* <Code.websiteRoot>
		*/
		resolveUrl: function(url) {
			if (!Code.Type.isString(url)) {
				return Code.websiteRoot;
			}
			return url.replace(/~\//, Code.websiteRoot);
		}

	};


	/*
	* Class: Code.QueryString
	* Can be used to obtain values from the current querystring 
	* as well as converting querystrings to JavaScript objects and objects to strings.
	* 
	* This is a singleton class.
	*/
	Code.QueryString = {

		_init: function() {
			Code.QueryString.current =
				Code.QueryString.toObject(location.search.substring(1, location.search.length));
		},

		/*
		* object: current
		* Object representation of the current location's querystring.
		*/
		current: {},

		/*
		* Function: getValue
		* Returns a single value from a querystring object.
		* 
		* You can optionally:
		* 	- specify a default value if the key does not exist in the querystring.
		*	- supply a custom querystring object (default is current location's querystring).
		*
		* Parameters:
		* 	string: - key
		* 	string: - defaultValue - optional
		* 	object: - queryStringObject - optional
		* 
		* Returns:
		* 	string
		*/
		getValue: function(key, defaultValue, queryStringObject) {
			if (!Code.Type.isString(defaultValue)) {
				defaultValue = '';
			}
			if (!Code.Type.isNothing(queryStringObject)) {
				return Code.coalesce(queryStringObject[key], defaultValue);
			}
			return Code.coalesce(Code.QueryString.current[key], defaultValue);
		},

		/*
		* Function: toObject
		* Takes a querystring string and converts to an object.
		* 
		* Parameters:
		* 	string: - text
		* 	boolean: - overwrite - Items of the same name will overwrite previous values instead of creating an an array (Optional, Defaults to false) 
		* 
		* Returns:
		* 	object
		* 
		* Example 1:
		* Code.QueryString.toObject("foo=1&bar=2"); would return {foo: 1, bar: 2}
		* 
		* Example 2: 
		* Code.QueryString.toObject("foo=1&bar=2&bar=3&bar=4", true); would return {foo: 1, bar: [2, 3, 4]}
		*/
		toObject: function(text, overwrite) {

			if (!Code.Type.isString(text)) {
				return {};
			}

			var obj = {};
			var pairs = text.split('&');
			var pair, name, value;
			for (var i = 0, len = pairs.length; i < len; i++) {
				pair = pairs[i].split('=');
				name = decodeURIComponent(pair[0]);
				value = decodeURIComponent(pair[1]);
				if (overwrite !== true) {
					if (Code.Type.isUndefined(obj[name])) {
						obj[name] = value;
					}
					else if (Code.Type.isString(obj[name])) {
						obj[name] = [obj[name]];
						obj[name].push(value);
					}
					else {
						obj[name].push(value);
					}
				}
				else {
					obj[name] = value;
				}
			}
			return obj;

		},

		/*
		* Function: fromObject
		* Takes a object and converts to a querystring string.
		* 
		* Parameters:
		* 	object: - obj
		* 
		* Returns:
		* 	string
		* 
		* Example:
		* Code.QueryString.fromObject({foo: 1, bar: 2}); would return "foo=1&bar=2"
		*/
		fromObject: function(obj) {
			if (Code.Type.isNothing(obj)) {
				return '';
			}
			var buf = [];
			for (var key in obj) {
				var ov = obj[key], k = encodeURIComponent(key);
				if (Code.Type.isUndefined(ov)) {
					buf.push(k, "=&");
				}
				else if (!Code.Type.isFunction(ov) && !Code.Type.isObject(ov)) {
					buf.push(k, "=", encodeURIComponent(ov), "&");
				}
				else if (Code.Type.isArray(ov)) {
					if (ov.length) {
						for (var i = 0, len = ov.length; i < len; i++) {
							buf.push(k, "=", encodeURIComponent(ov[i] === undefined ? '' : ov[i]), "&");
						}
					}
					else {
						buf.push(k, "=&");
					}
				}
			}
			buf.pop();
			return buf.join("");
		}

	};


	/*
	* Class: Code.String
	* String utilities.
	* 
	* This is a singleton class.
	*/
	Code.String = {

		/*
		* Function: endsWith
		* Whether a string ends with a pattern.
		* 
		* Parameters:
		* 	string: - str
		* 	string: - pattern
		* 	boolean: - ignoreCase - (optional, default false)
		* 
		* Returns:
		* 	string
		*/
		endsWith: function(str, pattern, ignoreCase) {
			if (!Code.Type.isString(str)) { return ''; }
			if (!Code.Type.isNothing(ignoreCase)) {
				if (ignoreCase) {
					str = str.toLowerCase();
					pattern = pattern.toLowerCase();
				}
			}
			var d = str.length - pattern.length;
			return d >= 0 && str.lastIndexOf(pattern) === d;
		},

		/*
		* Function: escapeHtml
		* Escapes HTML.
		* 
		* Parameters:
		* 	string: - str
		* 
		* Returns:
		* 	string
		*/
		escapeHtml: function(str) {
			if (!Code.Type.isString(str)) { return ''; }
			return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
		},

		/*
		* Function: left
		* Returns a number of characters starting from the left.
		* 
		* Parameters:
		* 	string: - str
		* 	integer: - length
		* 
		* Returns:
		* 	string
		*/
		left: function(str, length) {
			if (!Code.Type.isString(str)) { return ''; }
			return str.substring(0, length);
		},

		/*
		* Function: makeUrlSafe
		* Given a string, make it safe to be used as a url.
		* 
		* Parameters:
		* 	string: - str
		* 	boolean: - toLower - (optional, default true)
		* 	string: - wordSeparator - (optional, default '-')
		* 
		* Returns:
		* 	string
		*/
		makeUrlSafe: function(str, toLower, wordSeparator) {
			if (!Code.Type.isString(str)) { return ''; }
			// Replace non aplha-numeric characters with a space
			str = str.replace(/[^a-zA-Z0-9 ]/g, ' ');
			// Replace multiple spaces with 1 space
			str = str.replace(/\s{2,}/g, ' ');
			if (!Code.Type.isString(wordSeparator)) {
				wordSeparator = '-';
			}
			str = str.replace(/\s/g, wordSeparator);
			if (Code.Type.isNothing(toLower)) {
				toLower = true;
			}
			if (toLower) {
				str = str.toLowerCase();
			}
			return str;
		},

		/*
		* Function: mid
		* Returns a substring.
		* 
		* Parameters:
		* 	string: - str
		* 	integer: - start
		* 	integer: - end
		* 
		* Returns:
		* 	string
		*/
		mid: function(str, start, end) {
			if (!Code.Type.isString(str)) { return ''; }
			if (!start) { start = 0 };
			if (!end || end > str.length) { end = str.length };
			if (end != str.length) { end = start + end };
			return str.substring(start, end);
		},

		/*
		* Function: right
		* Returns a number of characters starting from the right.
		* 
		* Parameters:
		* 	string: - str
		* 	integer: - length
		* 
		* Returns:
		* 	string
		*/
		right: function(str, length) {
			if (!Code.Type.isString(str)) { return ''; }
			return str.substring((str.length - length), str.length);
		},

		/*
		* Function: startsWith
		* Whether a string starts with a pattern.
		* 
		* Parameters:
		* 	string: - str
		* 	string: - pattern
		* 	boolean: - ignoreCase - (optional, default false)
		* 
		* Returns:
		* 	string
		*/
		startsWith: function(str, pattern, ignoreCase) {
			if (!Code.Type.isString(str)) { return ''; }
			if (!Code.Type.isNothing(ignoreCase)) {
				if (ignoreCase) {
					str = str.toLowerCase();
					pattern = pattern.toLowerCase();
				}
			}
			return str.indexOf(pattern) === 0;
		},

		/*
		* Function: stripWhitespace
		* Removes whitespace from a string.
		* 
		* Parameters:
		* 	string: - str
		* 
		* Returns:
		* 	string
		*/
		stripWhitespace: function(str) {
			if (!Code.Type.isString(str)) { return ''; }
			var exp = new RegExp('\\s{1,}', 'gi');
			return str.replace(exp, '');
		},

		/*
		* Function: stripTags
		* Removes HTML/XML tags from a string.
		* 
		* Parameters:
		* 	string: - str
		* 
		* Returns:
		* 	string
		*/
		stripTags: function(str) {
			if (!Code.Type.isString(str)) { return ''; }
			return str.replace(/<\/?[^>]+>/gi, '');
		},

		/*
		* Function: times
		* Returns the string multiplied by 'n' times.
		* 
		* Example:
		* Code.String.times('n', 3); would return 'nnn'
		* 
		* Parameters:
		* 	string: - str
		* 	integer: - count
		* 
		* Returns:
		* 	string
		*/
		times: function(str, count) {
			if (!Code.Type.isString(str)) { return ''; }
			return count < 1 ? '' : new Array(count + 1).join(str);
		},

		/*
		* Function: trim
		* Trims whitespace from the start and end of a string.
		* 
		* Parameters:
		* 	string: str
		* 
		* Returns:
		* 	string
		*/
		trim: function(str) {
			if (!Code.Type.isString(str)) { return ''; }
			return Code.String.trimEnd(Code.String.trimStart(str));
		},

		/*
		* Function: trimEnd
		* Trims whitespace from the end of a string.
		* 
		* Parameters:
		* 	string: str
		* 
		* Returns:
		* 	string
		*/
		trimEnd: function(str) {
			if (!Code.Type.isString(str)) { return ''; }
			while (str.charAt((str.length - 1)) == " ") {
				str = str.substring(0, str.length - 1);
			}
			return str;
		},

		/*
		* Function: trimStart
		* Trims whitespace from the start of a string.
		* 
		* Parameters:
		* 	string: str
		* 
		* Returns:
		* 	string
		*/
		trimStart: function(str) {
			if (!Code.Type.isString(str)) { return ''; }
			while (str.charAt(0) == " ") {
				str = str.replace(str.charAt(0), "");
			}
			return str;
		},

		/*
		* Function: truncate
		* Truncates a string and appends '...' if string exceeds maximum length.
		* 
		* Returned value will not exceed length.
		* 
		* Parameters:
		* 	string: - str
		* 	integer: - length
		* 	string: - truncation (optional, default '...')
		* 
		* Returns:
		* 	string
		*/
		truncate: function(str, length, truncation) {
			if (!Code.Type.isString(str)) { return ''; }
			length = length || 30;
			truncation = Code.Type.isNothing(truncation) ? '...' : truncation;
			return str.length > length ?
			str.slice(0, length - truncation.length) + truncation : String(str);
		},

		/*
		* Function: unescapeHtml
		* Unescapes HTML in a string.
		* 
		* Parameters:
		* 	string: - str
		* 
		* Returns:
		* 	string
		*/
		unescapeHtml: function(str) {
			if (!Code.Type.isString(str)) { return ''; }
			return str.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
		}

	};


	/*
	* Class: Code.Type
	* Type checking utilities.
	*/
	Code.Type = {

		/*
		* Function: isArray
		* Determines whether the parameter is an Array.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isArray: function(obj) {
			return obj && Code.Type.isFunction(obj.pop);
		},

		/*
		* Function: isDate
		* Determines whether the parameter is a Date.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isDate: function(obj) {
			return obj && Code.Type.isFunction(obj.getFullYear);
		},

		/*
		* Function: isDOMElement
		* Determines whether the parameter is a DOM element.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isDOMElement: function(obj) {
			return obj && obj.nodeType == 1;
		},

		/*
		* Function: isFunction
		* Determines whether the parameter is a Function.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isFunction: function(obj) {
			return typeof obj == "function";
		},

		/*
		* Function: isString
		* Determines whether the parameter is a String.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isString: function(obj) {
			return typeof obj == "string";
		},

		/*
		* Function: isNull
		* Determines whether the parameter is Null.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isNull: function(obj) {
			return obj === null;
		},

		/*
		* Function: isNothing
		* Determines whether the parameter is either Null or Undefined.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isNothing: function(obj) {
			if (Code.Type.isUndefined(obj) || Code.Type.isNull(obj)) {
				return true;
			}
			return false;
		},

		/*
		* Function: isNumber
		* Determines whether the parameter is a number.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isNumber: function(obj) {
			return typeof obj == "number";
		},

		/*
		* Function: isObject
		* Determines whether the parameter is an Object.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isObject: function(obj) {
			return typeof obj == "object";
		},

		/*
		* Function: isUndefined
		* Determines whether the parameter is Undefined.
		* 
		* Parameters:
		* 	object: val
		* 
		* Returns:
		* 	boolean
		*/
		isUndefined: function(obj) {
			return typeof obj == "undefined";
		}

	};


	/*
	* Class: Code.JSON
	* JSON utilities.
	*/
	Code.JSON = {

		//Private
		_pad: function(n) {
			return n < 10 ? "0" + n : n;
		},

		_m: {
			"\b": '\\b',
			"\t": '\\t',
			"\n": '\\n',
			"\f": '\\f',
			"\r": '\\r',
			'"': '\\"',
			"\\": '\\\\'
		},

		_encodeString: function(s) {
			if (/["\\\x00-\x1f]/.test(s)) {
				return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
					var c = Code.JSON._m[b];
					if (c) {
						return c;
					}
					c = b.charCodeAt();
					return "\\u00" +
						Math.floor(c / 16).toString(16) +
						(c % 16).toString(16);
				}) + '"';
			}
			return '"' + s + '"';
		},

		_encodeArray: function(o) {
			var a = ["["], b, i, l = o.length, v;
			for (i = 0; i < l; i += 1) {
				v = o[i];
				switch (typeof v) {
					case "undefined":
					case "function":
					case "unknown":
						break;
					default:
						if (b) {
							a.push(',');
						}
						a.push(v === null ? "null" : Code.JSON.fromObject(v));
						b = true;
				}
			}
			a.push("]");
			return a.join("");
		},

		_encodeDate: function(o) {
			return '"' + o.getFullYear() + "-" +
				Code.JSON._pad(o.getMonth() + 1) + "-" +
				Code.JSON._pad(o.getDate()) + "T" +
				Code.JSON._pad(o.getHours()) + ":" +
				Code.JSON._pad(o.getMinutes()) + ":" +
				Code.JSON._pad(o.getSeconds()) + '"';
		},

		/*
		* Function: fromObject
		* Converts an object into a JSON string.
		* 
		* Parameters:
		* 	object: - obj
		* 
		* Returns:
		* 	string
		*/
		fromObject: function(obj) {
			if (typeof obj == "undefined" || obj === null) {
				return "null";
			}
			else if (Code.Type.isArray(obj)) {
				return Code.JSON._encodeArray(obj);
			}
			else if (Code.Type.isDate(obj)) {
				return Code.JSON._encodeDate(obj);
			}
			else if (typeof obj == "string") {
				return Code.JSON._encodeString(obj);
			}
			else if (typeof obj == "number") {
				return isFinite(obj) ? String(obj) : "null";
			}
			else if (typeof obj == "boolean") {
				return String(obj);
			}
			else {
				var a = ["{"], b, i, v;
				for (i in obj) {

					if (obj.hasOwnProperty(i)) {
						v = obj[i];
						switch (typeof v) {
							case "undefined":
							case "function":
							case "unknown":
								break;
							default:
								if (b) {
									a.push(',');
								}
								a.push(Code.JSON.fromObject(i), ":", v === null ? "null" : Code.JSON.fromObject(v));
								b = true;
						}
					}
				}
				a.push("}");
				return a.join("");
			}
		},


		/*
		* Function: toObject
		* Converts an JSON string into an object.
		* 
		* Parameters:
		* 	string: - json
		* 
		* Returns:
		* 	object
		*/
		toObject: function(json) {
			return eval("(" + json + ')');
		}

	};


	/*
	* Class: Code.UserAgent
	* Useragent utilities / browser sniffing
	*/
	Code.UserAgent = {

		// string: userAgentString
		// Current UserAgent as a string
		userAgentString: '',

		/*
		* object: browser
		* Detailed browser information.
		* 
		* Attributes:
		* 	string: - id
		*  string: - version
		*  string: - majorVersion
		*  string: - minorVersion
		*  string: - engine
		*  string: - engineVersion
		*  string: - engineMajorVersion
		*  string: - engineMinorVersion
		*  boolean: - isStrict
		*  boolean: - isBorderBox
		*/
		browser: {
			id: 'unknown',
			version: '0.0',
			majorVersion: '0',
			minorVersion: '0',
			engine: 'unknown',
			engineVersion: '0.0',
			engineMajorVersion: '0',
			engineMinorVersion: '0',
			isStrict: false,
			isBorderBox: false
		},


		/*
		* object: os
		* Detailed operating system information.
		* 
		* Attributes:
		* 	string: - id
		* 	string: - version
		*/
		os: {
			id: 'unknown',
			version: '0.0'
		},


		/*
		* object: flash
		* Detailed Flash information.
		* 
		* Attributes:
		* 	string: - status - ('unknown', 'installed', 'notinstalled')
		* 	string: - version
		*/
		flash: {
			status: 'unknown',
			version: '0'
		},

		// boolean: isChrome
		isChrome: false,
		// boolean: isIE
		isIE: false,
		// boolean: isIE5_5
		// Is IE 5.5
		isIE5_5: false,
		// boolean: isIE6
		isIE6: false,
		// boolean: isIElt7
		// Is IE less than 7
		isIElt7: false,
		// boolean: isIE7
		isIE7: false,
		// boolean: isIE8
		isIE8: false,
		// boolean: isSafari
		isSafari: false,
		// boolean: isSafari2
		isSafari2: false,
		// boolean: isSafari3
		isSafari3: false,
		// boolean: isOpera
		isOpera: false,
		// boolean: isFirefox
		isFirefox: false,
		// boolean: isFirefox2
		isFirefox2: false,
		// boolean: isFirefox3
		isFirefox3: false,
		// boolean: isGecko
		isGecko: false,
		// boolean: isKHTML
		isKHTML: false,
		// boolean: isWebKit
		isWebKit: false,

		// boolean: hasFlash
		hasFlash: false,
		// boolean: hasFlash9
		hasFlash9: false,
		// boolean: hasFlash8
		hasFlash8: false,
		// boolean: hasFlash7
		hasFlash7: false,

		// boolean: isWindows
		isWindows: false,
		// boolean: isWindowsXP
		isWindowsXP: false,
		// boolean: isWindowsVista
		isWindowsVista: false,
		// boolean: isLinux
		isLinux: false,
		// boolean: isMacOSX
		isMacOSX: false,

		_init: function() {

			var ua = navigator.userAgent.toLowerCase();
			this.userAgentString = ua;

			// Browser details

			this.browser.isStrict = document.compatMode == "CSS1Compat";

			if (ua.search(/msie\s(\d+(\.?\d)*)/) != -1) {
				// MSIE
				this.browser.id = "msie";
				this.browser.version = ua.match(/msie\s(\d+(\.?\d)*)/)[1];
				this.browser.engine = "msie";
				this.browser.engineVersion = this.browser.version;
				this.isIE = true;
				switch (parseInt(this.browser.version)) {
					case 8:
						this.isIE8 = true;
						break;
					case 7:
						this.isIE7 = true;
						break;
					case 6:
						this.isIE6 = true;
						this.isIElt7 = true;
						break;
					default:
						this.isIElt7 = true;
						if (this.browser.version == '5.5') {
							this.isIE5_5 = true;
						}
						break;
				}
				if (!this.browser.isStrict) {
					this.browser.isBorderBox = true;
				}
			}
			else if (ua.search(/firefox[\/\s](\d+([\.-]\d)*)/) != -1) {
				// Firefox
				this.browser.id = "firefox";
				this.browser.version = ua.match(/firefox[\/\s](\d+([\.-]\d)*)/)[1];
				this.browser.engine = "gecko";
				this.browser.engineVersion = getGeckoVersion();
				this.isGecko = true;
				this.isFirefox = true;
				switch (parseInt(this.browser.version)) {
					case 3:
						this.isFirefox3 = true;
						break;
					case 2:
						this.isFirefox2 = true;
						break;
					default:
						break;
				}
			}
			else if (ua.search(/chrome\/(\d)*/) != -1) {
				// Google Chrome
				this.browser.id = "chrome";
				this.browser.version = ua.match(/chrome\/(\d+(\.?\d*)*)/)[1];
				this.browser.engine = "webkit";
				this.browser.engineVersion = ua.match(/applewebkit\/(\d+(\.?\d*)*)/)[1];
				this.isChrome = true;
				this.isWebKit = true;
			}
			else if (ua.search(/safari\/(\d)*/) != -1) {
				// Safari
				this.browser.id = "safari";
				this.browser.version = ua.match(/safari\/(\d+(\.?\d*)*)/)[1];
				this.browser.engine = "webkit";
				this.browser.engineVersion = ua.match(/applewebkit\/(\d+(\.?\d*)*)/)[1];
				this.isSafari = true;
				this.isWebKit = true;
				if (ua.indexOf('webkit/5') != -1) {
					this.isSafari3 = true;
				}
				else if (ua.indexOf('webkit/4') != -1) {
					this.isSafari2 = true;
				}
			}
			else if (ua.search(/opera[\/\s](\d+(\.?\d)*)/) != -1) {
				// Opera
				this.browser.id = "opera";
				this.browser.version = ua.match(/opera[\/\s](\d+(\.?\d)*)/)[1];
				this.browser.engine = "opera";
				this.browser.engineVersion = this.browser.version;
				this.isOpera = true;
			}

			else if (ua.search(/camino[\/\s](\d+([\.-]\d)*)/) != -1) {
				// Camino
				this.browser.id = "camino";
				this.browser.version = brs.match(/camino[\/\s](\d+([\.-]\d)*)/)[1];
				this.browser.engine = "gecko";
				this.browser.engineVersion = getGeckoVersion();
				this.isGecko = true;
			}
			else if (ua.search(/konqueror[\/\s](\d+([\.-]\d)*)/) != -1) {
				// Konqueror
				this.browser.id = "konqueror";
				this.browser.version = ua.match(/konqueror[\/\s](\d+([\.-]\d)*)/)[1];
				this.browser.engine = "khtml";
			}
			else if (brs.search(/netscape6[\/\s](\d+([\.-]\d)*)/) != -1) {
				// Netscape 6.x
				this.browser.id = "netscape";
				this.browser.version = ua.match(/netscape6[\/\s](\d+([\.-]s\d)*)/)[1];
				this.browser.engine = "gecko";
				this.browser.engineVersion = getGeckoVersion();
				this.isGecko = true;
			}
			else if (ua.search(/netscape\/(7\.\d*)/) != -1) {
				// Netscape 7.x
				this.browser.id = "netscape";
				this.browser.version = ua.match(/netscape\/(7\.\d*)/)[1];
				this.browser.engine = "gecko";
				this.browser.engineVersion = getGeckoVersion();
				this.isGecko = true;
			}
			else if (ua.search(/netscape4\/(\d+([\.-]\d)*)/) != -1) {
				// Netscape 4.x
				this.browser.id = "netscape";
				this.browser.version = ua.match(/netscape4\/(\d+([\.-]\d)*)/)[1];
				this.browser.engine = "mozold";
				this.browser.engineVersion = this.browser.version;
			}
			else if ((ua.search(/mozilla\/(4.\d*)/) != -1) && (ua.search(/msie\s(\d+(\.?\d)*)/) == -1)) {
				this.browser.id = "netscape";
				this.browser.version = ua.match(/mozilla\/(4.\d*)/)[1];
				this.browser.engine = "mozold";
				this.browser.engineVersion = this.browser.version;
			}
			else if ((ua.search(/mozilla\/5.0/) != -1) && (ua.search(/gecko\//) != -1)) {
				// Mozilla Seamonkey
				this.browser.id = "mozsea";
				this.browser.version = ua.match(/rv\x3a(\d+(\.?\d)*)/)[1];
				this.browser.engine = "gecko";
				this.browser.engineVersion = getGeckoVersion();
				this.isGecko = true;
			}

			this.browser.majorVersion = getMajorVersion(this.browser.version);
			this.browser.minorVersion = getMinorVersion(this.browser.version);
			this.browser.engineMajorVersion = getMajorVersion(this.browser.engineVersion);
			this.browser.engineMinorVersion = getMinorVersion(this.browser.engineVersion);

			// Operating System details
			if ((ua.search(/windows/) != -1) || ((ua.search(/win9\d{1}/) != -1))) {
				this.isWindows = true;
				this.os.id = "windows";

				if (ua.search(/nt\s6\.0/) != -1) {
					this.os.version = "vista";
					this.isWindowsVista = true;
				}
				else if (ua.search(/nt\s5\.1/) != -1) {
					this.os.version = "xp";
					this.isWindowsXP = true;
				}
				else if (ua.search(/nt\s5\.0/) != -1) {
					this.os.version = "2000";
				}
				else if ((ua.search(/win98/) != -1) || (ua.search(/windows\s98/) != -1)) {
					this.os.version = "98";
				}
				else if (ua.search(/windows\sme/) != -1) {
					this.os.version = "me";
				}
				else if (ua.search(/nt\s5\.2/) != -1) {
					this.os.version = "2003";
				}
				else if ((ua.search(/windows\s95/) != -1) || (ua.search(/win95/) != -1)) {
					this.os.version = "95";
				}
				else if ((ua.search(/nt\s4\.0/) != -1) || (ua.search(/nt4\.0/)) != -1) {
					this.os.version = "nt4";
				}
			}
			else if (ua.search(/linux/) != -1) {
				this.isLinux = true;
				this.os.id = "linux";
				try {
					this.os.version = ua.match(/linux\s?(\d+(\.?\d)*)/)[1];
				} catch (e) { }
			}
			else if (ua.search(/mac\sos\sx/) != -1) {
				this.isMacOSX = true;
				this.os.id = "macosx";
			}
			else if ((ua.search(/macintosh/) != -1) || (brs.search(/mac\x5fpowerpc/) != -1)) {
				this.os.id = "macclassic";
			}


			// Flash check
			if (this.isIE) {

				for (var i = 15; i > 0; i--) {
					try {
						var flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash." + i);
						this.flash.version = i;
						break;
					} catch (e) { }
				}

				if (this.flash.version > 0) {
					this.flash.status = 'installed';
				}
				else {
					this.flash.status = 'notinstalled';
				}

			}
			else {

				var x, y;
				if (navigator.plugins && navigator.plugins.length) {
					x = navigator.plugins["Shockwave Flash"];
					if (x) {
						this.flash.status = 'installed';
						if (x.description) {
							y = x.description;
							this.flash.version = y.substring(y.indexOf('.') - 2, y.indexOf('.'));
							this.flash.version = this.flash.version.replace(/^\s+|\s+$/g, '');
						}
					}
					else {
						this.flash.status = 'notinstalled';
					}
					if (navigator.plugins["Shockwave Flash 2.0"]) {
						this.flash.status = 'installed';
					}
				}
				else if (navigator.mimeTypes && navigator.mimeTypes.length) {
					x = navigator.mimeTypes['application/x-shockwave-flash'];
					if (x && x.enabledPlugin) {
						this.flash.status = 'installed';
					}
					else {
						this.flash.status = 'notinstalled';
					}
				}

			}

			if (this.flash.status == 'installed') {
				this.hasFlash = true;
				switch (parseInt(this.flash.version)) {
					case 9:
						this.hasFlash9 = true;
						break;
					case 8:
						this.hasFlash8 = true;
						break;
					case 7:
						this.hasFlash7 = true;
						break;
					default:
						break;
				}
			}

			// Helper methods
			function getGeckoVersion() {
				return ua.match(/gecko\/([0-9]+)/)[1];
			}

			// Return browser's (actual) major version or -1 if bad version entered
			function getMajorVersion(v) {
				return (isEmpty(v) ? -1 : (hasDot(v) ? v : v.match(/(\d*)(\.\d*)*/)[1]))
			}

			// Return browser's (actual) minor version or -1 if bad version entered
			function getMinorVersion(v) {
				return (!isEmpty(v) ? (!hasDot(v) ? v.match(/\.(\d*([-\.]\d*)*)/)[1] : 0) : -1)
			}

			function isEmpty(input) {
				return (input == null || input == "")
			}

			function hasDot(input) {
				return (input.search(/\./) == -1)
			}
		}

	};


	/*
	* Class: Code.Resources
	* String resources required for your scripts.
	*/
	Code.Resources = {

		//Private 
		_resources: {},


		//Public

		/*
		* Function: setResources
		* Sets the current resources object.
		* 
		* Parameters:
		* 	object: - resourcesObj
		* 
		* Format:
		* Resource object must be in the following format:
		* (start code)
		* {
		* 	ClassKey1: {
		* 		ResourceKey1: 'This is ClassKey1.ResourceKey1',
		* 		ResourceKey2: 'This is ClassKey1.ResourceKey2'
		* 	},
		* 	ClassKey2: {
		* 		ResourceKey1: 'This is ClassKey2.ResourceKey1',
		* 		ResourceKey2: 'This is ClassKey2.ResourceKey2',
		* 	}
		* }
		* (end code)
		*/
		setResources: function(resourcesObj) {
			Code.Resources._resources = resourcesObj;
		},


		/*
		* Function: getResource
		* Returns a resource value from the current resource object
		* 
		* Parameters:
		* 	string: - classKey
		* 	string: - resourceKey
		* 	string: - defaultValue (optional)
		* 
		* Returns:
		* 	string
		* 
		* Example:
		* 	Code.Resources.getResource('ClassKey1', 'ResourceKey2');
		*/
		getResource: function(classKey, resourceKey, defaultValue) {
			var resourceObj = Code.Resources._resources[classKey];
			if (defaultValue == null) {
				defaultValue = 'Resource "#classKey#, #resourceKey#" not found.'
			}
			defaultValue = defaultValue
											.replace(/#classKey#/g, classKey)
											.replace(/#resourceKey#/g, resourceKey);

			if (resourceObj == null) {
				return defaultValue;
			}

			var value = resourceObj[resourceKey];
			return (Code.Type.isNothing(value)) ? defaultValue : value;
		}


	};


	Code._init();


	var initializingClass = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;

	/*
	* Class: Code.Class
	* Used for simple class inheritance.
	* 
	* Based on code by John Resig: <http://ejohn.org/blog/simple-javascript-inheritance/>
	* 
	* Example:
	* (start code)
	* var Person = Code.Class.extend({
	* 	init: function(isDancing){
	* 		this.dancing = isDancing;
	* 	},
	* 	dance: function(){
	* 		return this.dancing;
	* 	},
	* 
	* 	dancing: null
	* });
	* 
	* var Ninja = Person.extend({
	* 	init: function(){
	* 		this._super( false );
	* 	},
	* 	dance: function(){
	* 		// Call the inherited version of dance()
	* 		return this._super();
	* 	},
	* 	swingSword: function(){
	* 		return true;
	*	}
	* });
	* 
	* var p = new Person(true);
	* p.dance(); // => true
	* 
	* var n = new Ninja();
	* n.dance(); // => false
	* n.swingSword(); // => true
	* 
	* // Should all be true
	* p instanceof Person && p instanceof Class &&
	* n instanceof Ninja && n instanceof Person && n instanceof Class
	* (end code)
	* 
	* Notes:
	* 	- *init()* acts as the class constructor
	* 	- It is best practice to initialise class properties (i.e. Person.dancing) in the *init()* method
	* 
	*/
	Code.Class = function() { };

	// Create a new Class that inherits from this class
	Code.Class.extend = function(prop) {
		var _super = this.prototype;

		// Instantiate a base class (but only create the instance,
		// don't run the init constructor)
		initializingClass = true;
		var prototype = new this();
		initializingClass = false;

		// Copy the properties over onto the new prototype
		for (var name in prop) {
			// Check if we're overwriting an existing function
			prototype[name] = typeof prop[name] == "function" &&
			typeof _super[name] == "function" && fnTest.test(prop[name]) ?
			(function(name, fn) {
				return function() {
					var tmp = this._super;

					// Add a new ._super() method that is the same method
					// but on the super-class
					this._super = _super[name];

					// The method only need to be bound temporarily, so we
					// remove it when we're done executing
					var ret = fn.apply(this, arguments);
					this._super = tmp;
					return ret;
				};
			})(name, prop[name]) :
			prop[name];
		}

		// The dummy class constructor
		function Class() {
			// All construction is actually done in the init method
			if (!initializingClass && this.init)
				this.init.apply(this, arguments);
		}

		// Populate our constructed prototype object
		Class.prototype = prototype;

		// Enforce the constructor to be what we expect
		Class.constructor = Class;

		// And make this class extendable
		Class.extend = arguments.callee;

		return Class;
	};



})();
