(function ($) {
    var $cluetip, $cluetipInner, $cluetipOuter, $cluetipTitle, $cluetipArrows, $dropShadow, imgCount;
    $.fn.cluetip = function (js, options) {
        if (typeof js == 'object') {
            options = js;
            js = null;
        }
        return this.each(function (index) {
            var $this = $(this);
            var opts = $.extend(false, {},
            $.fn.cluetip.defaults, options || {},
            $.metadata ? $this.metadata() : $.meta ? $this.data() : {});
            var cluetipContents = false;
            var cluezIndex = parseInt(opts.cluezIndex, 10) - 1;
            var isActive = false,
                closeOnDelay = 0;
            if (!$('#cluetip').length) {
                $cluetipInner = $('<div id="cluetip-inner"></div>');
                $cluetipTitle = $('<h3 id="cluetip-title"></h3>');
                $cluetipOuter = $('<div id="cluetip-outer"></div>').append($cluetipInner).prepend($cluetipTitle);
                $cluetip = $('<div id="cluetip"></div>').css({
                    zIndex: opts.cluezIndex
                }).append($cluetipOuter).append('<div id="cluetip-extra"></div>')[insertionType](insertionElement).hide();
                $('<div id="cluetip-waitimage"></div>').css({
                    position: 'absolute',
                    zIndex: cluezIndex - 1
                }).insertBefore('#cluetip').hide();
                $cluetip.css({
                    position: 'absolute',
                    zIndex: cluezIndex
                });
                $cluetipOuter.css({
                    position: 'relative',
                    zIndex: cluezIndex + 1
                });
                $cluetipArrows = $('<div id="cluetip-arrows" class="cluetip-arrows"></div>').css({
                    zIndex: cluezIndex + 1
                }).appendTo('#cluetip');
            }
            var dropShadowSteps = (opts.dropShadow) ? +opts.dropShadowSteps : 0;
            if (!$dropShadow) {
                $dropShadow = $([]);
                for (var i = 0; i < dropShadowSteps; i++) {
                    $dropShadow = $dropShadow.add($('<div></div>').css({
                        zIndex: cluezIndex - i - 1,
                        opacity: .1,
                        top: 1 + i,
                        left: 1 + i
                    }));
                };
                $dropShadow.css({
                    position: 'absolute',
                    backgroundColor: '#000'
                }).prependTo($cluetip);
            }
            var tipAttribute = $this.attr(opts.attribute),
                ctClass = opts.cluetipClass;
            if (!tipAttribute && !opts.splitTitle && !js) return true;
            if (opts.local && opts.hideLocal) {
                $(tipAttribute + ':first').hide();
            }
            var tOffset = parseInt(opts.topOffset, 10),
                lOffset = parseInt(opts.leftOffset, 10);
            var tipHeight, wHeight;
            var defHeight = isNaN(parseInt(opts.height, 10)) ? 'auto' : (/\D/g).test(opts.height) ? opts.height : opts.height + 'px';
            var sTop, linkTop, posY, tipY, mouseY, baseline;
            var tipInnerWidth = isNaN(parseInt(opts.width, 10)) ? 275 : parseInt(opts.width, 10);
            var tipWidth = tipInnerWidth + (parseInt($cluetip.css('paddingLeft')) || 0) + (parseInt($cluetip.css('paddingRight')) || 0) + dropShadowSteps;
            var linkWidth = this.offsetWidth;
            var linkLeft, posX, tipX, mouseX, winWidth;
            var tipParts;
            var tipTitle = (opts.attribute != 'title') ? $this.attr(opts.titleAttribute) : '';
            if (opts.splitTitle) {
                if (tipTitle == undefined) {
                    tipTitle = '';
                }
                tipParts = tipTitle.split(opts.splitTitle);
                tipTitle = tipParts.shift();
            }
            var localContent;
            var activate = function (event) {
                if (!opts.onActivate($this)) {
                    return false;
                }
                isActive = true;
                $cluetip.removeClass().css({
                    width: tipInnerWidth
                });
                if (tipAttribute == $this.attr('href')) {
                    $this.css('cursor', opts.cursor);
                }
                $this.attr('title', '');
                if (opts.hoverClass) {
                    $this.addClass(opts.hoverClass);
                }
                linkTop = posY = $this.offset().top;
                linkLeft = $this.offset().left;
                mouseX = event.pageX;
                mouseY = event.pageY;
                if ($this[0].tagName.toLowerCase() != 'area') {
                    sTop = $(document).scrollTop();
                    winWidth = $(window).width();
                }
                if (opts.positionBy == 'fixed') {
                    posX = linkWidth + linkLeft + lOffset;
                    $cluetip.css({
                        left: posX
                    });
                } else {
                    posX = (linkWidth > linkLeft && linkLeft > tipWidth) || linkLeft + linkWidth + tipWidth + lOffset > winWidth ? linkLeft - tipWidth - lOffset : linkWidth + linkLeft + lOffset;
                    if ($this[0].tagName.toLowerCase() == 'area' || opts.positionBy == 'mouse' || linkWidth + tipWidth > winWidth) {
                        if (mouseX + 20 + tipWidth > winWidth) {
                            $cluetip.addClass(' cluetip-' + ctClass);
                            posX = (mouseX - tipWidth - lOffset) >= 0 ? mouseX - tipWidth - lOffset - parseInt($cluetip.css('marginLeft'), 10) + parseInt($cluetipInner.css('marginRight'), 10) : mouseX - (tipWidth / 2);
                        } else {
                            posX = mouseX + lOffset;
                        }
                    }
                    var pY = posX < 0 ? event.pageY + tOffset : event.pageY;
                    $cluetip.css({
                        left: (posX > 0 && opts.positionBy != 'bottomTop') ? posX : (mouseX + (tipWidth / 2) > winWidth) ? winWidth / 2 - tipWidth / 2 : Math.max(mouseX - (tipWidth / 2), 0)
                    });
                }
                wHeight = $(window).height();
                if (js) {
                    $cluetipInner.html(js);
                    cluetipShow(pY);
                } else if (tipParts) {
                    var tpl = tipParts.length;
                    for (var i = 0; i < tpl; i++) {
                        if (i == 0) {
                            $cluetipInner.html(tipParts[i]);
                        } else {
                            $cluetipInner.append('<div class="split-body">' + tipParts[i] + '</div>');
                        }
                    };
                    cluetipShow(pY);
                } else if (!opts.local && tipAttribute.indexOf('#') != 0) {
                    if (cluetipContents && opts.ajaxCache) {
                        $cluetipInner.html(cluetipContents);
                        cluetipShow(pY);
                    } else {
                        var ajaxSettings = opts.ajaxSettings;
                        ajaxSettings.url = tipAttribute;
                        ajaxSettings.beforeSend = function () {
                            $cluetipOuter.children().empty();
                            if (opts.waitImage) {
                                $('#cluetip-waitimage').css({
                                    top: mouseY + 20,
                                    left: mouseX + 20
                                }).show();
                            }
                        };
                        ajaxSettings.error = function () {
                            if (isActive) {
                                $cluetipInner.html('<i>sorry, the contents could not be loaded</i>');
                            }
                        };
                        ajaxSettings.success = function (data) {
                            cluetipContents = opts.ajaxProcess(data);
                            if (isActive) {
                                $cluetipInner.html(cluetipContents);
                            }
                        };
                        ajaxSettings.complete = function () {
                            imgCount = $('#cluetip-inner img').length;
                            if (imgCount && !$.browser.opera) {
                                $('#cluetip-inner img').load(function () {
                                    imgCount--;
                                    if (imgCount < 1) {
                                        $('#cluetip-waitimage').hide();
                                        if (isActive) cluetipShow(pY);
                                    }
                                });
                            } else {
                                $('#cluetip-waitimage').hide();
                                if (isActive) cluetipShow(pY);
                            }
                        };
                        $.ajax(ajaxSettings);
                    }
                } else if (opts.local) {
                    var $localContent = $(tipAttribute + ':first');
                    var localCluetip = $.fn.wrapInner ? $localContent.wrapInner('<div></div>').children().clone(true) : $localContent.html();
                    $.fn.wrapInner ? $cluetipInner.empty().append(localCluetip) : $cluetipInner.html(localCluetip);
                    cluetipShow(pY);
                }
            };
            var cluetipShow = function (bpY) {
                $cluetip.addClass('cluetip-' + ctClass);
                if (opts.truncate) {
                    var $truncloaded = $cluetipInner.text().slice(0, opts.truncate) + '...';
                    $cluetipInner.html($truncloaded);
                }

                function doNothing() {};
                tipTitle ? $cluetipTitle.show().html(tipTitle) : (opts.showTitle) ? $cluetipTitle.show().html('&nbsp;') : $cluetipTitle.hide();
                if (opts.sticky) {
                    var $closeLink = $('<div id="cluetip-close"><a href="#">' + opts.closeText + '</a></div>');
                    (opts.closePosition == 'bottom') ? $closeLink.appendTo($cluetipInner) : (opts.closePosition == 'title') ? $closeLink.prependTo($cluetipTitle) : $closeLink.prependTo($cluetipInner);
                    $closeLink.click(function () {
                        cluetipClose();
                        return false;
                    });
                    if (opts.mouseOutClose) {
                        if ($.fn.hoverIntent && opts.hoverIntent) {
                            $cluetip.hoverIntent({
                                over: doNothing,
                                timeout: opts.hoverIntent.timeout,
                                out: function () {
                                    $closeLink.trigger('click');
                                }
                            });
                        } else {
                            $cluetip.hover(doNothing, function () {
                                $closeLink.trigger('click');
                            });
                        }
                    } else {
                        $cluetip.unbind('mouseout');
                    }
                }
                var direction = '';
                $cluetipOuter.css({
                    overflow: defHeight == 'auto' ? 'visible' : 'auto',
                    height: defHeight
                });
                tipHeight = defHeight == 'auto' ? Math.max($cluetip.outerHeight(), $cluetip.height()) : parseInt(defHeight, 10);
                tipY = posY;
                baseline = sTop + wHeight;
                if (opts.positionBy == 'fixed') {
                    tipY = posY - opts.dropShadowSteps + tOffset;
                } else if ((posX < mouseX && Math.max(posX, 0) + tipWidth > mouseX) || opts.positionBy == 'bottomTop') {
                    if (posY + tipHeight + tOffset > baseline && mouseY - sTop > tipHeight + tOffset) {
                        tipY = mouseY - tipHeight - tOffset;
                        direction = 'top';
                    } else {
                        tipY = mouseY + tOffset;
                        direction = 'bottom';
                    }
                } else if (posY + tipHeight + tOffset > baseline) {
                    tipY = (tipHeight >= wHeight) ? sTop : baseline - tipHeight - tOffset;
                } else if ($this.css('display') == 'block' || $this[0].tagName.toLowerCase() == 'area' || opts.positionBy == "mouse") {
                    tipY = bpY - tOffset;
                } else {
                    tipY = posY - opts.dropShadowSteps;
                }
                if (direction == '') {
                    posX < linkLeft ? direction = 'left' : direction = 'right';
                }
                $cluetip.css({
                    top: tipY + 'px'
                }).removeClass().addClass('clue-' + direction + '-' + ctClass).addClass(' cluetip-' + ctClass);
                if (opts.arrows) {
                    var bgY = (posY - tipY - opts.dropShadowSteps);
                    $cluetipArrows.css({
                        top: (/(left|right)/.test(direction) && posX >= 0 && bgY > 0) ? bgY + 'px' : /(left|right)/.test(direction) ? 0 : ''
                    }).show();
                } else {
                    $cluetipArrows.hide();
                }
                $dropShadow.hide();
                $cluetip.hide()[opts.fx.open](opts.fx.open != 'show' && opts.fx.openSpeed);
                if (opts.dropShadow) $dropShadow.css({
                    height: tipHeight,
                    width: tipInnerWidth
                }).show();
                if ($.fn.bgiframe) {
                    $cluetip.bgiframe();
                }
                if (opts.delayedClose > 0) {
                    closeOnDelay = setTimeout(cluetipClose, opts.delayedClose);
                }
                opts.onShow($cluetip, $cluetipInner);
            };
            var inactivate = function () {
                isActive = false;
                $('#cluetip-waitimage').hide();
                if (!opts.sticky || (/click|toggle/).test(opts.activation)) {
                    cluetipClose();
                    clearTimeout(closeOnDelay);
                };
                if (opts.hoverClass) {
                    $this.removeClass(opts.hoverClass);
                }
                $('.cluetip-clicked').removeClass('cluetip-clicked');
            };
            var cluetipClose = function () {
                $cluetipOuter.parent().hide().removeClass().end().children().empty();
                if (tipTitle) {
                    $this.attr(opts.titleAttribute, tipTitle);
                }
                $this.css('cursor', '');
                if (opts.arrows) $cluetipArrows.css({
                    top: ''
                });
            };
            if ((/click|toggle/).test(opts.activation)) {
                $this.click(function (event) {
                    if ($cluetip.is(':hidden') || !$this.is('.cluetip-clicked')) {
                        activate(event);
                        $('.cluetip-clicked').removeClass('cluetip-clicked');
                        $this.addClass('cluetip-clicked');
                    } else {
                        inactivate(event);
                    }
                    this.blur();
                    return false;
                });
            } else if (opts.activation == 'focus') {
                $this.focus(function (event) {
                    activate(event);
                });
                $this.blur(function (event) {
                    inactivate(event);
                });
            } else {
                $this.click(function () {
                    if ($this.attr('href') && $this.attr('href') == tipAttribute && !opts.clickThrough) {
                        return false;
                    }
                });
                var mouseTracks = function (evt) {
                    if (opts.tracking == true) {
                        var trackX = posX - evt.pageX;
                        var trackY = tipY ? tipY - evt.pageY : posY - evt.pageY;
                        $this.mousemove(function (evt) {
                            $cluetip.css({
                                left: evt.pageX + trackX,
                                top: evt.pageY + trackY
                            });
                        });
                    }
                };
                if ($.fn.hoverIntent && opts.hoverIntent) {
                    $this.mouseover(function () {
                        $this.attr('title', '');
                    }).hoverIntent({
                        sensitivity: opts.hoverIntent.sensitivity,
                        interval: opts.hoverIntent.interval,
                        over: function (event) {
                            activate(event);
                            mouseTracks(event);
                        },
                        timeout: opts.hoverIntent.timeout,
                        out: function (event) {
                            inactivate(event);
                            $this.unbind('mousemove');
                        }
                    });
                } else {
                    $this.hover(function (event) {
                        activate(event);
                        mouseTracks(event);
                    },
                    function (event) {
                        inactivate(event);
                        $this.unbind('mousemove');
                    });
                }
            }
        });
    };
    $.fn.cluetip.defaults = {
        width: 275,
        height: 'auto',
        cluezIndex: 97,
        positionBy: 'auto',
        topOffset: 15,
        leftOffset: 15,
        local: false,
        hideLocal: true,
        attribute: 'rel',
        titleAttribute: 'title',
        splitTitle: '',
        showTitle: true,
        cluetipClass: 'default',
        hoverClass: '',
        waitImage: true,
        cursor: 'help',
        arrows: false,
        dropShadow: true,
        dropShadowSteps: 6,
        sticky: false,
        mouseOutClose: false,
        activation: 'hover',
        clickThrough: false,
        tracking: false,
        delayedClose: 0,
        closePosition: 'top',
        closeText: 'Close',
        truncate: 0,
        fx: {
            open: 'show',
            openSpeed: ''
        },
        hoverIntent: {
            sensitivity: 3,
            interval: 50,
            timeout: 0
        },
        onActivate: function (e) {
            return true;
        },
        onShow: function (ct, c) {},
        ajaxCache: true,
        ajaxProcess: function (data) {
            data = data.replace(/<s(cript|tyle)(.|\s)*?\/s(cript|tyle)>/g, '').replace(/<(link|title)(.|\s)*?\/(link|title)>/g, '');
            return data;
        },
        ajaxSettings: {
            dataType: 'html'
        },
        debug: false
    };
    var insertionType = 'appendTo',
        insertionElement = 'body';
    $.cluetip = {};
    $.cluetip.setup = function (options) {
        if (options && options.insertionType && (options.insertionType).match(/appendTo|prependTo|insertBefore|insertAfter/)) {
            insertionType = options.insertionType;
        }
        if (options && options.insertionElement) {
            insertionElement = options.insertionElement;
        }
    };
})(jQuery);;
