(function ($) {
    function opposite(side) {
        switch (side) {
        case 'up':
            return 'down';
        case 'down':
            return 'up';
        case 'left':
            return 'right';
        case 'right':
            return 'left';
        default:
            throw new Error("Weird side: " + side);
        }
    }


    function dir2pos(dir) {
        switch (dir) {
        case 'up':
            return 'top';
        case 'down':
            return 'bottom';
        default:
            return dir;
        }
    }


    function synchArrowColor(element, arrowSide) {
        // Synch arrow color
        var bgcolor = element.css('background-color'),
        border = dir2pos(opposite(arrowSide));
        element.children('div').children('div').css("border-" + border + "-color", bgcolor);
    }


    function clamp(min, value, max) {
        return value < min? min: (value > max? max: value);
    }


    $.widget("ui.tooltip", {
        options: {
            arrowSide: 'up'
        },

        _init: function() {
            this.element
              .addClass('fg-tooltip ui-widget ui-widget-header')
              .append($('<span>').css('text-align', 'center'))
              .append($('<div>').addClass('fg-tooltip-pointer-up ui-widget-header')
                                .css({ position: 'absolute', background: 'none' })
                                .append($('<div>').addClass('fg-tooltip-pointer-up-inner')
                                                  .css({ position: 'absolute', background: 'none' })))
              .hide();
        },

        target: function() {
            return this._getData('target');
        },


        content: function(cont) {
            var span = $('span', this.element);
            return span.html(cont);
        },


        /**
         * Points the tooltip at the given target location.
         */
        pointAt: function(x, y, arrowSide) {
            var that = this;

            function setArrowSide() {
                var outerArrow = that.element.children('div'),
                    innerArrow = outerArrow.children('div');

                $.each(['up', 'down', 'left', 'right'], function (i, v) {
                    outerArrow.removeClass("fg-tooltip-pointer-" + v);
                    innerArrow.removeClass("fg-tooltip-pointer-" + v + "-inner");
                });

                outerArrow.addClass("fg-tooltip-pointer-" + arrowSide);
                innerArrow.addClass("fg-tooltip-pointer-" + arrowSide + "-inner");

                synchArrowColor(that.element, arrowSide);
            }

            function setPosition(x, y) {
                that.element.css({ left: x, top: isNaN(y) ? that.element.css('top') : y });
            }

            function recenterArrow() {
                if (arrowSide === 'up' || arrowSide === 'down') {
                    $('.fg-tooltip-pointer-' + arrowSide, that.element).css('left', that.element.outerWidth() / 2);
                } else {
                    $('.fg-tooltip-pointer-' + arrowSide, that.element).css('top', that.element.height() / 2);
                }
            }

            var width = that.element.outerWidth(),
                height = that.element.outerHeight(),
                top,
                left;

            switch (arrowSide) {
            case 'up':
                left = x - width / 2;
                top = y + height / 2;
                break;
            case 'down':
                left = x - width / 2;
                top = y - height - 10;
                break;
            case 'left':
                left = x + 10;
                top = y - height / 2;
                break;
            case 'right':
                left = x - width - 10;
                top = y - height / 2;
                break;
            }

            setPosition(left, top);
            setArrowSide();
            recenterArrow();

            this._setOption('target', { x: x, y: y });
            this._setOption('arrowSide', arrowSide);
        },


        /**
         * Similar to #pointAt(), but doesn't move the tooltip. Only the arrow's
         * side and position are modified.
         */
        directAt: function(x, y) {
            var left, top,
                arrowSide = this.options.arrowSide;
            // TODO: This needs to be much smarter.
            // Adjust the arrow
            switch (arrowSide) {
            case 'up':
                left = x - this.element.position().left;
                left = clamp(12, left, this.element.outerWidth() - 12);
                $('.fg-tooltip-pointer-up', this.element).css('left', left);
                break;
            default:
                throw new Error("not implemented!: " + arrowSide);
                break;
            }

            synchArrowColor(this.element, arrowSide);
            this._setOption('target', { x: x, y: y });
        },


        switchSide: function() {
            var side = opposite(this.options.arrowSide);
            this.pointAt(this.options.target.x, this.options.target.y, side);
        }
    });
})(jQuery);

