/*
 * Poshy Tip jQuery plugin v1.1
 * http://vadikom.com/tools/poshy-tip-jquery-plugin-for-stylish-tooltips/
 * Copyright 2010-2011, Vasil Dinkov, http://vadikom.com/
 */

(function($) {

	var tips = [],
		reBgImage = /^url\(["']?([^"'\)]*)["']?\);?$/i,
		rePNG = /\.png$/i,
		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);

	$.Poshytip = function(elm, options) {
		this.$elm = $(elm);
		this.opts = $.extend({}, $.fn.poshytip.defaults, options);
		this.$tip = $(['<div id="tip_',$(elm).attr('id'),'" class="',this.opts.className,(this.opts.classNameExtra!="" ? " "+this.opts.classNameExtra:""),'">',
				'<div class="tip-inner tip-bg-image"></div>',
				'<div class="tip-arrow tip-arrow-top tip-arrow-right tip-arrow-bottom tip-arrow-left"></div>',
			'</div>'].join('')).appendTo(document.body);
		this.$arrow = this.$tip.find('div.tip-arrow');
		this.$inner = this.$tip.find('div.tip-inner');
		this.disabled = false;
		this.content = null;
		this.init();
	};

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

			// save the original title and a reference to the Poshytip object
			var title = this.$elm.attr('title');
			this.$elm.data('title.poshytip', title !== undefined ? title : null)
				.data('poshytip', this);

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

			this.$elm.attr('title', '');
			if(this.opts.showOn == 'focus')
				return true;

			this.clearTimeouts();
			this.showTimeout = setTimeout($.proxy(this.show, this), this.opts.showTimeout);
		},
		mouseleave: function(e) {
			if(this.disabled || this.asyncAnimating && (this.$tip[0] === e.relatedTarget || jQuery.contains(this.$tip[0], e.relatedTarget)))
				return true;

			var title = this.$elm.data('title.poshytip');
			if(title !== null)
				this.$elm.attr('title', title);
			if(this.opts.showOn == 'focus')
				return true;

			this.clearTimeouts();
			this.hideTimeout = setTimeout($.proxy(this.hide, this), this.opts.hideTimeout);
		},
		mousemove: function(e) {
			if(this.disabled) return true;

			this.eventX = e.pageX;
			this.eventY = e.pageY;
			if(this.opts.followCursor && this.$tip.data('active')) {
				this.calcPos();
				this.$tip.css({left: this.pos.l, top: this.pos.t});
				if(this.pos.arrow) this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
			}
		},
		show: function() {
			if(this.disabled || this.$tip.data('active')) return;

			this.reset();
			this.update();
			this.display();

			if(this.opts.timeOnScreen) setTimeout($.proxy(this.hide, this), this.opts.timeOnScreen);
		},
		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('*').poshytip('hide');
			if(this.opts.fade) this.$tip.css('opacity', this.opacity);
			this.$arrow[0].className = 'tip-arrow tip-arrow-top tip-arrow-right tip-arrow-bottom tip-arrow-left';
			this.asyncAnimating = false;
		},
		update: function(content, dontOverwriteOption) {
			if(this.disabled) return;

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

			// update content only ifit has been changed since last time
			var self = this,
				newContent = typeof content == 'function' ?
					content.call(this.$elm[0], function(newContent) {
						self.update(newContent);
					}) :
					content == '[title]' ? this.$elm.data('title.poshytip') : content;
			if(this.content !== newContent) {
				this.$inner.empty().append(newContent);
				this.content = newContent;
			}

			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');

			// check for images - this code is here (i.e. executed each time we show the tip and not on init) due to some browser inconsistencies
			var bgImage = this.$tip.css('background-image').match(reBgImage),
				arrow = this.$arrow.css('background-image').match(reBgImage);

			if(bgImage) {
				var bgImagePNG = rePNG.test(bgImage[1]);
				// fallback to background-color/padding/border in IE6 ifa PNG is used
				if(ie6 && bgImagePNG) {
					this.$tip.css('background-image', 'none');
					this.$inner.css({margin: 0, border: 0, padding: 0});
					bgImage = bgImagePNG = false;
				} else {
					this.$tip.prepend('<table border="0" cellpadding="0" cellspacing="0"><tr><td class="tip-top tip-bg-image" colspan="2"><span></span></td><td class="tip-right tip-bg-image" rowspan="2"><span></span></td></tr><tr><td class="tip-left tip-bg-image" rowspan="2"><span></span></td><td></td></tr><tr><td class="tip-bottom tip-bg-image" colspan="2"><span></span></td></tr></table>')
						.css({border: 0, padding: 0, 'background-image': 'none', 'background-color': 'transparent'})
						.find('.tip-bg-image').css('background-image', 'url("' + bgImage[1] +'")').end()
						.find('td').eq(3).append(this.$inner);
				}
				// disable fade effect in IE due to Alpha filter + translucent PNG issue
				if(bgImagePNG && !$.support.opacity)
					this.opts.fade = false;
			}
			// IE arrow fixes
			if(arrow && !$.support.opacity) {
				// disable arrow in IE6 ifusing a PNG
				if(ie6 && rePNG.test(arrow[1])) {
					arrow = false;
					this.$arrow.css('background-image', 'none');
				}
				// disable fade effect in IE due to Alpha filter + translucent PNG issue
				this.opts.fade = false;
			}

			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 ifwe are using a background image
				// IE9, FF4 use float numbers for width/height so use getComputedStyle for them to avoid text wrapping
				// for details look at: http://vadikom.com/dailies/offsetwidth-offsetheight-useless-in-ie9-firefox4/
				$table.width('auto').find('td').eq(3).width('auto').end().end().width(document.defaultView && document.defaultView.getComputedStyle && parseFloat(document.defaultView.getComputedStyle(this.$tip[0], null).width) || this.$tip.width()).find('td').eq(3).width('100%');
			}
			this.tipOuterW = this.$tip.outerWidth();
			this.tipOuterH = this.$tip.outerHeight();

			this.calcPos();

			// position and show the arrow image
			if(arrow && this.pos.arrow) {
				this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
				this.$arrow.css('visibility', 'inherit');
			}

			if(async) {
				this.asyncAnimating = true;
				var self = this;
				this.$tip.css(currPos).animate({left: this.pos.l, top: this.pos.t}, 200, function() { self.asyncAnimating = false; });
			} 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.opts.slide && this.pos.arrow || this.opts.fade) && (hide && this.opts.hideAniDuration || !hide && this.opts.showAniDuration)) {
				var from = {}, to = {};
				// this.pos.arrow is only undefined when alignX == alignY == 'center' and we don't need to slide in that rare case
				if(this.opts.slide && this.pos.arrow) {
					var prop, arr;
					if(this.pos.arrow == 'bottom' || this.pos.arrow == 'top') {
						prop = 'top';
						arr = 'bottom';
					} else {
						prop = 'left';
						arr = 'right';
					}
					var val = parseInt(this.$tip.css(prop));
					from[prop] = val + (hide ? 0 : (this.pos.arrow == arr ? -this.opts.slideOffset : this.opts.slideOffset));
					to[prop] = val + (hide ? (this.pos.arrow == arr ? this.opts.slideOffset : -this.opts.slideOffset) : 0) + 'px';
				}
				if(this.opts.fade) {
					from.opacity = hide ? this.$tip.css('opacity') : 0;
					to.opacity = hide ? 0 : this.opacity;
				}
				this.$tip.css(from).animate(to, this.opts[hide ? 'hideAniDuration' : 'showAniDuration']);
			}
			hide ? this.$tip.queue($.proxy(this.reset, this)) : this.$tip.css('visibility', 'inherit');
			this.$tip.data('active', !active);
		},
		disable: function() {
			this.reset();
			this.disabled = true;
		},
		enable: function() {
			this.disabled = false;
		},
		destroy: function() {
			this.reset();
			this.$tip.remove();
			delete this.$tip;
			this.content = null;
			this.$elm.unbind('.poshytip').removeData('title.poshytip').removeData('poshytip');
			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.opts.alignTo == 'cursor') {
				if(this.opts.cursorViewport==1) {
					xL = this.eventX + (this.opts.alignX != 'inner-right' ? 0 : this.tipOuterW);	// left edge
					xC = this.eventX + 1;// - Math.floor(this.tipOuterW / 2);				// h center
					xR = this.eventX - (this.opts.alignX != 'inner-left' ? 0 : this.tipOuterW);	// right edge
					yT = this.eventY;// + (this.opts.alignY != 'inner-bottom' ? 0 : 0);	// top edge
					yC = this.eventY;// + 8;				// v center
					yB = this.eventY;// (this.opts.alignY != 'inner-top' ? 0 : 0);	// bottom edge
				} else {
					xL = xC = xR = this.eventX;
					yT = yC = yB = this.eventY;
				}
			} else { // this.opts.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.opts.alignX != 'inner-right' ? 0 : elm.w);	// left edge
				xC = xL + Math.floor(elm.w / 2);				// h center
				xR = xL + (this.opts.alignX != 'inner-left' ? 0 : elm.w);	// right edge
				yT = elm.t + (this.opts.alignY != 'inner-bottom' ? 0 : elm.h);	// top edge
				yC = yT + Math.floor(elm.h / 2);				// v center
				yB = yT + (this.opts.alignY != 'inner-top' ? elm.h : 0);	// bottom edge
			}

			// keep in viewport and calc arrow position

//if(this.opts.alignX=="inner-left") this.opts.alignX="inner-right";

			switch (this.opts.alignX) {
				case 'right':
				case 'inner-left':
					if(this.opts.alignX == 'inner-left') pos.arrow="bottominleft";
					if(this.opts.alignX == 'right' || this.opts.alignY == 'center') pos.arrow = 'left';
					pos.l = xR + this.opts.offsetX;
					if(this.opts.cursorViewport==1 && this.opts.alignTo=='cursor') {
						if(pos.l + this.tipOuterW + 5 > win.l + win.w) {
							pos.l = xR - this.opts.offsetX - 1 - this.tipOuterW;
							pos.arrow = 'right';
						}
					} else {
						pos.l = xL + this.opts.offsetX;

						if(this.opts.alignX=='inner-left') {
							if(pos.l + this.tipOuterW + 5 > win.l + win.w || this.opts.forceToLeft==true) {
								pos.l = xR - this.tipOuterW - this.opts.offsetX;
								pos.arrow = 'bottominright';
							}
						} else {
							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'
					if(this.opts.alignX == 'left' || this.opts.alignY == 'center') pos.arrow = 'right';
					if(this.opts.cursorViewport==1 && this.opts.alignTo=='cursor') {
						pos.l = xL - this.tipOuterW - this.opts.offsetX;
						if(this.opts.alignX=='inner-right') {
							pos.arrow = 'bottominleft';
							if(pos.l + this.tipOuterW + 5 > win.l + win.w) {
								pos.l = xR - this.tipOuterW + this.opts.offsetX;
								pos.arrow = 'bottominright';
							}
						} else {
							if(pos.l - 5 < win.l) {
								pos.l = xL + this.opts.offsetX;
								pos.arrow = 'left';
							}
						}
					} else {
						pos.l = xL - this.tipOuterW - this.opts.offsetX;
						if(pos.l < win.l) pos.l = win.l;
					}
			}

			switch (this.opts.alignY) {
				case 'bottom':
				case 'inner-top':
					pos.t = yB + this.opts.offsetY;
					// 'left' and 'right' need priority for 'target'
					if(!pos.arrow || this.opts.alignTo == 'cursor') pos.arrow = 'top';
					if(pos.t + this.tipOuterH > win.t + win.h) {
						pos.t = yT - this.tipOuterH - this.opts.offsetY;
						if(pos.arrow == 'top') pos.arrow = 'bottom';
					}
				break;
				case 'center':
					if(this.opts.cursorViewport==1) {
						pos.t = yT - 16;
						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;

					} else {
						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'
					if(this.opts.cursorViewport==1) {
						if(!pos.arrow || this.opts.alignTo == 'cursor') {
							if(pos.arrow!='bottominleft' && pos.arrow!='bottominright') pos.arrow = 'bottom';
						}
						pos.t = yT - this.tipOuterH - this.opts.offsetY;
						if(pos.t < win.t) {
							pos.t = yB + (this.opts.alignTo=='cursor' ? 16:0) + this.opts.offsetY;
							if(pos.arrow == 'bottom') pos.arrow = 'top';
							if(pos.arrow == 'bottominleft') pos.arrow = 'topinleft';
							if(pos.arrow == 'bottominright') pos.arrow = 'topinright';
						}
					} else {
						if(!pos.arrow || this.opts.alignTo == 'cursor') pos.arrow = 'bottom';
						// 'left' and 'right' need priority for 'target'
						if(!pos.arrow || this.opts.alignTo == 'cursor') pos.arrow = 'bottom';

						if(pos.t < win.t) {
							pos.t = yB + this.opts.offsetY;
							if(pos.arrow == 'bottom') 	pos.arrow = 'top';
						}
					}
			}
			this.pos = pos;
		}
	};

	$.fn.poshytip = function(options) {
		if(typeof options == 'string') {
			var args = arguments, method = options;
			Array.prototype.shift.call(args);
			// unhook live events if'destroy' is called
			if(method=='destroy') this.die('mouseenter.poshytip').die('focus.poshytip');
			if(method=='exists') return ($(this).data('poshytip')==undefined ? false : true);

			return this.each(function() {
				var poshytip = $(this).data('poshytip');
				if(poshytip && poshytip[method]) poshytip[method].apply(poshytip, args);
			});
		}

		var opts = $.extend({}, $.fn.poshytip.defaults, options);

		// generate CSS for this tip class ifnot already generated
		if(!$('#poshytip-css-' + opts.className)[0])
			$(['<style id="poshytip-css-',opts.className,'" type="text/css">',
				'div.',opts.className,'{visibility:hidden;position:absolute;top:0;left:0;}',
				'div.',opts.className,' table, div.',opts.className,' td{margin:0;font-family:inherit;font-size:inherit;font-weight:inherit;font-style:inherit;font-variant:inherit;}',
				'div.',opts.className,' td.tip-bg-image span{display:block;font:1px/1px sans-serif;height:',opts.bgImageFrameSize,'px;width:',opts.bgImageFrameSize,'px;overflow:hidden;}',
				'div.',opts.className,' td.tip-right{background-position:100% 0;}',
				'div.',opts.className,' td.tip-bottom{background-position:100% 100%;}',
				'div.',opts.className,' td.tip-left{background-position:0 100%;}',
				'div.',opts.className,' div.tip-inner{background-position:-',opts.bgImageFrameSize,'px -',opts.bgImageFrameSize,'px;}',
				'div.',opts.className,' div.tip-arrow{visibility:hidden;position:absolute;overflow:hidden;font:1px/1px sans-serif;}',
			'</style>'].join('')).appendTo('head');

		// check ifwe need to hook live events
		if(opts.liveEvents && opts.showOn != 'none') {
			var deadOpts = $.extend({}, opts, { liveEvents: false });
			switch (opts.showOn) {
				case 'hover':
					this.live('mouseenter.poshytip', function() {
						var $this = $(this);
						if(!$this.data('poshytip'))
							$this.poshytip(deadOpts).poshytip('mouseenter');
					});
					break;
				case 'focus':
					this.live('focus.poshytip', function() {
						var $this = $(this);
						if(!$this.data('poshytip'))
							$this.poshytip(deadOpts).poshytip('show');
					});
					break;
			}
			return this;
		}

		return this.each(function() {
			new $.Poshytip(this, opts);
		});
	};

	// default settings
	$.fn.poshytip.defaults = {
		content: 		'[title]',	// content to display ('[title]', 'string', element, function(updateCallback){...}, jQuery)
		className:		'tip',	// class for the tips
		classNameExtra:  '',
		bgImageFrameSize:	10,		// size in pixels for the background-image (ifset in CSS) frame around the inner content of the tip
		showTimeout:		0,		// timeout before showing the tip (in milliseconds 1000 == 1 second)
		hideTimeout:		0,		// timeout before hiding the tip
		timeOnScreen:		0,		// timeout before automatically hiding the tip after showing it (set to > 0 in order to activate)
		showOn:				'hover',	// handler for showing the tip ('hover', 'focus', 'none') - use 'none' to trigger it manually
		liveEvents:			false,		// use live events
		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 ifalignTo:'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 ifalignTo:'target'
		offsetX:				-22,		// offset X pixels from the default position - doesn't matter ifalignX:'center'
		offsetY:				18,		// offset Y pixels from the default position - doesn't matter ifalignY:'center'
		allowTipHover:		false,		// allow hovering the tip without hiding it onmouseout of the target - matters only ifshowOn:'hover'
		followCursor:		true,		// ifthe tip should follow the cursor - matters only ifshowOn:'hover' and alignTo:'cursor'
		cursorViewport:	true,
		forceToLeft:		false,
		fade: 				false,		// use fade animation
		slide: 				false,		// use slide animation
		slideOffset: 		4,		// slide animation offset
		showAniDuration: 	0,		// show animation duration - set to 0 ifyou don't want show animation
		hideAniDuration: 	0		// hide animation duration - set to 0 ifyou don't want hide animation
	};

})(jQuery);