

/*jslint evil: true, forin: true,*/
/*global window*/
if(!window.console){
	window.console = {log: function(){}};
}
(function(){
	var S = {};
	
	(function(){
		// from dojo
		var n = navigator;
		var ua = n.userAgent;
		var av = n.appVersion;
		var tv = parseFloat(av);
		console.log(ua, av);

		S.isOpera = (ua.indexOf("Opera") >= 0) ? tv : 0;
		S.isSafari = av.indexOf("Safari") >= 0;
		S.isKhtml = (av.indexOf("Konqueror") >= 0) || (av.indexOf("Safari") >= 0) ? tv : 0;
		if(S.isSafari){
			S.isSafari = parseFloat(av.split("Version/")[1]) || 2;
		}
		var geckoPos = ua.indexOf("Gecko");
		S.isMozilla = S.isMoz = ((geckoPos >= 0)&&(!S.isKhtml)) ? tv : 0;
		S.isFF = 0;
		S.isIE = 0;
		try{
			if(S.isMoz){
				S.isFF = parseFloat(ua.split("Firefox/")[1].split(" ")[0]);
			}
			if((document.all) && (!S.isOpera)){
				S.isIE = parseFloat(av.split("MSIE ")[1].split(";")[0]);
			}
		}catch(e){}

		var cm = document.compatMode;
		S.isQuirks = (cm == "BackCompat") || (cm == "QuirksMode") || (S.isIE < 6);
	})();	
	
	S.lambda = {
		empty: function(){},
		identity: function(x){ return x;},
		not: function(x){ return !x;},
		constant: function(c){ return function(){ return c;};}
	};
	
	S.overwrite = function(dest, o){
		if(!dest){
			dest = {};
		}
		for(var k in o){
			dest[k] = o[k];
		}
		return dest;
	};
	S.mixin = function(dest, o){
		if(!dest){
			dest = {};
		}
		if(!o){
			return dest;
		}
		for(var k in o){
			if(!(k in dest)){
				dest[k] = o[k];
			}
		}
		return dest;
	};
	
	function typeofPredicate(type){
		return function(x){
			return typeof(x) === type;
		};
	}
	
	S.isFunction = typeofPredicate('function');	
	S.isString = typeofPredicate('string');	
	S.isUndefined = typeofPredicate('undefined');	
	S.isNumber = typeofPredicate('number');	
	S.isBoolean = typeofPredicate('boolean');
	
	S.isObject = function(x){
		return x && typeof(x) == 'object';
	};
	
	S.isDefined = function(x){
		return typeof(x) != 'undefined';
	};
	
	S.isArray = function(x){
		return x && x instanceof Array || typeof x == "array";
	};	
	
	S.isElement = function(x){
		return x && x.nodeType == 1;
	};
	
	S.isArrayLike = function(x){
		return S.isObject(x) && S.isNumber(x.length);
	};
	
	S.isPlainObject = function(x){
		return x && x.constructor === Object;
	};
	
	S.isInteger = function(x){
		return parseInt(x) === x;
	};
	
	S.isFloat = function(x){
		return parseFloat(x) === x;
	};
			
	var boundMethodRepr = function(){
		return '<bound method '+S.repr(this.im_func)+' self='+this.im_self+'>';
	};
	var boundFunctionRepr = function(){
		return '<bound function' + S.repr(this.im_func) + ' self='+this.im_self+'>';
	};
	
	S.bind = function(self, f){
		if(S.isString(f)){
			f = self[f];
		}
		var bound = function(){
			return f.apply(self, arguments);
		};
		bound.im_self = self;
		bound.im_func = f;		
		bound.__repr__ = f.im_class ? boundMethodRepr : boundFunctionRepr;
		return bound;
	};
	
	S.defer = function(f, msec){
		setTimeout(f, msec || 1);
	};

	S.deferred = function(f, msec){
		return function(){
			var args = arguments;
			setTimeout(function(){
				f.apply(null, args);
			}, msec || 1);			
		};
	};	

	S.args = function(args, offset, defaults){
		if(!defaults){
			return Array.prototype.slice.call(args, offset);
		}
		var len = Math.max(defaults.length, args.length);
		var a = [];
		for(var i=offset;i<len;i++){
			a[i] = S.isDefined(args[i]) ? args[i] : defaults[i];
		}
		return a;
	};
	
	S.getCaller = function(func){
		return func.arguments.callee.caller;
	};
	
	S.getTraceback = function(offset){
		var func = arguments.callee.caller;
		var stack = [];
		var nativeStack = null;
		offset = offset || 0;
		try{
			throw new Error();
		}
		catch(e){
			nativeStack = e.stack.split("\n").slice(2);
		}
		for(var k=0;k<offset;k++){
			func = S.getCaller(func);
		}
		for(var i=offset;i<nativeStack.length;i++){
			if(!func){
				break;
			}
			var fileAndLine = ['', 'unknown', 0];
			try{
				fileAndLine = nativeStack[i].split('@')[1].match(/^(.*):(\d+)$/);
			}
			catch(ignore){
			}
			stack.push({
				'function': func,
				'arguments': func.arguments,
				'file': fileAndLine[1],
				'line': fileAndLine[2]
			});
			func = S.getCaller(func);			
		}
		return stack;
	};
	
	S.logTraceback = function(traceback){
		if(!traceback || S.isInteger(traceback)){
			traceback = S.getTraceback(traceback ? traceback + 1 : 1);
		}
		var args = [];
		var format = [];
		var skipNext = 
		traceback.reverse().forEach(function(frame){
			if(skipNext){
				skipNext = false;
				return;
			}
			var func = frame['function'];
			var funcName = S.repr(func);
			var info = '';
			args.push(frame.arguments);
			if(func.im_self){
				skipNext = true;				
				funcName =  S.repr(func.im_func);
				info = ' bound to %o';
				args.push(func.im_self);
			}			
			format.push('\t' + funcName + '(%o)' + info + '\n\t   in ' + frame.file + ' at line ' + frame.line);
		});		
		console.log.apply(console, [format.reverse().join('\n')].concat(args.reverse()));
	};
	
	S.trace = function(){
		S.logTraceback(1);
	};
	
	S.partial = function(f){
		var args = S.args(arguments, 1);
		return function(){
			return f(args.concat(arguments));
		};
	};	
		
	S.each = function(a, f, self){
		if(!a || !a.length){
			return;
		}
		for(var i=0;i<a.length;i++){
			f.call(self, a[i], i, a);
		}
	};	
	
	S.find = function(a, p, d){
		for(var i=0;i<a.length;i++){
			if(p(a[i])){
				return a[i];
			}
		}
		return d;
	};		
		
	S.remove = function(a, o){
		var i = a.indexOf(o);
		if(i >= 0){
			a.splice(i, 1);
		}
		return i >= 0;
	};
	
	S.removeAll = function(a, p, callback){
		var modified = false;
		for(var i=a.length-1;i>=0;i--){
			if((S.isFunction(p) && p(a[i])) || a[i] in p){
				var ai = a[i];
				a.splice(i, 1);
				if(callback){
					callback(ai, i);
				}
				modified = true;				
			}			
		}
		return modified;
	};
	
	S.first = function(a){
		return a[0];
	};
	
	S.last = function(a){
		return a[a.length - 1];
	};
	
	S.min = function(a, f){
		f = f || S.lambda.identity;
		var m, mv = Infinity;
		for(var i=0;i<a.length;i++){			
			var v = f(a[i], i);
			if(v < mv){
				mv = v;
				m = a[i];
			}
		}
		return m;
	};
	S.max = function(a, f){
		f = f || S.lambda.identity;
		var m, mv = -Infinity;
		for(var i=0;i<a.length;i++){
			var v = f(a[i], i);
			if(v > mv){
				mv = v;
				m = a[i];
			}
		}
		return m;
	};
	
	S.zip = function(){
		var args = arguments;
		var minLength = S.min(arguments, function(arg){ return arg.length;}).length;
		var result = [];
		for(var i=0;i<minLength;i++){
			var item = [];
			for(var k=0;k<args.length;k++){
				item[k] = args[k][i];
			}
			result.push(item);
		}
		return result;
	};
	
	S.any = function(a, f){
		for(var i=0;i<a.length;i++){
			if(f(a[i])){
				return true;
			}
		}
		return false;
	};	
	
	S.all = function(a, f){
		return !S.any(a, function(x){ return !f(x);});
	};
	
	S.contains = function(a, o){
		return a.indexOf(o) != -1;
	};
	
	S.equal = function(a, b){
		if(a === b){
			return true;
		}
		var eq = '__equals__';
		if(S.isObject(a) && S.isObject(b) && (a[eq] || b[eq])){
			return a[eq] && a[eq](b) || b[eq] && b[eq](a);
		}
		if(S.isArray(a) && S.isArray(b)){
			if(a.length != b.length){
				return false;
			}
			for(var i=0;i<a.length;i++){
				if(!S.equal(a[i], b[i])){
					return false;
				}
			}
			return true;
		}
		return a == b;
	};
	
	S.string = {
		startsWith: function(str, prefix){
			return str.indexOf(prefix) === 0;
		},
		
		empty: S.lambda.not,
		
		blank: function(str){
			return /^\s*$/.test(str); 
		},
		strip: function(str){
			return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
		},
		stripTags: function(str){
			return str.replace(/<.*?>/g, '');
		},
		trim: function(str){
			return S.string.strip(str);
		},
		reEscape: function(str){
			return str.replace('(','\\(').replace(')','\\)');
		},
		contains: function(str, substr){
			return str.indexOf(substr) >= 0;
		},
		capfirst: function(str){
			return str.charAt(0).toUpperCase() + str.substring(1);
		},
		times: function(str, n){
			var r = '';
			for(var i=0;i<n;i++){
				r += str;
			}
			return r;
		},
		pad: function(str, len, pad){
			pad = pad || '0';
			str = String(str);
			while(str.length < len){
				str = pad + str;
			}
			return str;
		},
		
		smartSplit: function(str){
			var result = [];
			console.log(str);
			var rest = str.replace(/"(([^"\\]|\\\\|\\")*)"|'(([^'\\]|\\\\|\\')*)'|([^\s]+)/g, function(match, dq, _dq, sq, _sq, uq){ //"
				var part = dq || sq || uq;				
				result.push(part.replace('\\"', '"').replace("\\'", "'").replace('\\\\', '\\'));
				return '';
			});
			console.log(rest);
			return result;
		},
		
		format: function(str, args){
			var result = str;
			var getter = args;
			if(S.isObject(args)){
				getter = function(name){
					return args[name];
				};
			}
			if(S.isArray(args)){
				var index = 0;
				result = str.replace(/(%+)s/g, function(match, p){
					if(!(p.length % 2)){
						return p + 's';
					}
					return p.substring(1) + args[index++];
				});
			}
			else if(S.isFunction(getter)){
				result = str.replace(/(%+)\(([^)]+)\)s/g, function(match, p, name){
					if(!(p.length % 2)){
						return p + '(' + name + ')s';
					}
					var parts = name.split('?');
					if(parts.length == 3){
						var val = getter(parts[1]);
						if(val){
							return p.substring(1) + parts[0] + val + parts[2];
						}
						return '';
					}
					return p.substring(1) + getter(name);
				});
			}
			return result.replace(/%%/g,'%');
		},
		
		firstDiffIndex: function(a, b){
			var len = Math.min(a.length, b.length);
			for(var i=0;i<len;i++){
				if(a.charAt(i) != b.charAt(i)){
					return i;
				}
			}
			return -1;
		}
	};	
		
	S.repr = function(x, depth){
		depth = S.isDefined(depth) ? depth : 3;
		if(S.isUndefined(x)){
			return "undefined";
		}
		if(x === null){
			return 'null';
		}
		if(S.isFunction(x.__repr__)){
			return x.__repr__();
		}
		if(S.isArray(x) || S.isArrayLike(x)){
			if(!depth){
				return '[...]';
			}
			return '[' + S.copy(x).map(function(x){ S.repr(x, depth - 1);}).join(', ') + ']';
		}		
		if(S.isObject(x)){
			if(S.isPlainObject(x)){
				if(!depth){
					return '{...}';
				}
				var props = [];
				for(var p in x){
					props.push(p + ': ' + S.repr(x[p], depth - 1));
				}
				return '{' + props.join(', ') + '}';
			} 
			return x.toString();
		}
		if(S.isString(x)){
			return '"' + x.replace('"', '\\"') + '"';
		}
		if(S.isFunction(x)){
			return "<anonymous function>";
		}
		return String(x);
	};
	
	S.keys = function(o){
		var keys = [];
		for(var k in o){
			keys.push(k);
		}
		return keys;
	};	
		
	S.toQueryString = function(o){
		var p = [];
		for(var k in o){
			p.push(k + '=' + encodeURIComponent(o[k]));
		}
		return p.join('&');
	};
	
	function reprDefinedObject(){
		var module = this.__module__;
		return module ? module + '.' + this.__name__ : this.__name__;
	}

	S.def = function(path, obj){
		var scope = window;
		path = path.split('.');
		for(var i=0;i<path.length-1;i++){
			var name = path[i];
			if(!S.isDefined(scope[name])){
				scope[name] = {};				
			}
			scope[name].__name__ = name;
			scope[name].__module__ = path.slice(0, i).join('.');
			if(!scope[name].__repr__){
				scope[name].__repr__ = reprDefinedObject;
			}
			scope = scope[name];
		}
		var objName = S.last(path);
		scope[objName] = obj;
		obj.__name__ = objName;
		obj.__module__ = path.slice(0, -1).join('.');
		if(!obj.__repr__){
			obj.__repr__ = reprDefinedObject;
		}
		return obj;
	};
			
	S.Class = {
		objectPrototype: {
			call: function(klass, method, args){
				args = S.isDefined(args) ? args : arguments.callee.caller.arguments;
				return klass.prototype[method].apply(this, args || []);
			},
			init: function(klass, args){
				args = S.isDefined(args) ? args : arguments.callee.caller.arguments;
				klass.prototype.__init__.apply(this, args || []);
			},
			__getattr__: function(name, defaultValue){
				return S.getattr(this, name, defaultValue);
			},
			instanceOf: function(klass){
				return this.prototype.constructor.isSubclassOf(klass);
			},
			__repr__: function(){
				return '<' + this.constructor.getName() + ' instance>';
			}
		},
		methods: {
			isSubclassOf: function(c){
				if(c === this){
					return true;
				}
				return this.__parents__.some(function(p){
					return p.isSubclassOf(c);
				});
			},
			isSuperclassOf: function(c){
				return c.isSubclassOf(this);
			},
			instance: function(a, b, c, d, e, f){
				return new this(a, b, c, d, e, f);
			},
			getName: function(){
				var module = this.__module__;
				var name = this.__name__ || 'anonymous';
				return module ? module + '.' + name : name;
			},
			__repr__: function(){
				var name = this.__name__;
				if(!name){
					return '<anonymous class>';
				}
				var module = this.__module__;
				return "<class '" + this.getName() + "'>";
			}
		},
		methodMethods: {
			__repr__: function(){
				return S.string.format('<method %s.%s>', [this.im_class.getName(), this.__name__]);
			}
		},
		define: function(path){			
			var args = S.args(S.copy(arguments), 1);
			var c = this.create.apply(this, args);
			S.def(path, c);
		},
		create: function(){
			var parents = [];
			var a0 = arguments[0];
			var a1 = arguments[1];
			if(S.isFunction(a0)){
				parents = [a0];
			}
			else if(S.isArray(a0)){
				parents = a0;
			}
			var properties = (parents.length > 0) ? a1 : a0;
			var staticProperties = (parents.length > 0) ? arguments[2] : a1;
						
			var c = function(){
				this.__init__.apply(this, arguments);
			};			

			if(!parents.length){
				S.overwrite(c, S.Class.methods);
				S.overwrite(c.prototype, S.Class.objectPrototype);
			}

			for(var i=0;i<parents.length;i++){
				var p = parents[i];
				S.overwrite(c.prototype, p.prototype);
				for(var staticProperty in p){
					if(staticProperty != 'prototype'){
						var val = p[staticProperty];
						c[staticProperty] = S.isFunction(val) ? val : S.copy(val);
					}
				}				
			}
			
			for(var name in properties){
				var value = properties[name];
				if(S.isFunction(value)){
					value.__name__ = name;
					value.im_class = c;
					S.overwrite(value, S.Class.methodMethods);
				}
				c.prototype[name] = value;
			}
			if(S.isIE && properties.toString !== Object.prototype.toString){
				c.prototype.toString = properties.toString;
			}
			
			if(staticProperties){
				S.overwrite(c, staticProperties);
			}
			
			if(!c.prototype.__init__){
				c.prototype.__init__ = S.lambda.empty;
			}
			c.prototype.constructor = c;
			c.__parents__ = parents;
			if(c.prototype.__meta__){
				c.prototype.__meta__(c);
			}
			return c;
		}
	};
	
	S.Module = {
		define: function(path, members){
			members = members || {};
			for(var name in members){
				S.def(path + '.' + name, members[name]);
			}
			return S.lookup(path);
		}
	};
	
	S.getattr = function(o, p, d){
		if(p == 'this'){
			return o;
		}
		var getter = 'get' + S.string.capfirst(p);
		if(S.isFunction(o[getter])){
			return o[getter].apply(o);
		}
		if(S.isDefined(o[p])){
			return o[p];
		}
		return d;
	};
	
	S.setattr = function(o, p, v){
		var setter = 'set' + S.string.capfirst(p);
		if(S.isFunction(o[setter])){
			o[setter].apply(o, [v]);
		}
		else{			
			o[p] = v;
		}
	};
	
	S.hasattr = function(o, p){
		return S.isDefined(o[p]);
	};
	
	S.getter = function(p){
		return function(){
			return this[p];
		};
	};
	S.setter = function(p){
		return function(x){
			this[p] = x;
		};
	};
	S.alias = function(name){
		return function(){
			this[name].apply(this, arguments);
		};
	};
	
	S.dict = function(){
		var d = {};
		var pairs = arguments;
		for(var i=0;i<pairs.length;i++){
			d[pairs[i][0]] = pairs[i][1];
		}
		return d;
	};
	
	S.lookup = function(path, object, separator){
		object = object || window;
		path = path.split(separator || '.');
		for(var i=0;i<path.length;i++){
			object = object[path[i]];
			if(!S.isDefined(object)){
				return /* undefined */;
			}
		}
		return object;
	};

	S.define = function(path, value, object, separator){
		object = object || window;
		path = path.split(separator || '.');
		for(var i=0;i<path.length-1;i++){
			var p = path[i];
			if(!S.isDefined(object[p])){
				object[p] = {};
			}
			object = object[p];
		}
		object[S.last(path)] = value;
	};		
	
	S.copy = function(x){
		var c;
		if(S.isArrayLike(x)){
			c = [];
			for(var i=0;i<x.length;i++){
				c.push(x[i]);
			}
			return c;
		}
		if(S.isObject(x)){
			if(S.isFunction(x.__copy__)){
				return x.__copy__();
			}
			c = {};
			for(var p in x){
				c[p] = x[p];
			}
			return c;
		}
		// should be immutable (function, number, string, boolean, null)
		return x;
	};
	
	// experimental
	S.deepcopy = function(x, memo){
		if(x instanceof Array){
			return x.map(S.deepcopy);
		}
		if(S.isObject(x)){			
			// don't copy elements
			if(S.isElement(x)){
				return x;
			}
			memo = memo || {};
			var hash = S.hash(x);
			if(S.contains(memo, hash)){
				return memo[hash];
			}
			// custom copy() method
			if(S.isFunction(x.__deepcopy__)){
				return x.__deepcopy__(memo);
			}
			var c = {};
			for(var k in x){
				c[k] = S.deepcopy(x[k], memo);
			}
			return c;
		}
		return x;
	};
	
	S.round = function(x, precision){
		precision = precision || 1;
		return precision * Math.round(x / precision);
	};
	
	S.log = function(msg, args){
		if(!S.isString(msg)){
			return console.log.apply(console, arguments.map(S.repr));
		}
		var argc = arguments.length;
		if(argc == 1){
			return console.log(msg);
		}
		if(argc > 2){
			args = Array.prototype.slice.call(arguments, 1);
		}
		return console.log(S.string.format(msg, args));
	};
		
	S.cookies = {
		parse: function(cookieString){
			cookieString = cookieString || window.document.cookie || "";
			var pairs = cookieString.split('; ');
			var cookies = {};
			pairs.forEach(function(p){
				var c = p.split('=');
				cookies[c[0]] = c[1];
			});		
			return cookies;
		}
	};
	
	S.Module.define('shrubbery', S);
		
})();


// from http://erik.eae.net/playground/arrayextras/arrayextras.js

// Mozilla 1.8 has support for indexOf, lastIndexOf, forEach, filter, map, some, every
// http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array
(function(){
var S = shrubbery;
S.mixin(Array.prototype, {

	indexOf: function(obj, fromIndex){
		if(!fromIndex){
			fromIndex = 0;
		}
		else if(fromIndex < 0){
			fromIndex = Math.max(0, this.length + fromIndex);
		}
		for(var i = fromIndex; i < this.length; i++){
			if(this[i] === obj){
				return i;
			}
		}
		return -1;
	},
	
	lastIndexOf: function(obj, fromIndex){
		if(fromIndex == null){
			fromIndex = this.length - 1;
		}
		else if(fromIndex < 0){
			fromIndex = Math.max(0, this.length + fromIndex);
		}
		for(var i = fromIndex; i >= 0; i--){
			if(this[i] === obj){
				return i;
			}
		}
		return -1;
	},	

	forEach: function(f, obj){
		var l = this.length;
		for(var i = 0; i < l; i++){
			f.call(obj, this[i], i, this);
		}
	},
	
	filter: function(f, obj){
		var l = this.length;
		var res = [];
		for(var i = 0; i < l; i++){
			if(f.call(obj, this[i], i, this)){
				res.push(this[i]);
			}
		}
		return res;
	},

	map: function(f, obj){
		var l = this.length;
		var res = [];
		for(var i = 0; i < l; i++){
			res.push(f.call(obj, this[i], i, this));
		}
		return res;
	},
	
	some: function(f, obj){
		var l = this.length;
		for(var i = 0; i < l; i++){
			if(f.call(obj, this[i], i, this)){
				return true;
			}
		}
		return false;
	},

	every: function(f, obj){
		var l = this.length;
		for(var i = 0; i < l; i++){
			if(!f.call(obj, this[i], i, this)){
				return false;
			}
		}
		return true;
	},
	
	reduce: function(f, n){
		var offset = 0;
		if(!S.isDefined(n)){
			if(!this.length){
				return;
			}
			n = this[0];
			offset++;
		}
		for(var i=offset;i<this.length;i++){
			n = f(n, this[i], i, this);
		}
		return n;
	},
	
	reduceRight: function(f, n){
		var offset = this.length - 1;
		if(!S.isDefined(n)){
			if(!this.length){
				return;
			}
			n = this[offset];
			offset--;
		}
		for(var i=offset;i>=0;i--){
			n = f(n, this[i], i, this);
		}
		return n;	
	}
});

S.mixin(String.prototype, {
	trim: function(){
		return S.string.strip(this);
	}
});

})();

(function(){

var S = shrubbery;

S.Module.define('shrubbery.error');

S.Class.define('shrubbery.error.Interceptor', S.Observable, {
	__init__: function(name){
		this.init(S.Observable);
		this.name = name;
		this.addEvents('Exception');
		var globalInterceptor = S.error.globalInterceptor;
		
		if(globalInterceptor){
			this.addListener('Exception', globalInterceptor);
		}
	},
	
	intercept: function(label, f, scope, args, fin){
		try{
			return f.apply(scope, args);
		}
		catch(e){
			this.fireEvent('Exception', label, e, f);
		}
		finally{
			if(fin){
				fin();
			}
		}
	},
	
	intercepted: function(label, f, scope, fin){
		return S.bind(this, function(){
			return this.intercept(label, f, scope, arguments, fin);
		});
	},
	
	onException: function(source, label, e, f){
		this.fireEvent('Exception', label, e, f);
	},
	
	toString: function(){
		return 'Interceptor ' + this.name;
	}
});
	
S.Class.define('shrubbery.error.Exception', {
	__init__: function(msg){
		var args = null;
		if(arguments.length > 1){
			args = S.args(arguments, 1);
			if(args.length == 1 && S.isObject(args[0])){
				args = args[0];
			}
			msg = S.string.format(msg, args);
		}
		this.msg = msg;
		this.args = args;
	},
	toString: function(){
		return 'shrubbery.Exception: ' + this.msg;
	}
});

var Se = S.error;
Se.globalInterceptor = new Se.Interceptor('shrubbery.error');

})();
(function(){
	var S = shrubbery;
	
    var safariKeys = {
        63234 : 37, // left
        63235 : 39, // right
        63232 : 38, // up
        63233 : 40, // down
        63276 : 33, // page up
        63277 : 34, // page down
        63272 : 46, // delete
        63273 : 36, // home
        63275 : 35  // end
    };	
	
    S.event = {
    	keys: {
		  BACKSPACE: 8,
		  TAB:       9,
		  RETURN:   13,
		  ESCAPE:   27,
		  LEFT:     37,
		  UP:       38,
		  RIGHT:    39,
		  DOWN:     40,
		  DELETE:   46,
		  SPACE:    32,
		  HOME:     36,
		  END:      35,
		  PAGEUP:   33,
		  PAGEDOWN: 34,
		  INSERT:   45    	
    	},
    	
    	errors: new S.error.Interceptor('shrubbery.event'),
    	    	
    	addSaveListener: function(el, type, scope, f){
			if(scope && S.isString(f)){
				f = scope[f];
			}
			return this.addListener(el, type, null, S.event.errors.intercepted(type+'-event', f, scope));
    	},
    	
		addListener: function(el, type, scope, f){
			if(scope && S.isString(f)){
				f = scope[f];
			}			
			var callback = function(e){
				f.apply(scope, [e || window.event, el, type]);
			};
			if(el.addEventListener){
				el.addEventListener(type, callback, false);
			}
			else{
				el.attachEvent("on" + type, callback);
			}
			return {
				type: type,
				callback: callback,
				element: el
			};
		},
		
    	observe: function(el, type, f, options){
    		
    	},    			
		
		addMouseWheelListener: function(scope, f){
			
		},
		
		removeListener: function(handle){
			var el = handle.element;
			if(el.removeEventListener){
				el.removeEventListener(handle.type, handle.callback, false);
			}
			else{
				el.detachEvent("on" + handle.type, handle.callback);
			}
		},
		
		stopPropagation: function(e){
			if(S.isIE){
				e.cancelBubble = true;
			}
			else{
				e.stopPropagation();
			}
		},
						
		preventDefault: function(e){
			if(S.isIE){
				e.returnValue = false;
			}
			else{
				e.preventDefault();
			}
		},
		
		stop: function(e){
			this.preventDefault(e);
			this.stopPropagation(e);
		},
		
		getTarget: function(e){
			return S.isIE ? e.srcElement : e.target;
		},
				    
		pointer: function(e){
			return new S.Position(this.pointerX(e), this.pointerY(e));
		},	
		pointerX: function(event){
			return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));
		},
		pointerY: function(event){
			return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
		},
		
		relatedTarget: function(e){
			switch(e.type) {
				case 'mouseover': 
					return e.fromElement;
					break;
				case 'mouseout':  
					return e.toElement;
					break;
			}
			return null;
		},
		// from ext
        isNavKeyPress : function(e){
			var k = this.getKeyCode(e);
			return (k >= 33 && k <= 40) || k == this.keys.RETURN || k == this.keys.TAB || k == this.keys.ESC;
        },
        getKeyCode: function(e){
			var k = e.keyCode;
			return S.isSafari ? (safariKeys[k] || k) : k;
        },
        getCharCode: function(e){
        	return e.charCode || e.keyCode;
        },
        getWheelDelta : function(e){
            var delta = 0;
            if(e.wheelDelta){ /* IE/Opera. */
                delta = e.wheelDelta/120;
                if(S.isOpera){
                	delta = - delta;
                }
            }
            else if(e.detail){ /* Mozilla case. */
                delta = -e.detail/3;
            }
            return delta;
        }
		
    };    		

})();
(function(){
	var S = shrubbery;
	
	S.getViewportBox = function(el){
		return new S.Box(el.scrollTop, el.scrollLeft, el.offsetWidth, el.offsetHeight);
	};	
	
	// fixme	
	S.getBox = function(el){
		return S.getDimensions(el);
	};	
	// fixme	
	S.getContentWidth = function(el){
		return S.getDimensions(el).width;
	};
	// fixme	
	S.getContentHeight = function(el){
		return S.getDimensions(el).height;
	};	
	// fixme
	S.getContentDimensions = function(el){
		return S.getDimensions(el);
	};
	// fixme
	S.getContentBox = function(el){
		return S.getDimensions(el);
	};
	
	S.getHeight = function(el){
		return S.getDimensions(el).height;
	};
	
	S.getWidth = function(el){
		return S.getDimensions(el).width;
	};
		
	// from prototype
	S.getDimensions = function(element) {
		var display = S.html.getStyle(element, 'display');
		if (display != 'none' && display !== null){
			return {width: element.offsetWidth, height: element.offsetHeight};
		}
	
		// All *Width and *Height properties give 0 on elements with display none,
		// so enable the element temporarily
		var els = element.style;
		var originalVisibility = els.visibility;
		var originalPosition = els.position;
		var originalDisplay = els.display;
		els.visibility = 'hidden';
		els.position = 'absolute';
		els.display = 'block';
		var originalWidth = element.clientWidth;
		var originalHeight = element.clientHeight;
		els.display = originalDisplay;
		els.position = originalPosition;
		els.visibility = originalVisibility;
		return {width: originalWidth, height: originalHeight};
	};
	
	
	/* end fixme */
	
	S.$ = function(elOrId){
		if(S.isString(elOrId)){
			return document.getElementById(elOrId);
		}
		return elOrId;
	};						    
					
    S.getViewportDimensions = function(){
        var win = window;
        var b = document.body;
        var w,h;

        if (win.innerWidth) {
			w = win.innerWidth;
			h = win.innerHeight;
		}
        else if (b.parentElement.clientWidth) {
			w = b.parentElement.clientWidth;
			h = b.parentElement.clientHeight;
		}
        else if (b && b.clientWidth) {
			w = b.clientWidth;
			h = b.clientHeight;
        }
        return {width: w, height: h};
    };
				
	S.setPosition = function(el, x, y, unit){
		if(S.isObject(x)){
			unit = y;		
			y = x.y;
			x = x.x;
		}
		if(!S.isDefined(unit)){
			unit = 'px';
		}
		S.html.setStyle(el, 'left', x + unit);
		S.html.setStyle(el, 'top', y + unit);
	};
	
	S.getPosition = function(el){
		return new S.Position(parseInt(el.offsetLeft, 10), parseInt(el.offsetTop, 10));
	};
	
	(function() {
		/*
		from prototype
		*/
		/* Support for the DOMContentLoaded event is based on work by Dan Webb,
		Matthias Miller, Dean Edwards and John Resig. */
		
		var onLoadCallbacks = [];				
		var timer;
		var loadingState = {loaded: false};
		
		S.callOnLoad = function(f){
			if(document.loaded){
				f();
			}
			else{
				onLoadCallbacks.push(f);
			}
		};					
		
		function fireContentLoadedEvent(){
			if(loadingState.loaded){
				return;
			}
			if(timer){
				window.clearInterval(timer);
			}
			onLoadCallbacks.forEach(function(callback){
				S.error.globalInterceptor.intercept('shrubbery.callOnLoad', S.defer(callback, 1));
			});
			loadingState.loaded = true;
		}
		
		if(document.addEventListener){			
			if(S.isSafari){
				timer = window.setInterval(function() {
					if(/loaded|complete/.test(document.readyState)){
						fireContentLoadedEvent();
					}
				}, 0);
				S.event.addListener(window, "load", null, fireContentLoadedEvent);
			}
			else{
				document.addEventListener("DOMContentLoaded", fireContentLoadedEvent, false);
			}		
		}
		else{
			document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
			S.$("__onDOMContentLoaded").onreadystatechange = function() {
				if (this.readyState == "complete") {
					this.onreadystatechange = null;
					fireContentLoadedEvent();
				}
			};
		}
	})();		

})();
(function(){
	var S = shrubbery;
	S.html = {
		idPrefix: 'shrubbery_',
		id: (function(){
			var nextId = 1;
			return function(el){
				if(!el.id){
					el.id = this.idPrefix + (nextId++);
				}
				return el.id;
			};			
		})(),

		create: function(cls, content, attributes){
			var el = this.createElement('div', content, attributes);
			if(cls){
				if(S.isArray(cls)){
					cls = cls.join(' ');
				}			
				el.className = cls;
			}
			return el;
		},
		
		escape: function(str){
			return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
		},

		createElement: function(tagName, content, attributes){
			var el = document.createElement(tagName);
			if(content){
				if(S.isString(content)){
					el.innerHTML = content;
				}
				else if(S.isArray(content)){
					for(var i=0;i<content.length;i++){
						var c = this.toElement(content[i]);
						if(!c || !S.isDefined(c.nodeType)){
							throw sdkshdk;
						}
						el.appendChild(c);
					}
				}
				else if(S.isDefined(content.nodeType)){
					el.appendChild(content);
				}
				else{
					el.appendChild(this.toElement(content));
				}
			}
			if(attributes){
				if(S.isString(attributes)){
					el.className = attributes;
				}
				else{
					for(var attr in attributes){
						el[attr] = attributes[attr];
					}
				}
			}
			return el;
		},	
		
		append: function(parent, cls, content){
			return parent.appendChild(this.create.call(this, cls, content));
		},
		
		update: function(el, content){
			S.html.clear(el);
			if(content){
				if(S.isString(content)){
					el.innerHTML = content;
				}
				else{
					el.appendChild(this.toElement(content));
				}			
			}
		},		
		
		hasClass: function(el, cls){
			return ((" "+el.className+" ").indexOf(" "+cls+" ") >= 0);
		},
	
		addClass: function(el){
			for(var i=1;i<arguments.length;i++){
				var cls = arguments[i];
				if(S.isArray(cls)){
					S.html.addClass.apply(this, [el].concat(cls));
					continue;
				}
				if((" "+el.className+" ").indexOf(" "+cls+" ") < 0){
					el.className = el.className + (el.className ? ' ' : '') + cls;
				}
			}
		},

		removeClass: function(el, cls){
			var t = S.string.trim((" " + el.className + " ").replace(" " + cls + " ", " "));
			if(el.className != t){
				el.className = t; 
			}
		},
		
		toggleClass: function(el, cls, condition){
			if(!S.isDefined(condition)){
				condition = !S.html.hasClass(el, cls);
			}
			this[condition ? "addClass" : "removeClass"](el, cls);
		},
		
		setClass: function(el, cls, set){
			if(set === false){
				this.removeClass(el, cls);
			}
			else{
				this.addClass(el, cls);
			}
		},
		
		hide: function(el){
			el.style.display = 'none';
		},
	
		show: function(el, display){
			el.style.display = display || '';
		},
		
		getStyle: function(element, style){
			if(style == 'float'){
				style = 'cssFloat';
			}
			var value = element.style[style];
			if(!value || value == 'auto'){
				var cs = document.defaultView.getComputedStyle(element, null);
				value = cs ? cs[style] : null;
			}
			if(style == 'opacity'){
				return parseFloat(value) || 1;
			}
			//console.log(style, value);
			return value == 'auto' ? null : value;
		},		
		
		setStyle: function(el, style, value){
			if(S.isObject(style)){
				for(var s in style){
					this.setStyle(el, s, style[s]);
				}
			}
			else{			
				if(style == 'opacity'){
					if(value > 0.99999){
						value = 1;
					}
					else if(value && value < 0.00001){
						value = 0;
					}
					el.style.opacity = value === 1 ? '' : value;
				}
				else{
					if(style == 'float' || style == 'cssFloat'){
						style = S.isDefined(el.style.styleFloat) ? 'styleFloat' : 'cssFloat';
					}
					el.style[style] = value;
				}
			}
		},
		
		setWidth: function(el, w, unit){
			this.setStyle(el, 'width', w + (unit || 'px'));
		},
		
		setHeight: function(el, h, unit){
			this.setStyle(el, 'height', h + (unit || 'px'));
		},
		
		setOpacity: function(element, value) {
		},
		
		remove: function(el){
			el.parentNode.removeChild(el);
		},
		
		replace: function(el, node){
			el.parentNode.replaceChild(node, el);
		},
		
		clear: function(el){
			el.innerHTML = '';
		},
		
		getViewportOffset: function(){
			return {
				x: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
				y: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
			};
		},
		
		contains: function(parent, child){
			var el = child;
			while(el){
				if(el === parent){
					return true;
				}
				el = el.parentNode;
			}
			return false;
		},
		
		render: function(str){
			var h = document.createElement('div');
			h.innerHTML = str;
			var el = h.firstChild;
			//this.remove(el);
			return el;
		},
		
		toElement: function(el, cls){
			if(!el){
				return undefined;
			}
			if(S.isString(el)){
				el = this.render(el);
			}
			else if(S.isFunction(el.toElement)){
				el = el.toElement();
			}
			else if(S.isFunction(el)){
				el = this.toElement(el());
			}
			if(S.isElement(el)){
				if(cls){
					this.addClass(el, cls);
				}			
			}			
			return el;
		},
		
		getElementsByClassName: function(cls, root, tagName){
			root = root || document;
			var els = root.getElementsByTagName(tagName || '*');
			var matches = [];
			for(var i=0;i<els.length;i++){
				if(this.hasClass(els[i], cls)){
					matches.push(els[i]);
				}
			}
			return matches;
		}
		
	};	
	
	if(S.isIE){
		S.html.getStyle = function(element, style){
			if(style == 'float' || style == 'cssFloat'){
				style = 'styleFloat';
			}
			var value = element.style[style];
			
			if(!value || value == 'auto'){
				var cs = element.currentStyle;
				value = cs ? cs[style] : null;
			}
			
			if(style == 'opacity'){
				var match = this.getStyle('filter').match(/alpha\(opacity=(.*)\)/);
				if(match[1]){
					return parseFloat(match[1]) / 100;
				}
				return 1;			
			}			
			
			if(value == 'auto'){
				if(style == 'height'){
					value = element.offsetHeight;
				}
				else if(style == 'width'){
					value = element.offsetWidth;
				}				
			}
			
			return value;
		};
	}
	
})();

(function(){
	var S = shrubbery;
	var Sh = S.html;

	S.callOnLoad(function(){				
		
		var menu = Sh.getElementsByClassName('s-menu')[0];
		
		var RichMenu = S.Class.create({
			__init__: function(el){
				this.element = el;
				this.setup(el);
			},
			setup: function(ul){
				var items = ul.getElementsByTagName('li');
				for(var i=0;i<items.length;i++){
					var li = items[i];
					var sub = li.getElementsByTagName('ul');
					sub = sub.length ? sub[0] : null;							
					var item = {li: li, ul: sub, menu: this};
					S.event.addListener(li, 'mouseover', item, function(e){
						Sh.addClass(this.li, 's-menu-over');
						if(this.ul){
							Sh.show(this.ul, 'block');
						}
					});								
					S.event.addListener(li, 'mouseout', item, function(e){
						Sh.removeClass(this.li, 's-menu-over');
						if(this.ul){									
							Sh.hide(this.ul);
						}
					});
					if(sub){
						Sh.hide(sub);
						Sh.addClass(li, 's-menu-parent');							
						this.setup(sub);
					}
					var links = li.getElementsByTagName('a');
					for(var j=0;j<links.length;j++){
						var href = links[j].href;
						if(href && (href == document.location.href)){
							Sh.addClass(li, 's-menu-selected');
						}
					}					
				}
			}
		});
		
		var richMenu = new RichMenu(menu);
		
		var lightbox = {
			fog: Sh.create('s-fog'),
			box: Sh.create('lightbox s-image'),
			show: function(url, w, h, text){
				Sh.clear(this.box);
				this.box.appendChild(Sh.createElement('img', null, {src: url, width: w, height: h}));
				if(text){
					this.box.appendChild(Sh.create('s-caption', text));
				}

				Sh.setStyle(this.box, {marginLeft: -(w/2) + 'px', marginTop: -(h/2) + 'px'});
				Sh.show(this.fog);
				Sh.show(this.box);
				console.log(this.fog.style.display);
			},
			hide: function(url){
				Sh.hide(this.box);
				Sh.hide(this.fog);
			}
		};
		S.event.addListener(lightbox.box, 'click', lightbox, function(e){
			this.hide();
		});
		lightbox.hide();
		document.body.appendChild(lightbox.fog);
		document.body.appendChild(lightbox.box);
		
		
		var images = document.getElementsByTagName('img');
		for(var i=0;i<images.length;i++){
			var img = images[i];
			if(img.alt){						
				var w = img.width;
				var sImg = Sh.create('s-image', [
					Sh.createElement('img', null, {src: img.src, width: w}), 
					Sh.create('s-caption', img.alt)
				]);
				Sh.setStyle(sImg, 'width', w + 'px');
				Sh.replace(img, sImg);
				S.event.addListener(sImg, 'click', img.src, function(e){
					lightbox.show(this, img.width, img.height, img.alt);
				});
			}
		}
		
		/*
		var text = S.$('body-text');
		var nodes = text.childNodes;
		var tocTree = {headline: 'All', children: [], parent: null, level: 0};
		var current = tocTree;
		var uid = 0;
		for(var i=0;i<nodes.length;i++){
			if(S.isElement(nodes[i]) && nodes[i].tagName.match(/^h(\d)$/i)){
				var l = parseInt(RegExp.$1, 10);
				var parent = current;
				while(parent.level >= l){
					parent = parent.parent;
				}
				var headline = nodes[i].innerHTML;
				var name = 'TOC_'+(++uid);
				Sh.update(nodes[i], '<a name="'+name+'">'+headline+'<'+'/a>');
				var entry = {
					headline: headline,
					children: [],
					name: name,
					parent: parent,
					level: l
				};
				parent.children.push(entry);
				current = entry;							
			}
		}
		
		function buildTocUL(entries){
			var ul = Sh.createElement('ul');
			for(var i=0;i<entries.length;i++){						
				var li = Sh.createElement('li', '<a href="#'+entries[i].name+'">'+entries[i].headline+'<'+'/a>');
				if(entries[i].children.length){
					li.appendChild(buildTocUL(entries[i].children));
				}
				ul.appendChild(li);
			}
			return ul;
		}
		
		var tocContainer = S.$('body-text-toc');
		tocContainer.appendChild(buildTocUL(tocTree.children));
		*/
		
		var vscale = Sh.getElementsByClassName('s-layout-vscale');
		S.each(vscale, function(el){
			setTimeout(function(){
				var h = Math.max(700, parseInt(Sh.getStyle(el.parentNode, 'height')))+'px';
				//console.log(h);
				Sh.setStyle(el, 'height', h);
			}, 300);
		});
		
		//console.log('nodes: ' + document.getElementsByTagName('*').length);
		
	});

})();

