/*
 * 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 obj;
		},
				
		/*
		 * 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 = _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() + "-" +
				pad(o.getMonth() + 1) + "-" +
				pad(o.getDate()) + "T" +
				pad(o.getHours()) + ":" +
				pad(o.getMinutes()) + ":" +
				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.charAt(y.indexOf('.')-1);
						}
					} 
					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;
  };
	
	
	
})();