(function($){

  var tooltip;
  var arrow;
  var arrowWidth;
  var arrowHeight;
  var content;
  var win;
  var timeout;
  var removeInterval;

  function clearRemove() {
    if (removeInterval) {
      clearInterval(removeInterval);
      removeInterval = null;
    }
  }

  function getState(el, options){
    var s = {};
    var elementHeight = el.outerHeight();
    var elementWidth  = el.outerWidth();
    var offset = el.offset();
    s.height = tooltip.outerHeight(true);
    s.width  = tooltip.outerWidth(true);
    s.offset = {};
    s.offset.top = offset.top;
    s.offset.left = offset.left;
    s.offset.right = s.offset.left + elementWidth;
    s.offset.bottom = s.offset.top + elementHeight;
    s.offset.hCenter = s.offset.left + Math.floor(elementWidth / 2);
    s.offset.vCenter = s.offset.top + Math.floor(elementHeight / 2);
    s.css = {};
    s.on  = {};
    s.off = {};
    s.arrow = {};
    return s;
  }

  function checkBounds(s, direction, margin){
    var bound, alternate;
    margin = parseInt(margin);
    switch(direction){
      case 'top':
        bound = win.scrollTop();
        if(s.offset.top - s.height < bound) alternate = 'bottom';
        s.on.top  = s.offset.top - s.height - margin;
        s.off.top = s.on.top + 15;
        s.css.top = s.on.top - 15;
        s.css.left = getCenter(s, true);
        break;
      case 'left':
        bound = win.scrollLeft();
        if(s.offset.left - s.width < bound) alternate = 'right';
        s.on.left  = s.offset.left - s.width - margin;
        s.off.left = s.on.left + 15;
        s.css.top  = getCenter(s, false);
        s.css.left = s.on.left - 15;
        break;
      case 'bottom':
        bound = win.scrollTop() + win.height();
        if(s.offset.bottom + s.height > bound) alternate = 'top';
        s.on.top   = s.offset.bottom + margin;
        s.off.top  = s.offset.bottom - 15 + margin;
        s.css.top  = s.on.top + 15;
        s.css.left = getCenter(s, true);
        break;
      case 'right':
        bound = win.scrollLeft() + win.width();
        if(s.offset.right + s.width > bound) alternate = 'left';
        s.on.left  = s.offset.right + margin;
        s.off.left = s.on.left - 15;
        s.css.left = s.on.left + 15;
        s.css.top = getCenter(s, false);
        break;
    }
    getArrowOffset(s, direction);
    checkSlide(s, direction);
    if(alternate && !s.over){
      s.over = true;
      checkBounds(s, alternate, margin);
    } else {
      s.direction = direction;
    }
  }

  function checkSlide(s, dir){
    var offset;
    if(dir == 'top' || dir == 'bottom') {
      offset = win.scrollLeft() - s.css.left + 5;
      if(offset > 0){
        s.css.left += Math.abs(offset);
        s.arrow.left -= offset;
      }
      offset = (s.css.left + s.width) - (win.scrollLeft() + win.width()) + 5;
      if(offset > 0){
        s.css.left -= Math.abs(offset);
        s.arrow.left += offset;
      }
    } else if(dir == 'left' || dir == 'right') {
      offset = win.scrollTop() - s.css.top + 5;
      if(offset > 0){
        s.css.top += Math.abs(offset);
        s.arrow.top -= offset;
      }
      offset = (s.css.top + s.height) - (win.scrollTop() + win.height()) + 5;
      if(offset > 0){
        s.css.top -= Math.abs(offset);
        s.arrow.top += offset;
      }
    }
  }

  function getArrowOffset(s, dir){
    if(dir == 'left' || dir == 'right'){
      s.arrow.top = Math.floor((s.height / 2) - (arrowHeight / 2));
    } else {
      s.arrow.left = Math.floor((s.width / 2) - (arrowWidth / 2));
    }
    s.arrow[getInverseDirection(dir)] = -arrowHeight;
  }

  function getInverseDirection(dir){
    switch(dir){
      case 'top':    return 'bottom';
      case 'bottom': return 'top';
      case 'left':   return 'right';
      case 'right':  return 'left';
    }
  }

  function getCenter(s, horizontal){
    if(horizontal){
      return s.offset.hCenter + (-s.width / 2);
    } else {
      return s.offset.vCenter + (-s.height / 2);
    }
  }

  function animateTooltip(s, options, el, fn){
    var color = getDefault('color', options, el, 'white');
    var duration = getDefault('duration', options, el, 250);
		var delay = getDefault('delay', options, el, 0);
    tooltip.attr('class', color + ' ' + s.direction);
    tooltip.stop(true, true).css(s.css);
    arrow.attr('style', '').css(s.arrow);
    /*tooltip.animate(s.on, {
      duration: duration,
      queue: false,
      complete: fn
    });*/
    timeout = setTimeout(function() {
      tooltip.fadeIn(duration);
      timeout = null;
    }, delay);

    clearRemove();

    removeInterval = setInterval(function() {
      if (el.parents('body').length == 0) {
        clearRemove();
        animateTooltipOut(s, options, el);
      }
    }, 100);
  }

  function animateTooltipOut(s, options, el, fn){
    var duration = getDefault('duration', options, el, 250);
    /*tooltip.animate(s.off, {
      duration: duration,
      queue: false,
      complete: fn
    });*/
    clearRemove();
    tooltip.stop(true,true).fadeOut(duration);
  }

  function setContent(el, title) {
    var html;
    try {
      var ref = $(title);
    } catch(e){
      // May throw a malfolmed selector error
    }
    if(ref && ref.length > 0) {
      html = ref.html();
    } else {
      html = title;
    }
    content.html(html);
  }

  function getDefault(name, options, el, defaultValue) {
    return options[name] || el.data('tooltip-'+name) || defaultValue;
  }

  jQuery.fn.tooltip = function(options){
    options = options || {};
    this.each(function(){
      var el = $(this);
      var title = el.attr('title');
      var state;
      el.unbind('mouseenter').mouseenter(function(){
        var margin    = getDefault('margin', options, el, 20);
        var direction = getDefault('direction', options, el, 'top');
        var t = el.attr('title');
        if(t) {
          title = t;
        }
        el.removeAttr('title');
        setContent(el, title);
        state = getState(el, options);
        checkBounds(state, direction, margin);
        animateTooltip(state, options, el);
      });
      el.unbind('mouseleave').mouseleave(function(){
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        animateTooltipOut(state, options, el);
      });
    });
  };

  $(document).ready(function(){
    tooltip = $('<div id="tooltip" />').appendTo(document.body).css('position', 'absolute').hide();
    arrow   = $('<div class="arrow" />').appendTo(tooltip);
    content = $('<div class="content" />').appendTo(tooltip);
    win     = $(window);
    arrowWidth = arrow.width();
    arrowHeight = arrow.height();
  });

})(jQuery);

