function TipManager(e, tip, tipDescr) {
	this.descr = new Object();
	var _valid = [
		[ 'timeIn', 'number', 400 ],
		[ 'timeOut', 'number', 40 ],
		[ 'tweenIn', 'number', 200 ],
		[ 'tweenOut', 'number', 100 ],
		[ 'tipFace', 'string', false ],
		[ 'tipFaceHead', 'string', '' ],
		[ 'dx', 'number', -10 ],
		[ 'dy', 'number', 12 ],
		[ 'spring', 'number', .2],
		[ 'friction', 'number', .75 ],
		[ 'head', 'string', false ]
	];
	for(var i = 0; i < _valid.length; i++) {
		var v = _valid[i];
		if(v[1] === true) {
			if(typeof tipDescr[v[0]] == 'undefined') this.descr[v[0]] = v[2];
			else this.descr[v[0]] = tipDescr[v[0]];
		} else if(typeof tipDescr[v[0]] != v[1]) {
			this.descr[v[0]] = v[2];
		} else {
			this.descr[v[0]] = tipDescr[v[0]];
		}
	}
	_valid = null;
	tipDescr = null;

	this.tipJSON = null;
	this.tip = null;

	if(this.descr.tweenIn > 0 || this.descr.tweenOut > 0) {
		var _this = this;

		this.tw = new HTTween(function(x, mode) {
			if(x == 0 && mode == 2) {
				document.body.removeChild(_this.tip);
				_this.tip = null;
			} else {
				if(is.ie) _this.tip.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + (x * 100) + ")";
				else _this.tip.style.opacity = x;
			}
		});
	}

	var element = typeof e == 'string' ? element = document.getElementById(e) : e;
	if(element == null) {
		console.warn("Invalid tip anchor, skipping.");
		console.dir(e);
		console.dir(tipDescr);
		return;
	}

	function _json(s) {
		return s.replace(/\\/g, '\\\\').replace(/\"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t').replace(/\f/g, '\\f');
	}

	if(this.descr.tipFace !== false) {
		var head = '';
		if(this.descr.head !== false) {
			head = this.descr.tipFaceHead.replace(/#html#/i, "'innerHTML': \"" + _json(this.descr.head) + '"');
		}

		var j = this.descr.tipFace.replace(/#html#/i, "'innerHTML': \"" + _json(tip) + '"').replace(/#head#/, head).replace(/, *,/, ',');
		eval('this.tipJSON=' + j);

		delete this.descr.tipFace;
		delete this.descr.tipFaceHead;
		delete this.descr.head;
	} else {
		this.tipJSON = [ "DIV", { 'class': 'tooltip', 'style': 'position: absolute;', 'innerHTML': tip } ];
	}

	htTipEngine.attachManager(element, this);

	this.toString = function() {
		return "TipManager: " + element;
	}
}

TipManager.prototype.over = function(x, y, forced) {
	var _this = this;
	var xd = 0;
	var yd = 0;

	function _rugo() {
		if(_this.tip != null) {
			xd = (xd + (_this.x0 - x) * _this.descr.spring) * _this.descr.friction;
			yd = (yd + (_this.y0 - y) * _this.descr.spring) * _this.descr.friction;

			x += xd;
			y += yd;

			_this.setPosition(Math.round(x), Math.round(y));

			setTimeout(_rugo, 40);
		}
	}

	if(this.tip == null) {
		this.createTip();
		document.body.appendChild(this.tip);
		if(this.descr.spring > 0) {
			setTimeout(_rugo, 40);
		}
	}

	if(this.descr.spring > 0) {
		this.setPosition(x, y);
	}

	if(this.descr.tweenIn > 0) {
		this.tw.start(0, 1, this.descr.tweenIn);
		if(forced) {
			this.tw.cancel();
		}
	}

	this.move(x, y);
}

TipManager.prototype.out = function(x, y, forced) {
	if(this.tip) {
		if(!forced && this.descr.tweenOut > 0) {
			this.tw.start(1, 0, this.descr.tweenOut);
		} else {
			document.body.removeChild(this.tip);
			this.tip = null;
		}
	} else {
		console.warn('Tip, double hide: %s', this);
	}
}

TipManager.prototype.setPosition = function(x, y) {
	x -= this.descr.dx;
	y += this.descr.dy;

	var de = document.documentElement;
	if(x + this.tip.offsetWidth > de.scrollLeft + de.clientWidth) x = de.scrollLeft + de.clientWidth - this.tip.offsetWidth;
	else if(x < de.scrollLeft) x = de.scrollLeft;
	if(y + this.tip.offsetHeight > de.scrollTop + de.clientHeight) y -= this.tip.offsetHeight + 25;

	with(this.tip.style) {
		top = y + "px";
		left = x + "px";
	}
}

TipManager.prototype.move = function(x, y) {
	if(this.tip) {
		if(this.descr.spring > 0) {
			this.x0 = x;
			this.y0 = y;
		} else {
			this.setPosition(x, y);
		}
	} else {
		console.warn('Tip, hidden move: %s', this);
		console.dir(htTipEngine);
	}
}

TipManager.prototype.invisibleMove = function(x, y) {
	if(this.descr.spring > 0) {
		this.x0 = x;
		this.y0 = y;
	}
}

TipManager.prototype.createTip = function() {
	this.tip = jsonML(this.tipJSON);
}

var htTipEngine = {
	over: null,
	showing: null,

	__init: function() {
		function walk(e, d) {
			for(var e1 = e.firstChild; e1 != null; e1 = e1.nextSibling) {
				if(e1.nodeType == 1) {
					try {
						var d1 = d;
						var tf = e1.getAttribute('ht:tipFace');
						var tfh = e1.getAttribute('ht:tipFaceHead');
						var td = e1.getAttribute('ht:tipDescriptor');
						if(tf || tfh || td) {
							d1 = jgtc.clone(d1);
							if(tf) d1.tipFace = tf;
							if(tfh) d1.tipFaceHead = tfh;
							if(td) {
								var d2;
								eval('d2 = {' + td + '}');
								for(var d2i in d2) {
									d1[d2i] = d2[d2i];
								}
							}
						}

						var t = e1.getAttribute('ht:tip');
						if(t) {
							var d2 = d1;
							var fh = e1.getAttribute('ht:tipHead');
							if(fh) {
								d2 = jgtc.clone(d2);
								d2.head = fh;
							}

							new TipManager(e1, t, d2);
						} else {
							walk(e1, d1);
						}
					} catch(e) {
						console.warn("tip error:" + e);
						console.dir(e);
						walk(e1, d);
					}
				}
			}
		}

		walk(document.documentElement, new Object());
		console.log("Tip engine inited...");
	},

	attachManager: function(element, mngr) {
		var _this = this;
		var x;
		var y;

		function _pos(e) {
			if(typeof e != 'object') e = window.event;

			x = e.clientX + document.body.parentNode.scrollLeft;
			y = e.clientY + document.body.parentNode.scrollTop;
		}

		function move(e) {
			if(_this.showing === mngr) {
				_pos(e);
				mngr.move(x, y);
			} else if(_this.over === mngr) {
				_pos(e);
				mngr.invisibleMove(x, y);
			} else {
				jgtc.releaseEvent(document.body, "mousemove", move, true);
			}
		}

		function show(forced) {
			if(_this.over === mngr && _this.showing == null) {
				_this.showing = mngr;
				mngr.over(x, y, forced);
			}
		}

		function hide() {
			if(_this.showing === mngr && _this.over !== mngr) {
				mngr.out(x, y, false);
				_this.showing = null;
			}
		}

		jgtc.captureEvent(element, "mouseover", function(e) {
			function _capt() {
				jgtc.captureEvent(document.body, "mousemove", move, true);
			}

			_pos(e);

			with(_this) {
				if(over == null) {
					over = mngr;
					if(showing != null && showing !== mngr) {
						showing.out(x, y, true);
						showing = null;
						show(true);
						_capt();
					} else if(showing == null) {
						setTimeout(function() { show(false); }, mngr.tIn);
						_capt();
					}
				} else {
					console.err("Tip, DOM error, double mouseover...");
				}
			}
		}, true);
		
		jgtc.captureEvent(element, "mouseout", function() {
			if(_this.over === mngr) {
				_this.over = null;
				setTimeout(hide, mngr.tOut);
			} else {
				console.error("Tip, DOM error, double mouseout...");
			}
		}, true);
		
		jgtc.captureEvent(element, "click", function() {
		
		}, true);
	}
}

jgtc.captureEvent(window, "load", function() { htTipEngine.__init(); }, true);
