/*
 * Wildfire Spark
 * Copyright(c) 2003-2010, Wildfire Technology Limited
 * licensing@wildfire-it.com
 *
 * http://www.wildfire-it.com
 */

var Spark = {
	userAgent:navigator.userAgent.toLowerCase()

	,init:function() {
		var tb = this.browser;
		var ua = this.userAgent;

		tb.version = (ua.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1];
		tb.webkit = /webkit/.test(ua);
		tb.opera = /opera/.test(ua);
		tb.msie = /msie/.test(this.userAgent) && !/opera/.test(ua);
		tb.mozilla = /mozilla/.test(ua) && !/(compatible|webkit)/.test(ua);
		tb.msafari = /apple.*mobile.*safari/.test(ua);
		this.browserDetect.init(tb);
	}

	,log: function(s) {
		httpRequest('/_/ServletEngine/log?msg=' + s);
	}

	,load:function() {
		if (document.all) {
			HTMLDomElement = window.XMLHttpRequest ? HTMLElement || Element : HTMLElement;
		} else {
			HTMLDomElement = HTMLElement;
		}
		this.loadExtensions();
	}

	,browser: {
		getViewportWidth:function() {
			return window.innerWidth;
		}
		,getViewportHeight:function() {
			return window.innerHeight;
		}
		,getViewportSize:function() {
			return [this.getViewportWidth(), this.getViewportHeight()];
		}
		,getOrientation:function() {
			var o = window.orientation;
			if (o == 90 || o == -90)
				return 'landscape';

			return 'portrait';
		}
	}

	,extend: function() {
		function extend(ob, src) {
			for (var property in src) {
				ob[property] = src[property];
			}
		}

		if (arguments.length == 2) {
			extend(arguments[0], arguments[1]);
		} else {
			var l = arguments.length, src = arguments[l - 1], i = l - 1;
			while (i--) {
				extend(arguments[i], src);
			}
		}
	}

	,loadExtensions:function() {

		this.extend(HTMLDomElement.prototype, {
			getBox:function() {
				var obj = this;

				var left = 0;
				var top = 0;
				var border;
				if (obj.offsetParent) {
					do { // Note: If the element is position: relative we have to add borderWidth
						if (getStyle(obj, 'position') == 'relative') {
							if (border = getStyle(obj, 'border-top-width'))
								top += parseInt(border);

							if (border = getStyle(obj, 'border-left-width'))
								left += parseInt(border);
						}
						left += obj.offsetLeft;
						top += obj.offsetTop;
					} while (obj = obj.offsetParent)
				} else if (obj.x) {
					left += obj.x;
					top += obj.y;
				}

				return {
					x:left,
					y:top,
					w:this.offsetWidth,
					h:this.offsetHeight
				};
			}

			,getElementByClassName:function(cls, tag) {
				var sel = this.getElementsByClassName(cls, tag);
				return sel.length == 0 ? null : sel[0];
			}

			,getElementsByClassName:function(cls, tag) {
				var sel = [];

				if (!cls)
					return sel;

				var el = this;

				if (!tag)
					tag = "*";

				var els = (tag == "*" && el.all) ? el.all : el.getElementsByTagName(tag);

				cls = cls.replace(/\-/g, "\\-");

				var reg = new RegExp("(^|\\s)" + cls + "(\\s|$)");
				var ob;
				for (var i = 0; i < els.length; i++) {
					ob = els[i];
					if (reg.test(ob.className))
						sel.push(ob);
				}
				return sel;
			},

			show:function(mode, fn, delay) {

				mode = mode || 'display';

				this._style = this._style || {};

				if (mode == 'visibility') {
					this.style.visibility = this._style.visibility || 'inherit';
				} else if (mode == 'opacity') {
					this.style.opacity = 1;
				} else {
					this.style.display = this._style.display || 'inherit';
				}

				if (fn) {
					delay = delay || 0;

					setTimeout(function() {
						fn.call(this);
					}, delay);
				}
			},

			hide:function(mode, fn, delay) {
				mode = mode || 'display';

				this._style = this._style || {};

				var currentDisplay = getStyle(this, mode);

				if (mode == 'visibility') {
					if (currentDisplay != 'hidden')
						this._style.visibility = currentDisplay;

					this.style.visibility = 'hidden';
				} else if (mode == 'opacity') {
					this.style.opacity = 0;
				} else {
					if (currentDisplay != 'none')
						this._style.display = currentDisplay;

					this.style.display = 'none';
				}

				if (fn) {

					delay = delay || 0;

					setTimeout(function() {
						fn.call(this);
					}, delay);
				}
			}

			,visible:function() {
				return getStyle(this, 'display') != 'none' && getStyle(this, 'visibity') != 'hidden';
			}

			,hasClass:function(cls) {
				return new RegExp('(?:^|\\s+)' + cls + '(?:\\s+|$)').test(this.className);
			}

			,addClass:function(cls) {
				if (this.hasClass(cls))
					return;

				var c = this.className + ' ' + cls.trim();
				this.className = c.trim();
			}

			,removeClass:function(cls) {
				this.className = this.className.replace(new RegExp('(?:^|\\s+)' + cls + '(?:\\s+|$)'), ' ').trim();
			}

			,applyStyles:function(s) {
				for (var k in s) {
					this.style[k] = s[k];
				}
			}

			,css:function(o){
				this.applyStyles(o);
			}

			,applyStyle:function(k, v, fn, delay) {
				k = k.replace(/-([a-z])/g, function() {
					return arguments[1].toUpperCase();
				});
				this.style[k] = v;

				if (fn) {
					delay = delay || 0;

					setTimeout(function() {
						fn.call(this);
					}, delay);
				}
			}

			,parent:function(cls) {
				var p = this.parentNode;
				if (cls) {
					while (!p.hasClass(cls) && p != document.body) {
						p = p.parentNode;
					}

					if (p == document.body)
						return null;
				}
				return p;
			}

			,remove:function() {
				if (this.parentNode)
					this.parentNode.removeChild(this);
			}

			,del:function() {
				this.remove();
			}

			,add:function(el) {
				this.appendChild(el);
				return el;
			}

			,addFirst:function(el) {
				return this.insertFirst(el);
			}

			,before:function(el) {
				this.parentNode.insertBefore(el, this);
			}

			,insertFirst:function(el) {
				if (this.firstChild)
					this.insertBefore(el, this.firstChild);
				else
					this.appendChild(el);

				return el;
			}

			,insertSibling:function(el) {
				this.parentNode.insertBefore(el, this);

				return el;
			}

			,addBefore:function(el) {
				this.parentNode.insertBefore(el, this);

				return el;
			}

			,after:function(el) {
				this.addAfter(el);
			}

			,addAfter:function(el) {
				var sib = nextSibling(this);
				if (sib)
					this.parentNode.insertBefore(el, sib);
				else
					this.parentNode.appendChild(el);

				return el;
			}

			,attr:function(k, v) {
				if (arguments.length == 2) {
					if (!v)
						this.removeAttribute(k);
					else
						this.setAttribute(k, v);
				}
				return this.getAttribute(k);
			}

			,toggle:function() {
				var s = getStyle(this, 'display');

				this.style.display = 'inherit';

				var defaultDisplay = getStyle(this, 'display');

				this.style.display = s == 'none' ? defaultDisplay : 'none';
			}

			,val:function(v) {
				if (typeof v != 'undefined')
					this.value = v;

				return this.value;
			}

			,getFirstChild:function() {
				return firstChild(this);
			}
		});

		this.extend(Array.prototype, NodeList.prototype, {
			each: function(it, ctx) {
				it = (ctx) ? it.bind(ctx) : it;
				try {
					var c = this.length, i = 0;
					while (i < c) {
						if (typeof this[i] !== 'function') {
							it(i, this[i]);
						}
						i++;
					}
				} catch(e) {
					throw e;
				}
				return this;
			},
			eachAfter: function(it, intvl, dir, ctx, cb) {
				it = (ctx) ? it.bind(ctx) : it;
				cb = cb || function() {
				};
				var dir = dir || 1;
				var c = this.length, i = 0;
				if (dir < 0) {
					var t = c;
					c = i;
					i = (t == 0) ? 0 : t - 1;
				}
				try {
					var eachIterator = setInterval(function() {
						if (i === c) {
							if (cb) {
								cb();
							}
							clearInterval(eachIterator);
							return this;
						} else {
							// make sure it's not a function!
							if (typeof this[i] !== 'function') {
								it(this[i]);
							}
							i = i + dir;
						}
					}.bind(this), (intvl || 1000));
				} catch(e) {
					throw e;
				}
			}

			,val:function(v) {
				var val;

				for (var i = 0; i < this.length; i++) {
					val = this[i].val(v);
				}
				return val;
			}

			,parent:function(v) {
				if (this.length > 0) {
					return this[0].parent();
				}
			}

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

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

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

			show: function (mode, fn, delay) {
				for (var i = 0; i < this.length; i++) {
					this[i].show(mode, fn, delay);
				}
			},

			hide: function (mode, fn, delay) {
				for (var i = 0; i < this.length; i++) {
					this[i].hide(mode, fn, delay);
				}
			},

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

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

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

			hasClass: function (cls) {
				var valid = true;
				for (var i = 0; i < this.length; i++) {
					valid = this[i].hasClass(cls);
				}
				return valid;
			}

			,before:function(el) {
				if (this.length > 0)
					this[0].before(el);

				return el;
			}

			,after:function(el) {
				if (this.length > 0)
					this[0].addAfter(el);

				return null;
			}


		});

		this.extend(HTMLFormElement.prototype, {

			ajaxSubmission:function(cb) {
				var els = this.elements;
				var uri = this.action;

				var qs = '';

				for (var i = 0; i < els.length; i++) {
					var el = els[i];
					if (el.name && (el.type != 'checkbox' || el.checked))
						qs += (qs == '' ? '?' : '&') + el.name + '=' + escape(el.value);
				}

				uri += qs;

				httpRequest(uri, cb);

				return false;
			}

			,addField:function(k, cfg) {

				var field = document.createElement('input');
				field.setAttribute('type', cfg.type || 'text');

				if (k)
					field.setAttribute('name', k);

				field.setAttribute('value', cfg.value || '');

				this.addElement(field, cfg);

				return field;
			}

			,addTextarea:function(k, cfg) {
				cfg = cfg || {};
				var field = document.createElement('textarea');
				if (k)
					field.setAttribute('name', k);

				field.innerHTML = cfg.value || '';
				this.addElement(field, cfg);
				return field;
			}

			,addElement:function(field, cfg) {
				var el;

				if (cfg.label) {
					var label = document.createElement('label');

					if (!cfg.hideLabel)
						label.innerHTML = cfg.label;

					label.appendChild(field);
					el = label;
				} else
					el = field;

				this.appendChild(el);
			}
		});

		this.extend(HTMLSelectElement.prototype, {
			append:function(l, v) {
				var o = document.createElement('option');
				o.value = v;
				try {
					this.add(o, null);
					o.innerHTML = l;
				} catch(E) {
					o.text = l;
					this.add(o);
				}
			}

			,setValue:function(v) {
				for (var i = 0; i < this.length; i++) {
					if (this[i].value == v) {
						this.selectedIndex = i;
						break;
					}
				}
			}
		});

		this.extend(HTMLInputElement.prototype, {
			/*
			 autocomplete:function(dataUri, cfg) {
			 var t = this;
			 cfg = cfg || {};
			 cfg.minChars = cfg.minChars || 1;
			 cfg.requestParam = cfg.requestParam || 'q';
			 var KEY = {
			 BACKSPACE: 8,
			 COMMA: 188,
			 DEL: 46,
			 DOWN: 40,
			 ESC: 27,
			 PAGEDOWN: 34,
			 PAGEUP: 33,
			 RETURN: 13,
			 TAB: 9,
			 UP: 38
			 };
			 var list;
			 Spark.eventManager.onload(function() {
			 list = document.createElement('div');
			 list.className = cfg.cls || 'autocomplete-list';
			 var loc = t.getBox();
			 list.applyStyles({
			 display:'block'
			 ,position:'absolute'
			 ,left:addUnit(cfg.left || loc.x)
			 ,top:addUnit(cfg.top || loc.y + loc.h)
			 ,width:addUnit(cfg.width || loc.w)
			 ,zIndex:10000000
			 ,cursor:'pointer'
			 ,maxHeight:addUnit(cfg.height || 100)
			 ,overflowY:'hidden'
			 });

			 Spark.eventManager.add(list, 'mousedown', function(e) {
			 t.val(e.target.innerHTML);
			 });

			 t.addAfter(list);
			 list.hide();
			 list.selected = null;
			 list.select = function(option) {
			 if (!option)
			 option = firstChild(list);

			 if (!option)
			 return;

			 var selectedCls = cfg.selectedCls || 'autocomplete-option-selected';

			 if (this.selected)
			 this.selected.removeClass(selectedCls);

			 this.selected = option;
			 option.addClass(selectedCls);

			 if (option.offsetTop > (this.offsetHeight - this.scrollTop))
			 option.scrollIntoView(false);
			 };

			 list.next = function() {
			 var el = list.selected;
			 if (!el)
			 return;

			 list.select(nextSibling(el));
			 };

			 list.prev = function() {
			 var el = list.selected;
			 if (!el)
			 return;

			 list.select(previousSibling(el));
			 };
			 });

			 var timeout;
			 var lastKey;
			 var lastValue;

			 Spark.eventManager.add(this, 'blur', function(e) {
			 list.hide();
			 });

			 Spark.eventManager.add(this, 'keydown', function(e) {
			 lastKey = e.keyCode;
			 switch (lastKey) {
			 case KEY.UP:
			 e.preventDefault();
			 if (list.visible())
			 list.prev();
			 else
			 onChange(true);
			 break;

			 case KEY.DOWN:
			 e.preventDefault();
			 if (list.visible())
			 list.next();
			 else
			 onChange(true);
			 break;

			 case KEY.PAGEUP:
			 e.preventDefault();
			 if (list.visible())
			 list.pageUp();
			 else
			 onChange(true);
			 break;

			 case KEY.PAGEDOWN:
			 e.preventDefault();
			 if (list.visible())
			 list.pageDown();
			 else
			 onChange(true);
			 break;

			 // matches also semicolon
			 case KEY.TAB:
			 case KEY.RETURN:
			 if (list.selected) {
			 t.val(list.selected.innerHTML);
			 list.hide()
			 }
			 // stop default to prevent a form submit, Opera needs special handling
			 event.preventDefault();
			 return false;
			 break;

			 case KEY.ESC:
			 list.hide();
			 break;

			 default:
			 clearTimeout(timeout);
			 timeout = setTimeout(onChange, cfg.delay || 400);
			 break;
			 }
			 });

			 function onChange(skip) {
			 if (lastKey == KEY.DEL) {
			 list.hide();
			 return;
			 }

			 var v = t.val();

			 if (!skip && v == lastValue)
			 return;

			 lastValue = v;

			 if (v.length >= cfg.minChars) {
			 //$input.addClass(options.loadingClass);

			 list.show();

			 if (!cfg.caseSensitive)
			 v = v.toLowerCase();

			 httpRequest(dataUri + '?' + cfg.requestParam + '=' + v, function(response) {

			 try {
			 response = JSON.parse(response);
			 } catch(e) {

			 }

			 if (typeof response == 'string')
			 response = response.split(cfg.delimiter || ',');

			 list.innerHTML = '';

			 var baseCls = cfg.optionCls || 'autocomplete-option';

			 for (var i = 0; i < response.length; i++) {
			 var option = document.createElement('div');
			 option.className = baseCls + ' ' + (i % 2 ? baseCls + '-stripe' : '');
			 option.innerHTML = response[i];
			 list.appendChild(option);
			 }

			 list.select();
			 });
			 } else {
			 //!!stopLoading();
			 list.hide();
			 }
			 }
			 }

			 */
		});

		this.extend(String.prototype, {
			endsWith: function(s) {
				return new RegExp(s + '$').test(this);
			}

			,startsWith: function(s) {
				return new RegExp('^' + s).test(this);
			}

			,contains: function(s) {
				return this.indexOf(s) > -1;
			}

			,trim: function() {
				return this.replace(new RegExp('(^[\\s]+|[\\s]+$)', 'g'), '');
			}

			,capitalize: function() {
				return this.replace(/\b[a-z]/g, function(s) {
					return s.toUpperCase();
				});
			}
		});
	}

	,eventManager:{
		add:function(el, n, fn) {
			if (typeof window.addEventListener != 'undefined')
				el.addEventListener(n.replace(/^on/i, ''), fn, false);
			else if (typeof window.attachEvent != 'undefined')
				el.attachEvent(n, fn);
			else if (window[n] != null) {
				var evt = window[n];
				window[n] = function(e) {
					evt(e);
					el[fn]();
				};
			} else
				el[n] = fn;
		}

		,onload:function(fn) {
			this.add(window, 'onload', fn);
		}
	}

	,cookieManager:{
		get:function(name, decode) {
			if (arguments.length == 1)
				decode = true;

			var c = document.cookie;

			var s = null;

			if (c.length > 0) {
				var v;
				var n = name;
				var i = 1;
				while ((v = this.read(n))) {
					if (v) {
						if (!s)
							s = '';

						s += v;
					}

					n = name + '__' + i;
					i++;
				}

				if (s && decode)
					s = unescape(s);

				return s;
			}
			return null;
		},

		read:function(name) {
			var nameEQ = name + "=";
			var cookies = document.cookie.split(';');
			for (var i = 0; i < cookies.length; i++) {
				var c = cookies[i];
				while (c.charAt(0) == ' ')
					c = c.substring(1, c.length);

				if (c.indexOf(nameEQ) == 0) {
					//put('getting cookie: ' + name);
					var value = c.substring(nameEQ.length, c.length);

					//put(unescape(value));

					return value;
				}
			}
			return null;
		}

		,set:function(name, value, expires, path) {
			path = path || '/';

			var d = new Date();
			if (!expires)
				expires = null;
			else
				d.setDate(d.getDate() + expires);

			var values = [];

			value = escape(value);

			var len = 1024 * 3;

			if (value.length > len) {
				while (value.length > len) {
					values.push(value.slice(0, len));
					value = value.substring(len);
				}

				if (value)
					values.push(value);
			} else
				values.push(value);


			for (var i = 0; i < values.length; i++) {
				var n = i == 0 ? name : name + '__' + i;
				document.cookie = n + '=' + values[i] + ((expires == null) ? '' : '; expires=' + d.toGMTString()) + '; path=' + path;
			}


			//remove old cookies
			if (value) {
				var i = values.length;
				while (this.read(name + '__' + i)) {
					this.expire(name + '__' + i);
				}
			}
		}

		,expire:function(name, path) {
			this.set(name, '', -1, path);
		}
	}

	,browserDetect:{
		init: function (t) {
			t.name = this.searchString(this.dataBrowser) || "An unknown browser";
			t.version = this.searchVersion(navigator.userAgent)
					|| this.searchVersion(navigator.appVersion)
					|| "an unknown version";
			t.OS = this.searchString(this.dataOS) || "an unknown OS";
		}
		,searchString: function (data) {
			for (var i = 0; i < data.length; i++) {
				var dataString = data[i].string;
				var dataProp = data[i].prop;
				this.versionSearchString = data[i].versionSearch || data[i].identity;
				if (dataString) {
					if (dataString.indexOf(data[i].subString) != -1)
						return data[i].identity;
				}
				else if (dataProp)
					return data[i].identity;
			}
		}
		,searchVersion: function (dataString) {
			var index = dataString.indexOf(this.versionSearchString);
			if (index == -1) return;
			return parseFloat(dataString.substring(index + this.versionSearchString.length + 1));
		}
		,dataBrowser: [
			{
				string: navigator.userAgent,
				subString: "Chrome",
				identity: "Chrome"
			},
			{
				string: navigator.userAgent,
				subString: "OmniWeb",
				versionSearch: "OmniWeb/",
				identity: "OmniWeb"
			},
			{
				string: navigator.vendor,
				subString: "Apple",
				identity: "Safari",
				versionSearch: "Version"
			},
			{
				prop: window.opera,
				identity: "Opera"
			},
			{
				string: navigator.vendor,
				subString: "iCab",
				identity: "iCab"
			},
			{
				string: navigator.vendor,
				subString: "KDE",
				identity: "Konqueror"
			},
			{
				string: navigator.userAgent,
				subString: "Firefox",
				identity: "Firefox"
			},
			{
				string: navigator.vendor,
				subString: "Camino",
				identity: "Camino"
			},
			{
				// for newer Netscapes (6+)
				string: navigator.userAgent,
				subString: "Netscape",
				identity: "Netscape"
			},
			{
				string: navigator.userAgent,
				subString: "MSIE",
				identity: "Explorer",
				versionSearch: "MSIE"
			},
			{
				string: navigator.userAgent,
				subString: "Gecko",
				identity: "Mozilla",
				versionSearch: "rv"
			},
			{
				string: navigator.userAgent,
				subString: "Mozilla",
				identity: "Netscape",
				versionSearch: "Mozilla"
			}
		],
		dataOS : [
			{
				string: navigator.platform,
				subString: "Win",
				identity: "Windows"
			},
			{
				string: navigator.platform,
				subString: "Mac",
				identity: "Mac"
			},
			{
				string: navigator.userAgent,
				subString: "iPhone",
				identity: "iPhone/iPod"
			},
			{
				string: navigator.platform,
				subString: "Linux",
				identity: "Linux"
			}
		]
	}
};


var HTMLDomElement = null;

function addElement(tag, cfg, html) {
	cfg = cfg || {};
	var el = document.createElement(tag);
	for (var k in cfg) {
		if (k == 'cls' || k == 'class')
			el.className = cfg[k];
		else
			el.setAttribute(k == 'cls' ? 'class' : k, cfg[k]);
	}

	if (html)
		el.innerHTML = html;

	return el;
}

/**
 * Returns the specified computed style on an object.
 * @param {HTMLObject} el HTML Object
 * @param {String} p Property name.
 * @return {Mixed} Computed style on object.
 */
function getStyle(el, p) {
	if (el.currentStyle)
		return el.currentStyle[p];
	else if (window.getComputedStyle)
		return document.defaultView.getComputedStyle(el, null).getPropertyValue(p);

	return '';
}

function addUnit(s, u) {
	u = u || 'px';
	s = ('' + s).replace(new RegExp(u + '$'), '');
	return s + (parseInt(s) ? u : '');
}

function httpRequest(u, cb) {
	new MicroAjax(u, {
		cb:cb
	});
}

function put(s, t) {
	if (window.console) {
		s = s || '';

		if (t)
			s += ' (@' + new Date().getTime() + ')';

		window.console.log(s);
	}
}

/**
 * Throughout, whitespace is defined as one of the characters
 *  "\t" TAB \u0009
 *  "\n" LF  \u000A
 *  "\r" CR  \u000D
 *  " "  SPC \u0020
 *
 * This does not use Javascript's "\s" because that includes non-breaking
 * spaces (and also some other characters).
 */

/**
 * Determine whether a node's text content is entirely whitespace.
 *
 * @param nod  A node implementing the |CharacterData| interface (i.e.,
 *			 a |Text|, |Comment|, or |CDATASection| node
 * @return	 True if all of the text content of |nod| is whitespace,
 *			 otherwise false.
 */
function isAllWS(nod) {
	// Use ECMA-262 Edition 3 String and RegExp features
	return !(/[^\t\n\r ]/.test(nod.data));
}

/**
 * Determine if a node should be ignored by the iterator functions.
 *
 * @param nod  An object implementing the DOM1 |Node| interface.
 * @return	 true if the node is:
 *				1) A |Text| node that is all whitespace
 *				2) A |Comment| node
 *			 and otherwise false.
 */

function isIgnorable(nod) {
	return (nod.nodeType == 8) || // A comment node
			((nod.nodeType == 3) && isAllWS(nod)); // a text node, all ws
}

/**
 * Version of |previousSibling| that skips nodes that are entirely
 * whitespace or comments.  (Normally |previousSibling| is a property
 * of all DOM nodes that gives the sibling node, the node that is
 * a child of the same parent, that occurs immediately before the
 * reference node.)
 *
 * @param sib  The reference node.
 * @return	 Either:
 *			   1) The closest previous sibling to |sib| that is not
 *				  ignorable according to |isIgnorable|, or
 *			   2) null if no such node exists.
 */
function previousSibling(sib) {
	while ((sib = sib.previousSibling)) {
		if (!isIgnorable(sib)) return sib;
	}
	return null;
}

/**
 * Version of |nextSibling| that skips nodes that are entirely
 * whitespace or comments.
 *
 * @param sib  The reference node.
 * @return	 Either:
 *			   1) The closest next sibling to |sib| that is not
 *				  ignorable according to |isIgnorable|, or
 *			   2) null if no such node exists.
 */
function nextSibling(sib) {
	while ((sib = sib.nextSibling)) {
		if (!isIgnorable(sib)) return sib;
	}
	return null;
}

/**
 * Version of |lastChild| that skips nodes that are entirely
 * whitespace or comments.  (Normally |lastChild| is a property
 * of all DOM nodes that gives the last of the nodes contained
 * directly in the reference node.)
 *
 * @param sib  The reference node.
 * @return	 Either:
 *			   1) The last child of |sib| that is not
 *				  ignorable according to |isIgnorable|, or
 *			   2) null if no such node exists.
 */
function lastChild(par) {
	var res = par.lastChild;
	while (res) {
		if (!isIgnorable(res)) return res;
		res = res.previousSibling;
	}
	return null;
}

/**
 * Version of |firstChild| that skips nodes that are entirely
 * whitespace and comments.
 *
 * @param sib  The reference node.
 * @return	 Either:
 *			   1) The first child of |sib| that is not
 *				  ignorable according to |isIgnorable|, or
 *			   2) null if no such node exists.
 */
function firstChild(el) {
	var c = el.firstChild;
	while (c) {
		if (!isIgnorable(c))
			return c;
		c = c.nextSibling;
	}
	return null;
}


/**
 * Version of |data| that doesn't include whitespace at the beginning
 * and end and normalizes all whitespace to a single space.  (Normally
 * |data| is a property of text nodes that gives the text of the node.)
 *
 * @param txt  The text node whose data should be returned
 * @return	 A string giving the contents of the text node with
 *			 whitespace collapsed.
 */
function dataOf(txt) {
	var data = txt.data;
	// Use ECMA-262 Edition 3 String and RegExp features
	data = data.replace(/[\t\n\r ]+/g, " ");
	if (data.charAt(0) == " ")
		data = data.substring(1, data.length);
	if (data.charAt(data.length - 1) == " ")
		data = data.substring(0, data.length - 1);
	return data;
}

function MicroAjax(A, B) {
	B = B || {};

	this.bindFunction = function(E, F) {
		return function() {
			return E.apply(F, [F])
		}
	};
	this.stateChange = function(E) {
		if (this.request.readyState == 4 && this.callbackFunction) {
			var response;
			var responseText = this.request.responseText;

			switch (B.responseType) {
				case 'xml':
				case 'html':
					response = this.request.responseXML;
					break;
				case 'raw':
					response = this.request;
					break;
				default:
					response = responseText;
					responseText = null;
			}
			this.callbackFunction(response, responseText);
		}
	};
	this.getRequest = function() {
		if (window.ActiveXObject) {
			return new ActiveXObject("Microsoft.XMLHTTP")
		} else {
			if (window.XMLHttpRequest) {
				return new XMLHttpRequest()
			}
		}
		return false
	};
	this.postBody = (B.postBody || "");
	this.callbackFunction = B.cb;
	this.url = A;
	this.request = this.getRequest();
	if (this.request) {
		var R = this.request;
		R.onreadystatechange = this.bindFunction(this.stateChange, this);
		if (this.postBody !== "") {
			R.open("POST", A, true);
			R.setRequestHeader("X-Requested-With", "XMLHttpRequest");
			R.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			//R.setRequestHeader("Connection", "close")
		} else {
			R.open("GET", A, true)
		}

		//put(this.postBody);

		R.send(this.postBody)
	}
}

/**
 * Implements JSON stringify and parse functions
 * v1.0
 *
 * By Craig Buckler, Optimalworks.net
 *
 * As featured on SitePoint.com
 * Please use as you wish at your own risk.
 *
 * Usage:
 *
 * // serialize a JavaScript object to a JSON string
 * var str = JSON.stringify(object);
 *
 * // de-serialize a JSON string to a JavaScript object
 * var obj = JSON.parse(str);
 */

var JSON = JSON || {};

// implement JSON.stringify serialization
JSON.stringify = JSON.stringify || function (obj) {

	var t = typeof (obj);
	if (t != "object" || obj === null) {

		// simple data type
		if (t == "string") obj = '"' + obj + '"';
		return String(obj);

	}
	else {

		// recurse array or object
		var n, v, json = [], arr = (obj && obj.constructor == Array);

		for (n in obj) {
			v = obj[n];
			t = typeof(v);

			if (t == "string") v = '"' + v + '"';
			else if (t == "object" && v !== null) v = JSON.stringify(v);

			json.push((arr ? "" : '"' + n + '":') + String(v));
		}

		return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
	}
};

// implement JSON.parse de-serialization
JSON.parse = JSON.parse || function (str) {
	if (str === "") str = '""';
	eval("var p=" + str + ";");
	return p;
};

var SparkViewport = function() {
	var w;
	var h; // the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
	if (typeof window.innerWidth != 'undefined') {
		w = window.innerWidth;
		h = window.innerHeight;
	} // IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
	else if (typeof document.documentElement != 'undefined' && typeof document.documentElement.clientWidth != 'undefined' && document.documentElement.clientWidth != 0) {
		w = document.documentElement.clientWidth;
		h = document.documentElement.clientHeight;
	} // older versions of IE
	else {
		w = document.getElementsByTagName('body')[0].clientWidth;
		h = document.getElementsByTagName('body')[0].clientHeight;
	}

	this.width = w;
	this.height = h;
};

SparkViewport.prototype = {
	getWidth:function() {
		return this.width;
	}
	,getHeight:function() {
		return this.height;
	}
};

function actionScriptToJavaScript(s) {
	put(s);
}

var SparkHistoryManager = function(onChangeHandler) {
	this.listener = onChangeHandler || this.listener;
	unFocus.History.addEventListener('historyChange', this.listener);
	this.initHash = this.listener(unFocus.History.getCurrent());
};

SparkHistoryManager.prototype = {
	initHash:null

	,add:function(v) {
		if (v)
			unFocus.History.addHistory(v);
	}

	,listener:function(hash) {
		return hash;
	}
};

function sortObject(o) {
	var k, s = {}, a = [];
	for (k in o) {
		if (o[k])
			a.push(k);
	}
	a.sort();
	for (k = 0; k < a.length; k++) {
		s[a[k]] = o[a[k]];
	}
	return s;
}

var GoogleMap = function(el, latitude, longitude, options) {
	var t = this;
	if (typeof el == 'string')
		el = document.getElementById(el);

	t.element = el;

	options = options || {};
	options.zoom = options.zoom || 15;
	options.mapTypeId = options.mapTypeId || google.maps.MapTypeId.ROADMAP;

	t.options = options;

	if (t.options.directionsPanel)
		t.directionsPanel = document.getElementById(t.options.directionsPanel);

	t.setBox();

	t.options.center = new google.maps.LatLng(latitude, longitude);
};

GoogleMap.prototype = {
	showMap: function() {
		var t = this;
		return new google.maps.Map(t.element, t.options);
	}
	,showDirections: function(destination, origin) {
		var t = this;

		if (typeof origin == 'undefined') {
			t.getCurrentLocation(destination);
			return;
		}

		destination = destination || t.options.center;

		var map = t.showMap();

		t.directionsRenderer = new google.maps.DirectionsRenderer();
		t.directionsRenderer.setMap(map);

		if (!origin) {
			if (t.directionsPanel)
				t.directionsPanel.remove();

			var marker = new google.maps.Marker({
				position: destination,
				map: map
			});
			return;
		}

		if (t.directionsPanel)
			t.directionsRenderer.setPanel(t.directionsPanel);

		var request = {
			origin:origin,
			destination:destination,
			travelMode: google.maps.DirectionsTravelMode.DRIVING
		};

		t.directionsService = new google.maps.DirectionsService();
		t.directionsService.route(request, function(response, status) {
			if (status == google.maps.DirectionsStatus.OK) {
				t.directionsRenderer.setDirections(response);
			}
		});
	}
	,setBox:function() {
		var el = this.element;

		var hLock = false, vLock = false;

		while (el.parentNode) {
			el = el.parentNode;

			if (el.style) {

				if (el.hasClass('vLock'))
					vLock = true;

				el.style.width = '100%';

				if (!vLock)
					el.style.height = '100%';
			}
		}
	}
	,getCurrentLocation:function(destination) {
		var t = this;
		var geoLocation;
		try {
			geoLocation = JSON.parse(Spark.cookieManager.get('geocode'));
		} catch(e) {
			put(e);
		}

		if (!geoLocation || geoLocation.disabled) {
			if (t.directionsPanel)
				t.directionsPanel.remove();

			t.showDirections(destination, '');
			return;
		}

		var defaultPosition = new google.maps.LatLng(geoLocation.latitude, geoLocation.longitude);

		if (geoLocation.postcode && geoLocation.manual) {
			var geocoder = new google.maps.Geocoder();
			geocoder.geocode({ 'address': geoLocation.postcode}, function(results, status) {
				var postition;
				if (status == google.maps.GeocoderStatus.OK) {
					position = results[0].geometry.location;
				} else {
					position = defaultPosition;
				}

				t.showDirections(destination, position);
			});
		} else
			t.showDirections(destination, defaultPosition);
	}
};


//function initialize() {
//  localSearch = new GlocalSearch();
//  icon = new GIcon(G_DEFAULT_ICON);
//  addressMarkerOptions = { icon:icon , draggable: false};
//  map = new GMap2(document.getElementById("map"));
//  map.addControl(new GLargeMapControl());
//  map.addControl(new GMapTypeControl());
//  plotAddress("OX4 1FJ");
//}

/**
 * This takes either a postcode or an address string
 *
 */

/*
 var geocoder;
 var map;
 function initialize() {
 geocoder = new google.maps.Geocoder();
 var latlng = new google.maps.LatLng(-34.397, 150.644);
 var myOptions = {
 zoom: 8,
 center: latlng,
 mapTypeId: google.maps.MapTypeId.ROADMAP
 };

 //map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
 }
 */

function getRequest() {
	var request = {};
	var search = document.location.search;
	if (search) {
		search = search.replace(/^\?/, '').split('&');
		for (var i = 0; i < search.length; i++) {
			var tmp = search[i].split('=');
			request[tmp[0]] = tmp[1];
		}
	}
	return request;
}

var sparkIdIndex = 0;
function getSparkId(el) {

	var s = 'sparkAutoId';

	if (!el.id) {
		sparkIdIndex++;

		while (document.getElementById(s + sparkIdIndex))
			sparkIdIndex++;

		el.id = s + sparkIdIndex;
	}

	return el.id;
}

if (document.querySelectorAll) {
	window._$ = function(selector, context) {

		if (typeof selector == 'object')
			return [selector];

		context = (!!context) ? context : document;
		return context.querySelectorAll(selector);
	};
} else {
	var el = addElement('script', {type:'text/javascript',src:'/spark-core/frameworks/sizzle/sizzle.js'});
	var sizzleListener = 0;
	var sizzleLoader = setInterval(function() {
		if (window.Sizzle || sizzleListener > 50) {
			clearInterval(sizzleLoader);
			window._$ = window.Sizzle;
		}
		sizzleListener++;
	}, 50);
	document.getElementsByTagName('head')[0].appendChild(el);

}

(function() {
	Spark.init();
	if (Spark.browser.msie) {
		document.write('<script type="text/javascript" src="/spark-core/frameworks/spark/spark.ie.js"></script>');
	} else {
		Spark.load();
	}
})();

function zeroFill(i) {
	return i > 9 ? i : '0' + i;
}
