function getElementsByClass(className, tagName, parentNode) {
	var noClassTags = list('#comment,BASE,BASEFONT,HEAD,HTML,META,PARAM,SCRIPT,STYLE,TITLE');
	return filter(getAll(tagName,parentNode),
		function(elem) {
			return !arrayContains(noClassTags,elem.nodeName) && hasClass(elem, className) 
		});
}

function getAll(tagName, parent) {
	parent = isdef(parent)? getElem(parent) : document;
	if (undef(tagName)) tagName = '*';
	var r = parent.getElementsByTagName(tagName);
	
	return r.length || tagName != '*'?  map(r) :
		reduce(filterElementNodes(parent.childNodes), [], function(l,c){
			return l.concat([c], getAll(tagName, c))
		})
}

function hasClass(elem, className) {
	return arrayCountOccurrence(getElem(elem).className.split(' '), className);
}

function getElem(el) {
	var ge = (document.getElementById && function(id){return document.getElementById(id)} ) ||
			(document.all && function(id){return document.all[id]} ) ||
			function(){return null};
	return isElement(el)? el : isString(el) ? ge(el) : null;
}

function filterElementNodes(nodeList, tagName) {
	return filter(nodeList, function(n){
		return n.nodeType==1 && n.nodeName!='!' && 
			(undef(tagName) || tagName == '*' || n.nodeName.toUpperCase()==tagName.toUpperCase())
	})
}

function list(s, sep) {
	if (!isString(sep) && !isRegexp(sep))
		sep = sep? ',' : /\s*,\s*/;
	return s.split(sep);
}

function filter(list, fn) {
	if (typeof(fn)=='string') return filter(list, __strfn('item,idx,list', fn));

	var result = [];
	fn = fn || function(v) {return v};
	map(list, function(item,idx,list) { if (fn(item,idx,list)) result.push(item) } );
	return result;
}

function reduce(list, initial, fn) {
	if (undef(fn)) {
		fn      = initial;
		initial = window.undefined; 
	}
	if (typeof(fn)=='string') return reduce(list, initial, __strfn('a,b', fn));
	if (isdef(initial)) list.splice(0,0,initial);
	if (list.length===0) return false;
	if (list.length===1) return list[0];
	var result = list[0];
	var i = 1;
	while(i<list.length) result = fn(result,list[i++]);
	return result;
}

function __strfn(args, fn) {
	function quote(s) { return '"' + s.replace(/"/g,'\\"') + '"' }
	if (!/\breturn\b/.test(fn)) {
		fn = fn.replace(/;\s*$/, '');
		fn = fn.insert(fn.lastIndexOf(';')+1, ' return ');
	}
	return eval('new Function('
		+ map(args.split(/\s*,\s*/), quote).join()
		+ ','
		+ quote(fn)
		+ ')'
		);
}

function map(list, fn) {
	if (typeof(fn)=='string') return map(list, __strfn('item,idx,list', fn));

	var result = [];
	fn = fn || function(v) {return v};
	for (var i=0; i < list.length; i++) result.push(fn(list[i], i, list));
	return result;
}

function isRegexp(a)    { return a && a.constructor == RegExp }

function isString(a)    { return typeof a == 'string' }

function isFunction(a)  { return typeof a == 'function' }

function isObject(a) { return (a && typeof a == 'object') || isFunction(a); }

function isElement(o, strict) {
	return o && isObject(o) && ((!strict && (o==window || o==document)) || o.nodeType == 1)
}

function isUndefined(a) { return typeof a == 'undefined' }

function undef(v) { return  isUndefined(v) }

function isdef(v) { return !isUndefined(v) }

function arrayIndexOf(list, value, start, strict) {
	start = start || 0;
	for (var i=start; i<list.length; i++) {
		var item = list[i];
		if (strict            ? item === value   :
			isRegexp(value)   ? value.test(item) :
			isFunction(value) ? value(item)      :
			item == value)
			return i;
	}
	return -1;
}

function arrayCountOccurrence(list, value, strict) {
	var pos, start = 0, count = 0;
	while ((pos = arrayIndexOf(list, value, start, strict)) !== -1) {
		start = pos + 1;
		count++;
	}
	return count;
}

function arrayContains(list,value,strict) {
	return arrayIndexOf(list,value,0,strict) !== -1;
}

String.prototype.insert = function(idx,value) { return this.slice(0,idx) + value + this.slice(idx) }
