(function($) {
    $.fn.tipsy = function(opts) {
        opts = $.extend({ fade: false, gravity: 'n', title: 'notset', ignoreclick: false }, opts || {});
        var tip = null, cancelHide = false;
        $.data(this, 'cancel.tipsy', true);

        var tip = $.data(this, 'active.tipsy');
        if (!tip) {
            tip = $('<div class="tipsy"><div class="tipsy-inner">' + opts.title + '</div></div>');
            tip.css({ position: 'absolute', zIndex: 100000 });
            $(this).attr('title', '');
            $.data(this, 'active.tipsy', tip);
            if (!opts.ignoreclick) {
                $(this).focus(function() { $.data(this, 'active.tipsy', tip).remove(); $(this).unbind("focus"); });
                $('.tipsy').click(function(e) {
                    $(this).remove();
                });
                $(document).click(function(e) {
                    $(document).unbind("click");
                });
            }
        }

        var pos = $.extend({}, $(this).offset(), { width: this.width(), height: this.height() + 20 });
        tip.remove().css({ top: -200, left: -200, visibility: 'hidden', display: 'block' }).appendTo(document.body);
        var actualWidth = tip[0].offsetWidth, actualHeight = tip[0].offsetHeight;

        switch (opts.gravity.charAt(0)) {
            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) - 5, left: (pos.left - actualWidth) - 5 }).addClass('tipsy-east');
                break;
            case 'w':
                tip.css({ top: (pos.top + pos.height / 2 - actualHeight / 2) - 5, left: pos.left + pos.width + 5 }).addClass('tipsy-west');
                break;
            case 't':
                tip.css({ top: pos.top - 8, left: pos.left + pos.width }).addClass('tipsy-west');
                break;
        }

        if (tip.css("top").charAt(0) != '-') {
            if (opts.fade) {
                tip.css({ opacity: 0, display: 'block', visibility: 'visible' }).animate({ opacity: 1 });
            } else {
                tip.css({ visibility: 'visible' });
            }
        }
    };
})(jQuery);
