//! Amazon Popover
//! Copyright Amazon.com 2008

/***********************************************************************************
<doc>
    Amazon Popover:
    A replacement for JSF n2simplePopover
    
    amazonPopoverTrigger can be thought of as a popover factory. It sets up a
    trigger to spawn popovers when clicked or hovered on, and the spawned popovers
    provide these features:
    
    * Robust MouseEnter/MouseLeave support by binding to document.mousemove.
    * Blurs text fields to avoid cursor showing through
    * Sticks an iframe behind your popover to workaround the IE6 dropdown
        bug, and to block out underlying Flash
    * Hides underlying Flash if it can't be covered    
    
    amazonPopoverTrigger accepts a single argument which is a hash, and all 
    parameters are optional except you must provide a source of content.
    
    # Content #
    
    You must provide one of the three options:
    
    @localContent {css selector}
        Selector for local popover content. Normally the element is cut and pasted
        into position in the DOM, unless you provide @clone : true, which leaves the
        original localContent element in place and clones it into the popover.   
    @literalContent {html or plain text string}
        String of text or html to put in contents of popover
    @destination {url}
        Location of remote AJAX popover content
        
    If you omit all three of these, it will try to find an ajax url in the href 
    attribute of the trigger. Failing that, the popover will not work.
    
    @title {html}
        Text for the title bar, can include HTML formatting.
    @closeText {string} (default '')
        Text to show by the close button
    
    # Geometry #
    
    @width {int | function | null} (default 500)
        An int indicates the desired width of the popover in pixels.
        A function is called to generate the width, and should return an
        int. Passing null means "don't set the width".
    @location {string | callback}
        String should be the name of a built-in location function, currently either
        'centered' or 'belowTrigger'. Callback should use this signature:
            function(settings, trigger, popover) {
                return { left: X, top: Y };
            }
        For ajax-loaded content, the popover's height will not be established yet.
    G@locationOffset {pair}
        Pass in an [x,y] pair where the x-value is added to "left" or "right" and 
        the y-value is added to "top".
           
    # Behavior #
    
    @followScroll {boolean} (default false)
        Follow scroll when popover height exceeds viewport
    @modal {boolean} (default false)
        If modal, will show a lightbox
    @draggable {boolean} (default false)
        Enable drag & drop of non-modal popovers
    @showCloseButton {boolean} (default !showOnHover)
        Close X in upper right corner
    @showOnHover {boolean} (default false)
        True for hover triggering, False for click triggering
    @hoverShowDelay {milliseconds} (default 400)
        Mouse must stay in the trigger zone for this long to activate the popover,
        which avoids inadvertant triggering
    @hoverHideDelay {milliseconds} (default 200)
        Mouse must be outside of the trigger zone for this long to hide the
        popover. Use 0 for for instantaneous.
    @openEvent {bitmask}
        Logical OR of the conditions which open the popover. The default is just
        $.AmazonPopover.eventType.MOUSE_ENTER if "showOnHover" is set true, or
        $.AmazonPopover.eventType.CLICK_TRIGGER otherwise. You might set 
        CLICK_TRIGGER | MOUSE_ENTER if there's a long hover delay and you want 
        immediate creation on click.
    @closeEvent {bitmask}
        Logical OR of the conditions which close the popover. The default is just
        $.AmazonPopover.eventType.MOUSE_LEAVE if "showOnHover" is set true, or
        $.AmazonPopover.eventType.CLICK_TRIGGER otherwise. You can set to 0 for
        click-triggered popovers that shouldn't close when you re-click the trigger.
        You could also set to MOUSE_LEAVE | CLICK_TRIGGER for click-triggered
        popovers that go away by simply mousing out.
        
    # Other/Advanced #
    
    @zIndex {int} (default 100)
        Might need adjustment if you have cascading popovers
    @skin {skin name | skin html | null} (default: "default")
        The default html structure is a div based layout with rounded corners and
        drop shadows. You can use either the name of a built-in skin, or pass
        in a string of html to use as the skin. If you pass in html, it _must_
        have an element with class="ap_content" where the content goes. If you pass
        null, it means "use no skin", meaning, use the localContent itself as the
        popover, not wrapped by anything. The localContent will only be shown
        and positioned, not modified otherwise.
    @onShow {callback}
        function(popover) {
            this;       // {jQuery object} the trigger
            popover;    // {jQuery object} the popover
        }
    @onHide {callback}
        function(popover) {
            this;       // {jQuery object} the trigger
            popover;    // {jQuery object} the popover
        }
    @solidRectangle {boolean} (default false)
        This is an optional optimization for IE7. In that browser if you overlay a
        transparent iframe on top of a Flash movie, it "whites out" the rectangle
        underneath. For that reason we're hiding Flash altogether if it's under a
        popover. There's no reason to hide it if the popover is fully opaque and
        rectangular, since you'll never see the whited-out area. Set this option
        to true if the popover has no rounded corners or drop shadows or any other
        partially transparent areas.
    @ajaxSlideDuration {milliseconds}
        Duration of the slideDown effect once remote ajax content has loaded. To
        disable the slide down effect, set to 0.
</doc>
***********************************************************************************/

(function($) {
    
    // MOUSE TRACKER
    // Keeps track of intersection rectangles and fires callback events when the mouse
    // enters or leaves the set of rectangles.
    // 
    // This hash holds objects with the following members:
    //  @rects: {Array of Arrays}, 4-tuples representing rectangles [x, y, width, height]
    //      Mouse can be inside any rectangle to be considered in bounds
    //  @mouseOver: {Function} (optional), called when mouse goes over
    //  @mouseOut: {Function} (optional), called when mouse goes out
    var mouseTracker = function() {
        var regions = []; // array of regions
        var cursor = null;

        // Takes a rectangle, [x, y, w, h]
        var intersectsAnyRegion = function(bounds) {            
            for (var reg=0; reg < regions.length; reg++) {
                for (var i = 0; i < regions[reg].rects.length; i++) { // for-in loop won't work for array, gets Array prototype extensions
                    var r = regions[reg].rects[i];
                    var disparate = bounds[0] > r[0] + r[2] || r[0] > bounds[0] + bounds[2] || bounds[1] > r[1] + r[3] || r[1] > bounds[1] + bounds[3];
                    if (!disparate) return true;
                }
            }
            return false;
        };
    
        // Listen for mouse movement
        $(document).ready(function(){
            $(document).mousemove(function(e){
                for (var i=0; i < regions.length; i++) {
                    var r = regions[i];
                    var inside = $.grep(r.rects, function(n) { 
                        return e.pageX >= n[0] && e.pageY >= n[1] && e.pageX <= n[0] + n[2] && e.pageY <= n[1] + n[3];
                    }).length > 0;

                    // Callbacks only called on the transition, not continuously
                    if (r.inside !== null && inside && !r.inside && r.mouseEnter) {
                        r.mouseEnter(r.backing);
                    } else if (r.inside !== null && !inside && r.inside && r.mouseLeave) {
                        r.mouseLeave(r.backing);
                    }
                    r.inside = inside;
                }
            });
        });
            
        return {
            add: function(rectsArray, backing, options) {
                var r = { rects: rectsArray, inside: options.inside, backing: backing, solid: options.solidRectangle, hasIFrame:options.useIFrame !== false };
                if (options.mouseEnter) r.mouseEnter = options.mouseEnter;
                if (options.mouseLeave) r.mouseLeave = options.mouseLeave;
                regions.push(r);
                return r;
            },
            remove: function(region) {
                for (var i=0; i < regions.length; i++) {
                    if (regions[i] === region) {
                        regions.splice(i, 1);
                        return;
                    }
                }
            },

            // IE7 will pass skipSolid = true, indicating that intersection rectangles flagged as "solidRectangle"
            // can be ignored.
            //anyPopoverIntersects: function(bounds, skipSolid, canOverlayWmodeWindow) {
            intersectingRegions: function(bounds) {
                var intersecting = [];
                for (var reg=0; reg < regions.length; reg++) {
                    if (regions[reg].rects) { // skip Object prototype extensions
                        var r = regions[reg].rects[0]; // only care about first rect, which is the popover outline
                        var disparate = bounds[0] > r[0] + r[2] || r[0] > bounds[0] + bounds[2] || bounds[1] > r[1] + r[3] || r[1] > bounds[1] + bounds[3];
                        if (!disparate) intersecting.push(regions[reg]);
                    }
                }
                return intersecting;
            }
        };
    }();

    // IFRAME POOL
    // Utility for managing iframes for popovers.  Transparent Iframes are used as a
    // hack to account for a few known issues known to exist with the popovers (floating 
    // divs):
    //     1. IE Windowed controls such as form select elements bleed through the 
    //        popover no matter what the z-indexes are set to.  An iframe forces the
    //        popover to show over the windowed controls.
    //     2. Flash elements with 'wmode=window' bleeds through the popover, but can be
    //        set to correctly hide under the popover if the flash itself is in an
    //        iframe that has a z-index smaller than the popover iframe's z-index
    //
    // USE: 
    // keeps a single list of iframe jQuery objects and acts as a library where users
    // can check out iframes when they need them and and check back in when they are 
    // done.
    //
    // IMPORTANT:
    // 1. dynamically inserting an iframe even with 'src' set to javascript:void(false)
    //    causes the browser to act as if a refresh occurred causing annoying behavior
    //    (i.e. click sounds in IE). this is why preload() is provided so users can
    //    specify how many iframes they would like to preload.  if the library runs out
    //    of iframes to checkout, it will create new iframes and insert them as needed.
    // 2. although it happened to be implemented this way anyway, the iframe's display 
    //    property HAS TO first be set to 'none' before it's set to 'display'. 
    //    otherwise, the hover effects on links that lie in the region of an iframe 
    //    do not show in firefox.
    //
    // FIXME: will have to add a function that would allow the iframe positions to be 
    // updated so that popover will be able to use this since they are draggable.
    //
    var iframePool = function() {
        var HTML = '<iframe frameborder="0" tabindex="-1" src="javascript:void(false);" style="display:none;position:absolute;z-index:0;filter:Alpha(Opacity=\'0\');opacity:0;" />';
        var availIframes = [];

        // adds passed in number n (initially hidden) iframes to the library, also prepends to the body
        var addToLib = function(n) {
            for(i=0; i < n; i++) {
                var iframe = $(HTML);
                iframe.prependTo(document.body);
                availIframes.push(iframe);
            }
        };
    
        // Populate the pool of iframes. We want to avoid creating them on-demand because it causes
        // the browser to show the loading progress bar.
        $(document).ready(function(){ addToLib(6); });

        return {
            checkout: function(jqObj) {
                // add one more iframe if library is empty
                if(availIframes.length <= 0) addToLib(1);
                var iframe = availIframes.pop();
            
                // set display to show and match the size and position to the passed in jq object
                iframe.css({
                    display: 'block',
                    top: jqObj.offset().top,
                    left: jqObj.offset().left,
                    width: jqObj.outerWidth(),
                    height: jqObj.outerHeight()
                });
                return iframe;
            },
            checkin: function(iframe) {
                iframe.css({ display: 'none' });
                availIframes.push(iframe);
            }
        };
    }();

    // ELEMENT HIDING
    // Backing the popover with an iframe is often good enough, but not always. Flash
    // sometimes can still show through, and there's some legacy JSF stuff that is
    // supposed to disappear when under a popover.
    //
    var elementHidingManager = function() {
        var win = /Win/.test(navigator.platform);
        var mac = /Mac/.test(navigator.platform);
        var linux = /Linux/.test(navigator.platform);
        var version = parseInt($.browser.version);
        
        // Whitelist of browsers than can put iframe-backed DIVs on top of Flash wmode=window
        // Feel free to add to this as more testing is done.
        var canOverlayWmodeWindow = $.browser.mozilla && $.browser.version.indexOf("1.9.") == 0 && mac ||   // FF3/Mac
                                    $.browser.safari && version >= 522 && version <= 525 && mac ||          // Saf3/Mac
                                    $.browser.mozilla && $.browser.version == "1.8.1" && linux;             // FF2/Linux        
                
        var hiddenElements = [];
        
        var jsfAlwaysShow = function(obj) {
            // checks to make sure that the object's id doesn't exist as 'alwaysShow'
            // in JSF's _toggleControlElements for backwards compatibility.  Also 
            // checks to make sure the id or name attribute isn't prefixed with '_po_'
            // since those elements are not to be hidden by convention.
            var alwaysShow = typeof(goN2Events) != "undefined" && goN2Events._toggleControlElements && goN2Events._toggleControlElements[obj.attr('id')] == 'alwaysShow';
            var po = obj.attr('id') && obj.attr('id').substring(0,4) == '_po_' || obj.attr('name') && obj.attr('name').substring(0,4) == '_po_';
            return alwaysShow || po;
        };
        
        var jsfNeverShow = function(obj) {
            // check _toggleElements from JSF to hide anything put in as alwaysHide for backwards compatibility
            return typeof(goN2Events) != 'undefined' &&
                obj.attr("id") &&
                goN2Events._toggleControlElements &&
                goN2Events._toggleControlElements[obj.attr("id")] &&
                goN2Events._toggleControlElements[obj.attr("id")] != "alwaysShow";
        }     
        
        var shouldBeVisible = function(obj) {
            if (jsfAlwaysShow(obj)) return true;
            
            var intersecting = mouseTracker.intersectingRegions(obj);
            
            for (var i=0; i < intersecting.length; i++) {
                if (jsfNeverShow(obj)) return false;
                
                if (obj.is("object") || obj.is("embed")) {
                    var wmode = obj.is("embed") ? obj.attr("wmode") : obj.children("embed").attr("wmode");
                    if (typeof wmode == "undefined") wmode = "window";

                    if (wmode != "window") {
                        if (!intersecting[i].solid && $.browser.msie && version == 7) return false;
                        if (!intersecting[i].hasIFrame) return false;
                        if (!canOverlayWmodeWindow) return false;
                    }
                }
                
                if (obj.is("iframe")) {
                    if ($.browser.safari && /Windows/.test(window.navigator.userAgent)) return false;
                }
            }
            return true;
        };
        
        return {
            update: function() {
                
                // Unhide elements which no longer intersects
                var stillHidden = [];
                for (var i = 0; i < hiddenElements.length; i++) {  // for-in loop won't work, gets Array prototype extensions
                    if (!shouldBeVisible(hiddenElements[i])) {
                        stillHidden.push(hiddenElements[i]);
                    } else {
                        hiddenElements[i].css("visibility", "visible");
                    }
                }
                hiddenElements = stillHidden;

                // Hide what needs to be hidden
                $("object:visible,embed:visible,iframe:visible").each(function(){
                    var obj = jQuery(this);
                    if (!shouldBeVisible(obj)) {
                        hiddenElements.push(obj);
                        obj.css("visibility", "hidden");
                    }
                });
            }
        };
    }();
    



    /*
      options hash is optional, accepts these parameters:
  
      @mouseEnter {function}: Called when mouse enters popover/additonalRects region
      @mouseLeave {function}: Called when mouse leaves popover/additonalRects region. These
          two callbacks are passed a single parameter, a backing object
      @additionalCursorRects {array of 4-tuples}: array of rectangles, each rectangle being
          an [x, y, width, height] 4-tuple.
      @solidRectangle {boolean} (defaults to false): Underlying Flash can remain visible in this case.
          Otherwise the backing iframe "whites out" the bounding rectangle, creating ugly
          white areas, and it's better to hide the Flash in this case.
    */
    var applyBacking = function(popover, options) {
        var region = null;  // cursor intersection region
        var iframe = null;  // absolute positioned behind the popover
        options = options || {};

        var destroy = function() {
            if (region) {
                mouseTracker.remove(region);
                region = null;
            }
            if (iframe) {
                iframePool.checkin(iframe);
                iframe = null;
            }
            elementHidingManager.update();
        };
    
        var refreshBounds = function() {
            var newBounds = [popover.offset().left, popover.offset().top, popover.outerWidth(), popover.outerHeight()];
            if (region) {
                region.rects[0] = newBounds;
            }
            if (iframe) iframe.css({
                left: newBounds[0],
                top: newBounds[1],
                width: newBounds[2],
                height: newBounds[3]
            });
            elementHidingManager.update();
        };
        
        var reposition = function(x, y) {
            if (iframe) {
                iframe.css({
                    left:x,
                    top:y
                })
            }
            if (region) {
                region.rects[0][0] = x;
                region.rects[0][1] = y;
            }
        };
    
        var backing = {
            destroy: destroy,
            popoverObject: popover,
            refreshBounds: refreshBounds,
            reposition: reposition
        };
    
        // Blur Text Fields so cursor doesn't show through
        $('input[type=text]').blur();

        // Back with an iframe
        if (options.useIFrame !== false)
            iframe = iframePool.checkout(popover);

        // Mouse Cursor Tracking
        var bounds = [];
        bounds.push([popover.offset().left, popover.offset().top, popover.outerWidth(), popover.outerHeight()]);
        if (options.additionalCursorRects) {
            for (var i=0; i < options.additionalCursorRects.length; i++) {
                bounds.push(options.additionalCursorRects[i]);
            }
        }
        region = mouseTracker.add(bounds, backing, options);
    
        // Hide anything underneath that needs hiding
        elementHidingManager.update();
    
        popover.backing = backing;
        return backing;
    };    
    
    // Belongs here in "static class" space because there will only ever be one at a time
    var overlay = null;
    
    
    // Initialize popover triggers
    $.fn.amazonPopoverTrigger = function(customSettings) {
        var settings = $.extend({}, $.AmazonPopover.defaultSettings, customSettings);
        var trigger = this;
        var popover = null;

        initialize();
        if (!settings.showOnHover) this.mouseover(preparePopover);

        // Pull the ajax url from the trigger if it's an anchor tag
        if (!settings.localContent && !settings.destination && !settings.literalContent) {
            var href = trigger.attr("href");
            if (href && (href.indexOf("http") == 0 || href.indexOf("/") == 0)) {
                settings.destination = trigger.attr("href");
            }
        }
        if (settings.openEvent === null) {
            settings.openEvent = settings.showOnHover ? $.AmazonPopover.eventType.MOUSE_ENTER : $.AmazonPopover.eventType.CLICK_TRIGGER;
        }
        if (settings.closeEvent === null) {
            settings.closeEvent = settings.showOnHover ? $.AmazonPopover.eventType.MOUSE_LEAVE : $.AmazonPopover.eventType.CLICK_TRIGGER;
        }

        // Hover-Triggered
        if (settings.openEvent & $.AmazonPopover.eventType.MOUSE_ENTER) {
            var timerID = null;
            trigger.mouseover(function(e){
                if (!popover && !timerID) {
                    timerID = setTimeout(function() {
                        popover = displayPopover(settings, trigger, function(){ popover = null; });
                        timerID = null;
                    }, settings.hoverShowDelay);
                }
                return false;
            });
            this.mouseout(function(e){
                if (!popover && timerID) {
                    clearTimeout(timerID);
                    timerID = null;
                }
            });
        }
        // Click-Triggered
        if (settings.openEvent & $.AmazonPopover.eventType.CLICK_TRIGGER) {
            trigger.click(function(e) {
                if (popover) {
                    if (settings.closeEvent & $.AmazonPopover.eventType.CLICK_TRIGGER) {
                        popover.close();
                        popover = null;
                    }
                } else {
                    popover = displayPopover(settings, trigger, function(){ popover = null; });
                }
                return false;
            });            
        }
        return this;
    };

    
    var displayPopover = function(customSettings, trigger, destroyFunction) {
        // INITIALIZE SETTINGS
        var settings = $.extend({}, $.AmazonPopover.defaultSettings, customSettings);
        
        // No positioning function, use a reasonable default one
        if (!settings.location) {
            settings.location = settings.modal || !trigger ? locationFunction["centered"] : locationFunction["belowTrigger"];
        }
        // No close button for hover triggered popovers (by default)
        if (settings.showCloseButton === null) {
            settings.showCloseButton = !settings.showOnHover;
        }
        
        // CLOSE FUNCTION
        // Puts back the original content if it wasn't a clone
        var close = function() {
            if (popover.backing) {
                popover.backing.destroy();
                popover.backing = null;
            }
            if (original) {
                original.hide().appendTo(document.body); 
            }
            if (original != popover) {
                popover.remove();
            }

            if (destroyFunction) destroyFunction();
            if (settings.onHide) settings.onHide.call(trigger, popover);

            if (overlay) {
                overlay.remove();
                overlay = null;
            }
            $(document).unbind('scroll.AmazonPopover');
            $(document).unbind('click', close);
            return false; // so it can be used as event handler on A tags
        };
        
        // MODAL OVERLAY
        if (settings.modal && !overlay) {
            overlay = showOverlay(close, settings.zIndex);
        }        
        
        // CREATE POPOVER
        var popover = null;
        var original = null;
        var needsContent = true;
        
        if (settings.skin) {
            // Outer skin
            if (settings.skin == "default") {
                preparePopover();
                popover = $.AmazonPopover.preparedPopover;
                $.AmazonPopover.preparedPopover = null;
            } else {
                // Same regex that jQuery uses
                var skinIsHtml = /^[^<]*(<(.|\s)+>)[^>]*$/.test(settings.skin);
                var skin = (skinIsHtml ? settings.skin : $.AmazonPopover.skins[settings.skin]) || $.AmazonPopover.skins["default"];
                var popover = $(skin);                
            }
        } else if (settings.localContent) {
            // Content _is_ the skin
            if (settings.clone) {
                popover = $(settings.localContent).clone(true);
            } else {
                popover = original = $(settings.localContent);
            }
            needsContent = false;
        }
        
        if (needsContent) {
            if (settings.localContent) {
                if (settings.clone) {
                    popover.find('.ap_content').empty().append($(settings.localContent).clone(true).show());
                } else {
                    original = $(settings.localContent);
                    popover.find('.ap_content').empty().append(original.show());
                }
                needsContent = false;
            } else if (settings.literalContent) {
                popover.find('.ap_content').empty().html(settings.literalContent);
                needsContent = false;
            } else if (settings.destination) {
                if ($.AmazonPopover.ajaxCache[settings.destination]) {
                    popover.find('.ap_content').empty().html($.AmazonPopover.ajaxCache[settings.destination]);
                    needsContent = false;
                }
            }
        }
        
        popover.css({
          zIndex: settings.zIndex,
          position: "absolute"
        });
        
        if (settings.closeEvent & $.AmazonPopover.eventType.CLICK_OUTSIDE) {
            popover.click(function(e){ e.stopPropagation(); return false; });
            $(document).click(close);
        }
        
        var width = settings.width && (typeof settings.width == "function" ? settings.width() : settings.width);
        if (width) popover.css("width", width);
        
    
        // Bind scroll listener if necessary (disabled for now)
        if (settings.followScroll) {
            $(document).bind('scroll.AmazonPopover', function(e) { followScroll(e); });
        }
                
        // PNG Fix
        if (popover.pngFix) popover.pngFix();

        // Set the title bar
        if (settings.title) {
            var titleBar = popover.find('.ap_titlebar');
            if (settings.skin == "default") {
                titleBar.css({ width: (width - 36) + 'px' });
                popover.find('.ap_content').css({ paddingTop: '10px' });
            }
            popover.find('.ap_title').html(settings.title);

            if (settings.draggable && !settings.modal) {
                enableDragAndDrop(titleBar, popover);
            }
            titleBar.show();
        } else {
            popover.find('.ap_titlebar').hide();
        }

        // Close button (present by default, unless specifically 'false')
        if (settings.showCloseButton !== false) {
            popover.find('.ap_close').show().click(close)
                .mousedown(function(e){ e.preventDefault(); e.stopPropagation(); return false; })
                .css("cursor", "default");
        } else {
            popover.find('.ap_close').hide();
        }

        if (settings.closeText) {
          popover.find('.ap_closetext').text(settings.closeText).show();
        }
    
        // Any elements in the content with this class get the "close on click" behavior
        popover.find('.ap_custom_close').click(close);
        
        
        popover.appendTo(document.body);
        
        // Position it
        position(popover, settings, trigger);
        
        /************ SHOW POPOVER **************/
        popover.show();
        /****************************************/

        if (settings.onShow) { settings.onShow.call(trigger, popover); }
        
        // Apply Backing
        if (settings.closeEvent & $.AmazonPopover.eventType.MOUSE_LEAVE) {
            var timerID = null;
            applyBacking(popover, {
                solidRectangle:settings.solidRectangle,
                useIFrame:settings.useIFrame,
                mouseEnter:function(backing) {
                    if (timerID) {
                        clearTimeout(timerID);
                        timerID = null;
                    }
                },
                mouseLeave:function() {
                    if (timerID) clearTimeout(timerID);
                    timerID = setTimeout(function(){
                        close();
                        timerID = null;
                    }, settings.hoverHideDelay);
                },
                additionalCursorRects:trigger ? [[trigger.offset().left, trigger.offset().top, trigger.outerWidth(), trigger.outerHeight()]] : [],
                inside:true
            });
        } else {
            applyBacking(popover, {solidRectangle:settings.solidRectangle, useIFrame:settings.useIFrame} );
        }
        
        // If it still needs content it must need uncached Ajax
        if (needsContent && settings.destination) {
            var container = popover.find('.ap_content');
            $.get(settings.destination, function(data, status) {
            	$.AmazonPopover.ajaxCache[settings.destination] = data;

            	if (settings.ajaxSlideDuration > 0) {
                	container.slideDown(function(){
                	    container.hide();
                	    container.empty().html(data);
                	    popover.backing.refreshBounds();
                	}, settings.ajaxSlideDuration);
                } else {
            	    container.empty().html(data);
                    popover.backing.refreshBounds();
                }
            });
            needsContent = false;
        }
        
        // If it still needs content, they did something wrong
        // assert(!needsContent)
        
        
        // "popover" is the jQuery object for the popover, but we're sticking some extra methods in there.
        popover.close = close;
        return popover;
    };

    var showOverlay = function(closeFunction, z) {
        var overlay = $('<div id="ap_overlay"/>');
        overlay.css({
            width: getPageWidth(),
            height: getPageHeight(),
            position: ($.browser.mozilla || $.browser.safari) ? 'fixed' : '',
            opacity: 0.4,
            zIndex: z
        });
        overlay.click(closeFunction).appendTo(document.body).fadeIn(500);
        return overlay;
    };
    
    var position = function(popover, settings, trigger) {
        if (typeof settings.location == "function") {
            var location = settings.location(settings, trigger, popover);
        } else if (typeof settings.location == "string") {
            var location = locationFunction[settings.location](settings, trigger, popover);
        }
        if (typeof location.left == "number") location.left += settings.locationOffset[0];
        if (typeof location.right == "number") location.right += settings.locationOffset[0];
        if (typeof location.top == "number") location.top += settings.locationOffset[1];
        popover.css({
            top: location.top,
            left: location.left,
            margin: location.margin,
            right: location.right            
        });
    };

    // Private functions
    var getPageWidth = function() {
        return $.browser.msie ? $(window).width() + 'px' : '100%';
    };

    var getPageHeight = function() {
        return $.browser.msie ? $(window).height() + 'px' : '100%';
    };
    
    var locationFunction = {
        "belowTrigger": function(settings, trigger, popover) {
            var triggerLocation = trigger.offset();

            // Each skin can optionally include a "surround" attribute on the root element, which 
            // is the distance from each edge where the popover _really_ starts, i.e. with the drop 
            // shadow subtracted off. It's in top,right,bottom,left order (same as CSS uses).
            var surround = jQuery.map((popover.attr("surround") || "0,0,0,0").split(","), function(n){return Number(n);});

            return {
                  left: triggerLocation.left - surround[3],
                  top: triggerLocation.top + $(trigger).outerHeight() - surround[0],
                  margin: '0px'
            };            
        },
        "centered": function(settings, trigger, popover) {
            var y = $(window).scrollTop() + 100;
            return {
                left: -(settings.width / 2) + 'px',
                right: '0px',
                top: y + 'px',
                margin: '0% 50%'
            };
            
        }
    };
    
    var getPopoverLocation = function(settings, trigger, popover) {
        var triggerLocation = trigger.offset();
        
        // Each skin can optionally include a "surround" attribute on the root element, which 
        // is the distance from each edge where the popover _really_ starts, i.e. with the drop 
        // shadow subtracted off. It's in top,right,bottom,left order (same as CSS uses).
        var surround = jQuery.map((popover.attr("surround") || "0,0,0,0").split(","), function(n){return Number(n);});
        
        return {
              left: triggerLocation.left - surround[3],
              top: triggerLocation.top + $(trigger).outerHeight() - surround[0],
              margin: '0px'
        };
    };

    var getModalLocation = function(settings) {
        var y = $(window).scrollTop() + 100;
        return {
            left: -(settings.width / 2) + 'px',
            right: '0px',
            top: y + 'px',
            margin: '0% 50%'
        };
    };  

    var initialize = function() {
        if ($.AmazonPopover.initialized) return;
        $.AmazonPopover.initialized = true;

        // Initialization stuff here
        if ($.browser.msie) {
            $(window).resize(function() {
                $('#ap_overlay').css({
                    width: getPageWidth(),
                    height: getPageHeight()
                });
            });
        }
    };

    var preparePopover = function() {
        if (!$.AmazonPopover.preparedPopover) {
            var popover = $($.AmazonPopover.skins["default"]);

            popover.css({
            top: '-2000px',
            left: '-2000px'
            });

            $('body').append(popover);
            $.AmazonPopover.preparedPopover = popover;
        }
    };

    var enableDragAndDrop = function(titlebar, popover) {
        titlebar.css('cursor', 'move');
        disableSelect(titlebar.get(0));
        
        titlebar.mousedown(function(e){
            e.preventDefault(); // Prevent text selection while dragging
            disableSelect(document.body); // For IE, for which preventDefault isn't quite enough
            var offset = [e.pageX - popover.offset().left, e.pageY - popover.offset().top]; // Initial mousedown location in popover-local coordinates

            var mousemove = function(e){
                e.preventDefault();
                popover.css({
                    left: e.pageX - offset[0],
                    top: e.pageY - offset[1]
                });
                if (popover.backing) {
                    popover.backing.reposition(e.pageX - offset[0], e.pageY - offset[1]);
                }
            };
            var mouseup = function(e){
                enableSelect(document.body);              
                $(document).unbind("mousemove", mousemove);
                $(document).unbind("mouseup", mouseup);
            };
            $(document).mousemove(mousemove).mouseup(mouseup);
        });
    }
    
    // Storing the mousedown coordinates in $.AmazonPopover.mouseStart instead of
    // a local variable is presumably more memory-efficient, but in my option, less
    // readable.
    //
    // Not sure what's better -- namespaced events with anonymous callbacks, or 
    // non-namespaced events with named callbacks...
    var enableDragAndDrop2 = function(titleBar) {
        titleBar.css('cursor', 'move');
        titleBar.mousedown(function(e) {
            var parent = $(this).parent();
            $.AmazonPopover.draggedElement = parent;
            disableSelect(document.body);
            disableSelect(parent[0]);
            $.AmazonPopover.mouseStart = { x: e.pageX - $(this).offset().left, y: e.pageY - $(this).offset().top};
            $().bind('mousemove.popover', function(e) {
                var start = $.AmazonPopover.mouseStart;
                if (start) {
                    var x = e.pageX - start.x - 15;
                    var y = e.pageY - start.y - 10;
                    var elem = $.AmazonPopover.draggedElement;
                    if (elem) {
                        elem.css({
                            top: y + 'px',
                            left: x + 'px'
                        });
                    }
                }
            });
            $().one('mouseup.popover', function(e) {
                enableSelect(document.body);
                enableSelect(parent[0]);
                $().unbind('mousemove.popover');
            });
        });
    };

    var disableSelect = function(e) {
        if (e) {
            e.onselectstart = function(e) { return false; };
            e.style.MozUserSelect = 'none';
        }
    };

    var enableSelect = function(e) {
        if (e) {
            e.onselectstart = function(e) { return true; };
            e.style.MozUserSelect = '';
        }
    };

    // THIS IS EXPERIMENTAL
    var followScroll = function(e) {
        var popover = $('.ap_popover');
        var height = popover.height();
        var top = popover.position().top;
        var bottom = top + height;
        var scrollTop = $(document).scrollTop();
        var scrollBottom = scrollTop + $(window).height();

        var PADDING = 99;

        if (scrollTop < top - PADDING) {
          popover.css({ 'position': 'fixed', 'top': PADDING + 'px' });
          $.AmazonPopover.pinned = 'top';      
        } else if (scrollBottom > bottom + PADDING) {
          popover.css({ 'position': 'fixed', 'top': ($(window).height() - height - PADDING) + 'px' });
          $.AmazonPopover.pinned = 'bottom';      
        }

        if (scrollTop <= $.AmazonPopover.lastScrollTop && $.AmazonPopover.pinned == 'bottom'
        || scrollTop >= $.AmazonPopover.lastScrollTop && $.AmazonPopover.pinned == 'top') {
          popover.css({ 'position': 'absolute', 'top': top + 'px' });
          $.AmazonPopover.pinned = null;
        }

        $.AmazonPopover.lastScrollTop = scrollTop;
    };

    // Default settings
    $.AmazonPopover = {
        initialized: false,
        defaultSettings: {
            width: 500,             // Desired width of popover
            destination: null,      // Location of remote AJAX popover content
            localContent: null,     // Selector for local popover content
            literalContent: null,   // HTML and text
            followScroll: false,    // Follow scroll when popover height exceeds viewport
            location: null,         // Location function override. Takes in settings object and trigger
            locationOffset: [0,0],  // Shift the location after it is computed
            modal: false,           // If modal, will show a lightbox
            draggable: false,       // Enable drag & drop of non-modal popovers
            closeText: '',          // Text to show by the close button
            showCloseButton: null,  // Set programmatically to !showOnHover
            zIndex: 100,            // Should be on top of everything I would think
            showOnHover: false,     // True for hover triggering, False for click trigger
            hoverShowDelay: 400,    // milliseconds
            hoverHideDelay: 200,    // milliseconds
            skin: "default",        // default | classic | string of HTML
            useIFrame: true,        // Only disable iframe if there won't be any Flash nearby
            clone:false,            // Clone content instead of DOM cut n' paste
            openEvent:null,         // Logical OR of jQuery.AmazonPopover.eventType values
            closeEvent:null,        // Logical OR of jQuery.AmazonPopover.eventType values
            ajaxSlideDuration:400   // milliseconds
        },
        eventType: {
            MOUSE_ENTER:      0x01,
            MOUSE_LEAVE:      0x02, 
            CLICK_TRIGGER:    0x04,
            CLICK_OUTSIDE:    0x08
        },
        ajaxCache: {},
        preparedPopover: null,
        lastScrollTop: 0,
        pinned: null,
        dragegedElement: null,
        mouseStart: null,
        mouseOffset: null,
        displayPopover: displayPopover,
        skins: {
            "default":
                '<div class="ap_popover" surround="8,16,18,16"> \
                    <div class="ap_header"> \
                        <div class="ap_left"/> \
                        <div class="ap_middle"/> \
                        <div class="ap_right"/> \
                    </div> \
                    <div class="ap_body"> \
                        <div class="ap_left"/> \
                        <div class="ap_content"><img src="http://g-ecx.images-amazon.com/images/G/01/javascripts/lib/popover/images/snake.gif"/></div> \
                        <div class="ap_right"/> \
                    </div> \
                    <div class="ap_footer"> \
                        <div class="ap_left"/> \
                        <div class="ap_middle"/> \
                        <div class="ap_right"/> \
                    </div> \
                    <div class="ap_titlebar"> \
                        <div class="ap_title"/> \
                    </div> \
                    <div class="ap_close"><a href="#"><span class="ap_closetext"/><img border="0" src="http://g-ecx.images-amazon.com/images/G/01/javascripts/lib/popover/images/btn_close.gif"/></a></div> \
                </div>',
            "classic":
                // TODO: Factor out inline CSS
                '<div style="border-top:1px solid #ccc;border-left:1px solid #ccc;border-bottom:1px solid #2F2F1D; border-right:1px solid #2F2F1D;background-color:#EFEDD4;padding:3px;"> \
                    <div style="color:#86875D;font-size:12px;padding:0 0 0 0;line-height:1em;" class="ap_titlebar"> \
                        <div class="ap_close" style="float:right"> \
                            <img width="46" height="16" border="0" alt="close" onmouseup=\'this.src="http://g-ecx.images-amazon.com/images/G/01/nav2/images/close-tan-sm._V46903531_.gif";\' onmouseout=\'this.src="http://g-ecx.images-amazon.com/images/G/01/nav2/images/close-tan-sm._V46903531_.gif";\' onmousedown=\'this.src="http://g-ecx.images-amazon.com/images/G/01/nav2/images/close-tan-sm-dn._V46881222_.gif";\' src="http://g-ecx.images-amazon.com/images/G/01/nav2/images/close-tan-sm._V46903531_.gif" /> \
                        </div> \
                        <span class="ap_title" style="padding:0;"></span> \
                    </div> \
                    <div class="ap_content" style="clear:both;background-color:white;border:1px solid #ACA976;padding:8px;font-size:11px;"></div> \
                </div>'
        }
    };

    $(document).ready(function() {
        $('a[@role^="AmazonPopover"]').amazonPopoverTrigger();
    });
})(jQuery)
