/**
 * jQuery UI Bubbletip 1.0
 *
 * Copyright (c) 2011, SuccesScolaire.ca
 *
 * Version:
 *      1.0 (based on bubbletip v.1.0.6 by UhLeeKa)
 *
 * Licensed under the GNU Lesser General Public License:
 *     http://www.gnu.org/licenses/lgpl-3.0.html
 *
 * Depends:
 *   jquery.ui.core.js
 *   jquery.ui.widget.js
 */
(function($, undefined) {
	$.widget('ui.bubbletip', {
		version: '1.0',
		
		options: {
			positionAt: 'element', // element | body | mouse
			positionAtElement: this.element,
			offsetTop: 0,
			offsetLeft: 0,
			deltaPosition: 30,
			deltaDirection: 'up', // direction: up | down | left | right
			animationDuration: 250,
			animationEasing: 'swing', // linear | swing
			bindShow: 'mouseover', // mouseover | focus | click | etc.
			bindHide: 'mouseout', // mouseout | blur | etc.
			delayShow: 0,
			delayHide: 500,
			calculateOnShow: false,
			closable: true,
			width: false
		},
		
		timeoutAnimate: null,
		isActive: false,
		isHiding: false,
		
		// Calculated metrics
		metrics: {
			top: 0,
			left: 0,
			delta: 0,
			mouseTop: 0,
			mouseLeft: 0,
			tipHeight: 0 //,
			//bindShow: (this.options.bindShow + ' ').replace(/ +/g, '.bubbletip' + _bindIndex),
			//bindHide: (this.options.bindHide + ' ').replace(/ +/g, '.bubbletip' + _bindIndex)
		},
		
		_create: function() {
			var self = this;
			
			// Validate options
			if ( ! this.options.positionAt.match(/^element|body|mouse$/i)) {
				this.options.positionAt = 'element';
			}
			
			if ( ! this.options.deltaDirection.match(/^up|down|left|right$/i)) {
				this.options.deltaDirection = 'up';
			}
			
			var wrapper;
			var tip = this.element;
			
			var tipMetrics = {
				'width': this.options.width || tip.width(),
				'height': this.options.height || tip.height()
			};
			
			// create the wrapper table element
			if (this.options.deltaDirection.match(/^up$/i)) {
				wrapper = $('<table cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td><table class="bt-bottom" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-bottomright"></td></tr></tbody></table>');
			}
			else if (this.options.deltaDirection.match(/^down$/i)) {
				wrapper = $('<table cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td><table class="bt-top" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
			}
			else if (this.options.deltaDirection.match(/^left$/i)) {
				wrapper = $('<table cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right-tail"><div class="bt-right"></div><div class="bt-right-tail"></div><div class="bt-right"></div></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
			}
			else if (this.options.deltaDirection.match(/^right$/i)) {
				wrapper = $('<table cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left-tail"><div class="bt-left"></div><div class="bt-left-tail"></div><div class="bt-left"></div></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
			}
			
			// Move the tip element into the content section of the wrapper
			wrapper.find('.bt-content').append(tip);
			
			this.element = wrapper;
			
			// append the wrapper to the document body
			this.element.appendTo('body').hide();
			
			tip.show();
			
			// Restore tip width and height (hack for FF 3.6)
			tip.css(tipMetrics);
			
			this.element.addClass('ui-bubbletip');
			
			if (this.options.closable) {
				this.element.addClass('closable');
				
				this.element.find('td.bt-topright').click(function() {
					self.hide();
				});
			}
			
			// Handle left|right delta
			if (this.options.deltaDirection.match(/^left|right$/i)) {
				// Tail is 40px, so divide height by two and subtract 20px;
				this.metrics.tipHeight = parseInt(tip.height() / 2) + (tip.height() % 2);
				
				this.metrics.tipHeight = (this.metrics.tipHeight < 20) ? 1 : this.metrics.tipHeight - 20;
				
				if (this.options.deltaDirection.match(/^left$/i)) {
					this.element.find('div.bt-right').css('height', this.metrics.tipHeight + 'px');
				}
				else {
					this.element.find('div.bt-left').css('height', this.metrics.tipHeight + 'px');
				}
			}
			
			// Set the opacity of the wrapper to 0
			this.element.css('opacity', 0);
			
			// Execute initial calculations
			this._calculate();
			
			this.element.hide();
		},
		
		_destroy: function() {
			this.element.removeClass('ui-bubbletip');
		},
		
		show: function() {
			var self = this;
			
			if (this.timeoutAnimate) {
				clearTimeout(this.timeoutAnimate);
			}
			if (this.options.delayShow === 0) {
				this.doShow();
			} else {
				this.timeoutAnimate = setTimeout(function() {
					self.doShow();
				}, this.options.delayShow);
			}
		},
		
		doShow: function() {
			var self = this;
			var animation;
			
			if (this.isActive) { // The tip is currently showing; do nothing
				return;
			}
			
			this.isActive = true;
			
			if (this.isHiding) { // The tip is currently hiding; interrupt and start showing again
				this.element.stop(true, false);
			}
			
			if (this.options.calculateOnShow) {
				this._calculate();
			}
			
			if (this.options.positionAt.match(/^element|body$/i)) {
				if (this.options.deltaDirection.match(/^up|down$/i)) {
					if ( ! this.isHiding) {
						this.element.css('top', parseInt(this.metrics.top + this.metrics.delta) + 'px');
					}
					
					animation = {'top': this.metrics.top + 'px'};
				}
				else {
					if ( ! this.isHiding) {
						this.element.css('left', parseInt(this.metrics.left + this.metrics.delta) + 'px');
					}
					
					animation = {'left': this.metrics.left + 'px'};
				}
			}
			else {
				if (this.options.deltaDirection.match(/^up|down$/i)) {
					if ( ! this.isHiding) {
						this.metrics.mouseTop = e.pageY + this.metrics.top;
						this.element.css({'top': parseInt(this.metrics.mouseTop + this.metrics.delta) + 'px', 'left': parseInt(e.pageX - (this.element.width() / 2)) + 'px'});
					}
					
					animation = {'top': this.metrics.mouseTop + 'px'};
				}
				else {
					if ( ! this.isHiding) {
						this.metrics.mouseLeft = e.pageX + this.metrics.left;
						this.element.css({'left': parseInt(this.metrics.mouseLeft + this.metrics.delta) + 'px', 'top': parseInt(e.pageY - (this.element.height() / 2)) + 'px'});
					}
					
					animation = {'left': this.metrics.left + 'px'};
				}
			}
			
			this.isHiding = false;
			this.element.show();
			
			animation = $.extend(animation, { 'opacity': 1 });
			
			this.element.animate(animation, this.options.animationDuration, this.options.animationEasing, function() {
				self.element.css('opacity', '');
				self.isActive = true;
			});
		},
		
		hide: function() {
			var self = this;
			var animation;
			
			this.isActive = false;
			this.isHiding = true;
			
			if (this.options.positionAt.match(/^element|body$/i)) {
				if (this.options.deltaDirection.match(/^up|down$/i)) {
					animation = {'top': parseInt(this.metrics.top - this.metrics.delta) + 'px'};
				}
				else {
					animation = {'left': parseInt(this.metrics.left - this.metrics.delta) + 'px'};
				}
			}
			else {
				if (this.options.deltaDirection.match(/^up|down$/i)) {
					animation = {'top': parseInt(this.metrics.mouseTop - this.metrics.delta) + 'px'};
				}
				else {
					animation = {'left': parseInt(this.metrics.mouseLeft - this.metrics.delta) + 'px'};
				}
			}
			
			animation = $.extend(animation, {'opacity': 0});
			
			this.element.animate(animation, this.options.animationDuration, this.options.animationEasing, function() {
				self.element.hide();
				self.isHiding = false;
			});
		},
		
		_calculate: function() {
			// calculate values
			if (this.options.positionAt.match(/^element$/i)) {
				var offset = this.options.positionAtElement.offset();
				
				if (this.options.deltaDirection.match(/^up$/i)) {
					this.metrics.top = offset.top + this.options.offsetTop - this.element.outerHeight();
					this.metrics.left = offset.left + this.options.offsetLeft + ((this.options.positionAtElement.outerWidth() - this.element.outerWidth()) / 2);
					this.metrics.delta = this.options.deltaPosition;
				}
				else if (this.options.deltaDirection.match(/^down$/i)) {
					this.metrics.top = offset.top + this.options.positionAtElement.outerHeight() + this.options.offsetTop;
					this.metrics.left = offset.left + this.options.offsetLeft + ((this.options.positionAtElement.outerWidth() - this.element.outerWidth()) / 2);
					this.metrics.delta = -this.options.deltaPosition;
				}
				else if (this.options.deltaDirection.match(/^left$/i)) {
					this.metrics.top = offset.top + this.options.offsetTop + ((this.options.positionAtElement.outerHeight() - this.element.outerHeight()) / 2);
					this.metrics.left = offset.left + this.options.offsetLeft + 5 - this.element.outerWidth();
					this.metrics.delta = this.options.deltaPosition;
				}
				else if (this.options.deltaDirection.match(/^right$/i)) {
					this.metrics.top = offset.top + this.options.offsetTop + ((this.options.positionAtElement.outerHeight() - this.element.outerHeight()) / 2);
					this.metrics.left = offset.left + this.options.positionAtElement.outerWidth() + this.options.offsetLeft;
					this.metrics.delta = -this.options.deltaPosition;
				}
			}
			else if (this.options.positionAt.match(/^body$/i)) {
				if (this.options.deltaDirection.match(/^up|left$/i)) {
					this.metrics.top = this.options.offsetTop;
					this.metrics.left = this.options.offsetLeft;
					
					// up or left
					this.metrics.delta = this.options.deltaPosition;
				}
				else {
					if (this.options.deltaDirection.match(/^down$/i)) {
						this.metrics.top = parseInt(this.options.offsetTop + this.element.outerHeight());
						this.metrics.left = this.options.offsetLeft;
					}
					else {
						this.metrics.top = this.options.offsetTop;
						this.metrics.left = parseInt(this.options.offsetLeft + this.element.outerWidth());
					}
					
					// down or right
					this.metrics.delta = -this.options.deltaPosition;
				}
			}
			else if (this.options.positionAt.match(/^mouse$/i)) {
				if (this.options.deltaDirection.match(/^up|left$/i)) {
					if (this.options.deltaDirection.match(/^up$/i)) {
						this.metrics.top = -(this.options.offsetTop + this.element.outerHeight());
						this.metrics.left = this.options.offsetLeft;
					}
					else if (this.options.deltaDirection.match(/^left$/i)) {
						this.metrics.top = this.options.offsetTop;
						this.metrics.left = -(this.options.offsetLeft + this.element.outerWidth());
					}
					
					// up or left
					this.metrics.delta = this.options.deltaPosition;
				}
				else {
					this.metrics.top = this.options.offsetTop;
					this.metrics.left = this.options.offsetLeft;
					
					// down or right
					this.metrics.delta = -this.options.deltaPosition;
				}
			}
			
			// handle the wrapper (element|body) positioning
			if (this.options.positionAt.match(/^element|body$/i)) {
				this.element.css({
					'position': 'absolute',
					'top': this.metrics.top + 'px',
					'left': this.metrics.left + 'px'
				});
			}
		}
	});
})(jQuery);

