/*
* Copyright (c) 2009, UhLeeKa
* Licensed under the GPL license:
*     http://www.gnu.org/licenses/gpl.html
* Author Website: 
*     http://www.uhleeka.com
*
* Description: 
*     A bubble-styled tooltip extension
*      - multiple tips on a page
*      - multiple tips per jQuery element 
*      - tips open outward in four directions:
*         - up
*         - down
*         - left
*         - right
*      - tips can be: 
*         - anchored to the triggering jQuery element
*         - absolutely positioned
*         - opened at the current mouse coordinates
*         - anchored to a specified jQuery element
*      - IE png transparency is handled via filters
*/
; (function($) {
    var bindIndex = 0;
    $.fn.extend({
        bubbletip: function(tip, options) {
            var _this, _tip, _options, _calc, _timeoutAnimate, _timeoutRefresh, _isActive, _isHiding, _wrapper, _bindIndex;

            _this = $(this);
            _tip = $(tip);
            _options = {
                positionAt: 'element', // element | body | mouse
                positionAtElement: _this,
                offsetTop: 0,
                offsetLeft: 0,
                deltaPosition: 30,
                deltaDirection: 'up', // direction: up | down | left | right
                mouseoutDelay: 500,
                animationDuration: 250,
                animationEasing: 'swing', // linear | swing
                autoShow: false
            };
            if (options) {
                _options = $.extend(_options, options);
            }
            // calculated values
            _calc = {
                top: 0,
                left: 0,
                delta: 0,
                mouseTop: 0,
                mouseLeft: 0,
                tipHeight: 0
            };
            _timeoutAnimate = null;
            _timeoutRefresh = null;
            _isActive = false;
            _isHiding = false;
            _bindIndex = bindIndex++;  // for window.resize namespace binding

            // validate _options
            if (!_options.positionAt.match(/^element|body|mouse$/i)) {
                _options.positionAt = 'element';
            }
            if (!_options.deltaDirection.match(/^up|down|left|right$/i)) {
                _options.deltaDirection = 'up';
            }

            // create the wrapper table element
            if (_options.deltaDirection.match(/^up$/i)) {
                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td><table class="bt-bottom" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-bottomright"></td></tr></tbody></table>');
            } else if (_options.deltaDirection.match(/^down$/i)) {
                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td><table class="bt-top" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
            } else if (_options.deltaDirection.match(/^left$/i)) {
                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right-tail"><div class="bt-right"></div><div class="bt-right-tail"></div><div class="bt-right"></div></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
            } else if (_options.deltaDirection.match(/^right$/i)) {
                _wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left-tail"><div class="bt-left"></div><div class="bt-left-tail"></div><div class="bt-left"></div></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
            }

            // append the wrapper to the document body
            _wrapper.appendTo('body');

            // apply IE filters to _wrapper elements
            if ((/msie/.test(navigator.userAgent.toLowerCase())) && (!/opera/.test(navigator.userAgent.toLowerCase()))) {
                $('*', _wrapper).each(function() {
                    var image = $(this).css('background-image');
                    if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
                        image = RegExp.$1;
                        $(this).css({
                            'backgroundImage': 'none',
                            'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=' + ($(this).css('backgroundRepeat') == 'no-repeat' ? 'crop' : 'scale') + ', src=\'' + image + '\')'
                        }).each(function() {
                            var position = $(this).css('position');
                            if (position != 'absolute' && position != 'relative')
                                $(this).css('position', 'relative');
                        });
                    }
                });
            }

            // move the tip element into the content section of the wrapper
            $('.bt-content', _wrapper).append(_tip);
            // show the tip (in case it is hidden) so that we can calculate its dimensions
            _tip.show();
            // handle left|right delta
            if (_options.deltaDirection.match(/^left|right$/i)) {
                // tail is 40px, so divide height by two and subtract 20px;
                _calc.tipHeight = parseInt(_tip.height() / 2);
                // handle odd integer height
                if ((_tip.height() % 2) == 1) {
                    _calc.tipHeight++;
                }
                _calc.tipHeight = (_calc.tipHeight < 20) ? 1 : _calc.tipHeight - 20;
                if (_options.deltaDirection.match(/^left$/i)) {
                    $('div.bt-right', _wrapper).css('height', _calc.tipHeight + 'px');
                } else {
                    $('div.bt-left', _wrapper).css('height', _calc.tipHeight + 'px');
                }
            }
            // set the opacity of the wrapper to 0
            _wrapper.css('opacity', 0);
            // execute initial calculations
            _Calculate();

            // handle window.resize
            $(window).bind('resize.bubbletip' + _bindIndex, function() {
                if (_timeoutRefresh) {
                    clearTimeout(_timeoutRefresh);
                } else {
                    _wrapper.hide();
                }
                _timeoutRefresh = setTimeout(function() {
                    _Calculate();
                    _wrapper.show();
                }, 250);
            });

            // handle mouseover and mouseout events
            $([_wrapper.get(0), this.get(0)]).mouseover(function(e) {
                show();
            }).mouseout(function() {
                hide(0);
            });

            function show() {
                if (_timeoutAnimate) {
                    clearTimeout(_timeoutAnimate);
                }
                if (_isActive) {
                    return;
                }
                _isActive = true;
                if (_isHiding) {
                    _wrapper.stop(true, false);
                }

                var animation;

                if (_options.positionAt.match(/^element|body$/i)) {
                    if (_options.deltaDirection.match(/^up|down$/i)) {
                        if (!_isHiding) {
                            _wrapper.css('top', parseInt(_calc.top + _calc.delta) + 'px');
                        }
                        animation = { 'opacity': 1, 'top': _calc.top + 'px' };
                    } else {
                        if (!_isHiding) {
                            _wrapper.css('left', parseInt(_calc.left + _calc.delta) + 'px');
                        }
                        animation = { 'opacity': 1, 'left': _calc.left + 'px' };
                    }
                } else {
                    if (_options.deltaDirection.match(/^up|down$/i)) {
                        if (!_isHiding) {
                            _calc.mouseTop = e.pageY + _calc.top;
                            _wrapper.css({ 'top': parseInt(_calc.mouseTop + _calc.delta) + 'px', 'left': parseInt(e.pageX - (_wrapper.width() / 2)) + 'px' });
                        }
                        animation = { 'opacity': 1, 'top': _calc.mouseTop + 'px' };
                    } else {
                        if (!_isHiding) {
                            _calc.mouseLeft = e.pageX + _calc.left;
                            _wrapper.css({ 'left': parseInt(_calc.mouseLeft + _calc.delta) + 'px', 'top': parseInt(e.pageY - (_wrapper.height() / 2)) + 'px' });
                        }
                        animation = { 'opacity': 1, 'left': _calc.left + 'px' };
                    }
                }
                _isHiding = false;
                _wrapper.show();
                _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
                    _wrapper.css('opacity', '');
                    _isActive = true;
                });

                return false;
            }

            function hide(timeOutOverride) {
                if (timeOutOverride < 1)
                    timeOutOverride = _options.mouseoutDelay;
                
                if (_timeoutAnimate) {
                    clearTimeout(_timeoutAnimate);
                }
                _timeoutAnimate = setTimeout(function() {
                    var animation;

                    _isActive = false;
                    _isHiding = true;
                    if (_options.positionAt.match(/^element|body$/i)) {
                        if (_options.deltaDirection.match(/^up|down$/i)) {
                            animation = { 'opacity': 0, 'top': parseInt(_calc.top - _calc.delta) + 'px' };
                        } else {
                            animation = { 'opacity': 0, 'left': parseInt(_calc.left - _calc.delta) + 'px' };
                        }
                    } else {
                        if (_options.deltaDirection.match(/^up|down$/i)) {
                            animation = { 'opacity': 0, 'top': parseInt(_calc.mouseTop - _calc.delta) + 'px' };
                        } else {
                            animation = { 'opacity': 0, 'left': parseInt(_calc.mouseLeft - _calc.delta) + 'px' };
                        }
                    }
                    _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
                        _wrapper.hide();
                        _isHiding = false;
                    });

                }, timeOutOverride);

                return false;
            }

            function _Calculate() {
                // calculate values
                if (_options.positionAt.match(/^element$/i)) {
                    var offset = _options.positionAtElement.offset();
                    if (_options.deltaDirection.match(/^up$/i)) {
                        _calc.top = offset.top + _options.offsetTop - _wrapper.height();
                        _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.width() - _wrapper.width()) / 2);
                        _calc.delta = _options.deltaPosition;
                    } else if (_options.deltaDirection.match(/^down$/i)) {
                        _calc.top = offset.top + _options.positionAtElement.height() + _options.offsetTop;
                        _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.width() - _wrapper.width()) / 2);
                        _calc.delta = -_options.deltaPosition;
                    } else if (_options.deltaDirection.match(/^left$/i)) {
                        _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.height() - _wrapper.height()) / 2);
                        _calc.left = offset.left + _options.offsetLeft - _wrapper.width();
                        _calc.delta = _options.deltaPosition;
                    } else if (_options.deltaDirection.match(/^right$/i)) {
                        _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.height() - _wrapper.height()) / 2);
                        _calc.left = offset.left + _options.positionAtElement.width() + _options.offsetLeft;
                        _calc.delta = -_options.deltaPosition;
                    }
                } else if (_options.positionAt.match(/^body$/i)) {
                    if (_options.deltaDirection.match(/^up|left$/i)) {
                        _calc.top = _options.offsetTop;
                        _calc.left = _options.offsetLeft;
                        // up or left
                        _calc.delta = _options.deltaPosition;
                    } else {
                        if (_options.deltaDirection.match(/^down$/i)) {
                            _calc.top = parseInt(_options.offsetTop + _wrapper.height());
                            _calc.left = _options.offsetLeft;
                        } else {
                            _calc.top = _options.offsetTop;
                            _calc.left = parseInt(_options.offsetLeft + _wrapper.width());
                        }
                        // down or right
                        _calc.delta = -_options.deltaPosition;
                    }
                } else if (_options.positionAt.match(/^mouse$/i)) {
                    if (_options.deltaDirection.match(/^up|left$/i)) {
                        if (_options.deltaDirection.match(/^up$/i)) {
                            _calc.top = -(_options.offsetTop + _wrapper.height());
                            _calc.left = _options.offsetLeft;
                        } else if (_options.deltaDirection.match(/^left$/i)) {
                            _calc.top = _options.offsetTop;
                            _calc.left = -(_options.offsetLeft + _wrapper.width());
                        }
                        // up or left
                        _calc.delta = _options.deltaPosition;
                    } else {
                        _calc.top = _options.offsetTop;
                        _calc.left = _options.offsetLeft;
                        // down or right
                        _calc.delta = -_options.deltaPosition;
                    }
                }
                // hide
                _wrapper.hide();
                // handle the wrapper (element|body) positioning
                if (_options.positionAt.match(/^element|body$/i)) {
                    _wrapper.css({
                        'position': 'absolute',
                        'top': _calc.top + 'px',
                        'left': _calc.left + 'px'
                    });
                }
            };

            if (_options.autoShow) {
                show();
                hide(5000);
            }
            return this;
        }
    });
})(jQuery);
