(function($) {
	var tips = [],
		ie6 = $.browser.msie && $.browser.version == 6;
	// make sure the tips' position is updated on resize
	function handleWindowResize() {
		$.each(tips, function() {
			this.refresh(true);
		});
	}
	$(window).resize(handleWindowResize);
	$.gj_tip = function(elm, options) {
		this.$elm = $(elm);
		this.option = $.extend({}, $.fn.gj_tip.defaults, options);
		this.$tip = $(['<div class="',this.option.className,'">',
				'<div class="tooltip_img iconpos float-lft" style="width:25px;height:25px">&nbsp;</div><div class="tip-inner"></div>',
				'</div>'].join(''));
		this.$inner = this.$tip.find('div.tip-inner');
		this.disabled = false;
		this.init();
	};

	$.gj_tip.prototype = {
		init: function() {
			tips.push(this);

			// save the original title and a reference to the gj_tip object
			this.$elm.data('title.gj_tip', this.$elm.attr('title'))
				.data('gj_tip', this);

			// hook element events
			switch (this.option.showOn) {
				case 'hover':
					this.$elm.bind({
						'mouseenter.gj_tip': $.proxy(this.mouseenter, this),
						'mouseleave.gj_tip': $.proxy(this.mouseleave, this)
					});
					if (this.option.alignTo == 'cursor')
						this.$elm.bind('mousemove.gj_tip', $.proxy(this.mousemove, this));
					if (this.option.allowTipHover)
						this.$tip.hover($.proxy(this.clearTimeouts, this), $.proxy(this.hide, this));
					break;
				case 'focus':
					this.$elm.bind({
						'focus.gj_tip': $.proxy(this.show, this),
						'blur.gj_tip': $.proxy(this.hide, this)
					});
					break;
			}
		},
		mouseenter: function(e) {
			if (this.disabled)
				return true;

			this.clearTimeouts();
			this.$elm.attr('title', '');
			this.showTimeout = setTimeout($.proxy(this.show, this), this.option.showTimeout);
		},
		mouseleave: function() {
			if (this.disabled)
				return true;

			this.clearTimeouts();
			this.$elm.attr('title', this.$elm.data('title.gj_tip'));
			this.hideTimeout = setTimeout($.proxy(this.hide, this), this.option.hideTimeout);
		},
		mousemove: function(e) {
			if (this.disabled)
				return true;
			this.eventX = e.pageX;
			this.eventY = e.pageY;
			if (this.option.followCursor && this.$tip.data('active')) {
				this.calcPos();
				this.$tip.css({left: this.pos.l, top: this.pos.t});
			}
		},
		show: function() {
			if (this.disabled || this.$tip.data('active'))
				return;
			this.reset();
			this.update();
			this.display();
		},
		hide: function() {
			if (this.disabled || !this.$tip.data('active'))
				return;

			this.display(true);
		},
		reset: function() {
			this.$tip.queue([]).detach().css('visibility', 'hidden').data('active', false);
			this.$inner.find('*').gj_tip('hide');
			if (this.option.fade)
				this.$tip.css('opacity', this.opacity);
		},
		update: function(content) {
			if (this.disabled)
				return;

			var async = content !== undefined;
			if (async) {
				if (!this.$tip.data('active'))
					return;
			} else {
				content = this.option.content;
			}

			this.$inner.contents().detach();
			var self = this;
			this.$inner.append(
				typeof content == 'function' ?
					content.call(this.$elm[0], function(newContent) {
						self.update(newContent);
					}) :
					content == '[title]' ? this.$elm.data('title.gj_tip') : content
			);
			
			this.refresh(async);
		},
		refresh: function(async) {
			if (this.disabled)
				return;

			if (async) {
				if (!this.$tip.data('active'))
					return;
				// save current position as we will need to animate
				var currPos = {left: this.$tip.css('left'), top: this.$tip.css('top')};
			}

			// reset position to avoid text wrapping, etc.
			this.$tip.css({left: 0, top: 0}).appendTo(document.body);

			// save default opacity
			if (this.opacity === undefined)
				this.opacity = this.$tip.css('opacity');
			var $table = this.$tip.find('table');
			if (ie6) {
				// fix min/max-width in IE6
				this.$tip[0].style.width = '';
				$table.width('auto').find('td').eq(3).width('auto');
				var tipW = this.$tip.width(),
					minW = parseInt(this.$tip.css('min-width')),
					maxW = parseInt(this.$tip.css('max-width'));
				if (!isNaN(minW) && tipW < minW)
					tipW = minW;
				else if (!isNaN(maxW) && tipW > maxW)
					tipW = maxW;
				this.$tip.add($table).width(tipW).eq(0).find('td').eq(3).width('100%');
			} else if ($table[0]) {
				// fix the table width if we are using a background image
				$table.width('auto').find('td').eq(3).width('auto').end().end().width(this.$tip.width()).find('td').eq(3).width('100%');
			}
			this.tipOuterW = this.$tip.outerWidth();
			this.tipOuterH = this.$tip.outerHeight();

			this.calcPos();

			if (async)
				this.$tip.css(currPos).animate({left: this.pos.l, top: this.pos.t}, 200);
			else
				this.$tip.css({left: this.pos.l, top: this.pos.t});
		},
		display: function(hide) {
			var active = this.$tip.data('active');
			if (active && !hide || !active && hide)
				return;

			this.$tip.stop();
			if ((this.option.slide || this.option.fade) && (hide && this.option.hideAniDuration || !hide && this.option.showAniDuration)) {
				var from = {}, to = {};
				if (this.option.slide) {
					var prop, arr;
					var val = parseInt(this.$tip.css(prop));
					from[prop] = val + (hide ? 0 : this.option.slideOffset * (this.pos.arrow == arr ? -1 : 1));
					to[prop] = val + (hide ? this.option.slideOffset * (this.pos.arrow == arr ? 1 : -1) : 0);
				}
				if (this.option.fade) {
					from.opacity = hide ? this.$tip.css('opacity') : 0;
					to.opacity = hide ? 0 : this.opacity;
				}
				this.$tip.css(from).animate(to, this.option[hide ? 'hideAniDuration' : 'showAniDuration']);
			}
			hide ? this.$tip.queue($.proxy(this.reset, this)) : this.$tip.css('visibility', 'inherit');
			this.$tip.data('active', !active);
		},
		destroy: function() {
			this.reset();
			this.$tip.remove();
			this.$elm.unbind('gj_tip').removeData('title.gj_tip').removeData('gj_tip');
			tips.splice($.inArray(this, tips), 1);
		},
		clearTimeouts: function() {
			if (this.showTimeout) {
				clearTimeout(this.showTimeout);
				this.showTimeout = 0;
			}
			if (this.hideTimeout) {
				clearTimeout(this.hideTimeout);
				this.hideTimeout = 0;
			}
		},
		calcPos: function() {
			var pos = {l: 0, t: 0, arrow: ''},
				$win = $(window),
				win = {
					l: $win.scrollLeft(),
					t: $win.scrollTop(),
					w: $win.width(),
					h: $win.height()
				}, xL, xC, xR, yT, yC, yB;
			if (this.option.alignTo == 'cursor') {
				xL = xC = xR = this.eventX;
				yT = yC = yB = this.eventY;
			} else { // this.option.alignTo == 'target'
				var elmOffset = this.$elm.offset(),
					elm = {
						l: elmOffset.left,
						t: elmOffset.top,
						w: this.$elm.outerWidth(),
						h: this.$elm.outerHeight()
					};
				xL = elm.l + (this.option.alignX != 'inner-right' ? 0 : elm.w);	// left edge
				xC = xL + Math.floor(elm.w / 2);				// h center
				xR = xL + (this.option.alignX != 'inner-left' ? elm.w : 0);	// right edge
				yT = elm.t + (this.option.alignY != 'inner-bottom' ? 0 : elm.h);	// top edge
				yC = yT + Math.floor(elm.h / 2);				// v center
				yB = yT + (this.option.alignY != 'inner-top' ? elm.h : 0);	// bottom edge
			}

			// keep in viewport and calc arrow position
			switch (this.option.alignX) {
				case 'right':
				case 'inner-left':
					pos.l = xR + this.option.offsetX;
					if (pos.l + this.tipOuterW > win.l + win.w)
						pos.l = win.l + win.w - this.tipOuterW;
					break;
				case 'center':
					pos.l = xC - Math.floor(this.tipOuterW / 2);
					if (pos.l + this.tipOuterW > win.l + win.w)
						pos.l = win.l + win.w - this.tipOuterW;
					else if (pos.l < win.l)
						pos.l = win.l;
					break;
				default: // 'left' || 'inner-right'
					pos.l = xL - this.tipOuterW - this.option.offsetX;
					if (pos.l < win.l)
						pos.l = win.l;
			}
			switch (this.option.alignY) {
				case 'bottom':
				case 'inner-top':
					pos.t = yB + this.option.offsetY;
					// 'left' and 'right' need priority for 'target'
					if (pos.t + this.tipOuterH > win.t + win.h) {
						pos.t = yT - this.tipOuterH - this.option.offsetY;
					}
					break;
				case 'center':
					pos.t = yC - Math.floor(this.tipOuterH / 2);
					if (pos.t + this.tipOuterH > win.t + win.h)
						pos.t = win.t + win.h - this.tipOuterH;
					else if (pos.t < win.t)
						pos.t = win.t;
					break;
				default: // 'top' || 'inner-bottom'
					pos.t = yT - this.tipOuterH - this.option.offsetY;
					// 'left' and 'right' need priority for 'target'
						if (pos.t < win.t) {
						pos.t = yB + this.option.offsetY;
					}
			}
			this.pos = pos;
		}
	};

	$.fn.gj_tip = function(options){
		if (typeof options == 'string') {
			return this.each(function() {
				var gj_tip = $(this).data('gj_tip');
				if (gj_tip && gj_tip[options])
					gj_tip[options]();
			});
		}
		var option = $.extend({}, $.fn.gj_tip.defaults, options);
		return this.each(function() {
			new $.gj_tip(this, option);
		});
	}
$(document).ready(function(){
$.fn.dyntip = function(newtitle) {
  if (this.attr("title")) {
    var args = this.attr("title").split('|');
    for (var i = 0; i < args.length; i++) {
      var id = '\\$' + (i + 1);
	  var pattern = eval('/' + id + '/g');
      newtitle = newtitle.replace(pattern, args[i]);
    }
  }
  this.attr('title', newtitle);
  return this;
}
})

	// default settings
	$.fn.gj_tip.defaults = {
		content: 		'[title]',	// content to display ('[title]', 'string', element, function(updateCallback){...}, jQuery)
		className:		'mytip',	// class for the tips
		bgImageFrameSize:	10,		// size in pixels for the background-image (if set in CSS) frame around the inner content of the tip
		showTimeout:		100,		// timeout before showing the tip (in milliseconds 1000 == 1 second)
		hideTimeout:		0,		// timeout before hiding the tip
		showOn:			'hover',	// handler for showing the tip ('hover', 'focus', 'none') - use 'none' to trigger it manually
		alignTo:		'cursor',	// align/position the tip relative to ('cursor', 'target')
		alignX:			'right',	// horizontal alignment for the tip relative to the mouse cursor or the target element
							// ('right', 'center', 'left', 'inner-left', 'inner-right') - 'inner-*' matter if alignTo:'target'
		alignY:			'top',		// vertical alignment for the tip relative to the mouse cursor or the target element
							// ('bottom', 'center', 'top', 'inner-bottom', 'inner-top') - 'inner-*' matter if alignTo:'target'
		offsetX:		-12,		// offset X pixels from the default position - doesn't matter if alignX:'center'
		offsetY:		18,		// offset Y pixels from the default position - doesn't matter if alignY:'center'
		allowTipHover:		false,		// allow hovering the tip without hiding it onmouseout of the target - matters only if showOn:'hover'
		followCursor:		false,		// if the tip should follow the cursor - matters only if showOn:'hover' and alignTo:'cursor'
		fade: 			true,		// use fade animation
		slide: 			true,		// use slide animation
		slideOffset: 		0,		// slide animation offset
		showAniDuration: 	0,		// show animation duration - set to 0 if you don't want show animation
		hideAniDuration: 	0		// hide animation duration - set to 0 if you don't want hide animation
	};

})(jQuery);
