/*!
* qTip - Speech bubble tips plugin
*
* This plugin requires the main qTip library in order to function.
* Download it here: http://craigsworks.com/projects/qtip/
*
* Copyright (c) 2009 Craig Thompson
* http://craigsworks.com
*
* Launch: December 2009
* Version: UNSTABLE REVISION CODE! Visit http://craigsworks.com/projects/qtip/ for stable code
*
* Licensed under MIT
* http://www.opensource.org/licenses/mit-license.php
*/

"use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
/*jslint onevar: true, browser: true, forin: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, maxerr: 300 */
/*global window: false, jQuery: false */
(function($)
{
	// Munge the primitives - Paul Irish
	var TRUE = true,
		FALSE = false,
		NULL = null;

	// Check qTip library is present
	if(!$.fn.qtip) {
		if(window.console){ window.console.error('This plugin requires the qTip library.',''); }
		return FALSE;
	}

	// Tip coordinates calculator
	function calculate(corner, width, height)
	{
		// Define tip coordinates in terms of height and width values
		var tips = {
			bottomright:	[[0,0],				[width,height],		[width,0]],
			bottomleft:		[[0,0],				[width,0],				[0,height]],
			topright:		[[0,height],		[width,0],				[width,height]],
			topleft:			[[0,0],				[0,height],				[width,height]],
			topcenter:		[[0,height],		[width/2,0],			[width,height]],
			bottomcenter:	[[0,0],				[width,0],				[width/2,height]],
			rightcenter:	[[0,0],				[width,height/2],		[0,height]],
			leftcenter:		[[width,0],			[width,height],		[0,height/2]]
		};
		tips.lefttop = tips.bottomright; tips.righttop = tips.bottomleft;
		tips.leftbottom = tips.topright; tips.rightbottom = tips.topleft;

		return tips[corner];
	}

	function Tip(qTip, command)
	{
		var self = this;
		self.qTip = qTip;

		self.tip = NULL;
		self.corner = NULL;
		self.mimic = NULL;
		self.size = {
			width: qTip.options.style.tip.width,
			height: qTip.options.style.tip.height
		};
		self.adjust = qTip.options.style.tip.adjust || 0;
		self.method = qTip.options.style.tip.method || 'canvas';
		
		self.cache = { 
			top: 0, 
			left: 0, 
			corner: { string: function(){} }, 
			radius: FALSE
		};
		self.checks = {
			'^position.my$': function() {
				// Check if tip corner is automatic and update if so
				if(this.get('style.tip.corner') === TRUE) {
					self.checks['^style.tip.(corner|mimic|method)'].call(this);
				}
			},
			'^style.tip.(corner|mimic|method)': function() {
				// Re-determine tip type
				self.determine();
				
				// Update tip if needed
				if(this.get('style.tip.method') !== 'class') {
					self.update();
				}
				
				// Only update the position if mouse isn't the target
				if(this.get('position.target') !== 'mouse') {
					this.reposition();
				}
			},
			'^style.tip.(height|width)': function() {
				// Re-set dimensions and redraw the tip
				self.size = {
					width: qTip.options.style.tip.width,
					height: qTip.options.style.tip.height
				};
				self.create();
				self.update();

				// Reposition the tooltip
				qTip.reposition();
			},
			'^style.tip.fnCss': function() {
				self.update();
			}
		};

		// Tip position method
		function position(corner)
		{
			// Return if tips are disabled or tip is not yet rendered
			if(qTip.options.style.tip.corner === FALSE || !self.tip) { return FALSE; }
			
			// Inherit corner if not provided
			corner = corner || self.corner;
			
			var tip = self.tip,
				corners  = ['left', 'right'],
				ieAdjust = { left: 0, right: 0, top: 0, bottom: 0 },
				adjust = 0;

			// Reet initial position
			tip.css({ top: '', bottom: '', left: '', right: '', margin: '' });

			// Setup corners to be adjusted
			corners[ corner.precedance === 'y' ? 'push' : 'unshift' ]('top', 'bottom');

			// Setup adjustments
			if($.browser.msie && self.method === 'vml')
			{
				ieAdjust = {
					left: 1, top: 1,
					right: (corner.precedance === 'y') ? 1 : 2,
					bottom: (corner.precedance === 'x') ? 1 : 2
				};
			}

			// Adjust primary corners
			switch(corner[ corner.precedance === 'y' ? 'x' : 'y' ])
			{
				case 'center':
					tip.css(corners[0], '50%').css('margin-'+corners[0], -(self.size[ (corner.precedance === 'y') ? 'width' : 'height' ] / 2));
				break;

				case corners[0]:
					tip.css(corners[0], ieAdjust[ corners[0] ] + self.adjust);
				break;

				case corners[1]:
					tip.css(corners[1], ieAdjust[ corners[1] ] + self.adjust);
				break;
			}

			// Adjust secondary corners
			adjust = self.size[ (corner.precedance === 'x') ? 'width' : 'height' ];
			if(corner[corner.precedance] === corners[2]) {
				tip.css(corners[2], -ieAdjust[ corners[2] ] - adjust);
			}
			else {
				tip.css(corners[3], ieAdjust[ corners[3] ] - adjust);
			}
		}

		function adjust(event, api, position) {
			// Only continue if tip adjustments are enabled
			if(!self.corner.adjust) {
				return FALSE;
			}

			var newCorner = $.extend({}, self.corner),
				newType = self.mimic.adjust ? $.extend({}, self.mimic) : null,
				precedance = newCorner.precedance === 'y' ? ['y', 'top', 'left', 'height'] : ['x', 'left', 'top', 'width'],
				adjusted = position.adjusted,
				walk = [newCorner, newType];

			// Adjust tip corners
			$.each(walk, function() {
				if(adjusted.left) {
					this.x = this.x === 'center' ? (adjusted.left > 0 ? 'left' : 'right') : (this.x === 'left' ? 'right' : 'left');
				}
				if(adjusted.top) {
					this.y = this.y === 'center' ? (adjusted.top > 0 ? 'top' : 'bottom') : (this.y === 'top' ? 'bottom' : 'top');
				}
			});

			// Adjust tooltip position if needed in relation to tip element
			if(self.method !== 'class') {
				position[ precedance[1] ] += (newCorner[ precedance[0] ] === precedance[1] ? 1 : -1) * self.size[ precedance[3] ];
				position[ precedance[2] ] -= self.adjust;
			}

			// Update and redraw the tip if needed
			if(newCorner.string() !== self.cache.corner.string() && (self.cache.top !== adjusted.top || self.cache.left !== adjusted.left)) { 
				self.update(newCorner, newType);
			}

			// Cache overflow details
			self.cache.left = adjusted.left;
			self.cache.top = adjusted.top;
			self.cache.corner = newCorner;
		}

		$.extend(self, {
			init: function()
			{
				// Determine tip corner and type
				var properties = self.determine();
				if(properties === FALSE){ return FALSE; }

				// Bind update events
				qTip.elements.tooltip.bind('tooltipmove.tip', adjust);

				// Check if rendering method is possible and if not fall back to polygonal css
				if(self.method === 'canvas') {
					self.method = $.browser.msie ? 'vml' : !$('<canvas />')[0].getContext ? 'polygon' : 'canvas';
				}

				// Create a new tip
				self.create();
				self.update();

				return self;
			},

			determine: function()
			{
				var corner = qTip.options.style.tip.corner,
					type = qTip.options.style.tip.mimic || corner,
					at = qTip.options.position.at,
					my = qTip.options.position.my;
					if(my.string) { my = my.string(); }

				if(corner === FALSE || (my === FALSE && at === FALSE)) {
					return FALSE;
				}
				else{
					if(corner === TRUE) {
						self.corner = new $.fn.qtip.plugins.Corner(my);
						self.corner.adjust = TRUE;
					}
					else if(!corner.string) {
						self.corner = new $.fn.qtip.plugins.Corner(corner);
					}

					if(type === TRUE) {
						self.mimic = new $.fn.qtip.plugins.Corner(my);
					}
					else if(!type.string) {
						self.mimic = new $.fn.qtip.plugins.Corner(type);
						self.mimic.precedance = self.corner.precedance;
						self.mimic.adjust = TRUE;
					}
				}

				self.method = qTip.options.style.tip.method || 'canvas';

				return self.corner.string() !== 'centercenter';
			},

			create: function()
			{
				if(self.method === 'class') { return self; }

				var width = self.size.width,
					height = self.size.height;

				// Create tip element and prepend to the tooltip if needed
				if(self.tip){ self.tip.remove(); }
				self.tip = $('<div class="ui-tooltip-tip ui-widget-content"></div>').css(self.size).prependTo(qTip.elements.tooltip);

				// Create tip element
				switch(self.method)
				{
					case 'canvas':
						self.tip.append('<canvas height="'+height+'" width="'+width+'"></canvas>');
					break;
						
					case 'vml':
						self.tip.html('<vml:shape coordorigin="0 0" coordsize="'+width+' '+height+'" stroked="FALSE" ' +
							' style="behavior:url(#default#VML); display:inline-block; antialias:TRUE;' +
							' width:'+width+'px; height:'+height+'px; vertical-align:'+self.corner.y+';"></vml:shape>');
					break;

					case 'polygon':
						self.tip.append('<div class="ui-tooltip-tip-inner"></div>');
					break;
				}

				return self;
			},

			update: function(corner, type)
			{
				var width = self.size.width,
					height = self.size.height,
					regular = 'px solid ',
					transparent = 'px solid transparent',
					color, context, toSet, path, coords, inner;

				// Re-determine tip if not already set
				if(!type){ type = corner ? corner: self.mimic; }
				if(!corner){ corner = self.corner; }
				
				// Inherit tip corners from corner object if not present
				if(type.x === 'false') { type.x = corner.x; }
				if(type.y === 'false') { type.y = corner.y; }

				// If class rendering is chosen, simply set tooltip class
				if(self.method === 'class') {
					qTip.elements.tooltip.attr('class', function(i, val) {
						return $(this).attr('class').replace(/ui-tooltip-tip-\w+/i, '');
					})
					.addClass('ui-tooltip-tip-' + corner.abbreviation());
				}
				
				// Actual tip rendering enabled
				else {
					// Find inner child of tip element
					inner = self.tip.children().eq(0);
					
					// Detect new tip colour and reset background to transparent
					color = self.tip.css('background-color', '').css('background-color');
					color = (color === 'transparent' || color === 'rgba(0, 0, 0, 0)') ? qTip.elements.wrapper.css('border-top-color') : color;
					self.tip.css('background-color', 'transparent');
					regular += color;
					
					// Create tip element
					switch(self.method)
					{
						case 'canvas':
							// Determine tip coordinates based on dimensions
							coords = calculate(type.string(), width, height);
							
							// Setup canvas properties
							context = inner.get(0).getContext('2d');
							context.fillStyle = color;
							context.miterLimit = 0;
							
							// Draw the canvas tip (Delayed til after DOM creation)
							context.clearRect(0,0,3000,3000);
							context.beginPath();
							context.moveTo(coords[0][0], coords[0][1]);
							context.lineTo(coords[1][0], coords[1][1]);
							context.lineTo(coords[2][0], coords[2][1]);
							context.closePath();
							context.fill();
							break;
							
						case 'vml':
							// Determine tip coordinates based on dimensions
							coords = calculate(type.string(), width, height);
							
							// Create coordize and tip path using tip coordinates
							path = 'm' + coords[0][0] + ',' + coords[0][1] + ' l' + coords[1][0] +
							',' + coords[1][1] + ' ' + coords[2][0] + ',' + coords[2][1] + ' xe';
							
							// Set new attributes
							inner.attr({ 'path': path, 'fillcolor': color });
							break;
							
						case 'polygon':
							// Reset borders
							inner.removeAttr('style');
							
							// Determine what border corners to set
							toSet = {
								x: type.precedance === 'x' ? (type.x === 'left' ? 'right' : 'left') : type.x,
								y: type.precedance === 'y' ? (type.y === 'top' ? 'bottom' : 'top') : type.y
							};
							
							// Setup borders based on corner values
							if(type.x === 'center' || type.y === 'center') {
								path = type.x === 'center' ? ['top', 'bottom', toSet.y, width] : ['left', 'right', toSet.x, height];
								
								inner.css('border-' + path[2], path[3] + regular)
								.css('border-' + path[0], (path[3] / 2) + transparent)
								.css('border-' + path[1], (path[3] / 2) + transparent);
							}
							else
							{
								inner.css('border-width', (height / 2) + 'px ' + (width / 2) + 'px')
								.css('border-' + toSet.x, (width / 2) + regular)
								.css('border-' + toSet.y, (height / 2) + regular);
							}
							break;
					}
					
					// Update position
					position(corner);
				}

				return self;
			},

			destroy: function()
			{
				// Remove previous tip if present
				if(self.tip) {
					self.tip.remove();
				}

				// Remove bound events
				qTip.elements.tooltip.unbind('tooltipmove.tip');
			}
		});
	}

	$.fn.qtip.plugins.tip = function(qTip)
	{
		var api = qTip.plugins.tip,
			opts = qTip.options.style.tip;

		// Make sure tip options are present
		if(opts) {
			// An API is already present,
			if(api) {
				return api;
			}
			// No API was found, create new instance
			else {
				qTip.plugins.tip = new Tip(qTip);

				if(qTip.plugins.tip.determine()) {
					qTip.plugins.tip.init();
				}
				else {
					delete qTip.plugins.tip;
				}

				return qTip.plugins.tip;
			}
		}
	};

	// Initialize tip on render
	$.fn.qtip.plugins.tip.initialize = 'render';

	// Setup plugin sanitization options
	$.fn.qtip.plugins.tip.sanitize = function(opts)
	{
		if(opts.style !== undefined && opts.style.tip !== undefined) {
			if(typeof opts.style.tip !== 'object'){ opts.style.tip = { corner: opts.style.tip }; }
			if(typeof opts.style.tip.method !== 'string'){ opts.style.tip.method = 'canvas'; }
			if(!(/canvas|polygon|class/i).test(opts.style.tip.method)){ opts.style.tip.method = 'canvas'; }
			if(typeof opts.style.tip.width !== 'number'){ opts.style.tip.width = 14; }
			if(typeof opts.style.tip.height !== 'number'){ opts.style.tip.height = 14; }
		}
	};
}(jQuery));
