function newEl(el,attrs,styles) {
	if (el.toUpperCase() == "TEXT") {
		var el = document.createTextNode(attrs);
	} else {
		var el = document.createElement(el);
		
		if(attrs) {
			for(attr in attrs){
				if(attr == "class") {
					el.className = attrs[attr];
				} else {
					el[attr] = attrs[attr];
					el.setAttribute(attr,attrs[attr]);
				}
			}
		}
		
		if(styles) setStyle(el,styles);
	}
	
	return el;	
}

function appendChildren(parent,els){
	for(var i=0,el;i<els.length;i++){if(els[i]) parent.appendChild(els[i]);}
	return parent;
}

function removeChildren(el){
	while(el.hasChildNodes()) el.removeChild(el.firstChild);
}

function absPos(el){
	var pos = {"x":el.offsetLeft,"y":el.offsetTop,"width":el.offsetWidth,"height":el.offsetHeight};
	
	while(el && el.offsetParent){
		while (el.parentNode!=el.offsetParent && (el.parentNode.scrollLeft || el.parentNode.scrollTop)) {
			el=el.parentNode;
			pos['x'] -= el.scrollLeft ? el.scrollLeft : 0;
			pos['y'] -= el.scrollTop ? el.scrollTop : 0;
		}
		
		pos['x'] += el.offsetParent.offsetLeft;
		pos['y'] += el.offsetParent.offsetTop;
		
		el = el.offsetParent;
	}
	
	pos['n'] = pos['y'];
	pos['w'] = pos['x'];
	pos['s'] = pos['y']+pos['height'];
	pos['e'] = pos['x']+pos['width'];
	
	return pos;
}

function hasClassName(el,className) {
	var classRegexp = new RegExp("( |^)"+className+"( |$)", "i");
	return classRegexp.test(el.className);
}

function addEvent(obj,type,fn) {
	if (obj.attachEvent) {
    	obj['e'+type+fn] = fn;
    	
		obj[type+fn] = function() {
			obj['e'+type+fn](window.event);
		}
    	
		obj.attachEvent('on'+type,obj[type+fn]);
	} else {
		obj.addEventListener(type,fn,false);
	}
	
	return {'obj':obj,'type':type,'fn':fn};
}

function removeEvent(obj,type,fn) {
	if(obj) {
		if(obj.obj) {
			removeEvent(obj.obj,obj.type,obj.fn);
			delete obj;
			return;	
		} else {
			if (obj.detachEvent) {
				obj.detachEvent('on'+type,obj[type+fn]);
				obj[type+fn] = null;
			} else {
				obj.removeEventListener(type,fn,false);
			}
		}
	}
}

var helpBubble = new function(){
	/* options = {
	 * 		'target':	Specifies element which optionss are relative to
	 *		'anchor':	Specifies which corner or side the bubble should be in relation to target ('n','s','e', or 'w')
	 * 		'x':		Specifies horizontal options of help bubble (relative to 'anchor' of 'target' if it exists otherwise absolute) (in 'px')
	 * 		'y':		Specifies vertical options of help bubble (relative to 'anchor' of 'target' if it exists otherwise absolute) (in 'px')
	 *	 	'px':		Specifies vertical location of pointer relative to help bubble element (in 'px' or '%')
	 * 		'py':		Specifies horizontal location of pointer relative to help bubble element (in 'px' or '%') 
	 * }
	 */
	var show = function(bubble,options,fade_time) {
		bubble = typeof(bubble) == "string" ? $(bubble) : bubble;
		
		if(bubble){
			// if wanted, hide all open help bubbles
			if (options.hideall) {
				for(var hc = 0; hc < activeHelpBubbles.length; hc++) helpBubble.hide($(activeHelpBubbles[hc]));
				activeHelpBubbles = Array();
			}
			
			// default values
			if(fade_time == undefined)	fade_time = 0.5;
			if(typeof options != "object") options = {};
			if(options.autohide == undefined)	options.autohide = true;
			if(options.fixed) options.autohide = false;
			
			if(options.target) options.target = typeof(options.target) == "string" ? $(options.target) : options.target;
			if(options.target && options.target.nodeType != 1) options.target = null;
			if(!bubble) return false;
			
			if(!options.anchor) options.anchor = "n"; 	
			else options.anchor = options.anchor.toLowerCase();

			// load the element with the necessary elements
			if(!bubble.loaded) load(bubble, options);
			
			if (!options.protect) activeHelpBubbles.push(bubble.id);
			
			document.body.appendChild(bubble);
			bubble.style.display = 'block';
			move(bubble,options);
			
			// fade it in, if desired
			if(fade_time && typeof(Animate) == "function"){
				if(document.all) var tween = Animate.Tween(bubble.style, 'filter', 'regularEaseOut', 0, 100,fade_time,')','alpha(opacity:');
				else{
					var tween = Animate.Tween(bubble.style, 'opacity', 'regularEaseOut', 0, 1,fade_time,'','');
					var tween = Animate.Tween(bubble.style, 'MozOpacity', 'regularEaseOut', 0, 1,fade_time,'');
				}
			}
			
			if(options.autohide && !options.protect){
				window.setTimeout(function(){
					bubble.hide_event = addEvent(document, "mousedown", function(event){
						var s = new Date().getTime();
						for(k=(event.target?event.target:event.srcElement); k.parentNode && k.nodeName != 'BODY'; ){
							if(k == bubble) return false; 
							k = k.parentNode;
						}
						
						hide(bubble);
					});
				},10);
			}
		}
		
		return false;
	}
	
	var load = function(bubble, options){
		var pointer = newEl('DIV');
		var top = newEl('DIV',{'class':'top'});
		
		appendChildren(top,[newEl('SPAN',{'class':'l'}),newEl('SPAN',{'class':'r'})]);
		
		var middle = newEl('DIV',{'class':'middle','innerHTML':bubble.innerHTML});
		var close = newEl('SPAN',{'innerHTML':'X','class':'close'});
		
		close.onclick = function(){
			hide(bubble);
			return false;
		}
		
		middle.insertBefore(close,middle.firstChild);
		
		var bottom = newEl('DIV',{'class':'bottom'});
		
		appendChildren(bottom,[newEl('SPAN',{'class':'l'}),newEl('SPAN',{'class':'r'})]);
		removeChildren(bubble);
		appendChildren(bubble,[pointer,top,middle,bottom]);
		
		bubble.pointer = pointer;
		bubble.middle = middle;
		bubble.loaded = true;
	}
	
	var hide = function(bubble){
		bubble = typeof(bubble) == "string" ? $(bubble) : bubble;
		bubble.style.display = 'none';
		removeEvent(bubble.hide_event);
	}
	
	var move = function(bubble,options, already_moved){
		if (options.target) {
			if (options.anchor == 'e') var pointer_class = 'east';
			else if(options.anchor == 'w') var pointer_class = 'west';
			else if(options.anchor.indexOf('n') != -1) var pointer_class = 'north';
			else var pointer_class = 'south';
			bubble.pointer.className = 'pointer '+pointer_class;
		}
		
		if(options.target){
			bubble.style.display = 'block';
			var target_pos = absPos(options.target);
			var bubble_pos = absPos(bubble);
			var point = getPoint(options.target, options.anchor, 5);
			
			var width = bubble.offsetWidth;
			var height = bubble.offsetHeight;
			var pointer_left = bubble.pointer.offsetLeft;
			var pointer_top = bubble.pointer.offsetTop;
			
			// NORTH & SOUTH
			if(hasClassName(bubble.pointer, 'north') || hasClassName(bubble.pointer, 'south')){
				height += bubble.pointer.offsetHeight;
				
				// NORTH
				if(hasClassName(bubble.pointer, 'north')){
					pointer_top += bubble.pointer.offsetHeight;
					pointer_left += bubble.pointer.offsetWidth/2;
					
					// CHECK IF OFF SCREEN TOP
					if((y_diff = (height - point.y)) > 0){
						if(y_diff/height > .2){
							options.anchor = 's';
						} else{
							options.width = 'auto';
							options.height = (bubble.offsetHeight - y_diff - 5)+"px";
						}
						if(!already_moved) return move(bubble, options, true);
					}
					
				// SOUTH
				} else{
					pointer_left += bubble.pointer.offsetWidth/2;		
				}
				
				// CHECK IF OFF SCREEN LEFT
				if((x_diff = (pointer_left - point.x)) > 0){
					pointer_left = point.x - 5;
					bubble.pointer.style.left = pointer_left - (bubble.pointer.offsetWidth/2) + "px";
					
				// CHECK IF OFF SCREEN RIGHT
				/*} else if((x_diff = (pointer_left+point.x+bubble.offsetWidth-document.body.clientWidth)) > 0){
					pointer_left = x_diff-pointer_left+10*/
				}
				
			// EAST & WEST
			} else {
				width += bubble.pointer.offsetWidth;
				
				// WEST
				if(hasClassName(bubble.pointer, 'west')){
					pointer_left += bubble.pointer.offsetWidth;
					pointer_top += bubble.pointer.offsetHeight/2;
					
					// CHECK IF OFF SCREEN LEFT
					if((x_diff = (width - point.x)) > 0){
						if(x_diff/width > .4){
							options.anchor = 's';
						} else{
							options.height = 'auto';
							options.width = (bubble.offsetWidth - x_diff - 5)+"px";
						}
						if(!already_moved) return move(bubble, options, true);
					}
					
				// EAST	
				}else{
					pointer_top += bubble.pointer.offsetHeight/2;
					if((x_diff = (pointer_left+point.x+bubble.offsetWidth-document.body.clientWidth)) > 0){
						pointer_left = x_diff-pointer_left+10
						//bubble.pointer.style.left = pointer_left - (bubble.pointer.clientWidth/2) + "px";
					}
				}
				
				// CHECK IF OFF SCREEN TOP
				if((y_diff = (pointer_top - point.y)) > 0){
					pointer_top = point.y - 5;
					bubble.pointer.style.top = pointer_top - (bubble.pointer.offsetHeight/2) + "px";
				}
			}
			bubble.style.left = (point.x - pointer_left)+"px";
			bubble.style.top = (point.y - pointer_top)+"px";
		} else if(options.y || options.x){
			bubble.style.top = options.y+"px";
			bubble.style.left = options.x+"px";
		}
	}
	
	var getPoint = function(el, dir, buffer){
		var pos = absPos(el);
		switch(dir){
			case 'nw': return {'x':pos.w-buffer, 		'y':pos.n-buffer};
			case 'n':  return {'x':pos.w+(pos.e-pos.w)/2, 	'y':pos.n-buffer};
			case 'ne': return {'x':pos.e+buffer, 		'y':pos.n-buffer};
			case 'e':  return {'x':pos.e+buffer, 		'y':pos.n+(pos.s-pos.n)/2};
			case 'se': return {'x':pos.e+buffer, 		'y':pos.s+buffer};
			case 's':  return {'x':pos.w+(pos.e-pos.w)/2, 	'y':pos.s+buffer};
			case 'sw': return {'x':pos.w-buffer, 		'y':pos.s+buffer};
			case 'w':  return {'x':pos.w-buffer, 		'y':pos.n+(pos.s-pos.n)/2};
		}
	}
	
	this.show = show;
	this.hide = hide;
}