function emptyFunc(){}
function falseFunc(){ return false }

function addLoadEvent(f) { var old = window.onload
	if (typeof old != 'function') window.onload = f
	else { window.onload = function() { old(); f() }}
}
function prevent(e) {
	if (window.event) window.event.returnValue = false
	else e.preventDefault()
}

// return the time it takes to run a function in millisecs
function funcTimer(f){
	var then = new Date().getTime()
	f()
	return (new Date().getTime() - then)
}

function extend(dest, src){
	if(!src) return dest
	for(var k in src) dest[k] = src[k]
	return dest
}
function niceExtend(dest, src){
	if(!src) return dest
	if(src.html) { dest.innerHTML = src.html; delete src.html }
	if(src.css) { dest.className = src.css; delete src.css }
	if(src.attr) {
		var s = src.attr
		for(var k in s) dest.setAttribute(k, s[k])
		delete src.attr
	}
	if(src.style) {
		var d = dest.style, s = src.style
		for(var k in s) d[k] = s[k]
		delete src.style
	}
	for(var k in src) dest[k] = src[k]
	return dest
}

function isArray(o) { if(o && typeof o == 'object' && o.constructor == Array) return true; return false }

extend( String.prototype, {
	include: function(t) { return this.indexOf(t) >= 0 ? true : false },
	trim: function(){ return this.replace(/^\s+|\s+$/g,'') },
	splitrim: function(t){ return this.trim().split(new RegExp('\\s*'+t+'\\s*')) },
	encodeTag: function() { return encodeURIComponent(this).replace(/%2F/g, '/') },
	unescHtml: function(){ var i,e={'&lt;':'<','&gt;':'>','&amp;':'&','&quot;':'"'},t=this; for(i in e) t=t.replace(new RegExp(i,'g'),e[i]); return t },
	escHtml: function(){ var i,e={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'},t=this; for(i in e) t=t.replace(new RegExp(i,'g'),e[i]); return t },
	escRegExp: function(){ return this.replace(/[\\$*+?()=!|,{}\[\]\.^]/g,'\\$&') }
})

Number.prototype.times = function(f){
	var n = this
	while(n > 0) { f(); n-- }
}

// make a cheep hash from an array
Object.fromArray = function(src, value){
	if(typeof(value) == 'undefined') value = true
	var r = {}, l = src.length
	for (var i = 0; i < l; i++) r[src[i]] = value
	return r
}

// copy an arraylike obj to an actual array
Array.from = function(src){
	var l = src.length, r = []
	for (var i=0; i<l; i++) r[i] = src[i]
	return r
}
// push/collapse. needs a better name
Array.prototype.pushc = function(o){
	if (o === null || typeof(o) == 'undefined') return this
	else if (o.each) o.each( function(i){ this.pushc(i) }.bind(this) )
	else this.push(o)
	return this
}
Array.prototype.each = function(f){
	var l = this.length
	for(var i = 0; i < l; i++) f(this[i], i)
	return this
}
Array.prototype.map = function(f){
	var r = [], l = this.length
	for (var i = 0; i < l; i++) r.push(f(this[i], i))
	return r
}
// map/collapse. needs a better name
Array.prototype.mapc = function(f){
	var r = [], l = this.length
	for (var i = 0; i < l; i++) r.pushc(f(this[i], i))
	return r
}
Array.prototype.random = function(){ return this[Math.floor(Math.random() * this.length)] }
// array.get(0) is like array[0] except doesn't go out of bounds
// Array.prototype.get = function(n){ return this.splice(n,n+1) }

//function visible(o){ return o.style.display != 'none' }
function toggle(o){ if (visible(o)) hide(o); else show(o) }
function invisible(o) { o.style.visibility = 'hidden' }
function hide(o){ o.style.display = 'none'; }
function show(o){ o.style.display = ''; o.style.visibility = '' }
function remove(){ for(var i=0, o; o=arguments[i]; i++) if(o && o.parentNode) o.parentNode.removeChild(o) }
function create(o,t){
	if (o == 'text') return document.createTextNode(t||'')
	else {
		var e = document.createElement(o)
		if (t) {
			if (typeof t == 'string') e.innerHTML = t
			else niceExtend(e, t)
		}
		return e
}}

// todo: make this decent
function ROW(a, options) {
	options = options || {}
	var tr = create('tr')
	var first = true
	a.each(function(o) {
		var td = create('td', options)
		if(isArray(o)) o.each(function(item){ td.appendChild(item) })
		else {
			if(!o.nodeName) o = create('label', {html: o})
			td.appendChild(o)
		}
		if(first) { td.className = 'first'; first = false }
		tr.appendChild(td)
	})
	return tr
}

function TABLE(a, options) {
	options = options || {}
	var table = create('table', options), tbody = create('tbody')
	table.appendChild(tbody)
	a.each(function(tr) {
		tbody.appendChild(tr)
	})
	return table
}

// styling functions
function isA(o,klass){ if(!o.className) return false; return new RegExp('\\b'+klass+'\\b').test(o.className) }
function addClass(o,klass){ if(!isA(o,klass)) o.className += ' ' + klass } 
function rmClass(o,klass){ o.className = o.className.replace(new RegExp('\\s*\\b'+klass+'\\b'),'') }
function swapClass(o,klass,klass2){ var swap = isA(o,klass) ? [klass,klass2] : [klass2,klass]; rmClass(o,swap[0]); addClass(o,swap[1]) }
function getStyle(o,s) {
	if (document.defaultView && document.defaultView.getComputedStyle) return document.defaultView.getComputedStyle(o,null).getPropertyValue(s)
	else if (o.currentStyle) { return o.currentStyle[s.replace(/-([^-])/g, function(a,b){return b.toUpperCase()})] }
}

function getTextStyle(o){
	return { fontSize: getStyle(o, 'font-size'), fontFamily: getStyle(o, 'font-family'), fontWeight: getStyle(o, 'font-weight') }
}
function makeTextSize(style, appendTo){
	style = extend({zborder: '1px solid red', visibility: 'hidden', position: 'absolute', top: 0, left: 0}, style)
	var div = create('div', {style: style})
	appendTo.appendChild(div)
	return div
}
function getTextSize(text, o){
	o.innerHTML = text.escHtml().replace(/ /g, '&nbsp;')
	return o.offsetWidth
}
function getTextWidth(text, style, appendTo){
	style = extend({border: '1px solid red', zvisibility: 'hidden', position: 'absolute', top: 0, left: 0}, style)
	var div = create('div', {style: style, html: text.escHtml().replace(/ /g, '&nbsp;')})
	appendTo.appendChild(div)
	var w = div.offsetWidth
	remove(div)
	return w
}

function $id(id){ if (typeof id == 'string') return document.getElementById(id); return id }
function $tags(t,o){ o=o||document
	if (!o.getElementsByTagName) return []
	return Array.from(o.getElementsByTagName(t))
}
function $tag(t,o,i){ o=o||document; return o.getElementsByTagName(t)[i||0] }
// get all elements of class c starting at object o that are of tag t
// o + t are optional, and o can be an array of objects
// eg: $c('post', document, 'li')
function $c(c,o,t){ o=o||document
	if (!isArray(o)) o = [o]
	var elements = []
	o.each( function(e){
		var children = $tags(t||'*', e)
		children.each( function(child){ if(isA(child,c)) elements.push(child) } )
	})
	return elements
}

function previousElement(o) {
	do o = o.previousSibling; while(o && o.nodeType != 1)
	return o
}

function nextElement(o) {
	do o = o.nextSibling; while(o && o.nodeType != 1)
	return o
}

// get mouse pointer position
function pointerX(e) { return e.pageX || (e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)) }
function pointerY(e) { return e.pageY || (e.clientY + (document.documentElement.scrollTop || document.body.scrollTop)) }

// get window size
function windowHeight() { return self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0 }
function windowWidth() { return self.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0 }

function pageScrollY() { return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0 }
function pageScrollX() { return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0 }

// get pixel position of an object
function getY(o){ var y = 0
	if (o.offsetParent) while (o.offsetParent) { y += o.offsetTop; o = o.offsetParent }
	return y
}
function getX(o){ var x = 0
	if (o.offsetParent) while (o.offsetParent) { x += o.offsetLeft; o = o.offsetParent }
	return x
}
function setX(o, n){ o.style.left = n + 'px' }
function setY(o, n){ o.style.top = n + 'px' }

function getRadioValue(o) {
	for(var i = 0, r; r = o[i]; i++) if (r.checked && r.value) return r.value
	return false
}

function makeFocusFunc(obj, selectFrom){ return function(){
	var focusRange = obj.createTextRange()
	obj.focus()
	focusRange.collapse(false)
	focusRange.select()
}}

// focus the caret to end of a form input
// should work in all four browsers now! woo
function focusTo(obj, safariSelect) {
	var selectFrom = obj.value.length
	if(obj.createTextRange){ //ie + opera
		setTimeout(makeFocusFunc(obj, selectFrom), 10)
	} else if (obj.setSelectionRange){ //ff
		obj.select()
		obj.setSelectionRange(selectFrom, selectFrom)
	} else { //safari
		if(safariSelect) obj.select()
		obj.blur()
		obj.focus()
	}
//	obj.scrollTop = selectFrom
}

// the following two functions ganked from prototype (http://prototype.conio.net)
// (c) 2005 sam stephenson
var Class = {
	create: function() {
		return function() { this.initialize.apply(this, arguments) }
}}
Function.prototype.bind = function(o) {
	var __method = this
	return function() { return __method.apply(o, arguments) }
}

// the following chunk inspired by jquery (http://jquery.com)
// by john resig (http://ejohn.org)

function $(txt, context) { return $ize(find(txt, context)) }

function $ize(a) {
	return extend(a, {
		$: function(txt) {
			var r = []
			for(var i=0, o; o=this[i]; i++) {
				var f = find(txt, o)
				if (f.length) r = r.concat(f)
			}
			if (this.stack) { r.stack = this.stack; r.stack.push(this) }
			else r.stack = [this]
			return $ize(r)
		},
		addClass: function(c) { return this.each(function(o){ addClass(o, c) }) },
		rmClass: function(c) { return this.each(function(o){ rmClass(o, c) }) },
		end: function(){ return this.stack.pop() }
	})
}

find = function( t, context ) {
	context = context || document
	var token = t.charAt(0)
	if (token == '.') return ($c(t.substr(1), context) || [])
	else if (token == '@' ) {
		var a = $tags('*', context)
		var t = t.substr(1)
		return a.mapc( function(o){
			if (o.getAttribute && o.getAttribute(t) != null) return o; return null
		} )
	} else if (token == '#' ) {
		var o = $id(t.substr(1))
		if (o) return [o]
		else return []
	} else return ($tags(t, context) || [])
}

// the following code is a slightly mangled moo.fx (http://moofx.mad4milk.net)
// by valerio proietti (http://mad4milk.net)
// 10/24/2005 v(1.0.2) under mit-style license
var fx = {}
fx.Base = function(){}
fx.Base.prototype = {
	setOptions: function(options) {
		this.options = { duration: 500, onComplete: '' }
		extend(this.options, options)
	},
	go: function() {
		this.duration = this.options.duration
		this.startTime = (new Date).getTime()
		this.timer = setInterval (this.step.bind(this), 13)
	},
	step: function() {
		var time  = (new Date).getTime()
		var Tpos   = (time - this.startTime) / (this.duration)
		if (time >= this.duration+this.startTime) {
			this.now = this.to
			clearInterval (this.timer)
			this.timer = null
			if (this.options.onComplete) setTimeout(this.options.onComplete.bind(this), 10)
		}
		else {
			this.now = ((-Math.cos(Tpos*Math.PI)/2) + 0.5) * (this.to-this.from) + this.from
			//this time-position, sinoidal transition thing is from script.aculo.us
		}
		this.increase()
	},
	custom: function(from, to) {
		if (this.timer != null) return
		this.from = from
		this.to = to
		this.go()
	},
	hide: function() { this.now = 0; this.increase() },
	clearTimer: function() { clearInterval(this.timer); this.timer = null }
}

//stretchers
fx.Layout = Class.create()
fx.Layout.prototype = extend(new fx.Base(), {
	initialize: function(el, options) {
		this.el = $id(el)
		this.el.style.overflow = "hidden"
		this.el.iniWidth = this.el.offsetWidth
		this.el.iniHeight = this.el.offsetHeight
		this.setOptions(options)
	}
})

fx.Height = Class.create()
extend(extend(fx.Height.prototype, fx.Layout.prototype), {
	increase: function() { this.el.style.height = this.now + "px" },
	toggle: function() {
		if (this.el.offsetHeight > 0) this.custom(this.el.offsetHeight, 0)
		else this.custom(0, this.el.scrollHeight)
	}
})

fx.Width = Class.create()
extend(extend(fx.Width.prototype, fx.Layout.prototype), {
	increase: function() { this.el.style.width = this.now + "px" },
	toggle: function(){
		if (this.el.offsetWidth > 0) this.custom(this.el.offsetWidth, 0)
		else this.custom(0, this.el.iniWidth)
	}
})

//fader
fx.Opacity = Class.create()
fx.Opacity.prototype = extend(new fx.Base(), {
	initialize: function(el, options) {
		this.el = $id(el)
		this.now = 1
		this.increase()
		this.setOptions(options)
	},
	increase: function() {
		if (this.now == 1) this.now = 0.9999
		if (this.now > 0 && this.el.style.visibility == "hidden") this.el.style.visibility = "visible"
		if (this.now == 0) this.el.style.visibility = "hidden"
		if (window.ActiveXObject) this.el.style.filter = "alpha(opacity=" + this.now*100 + ")"
		this.el.style.opacity = this.now
	},
	toggle: function() {
		if (this.now > 0) this.custom(1, 0)
		else this.custom(0, 1)
	}
})

//composition effect, calls Opacity and (Width and/or Height) alltogether
fx.FadeSize = Class.create()
fx.FadeSize.prototype = {
	initialize: function(el, options) {
		this.el = $id(el)
		this.el.o = new fx.Opacity(el, options)
		if (options) options.onComplete = null
		this.el.h = new fx.Height(el, options)
		this.el.w = new fx.Width(el, options)
	},
	toggle: function() {
		this.el.o.toggle()
		for (var i = 0; i < arguments.length; i++) {
			if (arguments[i] == 'height') this.el.h.toggle()
			if (arguments[i] == 'width') this.el.w.toggle()
		}
	},
	hide: function(){
		this.el.o.hide()
		for (var i = 0; i < arguments.length; i++) {
			if (arguments[i] == 'height') this.el.h.hide()
			if (arguments[i] == 'width') this.el.w.hide()
}}}

Ajax = Class.create();
Ajax.prototype = {
	initialize: function(options){
		this.transport = this.getTransport();
		this.postBody = options.postBody || '';
		this.method = options.method || 'POST';
		this.onComplete = options.onComplete || null;
        this.onError = options.onError || null;
		this.update = $id(options.update) || null;
		this.request(options.url);
	},
	request: function(url){
		this.transport.open(this.method, url, true)
		this.transport.onreadystatechange = this.onStateChange.bind(this)
		if (this.method != 'GET') {
			this.transport.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
			if (this.transport.overrideMimeType) this.transport.setRequestHeader('Connection', 'close')
		}
		this.transport.send(this.postBody)
	},
	onStateChange: function(){
		if (this.transport.readyState == 4) {
            if (this.transport.status == 200) {
                if (this.onComplete)
                    setTimeout(function(){this.onComplete(this.transport)}.bind(this), 10)
                if (this.update)
                    setTimeout(function(){this.update.innerHTML = this.transport.responseText}.bind(this), 10)
            } else {
                if (this.onError)
                    setTimeout(function(){this.onError(this.transport)}.bind(this), 10);
            }
			this.transport.onreadystatechange = emptyFunc
	    }
    },
	getTransport: function(){
		if (window.ActiveXObject) return new ActiveXObject('Microsoft.XMLHTTP')
		else if (window.XMLHttpRequest) return new XMLHttpRequest()
		else return false
}}

/*
    Mini MochiKit
*/

function forEach(list, fn) {
    for (var i=0; i<list.length; i++) fn(list[i]);
}

function filter(fn, list) {
    var rv = [];
    for (var i=0; i<list.length; i++)
        if (fn(list[i])) 
            rv[rv.length] = list[i];
    return rv;
}

function map(fn, list) {
    var rv = [];
    for (var i=0; i<list.length; i++) rv[rv.length] = fn(list[i]);
    return rv;
}

function appendChildNodes(parent, nodes) {
    for (var i=0; i<nodes.length; i++) {
        var node = nodes[i];
        if (node.nodeType) 
            parent.appendChild(node);
        else if ( (typeof(node) == 'object') && node.length)
            appendChildNodes(parent, node);
        else
            parent.appendChild(document.createTextNode(''+node));
    }
}

function createDOM(name, attrs, nodes) {
    var elem = document.createElement(name);
    if (attrs) for (k in attrs) {
        var v = attrs[k];

        if (k.substring(0, 2) == "on") {
            if (typeof(v) == "string") {
                v = new Function(v);
            }
            elem[k] = v;
        } else {
            elem.setAttribute(k, v);
        }

        switch(k) {
            // MSIE seems to want this.
            case 'class': elem.className = v; break;
        }
    }
    if (nodes) appendChildNodes(elem, nodes);
    return elem;
}

function createDOMFunc(name) {
    return function(attrs) {
        var nodes = [];
        for (var i=1; i<arguments.length; i++) 
            nodes[nodes.length] = arguments[i];
        return createDOM(name, attrs, nodes);
    }
}

// Create $TAG() functions for commonly used HTML tags.  (Yes, this is ugly)
forEach([ 
    'A', 'BUTTON', 'BR', 'CANVAS', 'DIV', 'FIELDSET', 'FORM',
    'H1', 'H2', 'H3', 'HR', 'IMG', 'INPUT', 'LABEL', 'LEGEND', 'LI', 'OL',
    'OPTGROUP', 'OPTION', 'P', 'PRE', 'SELECT', 'SPAN', 'STRONG', 'TABLE', 'TBODY',
    'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TR', 'TT', 'UL', 'DL', 'DT', 'DD'
    ], function(n) { window['$'+n] = createDOMFunc(n); });


