(function($) {
    $.fn.tipsy = function(options)
	{
        options = $.extend({}, $.fn.tipsy.defaults, options);
        
        return this.each(function()
		{
            var opts = $.fn.tipsy.elementOptions(this, options);
			
            $(this)
				.bind("mouseleave",function()
					{ $.data(this, 'active.tipsy').remove(); })
				.click(function()
					{ $.data(this, 'active.tipsy').remove(); });
			
            $(this).hover(function()
			{

                $.data(this, 'cancel.tipsy', true);

                var tip = $.data(this, 'active.tipsy');
                if (!tip)
				{
                    tip = $('<div class="tipsy"><div class="tipsy-inner"/></div>');
                    tip.css({position: 'absolute', zIndex: 100000});
                    $.data(this, 'active.tipsy', tip);
                }


                //if ($(this).attr('title') || typeof($(this).attr('original-title')) != 'string')
				//	{ $(this).attr('original-title', $(this).attr('title') || '').removeAttr('title'); }

                var title;
                if (opts.title == 'title')
					{ title = $(this).attr('tipsy_title'); }
				else if (typeof opts.title == 'string')
					{ title = opts.title; }
				else if (typeof opts.title == 'function')
					{ title = opts.title.call(this); }

				tipsy_title = title || opts.fallback;
                tip.find('.tipsy-inner')[opts.html ? 'html' : 'text'](tipsy_title);

                var pos = $.extend({}, $(this).offset(), {width: this.offsetWidth, height: this.offsetHeight});
                tip.get(0).className = 'tipsy'; 
                tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
                var actualWidth = tip[0].offsetWidth, actualHeight = tip[0].offsetHeight;
                var gravity = (typeof opts.gravity == 'function') ? opts.gravity.call(this) : opts.gravity;
				
				if(opts.width) {  tip.css({width:opts.width}); }

                switch (gravity)
				{
                    case 'n':
                        tip.css({top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-north');
                        break;
                    case 's':
                        tip.css({top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-south');
                        break;
                    case 'e':
                        tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}).addClass('tipsy-east');
                        break;
                    case 'w':
                        tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}).addClass('tipsy-west');
                        break;
                    case 'f':
						tip.css({left: 2000 }).addClass('tipsy-north-f');
						$(this).mousemove(function(e)
							{ $(tip).css({ top: (e.pageY + 25) + "px", left: (e.pageX-17) + "px" }); });
                        break;
                    case 'fs':
						tip.css({left: 2000 }).addClass('tipsy-north-f');
						$(this).mousemove(function(e)
						{
							$(tip).css({ top: (e.pageY + 25) + "px", left: (e.pageX-17) + "px" });
							if(tipsy_title != '') { tip.find('.tipsy-inner').html(tipsy_title); }
						});
                        break;
                }

                if (opts.fade)
					{ tip.css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: 0.8}); }
				else
					{ tip.css({visibility: 'visible'}); }

            }, function()
			{
                $.data(this, 'cancel.tipsy', false);
                var self = this;
				
				if ($.data(this, 'cancel.tipsy')) return;
				var tip = $.data(self, 'active.tipsy');
				if (opts.fade)
					{ tip.stop().fadeOut(function() { $(this).remove(); }); }
				else
					{ tip.remove(); }

            });
            
        });
        
    };
    
    $.fn.tipsy.elementOptions = function(ele, options) {
        return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
    };
    
    $.fn.tipsy.defaults = {
        fade: false,
        fallback: '',
        gravity: 'n',
        html: false,
        title: 'title',
		width: 'auto'
    };
    
    $.fn.tipsy.autoNS = function() {
        return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
    };
    
    $.fn.tipsy.autoWE = function() {
        return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
    };
    
})(jQuery);

