/*
* bubbletip
*
* Copyright (c) 2009-2010, UhLeeKa.
* Version: 
*      1.0.6
* Licensed under the GNU Lesser General Public License:
*     http://www.gnu.org/licenses/lgpl-3.0.html
* Author Website: 
*     http://www.uhleeka.com
* Project Hosting on Google Code: 
*     http://code.google.com/p/bubbletip/
*/
; (function($) {
    var bindIndex = 0;
    $.fn.extend({
        bubbletip: function(tip, options) {
            // check to see if the tip is a descendant of 
            // a table.bubbletip element and therefore
            // has already been instantiated as a bubbletip
            if ($('table.bubbletip #' + $(tip).get(0).id).length > 0) {
                return this;
            }

            var _this, _tip, _options, _calc, _timeoutAnimate, _timeoutRefresh, _isActive, _isHiding, _wrapper, _bindIndex;
            // hack for IE6,IE7
            var _windowWidth, _windowHeight;

            _this = $(this);
            _tip = $(tip);
            _bindIndex = bindIndex++;  // for window.resize namespace binding
            _options = {
                positionAt: 'element', // element | body | mouse
                positionAtElement: _this,
                offsetTop: 0,
                offsetLeft: 0,
                deltaPosition: 30,
                deltaDirection: 'up', // direction: up | down | left | right
                animationDuration: 250,
                animationEasing: 'swing', // linear | swing
                bindShow: 'mouseover', // mouseover | focus | click | etc.
                bindHide: 'mouseout', // mouseout | blur | etc.
                delayShow: 0,
                delayHide: 500,
                calculateOnShow: true
            };
            if (options) {
                _options = $.extend(_options, options);
            }
            // calculated values
            _calc = {
                top: 0,
                left: 0,
                delta: 0,
                mouseTop: 0,
                mouseLeft: 0,
                tipHeight: 0,
                bindShow: (_options.bindShow + ' ').replace(/ +/g, '.bubbletip' + _bindIndex),
                bindHide: (_options.bindHide + ' ').replace(/ +/g, '.bubbletip' + _bindIndex)
            };
            _timeoutAnimate = null;
            _timeoutRefresh = null;
            _isActive = false;
            _isHiding = false;

            // store the tip id for removeBubbletip
            if (!_this.data('bubbletip_tips')) {
                _this.data('bubbletip_tips', [[_tip.get(0).id, _bindIndex]]);
            } else {
                _this.data('bubbletip_tips', $.merge(_this.data('bubbletip_tips'), [[_tip.get(0).id, _bindIndex]]));
            }


            // 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);
            // hack for FF 3.6
            _wrapper.css({ 'width': _wrapper.width(), 'height': _wrapper.height() });
            // execute initial calculations
            _Calculate();
            _wrapper.hide();

            // handle window.resize
            $(window).bind('resize.bubbletip' + _bindIndex, function() {
                var w = $(window).width();
                var h = $(window).height();

                if ((w === _windowWidth) && (h === _windowHeight)) {
                    return;
                }
                _windowWidth = w;
                _windowHeight = h;

                if (_timeoutRefresh) {
                    clearTimeout(_timeoutRefresh);
                }
                _timeoutRefresh = setTimeout(function() {
                    _Calculate();
                }, 250);
            });

            // handle mouseover and mouseout events
            $([_wrapper.get(0), this.get(0)]).bind(_calc.bindShow, function() {
                if (_timeoutAnimate) {
                    clearTimeout(_timeoutAnimate);
                }
                if (_options.delayShow === 0) {
                    _Show();
                } else {
                    _timeoutAnimate = setTimeout(function() {
                        _Show();
                    }, _options.delayShow);
                }
                return false;
            }).bind(_calc.bindHide, function() {
                if (_timeoutAnimate) {
                    clearTimeout(_timeoutAnimate);
                }
                if (_options.delayHide === 0) {
                    _Hide();
                } else {
                    _timeoutAnimate = setTimeout(function() {
                        _Hide();
                    }, _options.delayHide);
                }
                return false;
            });

            function _Show() {
                var animation;

                if (_isActive) { // the tip is currently showing; do nothing
                    return;
                }
                _isActive = true;
                if (_isHiding) { // the tip is currently hiding; interrupt and start showing again
                    _wrapper.stop(true, false);
                }

                if (_options.calculateOnShow) {
                    _Calculate();
                }
                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 = { 'top': _calc.top + 'px' };
                    } else {
                        if (!_isHiding) {
                            _wrapper.css('left', parseInt(_calc.left + _calc.delta) + 'px');
                        }
                        animation = { '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 = { '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 = { 'left': _calc.left + 'px' };
                    }
                }
                _isHiding = false;
                _wrapper.show();
                animation = $.extend(animation, { 'opacity': 1 });
                _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
                    _wrapper.css('opacity', '');
                    _isActive = true;
                });
            };
            function _Hide() {
                var animation;

                _isActive = false;
                _isHiding = true;
                if (_options.positionAt.match(/^element|body$/i)) {
                    if (_options.deltaDirection.match(/^up|down$/i)) {
                        animation = { 'top': parseInt(_calc.top - _calc.delta) + 'px' };
                    } else {
                        animation = { 'left': parseInt(_calc.left - _calc.delta) + 'px' };
                    }
                } else {
                    if (_options.deltaDirection.match(/^up|down$/i)) {
                        animation = { 'top': parseInt(_calc.mouseTop - _calc.delta) + 'px' };
                    } else {
                        animation = { 'left': parseInt(_calc.mouseLeft - _calc.delta) + 'px' };
                    }
                }
                animation = $.extend(animation, { 'opacity': 0 });
                _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
                    _wrapper.hide();
                    _isHiding = 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.outerHeight();
                        _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.outerWidth() - _wrapper.outerWidth()) / 2);
                        _calc.delta = _options.deltaPosition;
                    } else if (_options.deltaDirection.match(/^down$/i)) {
                        _calc.top = offset.top + _options.positionAtElement.outerHeight() + _options.offsetTop;
                        _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.outerWidth() - _wrapper.outerWidth()) / 2);
                        _calc.delta = -_options.deltaPosition;
                    } else if (_options.deltaDirection.match(/^left$/i)) {
                        _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.outerHeight() - _wrapper.outerHeight()) / 2);
                        _calc.left = offset.left + _options.offsetLeft - _wrapper.outerWidth();
                        _calc.delta = _options.deltaPosition;
                    } else if (_options.deltaDirection.match(/^right$/i)) {
                        _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.outerHeight() - _wrapper.outerHeight()) / 2);
                        _calc.left = offset.left + _options.positionAtElement.outerWidth() + _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.outerHeight());
                            _calc.left = _options.offsetLeft;
                        } else {
                            _calc.top = _options.offsetTop;
                            _calc.left = parseInt(_options.offsetLeft + _wrapper.outerWidth());
                        }
                        // 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.outerHeight());
                            _calc.left = _options.offsetLeft;
                        } else if (_options.deltaDirection.match(/^left$/i)) {
                            _calc.top = _options.offsetTop;
                            _calc.left = -(_options.offsetLeft + _wrapper.outerWidth());
                        }
                        // up or left
                        _calc.delta = _options.deltaPosition;
                    } else {
                        _calc.top = _options.offsetTop;
                        _calc.left = _options.offsetLeft;
                        // down or right
                        _calc.delta = -_options.deltaPosition;
                    }
                }
                // 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'
                    });
                }
            };
            return this;
        },
        removeBubbletip: function(tips) {
            var tipsActive;
            var tipsToRemove = new Array();
            var tipsActiveAdjusted = new Array();
            var arr, i, ix;
            var elem;

            tipsActive = $.makeArray($(this).data('bubbletip_tips'));

            // convert the parameter array of tip id's or elements to id's
            arr = $.makeArray(tips);
            for (i = 0; i < arr.length; i++) {
                tipsToRemove.push($(arr[i]).get(0).id);
            }

            for (i = 0; i < tipsActive.length; i++) {
                ix = null;
                if ((tipsToRemove.length == 0) || ((ix = $.inArray(tipsActive[i][0], tipsToRemove)) >= 0)) {
                    // remove all tips if there are none specified
                    // otherwise, remove only specified tips

                    // find the surrounding table.bubbletip
                    elem = $('#' + tipsActive[i][0]).get(0).parentNode;
                    while (elem.tagName.toLowerCase() != 'table') {
                        elem = elem.parentNode;
                    }
                    // attach the tip element to body and hide
                    $('#' + tipsActive[i][0]).appendTo('body').hide();
                    // remove the surrounding table.bubbletip
                    $(elem).remove();

                    // unbind show/hide events
                    $(this).unbind('.bubbletip' + tipsActive[i][1]);

                    // unbind window.resize event
                    $(window).unbind('.bubbletip' + tipsActive[i][1]);
                } else {
                    // tip is not being removed, so add it to the adjusted array
                    tipsActiveAdjusted.push(tipsActive[i]);
                }
            }
            $(this).data('bubbletip_tips', tipsActiveAdjusted);

            return this;
        }
    });
})(jQuery);
