/*!
 * jquery.qtip. The jQuery tooltip plugin
 *
 * Copyright (c) 2009 Craig Thompson
 * http://craigsworks.com
 *
 * Licensed under MIT
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Launch   : February 2009
 * Version  : TRUNK - NOT FOR USE IN PRODUCTION ENVIRONMENTS!!!!
 * Debugging: jquery.qtip.debug.js
 *
 * FOR STABLE VERSIONS VISIT: http://craigsworks.com/projects/qtip/download/
 */
(function($)
{
   // Implementation
   $.fn.qtip = function(options)
   {
      var apis, i, interfaces, command, opts;

      // Execute API command if present
      if(typeof options == 'string')
      {
         // Find all related API's
         apis = []; interfaces = $.fn.qtip.interfaces;
         i = interfaces.length; while(i--)
         {
            if(interfaces[i])
            {
               if($(this).attr('qtip') && $(this).add(interfaces[i].elements.tooltip).length == 1)
                  apis.unshift(interfaces[i]);
               else if($(this).add(interfaces[i].elements.target).length == 1)
                  apis.push(interfaces[i]);
            }
         }

         // API were found, return requested attribute
         if(apis.length > 0)
         {
            // Lowercase the command
            command = options.toLowerCase();

            if(command == 'id') return apis[0].id;
            else if(command == 'api') return apis[0];
            else if(command == 'interfaces') return apis;
            else
            {
               return $(this).each(function()
               {
                  // Execute command on chosen qTips
                  var i = apis.length; while(i--)
                  {
                     // Render and destroy commands don't require tooltip to be rendered
                     if(command == 'render') interfaces[i].render();
                     else if(command == 'destroy') interfaces[i].destroy();

                     // Only call API if tooltip is rendered and it wasn't a render or destroy call
                     else if(interfaces[i].status.rendered === true)
                     {
                        if(command == 'show') interfaces[i].show();
                        else if(command == 'hide') interfaces[i].hide();
                        else if(command == 'focus') interfaces[i].focus();
                        else if(command == 'disable') interfaces[i].disable(true);
                        else if(command == 'enable') interfaces[i].disable(false);
                     };
                  };
               })
            }
         }

         // No API's were found, no tooltips are present
         else
         {
            $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.NO_TOOLTIP_PRESENT, false);
            return $(this); // Return original elements as per jQuery guidelines
         }
      }

      // No API commands. validate provided options and setup qTips
      else
      {
         // Set null options object if no options are provided
         if(!options) options = {};

         // Sanitize option data
         if(typeof options.content != 'object' || (options.content.jquery && options.content.length > 0)) options.content = { text: options.content };
         if(typeof options.content.title != 'object') options.content.title = { text: options.content.title };
         if(typeof options.content.url != 'object') options.content.url = { path: options.content.url };
         if(options.content.data !== null) { options.content.url.data = options.content.data; delete options.content.data }
         if(options.content.method !== null) { options.content.url.method = options.content.method; delete options.content.method }
         if(typeof options.position != 'object') options.position = { corner: options.position };
         if(typeof options.position.corner != 'object') options.position.corner = { target: options.position.corner, tooltip: options.position.corner };
         if(typeof options.show != 'object') options.show = { when: options.show };
         if(typeof options.show.when != 'object') options.show.when = { event: options.show.when };
         if(typeof options.show.effect != 'object') options.show.effect = { type: options.show.effect };
         if(typeof options.show.effect.length == 'number') options.show.effect = { type: options.show.effect.type, duration: options.show.effect.length };
         if(typeof options.hide != 'object') options.hide = { when: options.hide };
         if(typeof options.hide.when != 'object') options.hide.when = { event: options.hide.when };
         if(typeof options.hide.effect != 'object') options.hide.effect = { type: options.hide.effect };
         if(typeof options.hide.effect.length == 'number') options.hide.effect = { type: options.hide.effect.type, duration: options.hide.effect.length };
         if(typeof options.style != 'object') options.style = { name: options.style };
         options.style = sanitizeStyle(options.style);

         // Build main options object if CSS isn't in use
         opts = $.extend(true, {}, $.fn.qtip.defaults, options);

         // Inherit all style properties into one syle object and include original options
         opts.style = buildStyle.call({ options: opts }, opts.style);
         opts.user = $.extend(true, {}, options);

         // Check for options with missing plugins
         if(opts.style.border.radius !== false && !$.fn.qtip.border) $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.PLUGIN_MISSING_BORDER);
         if(opts.style.tip !== false && !$.fn.qtip.tips) $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.PLUGIN_MISSING_TIPS);
         if($(opts.position.target || this).is('area') && !$.fn.qtip.imagemap) $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.PLUGIN_MISSING_IMAGEMAP);
         if($('select, object').length && $.fn.qtip.cache.ie6 && !$.fn.qtip.bgiframe) $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.PLUGIN_MISSING_BGIFRAME);

         // Iterate each matched element
         return $(this).each(function() // Return original elements as per jQuery guidelines
         {
            var id, i, self, attribute, api, targets, events, namespace;

            // Instantiate the qTip with next available ID
            id = $.fn.qtip.nextid++;
            self = Instantiate.call($(this), id, opts);

            // Remove title and alt attributes to prevent default tooltip
            if(typeof self.options.content.text !== 'string' && !self.options.content.text.jquery)
            {
               attribute = $(this).attr('title') ? 'title' : 'alt';
               if($(this).attr(attribute))
               {
                  // Setup content cache and remove attribute
                  self.cache.content = { type: attribute, text: $(this).attr(attribute) }
                  self.options.content.text = self.cache.content.text.replace("\\n/g", '<br />');
                  $(this).removeAttr(attribute);
               }

               // Attempt to find the cached content in first created tooltip
               else
               {
                  api = $(this).qtip('interfaces'); api = api[api.length-1];
                  if(api && api.cache.content.type !== false) self.options.content.text = api.options.content.text;
               }
            }

            // Determine hide and show targets
            targets = { show: self.options.show.when.target, hide: self.options.hide.when.target };
            events = {
               show: self.options.show.when.event,
               hide: (String(self.options.hide.when.event).search(/(inactive|unfocus)/i) > -1) ? 'mouseout' : self.options.hide.when.event
            };

            // If prerendering is disabled, create tooltip on show event
            if(self.options.content.prerender === false && self.options.show.ready !== true && self.options.show.when.event !== false)
            {
               // Setup temporary events namespace
               namespace = '.qtip-'+id+'-create';

               // Bind defined show event to show target to construct and show the tooltip
               targets.show.bind(events.show+namespace, { id: id }, function(event)
               {
                  // Cache the mouse data and start the event sequence
                  var mouse = { left: event.pageX, top: event.pageY };
                  self.timers.show = setTimeout(function()
                  {
                     // Cache mouse coords,render and show the tooltip
                     self.cache.mouse = mouse;
                     self.render(); self.show();

                     // Unbind show and hide event
                     targets.show.unbind(events.show+'.qtip-'+self.id+'-create');
                     targets.hide.unbind(events.hide+'.qtip-'+self.id+'-create');
                  }
                  , self.options.show.delay);
               });

               // If hide and show targets and events aren't identical, bind hide event to reset show timer
               if(targets.show != targets.hide && self.options.show.when.event !== self.options.hide.when.event)
                  targets.hide.bind(events.hide+namespace, function(event){ clearTimeout(self.timers.show); });
            }

            // Prerendering is enabled. Set mouse position cache and render qTip
            else{ self.cache.mouse = targets.show.offset(); self.render(); }
         });
      };
   };

   // Instantiator
   function Instantiate(id, opts)
   {
      var config, obj;

      // Create unique configuration object
      config = $.extend(true, {}, opts);

      // Sanitize target options
      if(config.position.container === false) config.position.container = $(document.body);
      if(config.position.target === false) config.position.target = $(this);
      if(config.show.when.target === false) config.show.when.target = $(this);
      if(config.hide.when.target === false) config.hide.when.target = $(this);

      // Instantiate the tooltip and add API reference
      obj = new qTip($(this), config, id);
      $.fn.qtip.interfaces.push(obj);
      return obj;
   }

   // qTip constructor
   function qTip(target, options, id)
   {
      // Declare this reference
      var self = this;

      // Setup class attributes
      self.id = id;
      self.options = options;
      self.status = {
         animated: false,
         rendered: false,
         disabled: false,
         focused: false,
         hidden: true
      };
      self.elements = {
         target: target.addClass(self.options.style.classes.target).data('qtip', true), // Apply deprecated data holder
         tooltip: null,
         wrapper: null,
         content: null,
         contentWrapper: null,
         title: null,
         button: null
      };
      self.cache = {
         content: { type: false, text: '' },
         mouse: {},
         position: {}
      };
      self.timers = {};

      // Define exposed API methods
      $.extend(self, self.options.api);
   };

   // Corner object parser
   function Corner(corner)
   {
      this.x = corner.match(/left|right|middle|center/i)[0].toLowerCase();
      this.y = corner.match(/top|bottom|middle|center/i)[0].toLowerCase();
      this.precedance = (corner.charAt(0).search(/t|b/) > -1) ? 'y' : 'x';
   };
   Corner.prototype.toString = function(){ return (this.precedance == 'y') ? this.y+this.x : this.x+this.y; };
   Corner.prototype.clone = function(){ return { x: this.x, y: this.y, precedance: this.precedance, toString: this.toString } };



   /*
    * Public core methods
    */
   qTip.prototype.render = function()
   {
      var self = this, content, i, options, ajax;

      // If tooltip has already been rendered, exit
      if(self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_ALREADY_RENDERED, 'render');

      // Call API method
      self.beforeRender.call(self);

      // Create initial tooltip elements
      self.elements.tooltip =  '<div qtip="'+self.id+'" ' +
         ' class="qtip '+(self.options.style.classes.tooltip || self.options.style)+'"' +
         ' style="display:none; -moz-border-radius:0; -webkit-border-radius:0; border-radius:0;' +
         ' position:'+self.options.position.type+';">' +
         '  <div class="qtip-wrapper" style="position:relative; overflow:hidden; text-align:left;">' +
         '    <div class="qtip-contentWrapper" style="overflow:hidden;">' +
         '       <div class="qtip-content '+self.options.style.classes.content+'"></div>' +
         '</div></div></div>';

      // Append to container element
      self.elements.tooltip = $(self.elements.tooltip);
      self.elements.tooltip.appendTo(self.options.position.container).data('qtip', true); // Apply deprecated data holder

      // Setup element references
      self.elements.wrapper = self.elements.tooltip.children('div:first');
      self.elements.contentWrapper = self.elements.wrapper.children('div:first').css({ background: self.options.style.background });
      self.elements.content = self.elements.contentWrapper.children('div:first').css( jQueryStyle(self.options.style) );

      // Apply IE hasLayout fix to wrapper and content elements
      if($.browser.msie) self.elements.wrapper.add(self.elements.content).css({ zoom: 1 });

      // Set rendered status to true
      self.status.rendered = true;

      // Convert position corner values into x and y strings
      self.options.position.corner.target = new Corner(self.options.position.corner.target);
      self.options.position.corner.tooltip = new Corner(self.options.position.corner.tooltip);
      if($.fn.qtip.tips && self.options.style.tip.corner !== false) self.options.style.tip.corner = new Corner(self.options.style.tip.corner);
      if($.fn.qtip.tips && self.options.style.tip.type !== false) self.options.style.tip.type = new Corner(self.options.style.tip.type);

      // If the positioning target element is an AREA element, cache the imagemap properties
      if($.fn.qtip.imagemap && self.options.position.target.is('area') === true) $.fn.qtip.imagemap.setup.call(self);

      // If an explicit width is set, updateWidth prior to setting content to prevent 'dirty' rendering
      if(typeof self.options.style.width.value == 'number') self.updateWidth();

      // Create borders and tips if supported by the browser
      if($('<canvas/>').get(0).getContext || $.browser.msie)
      {
         // Create border
         if($.fn.qtip.border && self.options.style.border.radius > 0)
            $.fn.qtip.border.create.call(self);
         else
         {
            self.elements.contentWrapper.css({
               borderWidth: self.options.style.border.width || 0,
               borderColor: self.options.style.border.color || '',
               borderStyle: 'solid'
            });
         }

         // Create tip if enabled
         if($.fn.qtip.tips && self.options.style.tip.corner !== false) $.fn.qtip.tips.create.call(self);
      }

      // Neither canvas or VML is supported, tips and borders cannot be drawn!
      else
      {
         // Set defined border width
         self.elements.contentWrapper.css({ border: self.options.style.border.width+'px solid '+self.options.style.border.color  });

         // Reset border radius and tip
         self.options.style.border.radius = 0;
         self.options.style.tip.corner = false;

         // Inform via log
         $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.CANVAS_VML_NOT_SUPPORTED, 'render');
      };

      // Use the provided content string or DOM array
      if((typeof self.options.content.text == 'string' && self.options.content.text.length > 0)
      || (self.options.content.text.jquery && self.options.content.text.length > 0))
         content = self.options.content.text;

      // No valid content was provided, inform via log
      else
      {
         $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_VALID_CONTENT, 'render');
         if(self.options.content.url.path === false) self.destroy();
         else content = 'Loading...';
      };

      // Set the tooltips content and create title if enabled
      self.updateContent(content, false);
      if(self.options.content.title.text !== false) createTitle.call(self);

      // Assign events and show & focus tooltip if needed
      assignEvents.call(self);
      if(self.options.show.ready === true) self.show(null, self.options.show.ready);

      // Retrieve ajax content if provided
      if(self.options.content.url.path !== false)
      {
         // Setup options and sanitize
         options = self.options.content.url;
         ajax = $.extend({}, self.options.content.url);
         for(i in $.fn.qtip.defaults.content.url) delete ajax[i];

         // Load the content with specified options
         self.loadContent(options.path, options.data, options.method || 'get', ajax, true);
      };

      // Call API method and log event
      self.onRender.call(self);
      $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_RENDERED, 'render');

      return self;
   };

   qTip.prototype.show = function(event, duration)
   {
      var self = this;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'show');

      // Make sure original target is still present and if not, destroy tooltip
      if(self.elements.target.length < 1) return $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.TARGET_ELEMENT_REMOVED, 'show');

      // Only continue if element is visible
      if(self.elements.tooltip.css('display') != 'none'){ self.status.hidden = false; return self; };

      // Use initial option duration if not passed manually
      if(typeof duration != 'number') duration = self.options.show.effect.duration;

      // Clear animation queue
      self.elements.tooltip.stop(true, false);

      // Call API method and if return value is false, halt
      if(self.beforeShow.call(self, event) === false) return self;

      // Define afterShow callback method
      function afterShow()
      {
         // Reset opacity to avoid bugs and focus if it isn't static
         self.elements.tooltip.css({ opacity: '' });
         if(self.options.position.type !== 'static') self.focus();

         // Call API method and set status
         self.status.animated = false;
         self.onShow.call(self, event);

         // Prevent antialias from disappearing in IE7 by removing filter attribute
         if($.browser.msie) self.elements.tooltip.get(0).style.removeAttribute('filter');
      };

      // Maintain toggle functionality if enabled
      self.status.hidden = false;

      // Update tooltip position if it isn't static
      if(self.options.position.type !== 'static')
         self.updatePosition(event, (duration > 0 && self.options.show.effect === false));

      // Hide other tooltips if tooltip is solo
      if(self.options.show.solo === true)
      {
         var i = $.fn.qtip.interfaces.length, api; while(i--)
         {
            // Access current elements API
            api = $.fn.qtip.interfaces[i];
            // Queue the animation so positions are updated correctly
            if(api && api.status.rendered && !api.status.hidden) api.hide();
         };
      }

      // Show tooltip
      if(typeof self.options.show.effect.type == 'function')
      {
         self.options.show.effect.type.call(self, self.elements.tooltip, duration);
         self.elements.tooltip.queue(function(){ afterShow(); $(this).dequeue(); });
      }
      else
      {
         if(self.options.show.effect.type === false)
         {
            self.elements.tooltip.show();
            afterShow();
         }
         else
         {
            self.status.animated = true;
            switch(self.options.show.effect.type.toLowerCase())
            {
               case 'fade':
                  self.elements.tooltip.fadeIn(duration, afterShow);
                  break;
               case 'slide':
                  self.elements.tooltip.slideDown(duration, function()
                  {
                     afterShow();
                     if(self.options.position.type !== 'static') self.updatePosition(event, true);
                  });
                  break;
               case 'grow':
                  self.elements.tooltip.show(duration, afterShow);
                  break;
               default:
                  self.elements.tooltip.show();
                  afterShow();
                  break;
            };
         };

         // Add active class to tooltip
         self.elements.tooltip.addClass(self.options.style.classes.active);
      };

      // If inactive hide method is set, active it
      self.options.show.when.target.trigger('qtip-inactive');

      // Log event and return
      $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_SHOWN, 'show');
      return self;
   };

   qTip.prototype.hide = function(event, duration)
   {
      var self = this;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'hide');

      // Only continue if element is visible
      if(self.elements.tooltip.css('display') === 'none'){ self.status.hidden = true; return self; };

      // Use initial option duration if not passed manually
      if(typeof duration != 'number') duration = self.options.hide.effect.duration;

      // Stop show timer and animation queue
      clearTimeout(self.timers.show);
      self.elements.tooltip.stop(true, false).css({ opacity: '' });

      // Call API method and if return value is false, halt
      if(self.beforeHide.call(self, event) === false) return self;

      // Define afterHide callback method
      function afterHide()
      {
         // Reset opacity to avoid bugs and call onHide event
         self.elements.tooltip.css({ opacity: '', height: '' });
         self.onHide.call(self, event);

         // Set the animated status
         self.status.animated = false;
      };

      // Maintain toggle functionality if enabled
      self.status.hidden = true;

      // Hide tooltip
      if(typeof self.options.hide.effect.type == 'function')
      {
         self.options.hide.effect.type.call(self, self.elements.tooltip, duration);
         self.elements.tooltip.queue(function(){ afterHide(); $(this).dequeue(); });
      }
      else
      {
         if(self.options.hide.effect.type === false)
         {
            self.elements.tooltip.hide();
            afterHide();
         }
         else
         {
            self.status.animated = true;
            switch(self.options.hide.effect.type.toLowerCase())
            {
               case 'fade':
                  self.elements.tooltip.fadeOut(duration, afterHide);
                  break;
               case 'slide':
                  self.elements.tooltip.slideUp(duration, afterHide);
                  break;
               case 'grow':
                  self.elements.tooltip.hide(duration, afterHide);
                  break;
               default:
                  self.elements.tooltip.hide();
                  afterHide();
                  break;
            };
         };

         // Remove active class to tooltip
         self.elements.tooltip.removeClass(self.options.style.classes.active);
      };

      // Log event and return
      $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_HIDDEN, 'hide');
      return self;
   };

   qTip.prototype.updatePosition = function(event, animate)
   {
      var self = this, target, tooltip, newPosition, ieAdjust, ie6Adjust, offset;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updatePosition');

      // If tooltip is static, return
      else if(self.options.position.type == 'static') return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_POSITION_STATIC, 'updatePosition');

      // Define property objects
      target = {
         position: { left: 0, top: 0 },
         dimensions: { height: 0, width: 0 },
         corner: self.options.position.corner.target
      };
      tooltip = {
         position: self.getPosition(),
         size: self.getDimensions(),
         corner: self.options.position.corner.tooltip
      };

      // Setup IE adjustment variables (Pixel gap bugs)
      ieAdjust = ($.browser.msie) ? 1 : 0; // And this is why I hate IE...
      ie6Adjust = ($.fn.qtip.cache.ie6) ? 1 : 0; // ...and even more so IE6!

      // Mouse is the target, set position to current mouse coordinates
      if(self.options.position.target === 'mouse')
      {
         // Setup target position and dimensions objects
         target.position = newPosition = { left: self.cache.mouse.left, top: self.cache.mouse.top, corner: tooltip.corner };
         target.size = { height: 1, width: 1 };
      }

      // Target is a regular HTML element
      else
      {
         // If the HTML element is an AREA element, calculate position manually
         if($.fn.qtip.imagemap) target = $.fn.qtip.imagemap.getPosition.call(self, target);

         // Target is the document
         else if(self.options.position.target.add(document.body).length === 1)
         {
            target.size = $.fn.qtip.cache.screen;
            target.position = target.size.scroll;
         }

         // Target is a regular HTML element, find position normally
         else
         {
            // Check if the target is another tooltip and if its animated, retrieve position from cached position
            if(self.options.position.target.attr('qtip'))
               target.position = self.options.position.target.qtip('api').cache.position;
            else
               target.position = self.options.position.target.offset();

            // Setup dimensions objects
            target.size = {
               height: self.options.position.target.outerHeight(),
               width: self.options.position.target.outerWidth()
            };
         };

         // Calculate correct target corner position
         newPosition = $.extend({}, target.position);
         if(target.corner.x == 'right') newPosition.left += target.size.width;
         if(target.corner.y == 'bottom') newPosition.top += target.size.height;
         if(target.corner.x == 'middle' || target.corner.x == 'center') newPosition.left += (target.size.width / 2);
         if(target.corner.y == 'middle' || target.corner.y == 'center') newPosition.top += (target.size.height / 2);
      };

      // Calculate correct target corner position
      if(tooltip.corner.x == 'right') newPosition.left -= tooltip.size.width;
      if(tooltip.corner.y == 'bottom') newPosition.top -= tooltip.size.height;
      if(tooltip.corner.x == 'middle' || tooltip.corner.x == 'center') newPosition.left -= (tooltip.size.width / 2);
      if(tooltip.corner.y == 'middle' || tooltip.corner.y == 'center') newPosition.top -= (tooltip.size.height / 2);

      // Adjust for border radius
      if($.fn.qtip.border && self.options.style.border.radius > 0)
      {
         if(tooltip.corner.precedance == 'y')
         {
            if(tooltip.corner.x == 'left') newPosition.left -= self.options.style.border.radius;
            else if(tooltip.corner.x == 'right') newPosition.left += self.options.style.border.radius;
         }
         else
         {
            if(tooltip.corner.y == 'top') newPosition.top -= self.options.style.border.radius;
            else if(tooltip.corner.y == 'bottom') newPosition.top += self.options.style.border.radius;
         }
      };

      // IE only adjustments (Pixel perfect!)
      if(ieAdjust)
      {
         if(tooltip.corner.y == 'top') newPosition.top -= ieAdjust
         else if(tooltip.corner.y == 'bottom') newPosition.top += ieAdjust;
         if(tooltip.corner.x == 'left') newPosition.left -= ieAdjust
         else if(tooltip.corner.x == 'right') newPosition.left += ieAdjust;
         if(tooltip.corner.y == 'middle') newPosition.top -= ieAdjust;
      };

      // If screen adjustment is enabled, apply adjustments
      if(self.options.position.adjust.screen === true) newPosition = screenAdjust.call(self, newPosition, target, tooltip);

      // If mouse is the target, prevent tooltip appearing directly under the mouse
      if(self.options.position.target === 'mouse' && self.options.position.adjust.mouse === true)
      {
         newPosition.left -= (newPosition.corner.x == 'right') ? 6 : -6;
         newPosition.top -= (newPosition.corner.y == 'bottom') ? 6 : -6;
      }

      // Initiate bgiframe plugin in IE6 if tooltip overlaps a select box or object element
      if($.fn.qtip.cache.ie6 && $.fn.qtip.bgiframe && self.elements.bgiframe == null)
      {
         $('select, object').each(function()
         {
            offset = $(this).offset();
            offset.bottom = offset.top + $(this).height();
            offset.right = offset.left + $(this).width();

            if(newPosition.top + tooltip.size.height >= offset.top
            && newPosition.left + tooltip.size.width >= offset.left)
               return $.fn.qtip.bgiframe.call(self);
         });
      };

      // Add user xy adjustments
      newPosition.left += self.options.position.adjust.x;
      newPosition.top += self.options.position.adjust.y;

      // Call API method and if return value is false, halt
      if(self.beforePositionUpdate.call(self, event) === false) return self;

      // Cache new position
      self.cache.position = newPosition;

      // Check if animation is enabled
      if(animate === true)
      {
         // Set animated status, animate and reset status at end
         self.status.animated = true;
         self.elements.tooltip.animate(newPosition, 200, 'swing', function(){ self.status.animated = false });
      }

      // Set new position via CSS
      else self.elements.tooltip.css(newPosition);

      // Call API method and log event if its not a mouse move
      self.onPositionUpdate.call(self, event);
      if(!event || (event.type && event.type != 'mousemove')) $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_POSITION_UPDATED, 'updatePosition');

      return self;
   };

   qTip.prototype.updateWidth = function(newWidth)
   {
      var self = this, hide, zoom;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateWidth');

      // Make sure supplied width is a number and if not, return
      else if(newWidth && typeof newWidth != 'number') return $.fn.qtip.log.error.call(self, 2, 'Width must be a number!', 'updateWidth');

      // Setup elements which must be hidden during width update
      hide = self.elements.contentWrapper.siblings().add(self.elements.tip);
      zoom = self.elements.content.add(self.elements.wrapper).add(self.elements.title);

      // Calculate the new width if one is not supplied
      if(!newWidth)
      {
         // Explicit numerical width is set
         if(typeof self.options.style.width.value == 'number') newWidth = self.options.style.width.value;

         // No width is set, proceed with auto detection
         else
         {
            // Set zoom to default to prevent IE hasLayout bug
            if($.browser.msie){ zoom.css({ zoom: 'normal' }); hide.hide(); }

            // Determine width
            self.elements.tooltip.css({ width: 'auto' });
            newWidth = self.getDimensions().width;

            // Make sure its within the maximum and minimum width boundries
            if(self.options.style.width.max && newWidth > self.options.style.width.max) newWidth = self.options.style.width.max
            if(self.options.style.width.min && newWidth < self.options.style.width.min) newWidth = self.options.style.width.min
         };
      };

      // Adjust newWidth by 1px if width is odd (IE6 rounding bug fix)
      if(typeof newWidth == 'number' && newWidth % 2 !== 0) newWidth -= 1;

      // Set the new calculated width and if width has not numerical, grab new pixel width
      self.elements.tooltip.width(newWidth);
      if(typeof newWidth != 'number') newWidth = self.getDimensions().width;

      // Set the border width, if enabled
      if($.fn.qtip.border && self.options.style.border.radius)
         self.elements.tooltip.find('.qtip-betweenCorners').css({ width: newWidth - (self.options.style.border.radius * 2) });

      // IE only adjustments
      if($.browser.msie)
      {
         // Set wrapper width and reset zoom to give the wrapper layout (IE hasLayout bug)
         zoom.css({ zoom: 1 });
         hide.show();

         // Adjust BGIframe height and width if enabled
         if($.fn.qtip.bgiframe && self.elements.bgiframe)
            self.elements.bgiframe.width(newWidth).height(self.getDimensions().height);
      };

      // Log event and return
      $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_WIDTH_UPDATED, 'updateWidth');
      return self;
   };

   qTip.prototype.updateStyle = function(name)
   {
      var self = this, tip, borders, context, corner, coordinates;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateStyle');

      // Return if style is not defined or name is not a string
      else if(typeof name != 'string' || !$.fn.qtip.styles[name]) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.STYLE_NOT_DEFINED, 'updateStyle');

      // Set the new style object
      self.options.style = buildStyle.call(self, self.options.user.style, $.fn.qtip.styles[name]);

      // Update initial styles of content and title elements
      self.elements.content.css( jQueryStyle(self.options.style) );
      if(self.options.content.title.text !== false)
         self.elements.title.css( jQueryStyle(self.options.style.title, true) );

      // Update CSS border colour
      self.elements.contentWrapper.css({ borderColor: self.options.style.border.color });

      // Update tip color if enabled
      if($.fn.qtip.tips && self.options.style.tip.corner !== false)
      {
         if($('<canvas/>').get(0).getContext)
         {
            // Retrieve canvas context and clear
            tip = self.elements.tooltip.find('.qtip-tip canvas:first');
            context = tip.get(0).getContext('2d');
            context.clearRect(0,0,300,300);

            // Draw new tip
            coordinates = $.fn.qtip.tips.calculate.call(self, self.cache.tip, self.options.style.tip.size.width, self.options.style.tip.size.height);
            $.fn.qtip.tips.draw.call(self, tip, coordinates, self.options.style.tip.color || self.options.style.border.color);
         }
         else if($.browser.msie)
         {
            // Set new fillcolor attribute
            tip = self.elements.tooltip.find('.qtip-tip [nodeName="shape"]');
            tip.attr('fillcolor', self.options.style.tip.color || self.options.style.border.color);
         };
      };

      // Update border colors if enabled
      if($.fn.qtip.border && self.options.style.border.radius > 0)
      {
         self.elements.tooltip.find('.qtip-betweenCorners').css({ backgroundColor: self.options.style.border.color });

         if($('<canvas/>').get(0).getContext)
         {
            borders = $.fn.qtip.border.calculate(self.options.style.border.radius)
            self.elements.tooltip.find('.qtip-wrapper canvas').each(function()
            {
               // Retrieve canvas context and clear
               context = $(this).get(0).getContext('2d');
               context.clearRect(0,0,300,300);

               // Draw new border
               corner = $(this).parent('div[rel]:first').attr('rel')
               $.fn.qtip.border.draw.call(self, $(this), borders[corner],
                  self.options.style.border.radius, self.options.style.border.color);
            });
         }
         else if($.browser.msie)
         {
            // Set new fillcolor attribute on each border corner
            self.elements.tooltip.find('.qtip-wrapper [nodeName="arc"]').each(function()
            {
               $(this).attr('fillcolor', self.options.style.border.color)
            });
         };
      };

      // Update width and position to coincide with new style
      self.updateWidth(); self.updatePosition(null, true);

      // Log event and return
     $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_STYLE_UPDATED, 'updateStyle');
     return self;
   };

   qTip.prototype.updateContent = function(content, reposition)
   {
      var self = this, images, loadedImages;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateContent');

      // Make sure content is defined before update
      else if(!content) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateContent');

      // Call API method and if return value is false, halt
      if(self.beforeContentUpdate.call(self, content) === false) return;

      // Append new content if its a DOM array and show it if hidden
      if(content.jquery && content.length > 0) self.elements.content.html( content.clone(true).removeAttr('id').css({ display: 'block' }) );

      // Content is a regular string, insert the new content
      else self.elements.content.html(content);

      // Check if images need to be loaded before position is updated to prevent mis-positioning
      loadedImages = 0; images = self.elements.content.find('img')
      if(images.length)
      {
         images.bind('load ready error unload', function()
         {
            // Set image dimensions to prevent incorrect positioning
            $(this).attr('width', $(this).innerWidth());
            $(this).attr('height', $(this).innerHeight());

            // Make sure all iamges are loaded before proceeding with position update
            if(++loadedImages == images.length) afterLoad();
         })
      }
      else afterLoad();

      function afterLoad()
      {
         // Update the tooltip width
         self.updateWidth();

         // If repositioning is enabled, update positions
         if(reposition !== false)
         {
            // Update positions
            if(self.options.position.type != 'static') self.updatePosition(null, false);
            if($.fn.qtip.tips) $.fn.qtip.tips.position.call(self);
         };

         // Call API method and log event
         self.onContentUpdate.call(self);
         return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_UPDATED, 'loadContent');
      }

      return self;
   };

   qTip.prototype.loadContent = function(url, data, method, ajax, reposition)
   {
      var self = this, returned, request;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'loadContent');

      // Call API method and if return value is false, halt
      if(self.beforeContentLoad.call(self) === false) return self;

      // Setup $.ajax option object and process the requeqst
      request = $.extend({}, ajax, { url: url, type: method, data: data, extended: ajax, success: setupContent, error: errorHandler });
      $.ajax(request);

      function errorHandler(xhr, status, error)
      {
         // Call user-defined error handler if present
         if($.isFunction(request.extended.error) && request.extended.error(xhr,status,error) === false) return;

         // Log error to console
         message = $.fn.qtip.constants.AJAX_ERROR + '[' + status + ']: ' + error;
         $.fn.qtip.log.error.call(self, 1, message, 'loadContent');

         // Update tooltip content to indicate error
         self.updateContent($.fn.qtip.constants.AJAX_ERROR, reposition);
      };

      function setupContent(content, status)
      {
         // Call user-defined success handler if present
         if($.isFunction(request.extended.success) && request.extended.success(content, status) === false) return;

         // Call API method and if return value is false, halt
         returned = self.onContentLoad.call(self, content);
         if(typeof returned == 'string') content = returned;

         // Log event and update content
         $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_LOADED, 'loadContent');
         self.updateContent(content, reposition);
      };

      return self;
   };

   qTip.prototype.updateTitle = function(content)
   {
      var self = this;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateTitle');

      // Make sure content is defined before update
      if(!content) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateTitle');

      // Call API method and if return value is false, halt
      if(self.beforeTitleUpdate.call(self) === false) return self;

      // Check if title is rendered
      if(self.elements.title)
      {
         // Set the new content and reappend the button if enabled
         if(self.elements.button) self.elements.button = self.elements.button.clone(true);
         self.elements.title.html(content)
         if(self.elements.button) self.elements.title.prepend(self.elements.button);
      }

      // No title is rendered, create the title
      else
      {
         self.options.content.title.text = content;
         createTitle.call(self);
      }

      // Call API method and log event
      self.onTitleUpdate.call(self);
      $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_TITLE_UPDATED, 'updateTitle');

      return self;
   };

   qTip.prototype.focus = function(event)
   {
      var self = this, i, api, curIndex, newIndex, elemIndex;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'focus');

      else if(self.options.position.type == 'static') return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_FOCUS_STATIC, 'focus');

      // Set z-index variables
      curIndex = parseInt( self.elements.tooltip.css('z-index') );
      newIndex = $.fn.qtip.constants.baseIndex + $.fn.qtip.interfaces.length;

      // Only update the z-index if it has changed and tooltip is not already focused
      if(!self.status.focused && curIndex !== newIndex)
      {
         // Call API method and if return value is false, halt
         if(self.beforeFocus.call(self, event) === false) return self;

         i = $.fn.qtip.interfaces.length; while(i--)
         {
            api = $.fn.qtip.interfaces[i];

            // Queue the animation so positions are updated correctly
            if(api && api.status.rendered)
            {
               // Reduce all other tooltip z-index by 1
               elemIndex = parseInt(api.elements.tooltip.css('z-index'));
               if(typeof elemIndex == 'number' && elemIndex > -1) api.elements.tooltip.css({ zIndex: elemIndex - 1 });

               // Set focused status to false
               api.status.focused = false;
            }
         };

         // Set the new z-index and set focus status to true
         self.elements.tooltip.css({ zIndex: newIndex });
         self.status.focused = true;

         // Call API method and log event
         self.onFocus.call(self, event);
         $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_FOCUSED, 'focus');
      };

      return self;
   };

   qTip.prototype.disable = function(state)
   {
      var self = this;

      if((state && self.status.disabled) || (!state && !self.status.disabled))
         $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.TOOLTIP_ALREADY_DISABLED, 'disable');

      if(state)
         self.status.disabled = !$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DISABLED, 'disable');
      else
         self.status.disabled = $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_ENABLED, 'disable');

      return self;
   };

   qTip.prototype.destroy = function()
   {
      var self = this, i, interfaces;

      // Call API method and if return value is false, halt
      if(self.beforeDestroy.call(self) === false) return self;

      // Check if tooltip is rendered
      if(self.status.rendered)
      {
         // Remove event handlers and remove element
         self.options.show.when.target.unbind('mousemove.qtip', self.updatePosition);
         self.options.show.when.target.unbind('mouseout.qtip', self.hide);
         self.options.show.when.target.unbind(self.options.show.when.event + '.qtip');
         self.options.hide.when.target.unbind(self.options.hide.when.event + '.qtip');
         self.elements.tooltip.unbind(self.options.hide.when.event + '.qtip');
         self.elements.tooltip.unbind('mouseover.qtip', self.focus);
         self.elements.tooltip.remove();
      }

      // Tooltip isn't yet rendered, remove render event
      else self.options.show.when.target.unbind(self.options.show.when.event+'.qtip-'+self.id+'-create');

      // Remove interfaces object
      interfaces = $.fn.qtip.interfaces;
      i = interfaces.length; while(i--) if(interfaces[i] && interfaces[i].id == self.id) interfaces.splice(i, 1);
      if(!self.elements.target.qtip('interfaces')) self.elements.target.removeData('qtip');

      // Restore content if taken from an attribute tag
      if(self.cache.content.type !== false) self.elements.target.attr(self.cache.content.type, self.cache.content.text);

      // Call API method and log destroy
      self.onDestroy.call(self);
      $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DESTROYED, 'destroy');

      return self.elements.target
   };

   qTip.prototype.getPosition = function()
   {
      var self = this, show, offset;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getPosition');

      show = (self.elements.tooltip.css('display') != 'none') ? false : true;

      // Show and hide tooltip to make sure coordinates are returned
      if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show();
      offset = self.elements.tooltip.offset();
      if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide();

      return offset;
   };

   qTip.prototype.getDimensions = function()
   {
      var self = this, show, dimensions;

      // Make sure tooltip is rendered and if not, return
      if(!self.status.rendered) return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getDimensions');

      show = (!self.elements.tooltip.is(':visible')) ? true : false;

      // Show and hide tooltip to make sure dimensions are returned
      if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show();
      dimensions = {
         height: self.elements.tooltip.outerHeight(),
         width: self.elements.tooltip.outerWidth()
      };
      if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide();

      return dimensions;
   };



   /*
    * Private core functions
    */

   // Create title bar for content
   function createTitle()
   {
      var self = this;

      // Destroy previous title element, if present
      if(self.elements.title !== null) self.elements.title.remove();

      // Create title element
      self.elements.title = $('<div class="'+self.options.style.classes.title+'">')
         .css( jQueryStyle(self.options.style.title, true) )
         .css({ zoom: ($.browser.msie) ? 1 : 0 })
         .prependTo(self.elements.contentWrapper);

      // Update title with contents if enabled
      if(self.options.content.title.text) self.updateTitle.call(self, self.options.content.title.text);

      // Create title close buttons if enabled
      if(self.options.content.title.button !== false && typeof self.options.content.title.button == 'string')
      {
         self.elements.button = $('<a class="'+self.options.style.classes.button+'" style="float:right; position: relative"></a>')
            .css( jQueryStyle(self.options.style.button, true) )
            .html(self.options.content.title.button)
            .prependTo(self.elements.title)
            .click(function(event)
            {
               if(self.status.disabled) return true;
               return Boolean(self.onButtonClick.call(self));
            });
      };
   };

   // Assign hide and show events
   function assignEvents()
   {
      var self, targets, events;
      self = this;

      // Setup event and target variables
      targets = { show: self.options.show.when.target, hide: self.options.hide.when.target };
      events = {
         show: self.options.show.when.event,
         hide: (self.options.hide.when.event === false) ? 'mouseout' : self.options.hide.when.event,
         inactive: ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseout', 'mouseover']
      };

      // Add tooltip as a hide target if fixed
      if(self.options.hide.fixed) targets.hide = targets.hide.add(self.elements.tooltip);

      // Check if the hide event is special 'inactive' type
      if(events.hide == 'inactive')
      {
         // Define 'inactive' event timer method and bind it as custom event
         function inactiveMethod(event)
         {
            if(self.status.disabled === true) return;

            //Clear and reset the timer
            clearTimeout(self.timers.inactive);
            self.timers.inactive = setTimeout(function(){ self.hide(event); }, self.options.hide.delay);
         };
         targets.show.bind('qtip-inactive', inactiveMethod);

         // Define events which reset the 'inactive' event handler
         $(events.inactive).each(function(){ targets.hide.add(self.elements.content).bind(this+'.qtip-inactive', inactiveMethod); });
      }

      // Check if the tooltip is 'fixed'
      else if(self.options.hide.fixed === true)
         self.elements.tooltip.bind('mouseover.qtip', function(){ if(self.status.disabled !== true) clearTimeout(self.timers.hide); });

      // Define hide method
      function hideMethod(event)
      {
         if(self.status.disabled === true) return;

         // Clear timers and stop animation queue
         clearTimeout(self.timers.show);
         clearTimeout(self.timers.hide);

         if(self.options.hide.when.event !== false)
         {
            // Prevent hiding if tooltip is fixed and event target is the tooltip
            if(self.options.hide.fixed === true
            && events.hide.search(/mouse(out|leave)/i) !== -1
            && $(event.relatedTarget).parents('div.qtip[qtip]').length > 0)
            {
               // Prevent default and popagation
               event.stopPropagation();
               event.preventDefault();

               // Reset the hide timer
               clearTimeout(self.timers.hide);
               return false;
            };

            // If tooltip has displayed, start hide timer
            self.elements.tooltip.stop(true, true);
            self.timers.hide = setTimeout(function(){ self.hide(event); }, self.options.hide.delay);
         }
      };

      // Define show event method
      function showMethod(event)
      {
         if(self.status.disabled === true) return;

         // If set, hide tooltip when inactive for delay period
         if(events.hide == 'inactive') targets.show.trigger('qtip-inactive');

         // Clear hide timers
         clearTimeout(self.timers.show);
         clearTimeout(self.timers.hide);

         // Start show timer
         self.timers.show = setTimeout(function(){ self.show(event); }, self.options.show.delay);
      };

      // Both events and targets are identical, apply events using a toggle
      if((self.options.show.when.target.add(self.options.hide.when.target).length === 1
      && events.show == events.hide && events.hide != 'inactive') || events.hide == 'unfocus')
      {
         targets.show.bind(events.show + '.qtip', function(event)
         {
            if(self.status.hidden == true) showMethod(event);
            else hideMethod(event);
         });
      }

      // Events are not identical, bind normally
      else
      {
         targets.show.bind(events.show + '.qtip', showMethod);

         // If the hide event is not 'inactive', bind the hide method
         if(events.hide != 'inactive') targets.hide.bind(events.hide + '.qtip', hideMethod);
         if(events.show == 'mouseover') targets.hide.bind('mouseout.qtip', hideMethod);
      };

      // Focus the tooltip on mouseover
      if(self.options.position.type.search(/(fixed|absolute)/) !== -1)
         self.elements.tooltip.bind('mouseover.qtip', function(){ self.focus() });

      // If mouse is the target, update tooltip position on mousemove
      if(self.options.position.target === 'mouse' && self.options.position.type != 'static')
      {
         targets.show.bind('mousemove.qtip', function(event)
         {
            // Set the new mouse positions if adjustment is enabled
            self.cache.mouse = { left: event.pageX, top: event.pageY };

            // Update the tooltip position only if the tooltip is visible and adjustment is enabled
            if(self.status.disabled === false && self.options.position.adjust.mouse === true
            && self.options.position.type != 'static' && self.elements.tooltip.css('display') != 'none')
               self.updatePosition(event);
         });
      };
   };

   // Build a jQuery style object from supplied style object
   function jQueryStyle(style, sub)
   {
      var styleObj, i;

      styleObj = $.extend(true, {}, style);
      for(i in styleObj)
      {
         if(sub === true && i.search(/(tip|classes)/i) !== -1)
            delete styleObj[i];
         else if(!sub && i.search(/(width|border|tip|title|classes|user)/i) !== -1)
            delete styleObj[i];
      };

      return styleObj;
   };

   // Sanitize styles
   function sanitizeStyle(style)
   {
      if(typeof style.tip != 'object') style.tip = { corner: style.tip };
      if(typeof style.tip.size != 'object') style.tip.size = { width: style.tip.size, height: style.tip.size };
      if(typeof style.border != 'object') style.border = { width: style.border };
      if(typeof style.width != 'object') style.width = { value: style.width };
      if(typeof style.width.max == 'string') style.width.max = parseInt(style.width.max.replace(/([0-9]+)/i, "$1"));
      if(typeof style.width.min == 'string') style.width.min = parseInt(style.width.min.replace(/([0-9]+)/i, "$1"));

      // Convert deprecated x and y tip values to width/height
      if(typeof style.tip.size.x == 'number')
      {
         style.tip.size.width = style.tip.size.x;
         delete style.tip.size.x;
      };
      if(typeof style.tip.size.y == 'number')
      {
         style.tip.size.height = style.tip.size.y;
         delete style.tip.size.y;
      };

      return style;
   };

   // Build styles recursively with inheritance
   function buildStyle()
   {
      var self, i, styleArray, styleExtend, finalStyle;
      self = this;

      // Build style options from supplied arguments
      styleArray = [true, {}];
      i = arguments.length; while(i--) styleArray.push(arguments[i]);
      styleExtend = [ $.extend.apply($, styleArray) ];

      // Loop through each named style inheritance
      while(styleExtend[0] && typeof styleExtend[0].name == 'string')
      {
         // Check style exists
         if(typeof $.fn.qtip.styles[ styleExtend[0].name ] != 'object')
         {
            $.fn.qtip.log.error.call(self, 1, styleExtend[0].name+': '+$.fn.qtip.constants.STYLE_NOT_DEFINED, 'buildStyle');
            styleExtend.shift();
            continue;
         }

         // Sanitize style data and append to extend array
         else styleExtend.unshift( sanitizeStyle($.fn.qtip.styles[ styleExtend[0].name ]) );
      };

      // Make sure resulting tooltip className represents final style
      styleExtend.unshift(true,
         { classes: { tooltip: 'qtip-' + (arguments[0].name || 'defaults') } },
         (!arguments[0].name || (arguments[0].name && arguments[0].name.search(/^css$/i) < 0)) ? $.fn.qtip.styles.defaults : null);

      // Extend into a single style object
      finalStyle = $.extend.apply($, styleExtend);

      // Adjust tip size if needed (IE 1px adjustment bug fix)
      finalStyle.tip.size.width += ($.browser.msie) ? 1 : 0;
      finalStyle.tip.size.height += ($.browser.msie) ? 1 : 0;

      // Force even numbers for pixel precision
      if(finalStyle.tip.size.width % 2 > 0) finalStyle.tip.size.width += 1;
      if(finalStyle.tip.size.height % 2 > 0) finalStyle.tip.size.height += 1;

      // Sanitize final styles tip corner value
      if(finalStyle.tip.corner === true)
         finalStyle.tip.corner = (self.options.position.corner.tooltip === 'center') ? false : self.options.position.corner.tooltip;

      return finalStyle;
   };

   // Screen position adjustment
   function screenAdjust(position, target, tooltip)
   {
      var self, newPosition, overflow;
      self = this;

      // TODO: 'center' corner adjustment
      if(tooltip.corner.x == 'center') return position;

      // Setup corner and adjustment variable
      winScroll = $.fn.qtip.cache.scroll;
      newPosition = {
         left: position.left, top: position.top,
         corner: tooltip.corner.clone()
      };

      // Determine which corners currently overflow off screen
      overflow = {
         left: (newPosition.left < winScroll.left),
         right: (newPosition.left + tooltip.size.width >= $(window).width() + winScroll.left),
         top: (newPosition.top < winScroll.top),
         bottom: (newPosition.top + tooltip.size.height >= $(window).height() + winScroll.top)
      };

      // Tooltip overflows off the left side of the screen
      if(overflow.left && (newPosition.corner.x == 'right' || (newPosition.corner.x != 'right' && !overflow.right)))
      {
         newPosition.left += tooltip.size.width - ($.fn.qtip.border ? (self.options.style.border.radius * 2 || 0) : 0);
         newPosition.corner.x = 'left';
      }

      // Tooltip overflows off the right side of the screen
      else if(overflow.right && (newPosition.corner.x == 'left' || (newPosition.corner.x != 'left' && !overflow.left)))
      {
         newPosition.left -= tooltip.size.width - ($.fn.qtip.border ? (self.options.style.border.radius * 2 || 0) : 0);
         newPosition.corner.x = 'right';
      };

      // Tooltip overflows off the top of the screen
      if(overflow.top && newPosition.corner.y != 'top')
      {
         if(self.options.position.target !== 'mouse')
            newPosition.top = target.position.top + target.size.height;
         else
            newPosition.top = self.cache.mouse.top

         newPosition.corner.y = 'top';
      }

      // Tooltip overflows off the bottom of the screen
      else if(overflow.bottom && newPosition.corner.y != 'bottom')
      {
         newPosition.top -= tooltip.size.height;
         newPosition.corner.y = 'bottom';
      };

      // Don't adjust if resulting position is negative
      if(newPosition.left < 0) newPosition.left += Math.abs(newPosition.left);
      if(newPosition.top < 0) newPosition.top += Math.abs(newPosition.top);

      // Change tip corner if positioning has changed and tips are enabled
      if($.fn.qtip.tips && self.options.style.tip.corner !== false && newPosition.corner.toString() !== self.cache.tip.toString())
         $.fn.qtip.tips.create.call(self, newPosition.corner);

      return newPosition;
   };

   // Setup id, interfaces array and log placeholder
   $.fn.qtip.nextid = 0;
   $.fn.qtip.interfaces = [];
   $.fn.qtip.log = { error: function(){ return false; } };

   // Setup cache
   $.fn.qtip.cache = {
      content: {},
      scroll: { left: 0, top: 0 },
      ie6: ($.browser.msie && parseInt($.browser.version) == 6)
   };

   // Adjust positions of the tooltips on window resize or scroll if enabled
   $(function()
   {
      $(window).bind('resize.qtip scroll.qtip', function(event)
      {
         var i, api;

         // Readjust cached scroll values
         if(event.type == 'scroll') $.fn.qtip.cache.scroll = { left: $(window).scrollLeft(), top: $(window).scrollTop() };

         i = $.fn.qtip.interfaces.length; while(i--)
         {
            // Access current elements API
            api = $.fn.qtip.interfaces[i];

            // Queue the animation so positions are updated correctly
            if(api && api.status.rendered && !api.status.hidden && api.options.position.type !== 'static'
            && ( (api.options.position.adjust.scroll && event.type == 'scroll') || (api.options.position.adjust.resize && event.type == 'resize') )
            )
               api.updatePosition(event, true);
         };
      })
      .trigger('scroll');

      // Hide unfocus toolips on document mousedown
      $(document).bind('mousedown.qtip', function(event)
      {
         if($(event.target).parents('div.qtip').length === 0)
         {
            i = $.fn.qtip.interfaces.length; while(i--)
            {
               var api = $.fn.qtip.interfaces[i];

               if(api && api.options.hide.when.event == 'unfocus' && !api.status.hidden
               && !api.status.disabled && $(event.target).add(api.elements.target).length > 1)
                  api.hide(event);
            }
         };
      });
   });

   // Define configuration defaults
   $.fn.qtip.constants = { baseIndex: 6000 };
   $.fn.qtip.defaults = {
      // Content
      content: {
         prerender: false,
         text: false,
         url: {
            path: false,
            data: null,
            method: 'GET'
         },
         title: {
            text: false,
            button: false
         }
      },
      // Position
      position: {
         target: false,
         corner: {
            target: 'bottomRight',
            tooltip: 'topLeft'
         },
         adjust: {
            x: 0, y: 0,
            mouse: true,
            screen: false,
            scroll: true,
            resize: true
         },
         type: 'absolute',
         container: false
      },
      // Effects
      show: {
         when: {
            target: false,
            event: 'mouseover'
         },
         effect: {
            type: 'fade',
            duration: 100
         },
         delay: 140,
         solo: false,
         ready: false
      },
      hide: {
         when: {
            target: false,
            event: 'mouseout'
         },
         effect: {
            type: 'fade',
            duration: 100
         },
         delay: 0,
         fixed: false
      },
      // Callbacks
      api: {
         beforeRender: function(){},
         onRender: function(){},
         beforePositionUpdate: function(){},
         onPositionUpdate: function(){},
         beforeShow: function(){},
         onShow: function(){},
         beforeHide: function(){},
         onHide: function(){},
         beforeContentUpdate: function(){},
         onContentUpdate: function(){},
         beforeContentLoad: function(){},
         onContentLoad: function(){},
         beforeTitleUpdate: function(){},
         onTitleUpdate: function(){},
         beforeDestroy: function(){},
         onDestroy: function(){},
         beforeFocus: function(){},
         onFocus: function(){},
         onButtonClick: function(){ this.hide(); }
      }
   };

   $.fn.qtip.styles = {
      defaults: {
         background: 'white',
         color: '#111',
         overflow: 'hidden',
         textAlign: 'left',
         width: {
            min: 0,
            max: 250
         },
         padding: '5px 9px',
         border: {
            width: 1,
            radius: 0,
            color: '#d3d3d3'
         },
         tip: {
            corner: false,
            type: false,
            color: false,
            size: { width: 13, height: 13 }
         },
         title: {
            background: '#e1e1e1',
            fontWeight: 'bold',
            padding: '7px 12px'
         },
         button: {
            cursor: 'pointer'
         },
         classes: {
            target: '',
            tip: 'qtip-tip',
            title: 'qtip-title',
            button: 'qtip-button',
            content: 'qtip-content',
            active: 'qtip-active'
         }
      },
      css: {
         width: 'auto',
         border: {
            width: 0,
            radius: 0
         },
         tip: {
            corner: false,
            type: false,
            color: false,
            size: { width: 13, height: 13 }
         },
         title: {},
         button: {}
      }
   };
   $.fn.qtip.styles.css.classes = $.fn.qtip.styles.defaults.classes;
   $.fn.qtip.styles.css.classes.tooltip = 'qtip-css';

})(jQuery);