/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */
var onePopup = false;
OpenLayers.Position = {
    TOP_LEFT: "top-left",
    TOP_RIGHT: "top-right",
    BOTTOM_LEFT: "bottom-left",
    BOTTOM_RIGHT: "bottom-right"   
};

/**
 * Class: OpenLayers.Popup.FramedBubble
 */
OpenLayers.Popup.FramedBubble = OpenLayers.Class({

    /**
     * Property: bubble
     * hash of properties for the bubble decoration
     */
    bubble: {
        /**
         * src - {String} url to the global popup image
         * size - {<OpenLayers.Size>} size of the global popup image
         */
        image: {
            src: OpenLayers.Util.getImagesLocation() + 'popup.png',
            size: new OpenLayers.Size(676, 736)
        },
        /**
         * popup minimum size
         */
        minSize: new OpenLayers.Size(105, 10),
        /**
         * outside margin of the popup including bubble decoration
         * usefull to prevent the popup to be too close to the map border
         */
        margin: new OpenLayers.Bounds(5, 5, 5, 5),
        /**
         * inside padding of the bubble
         * usefull for the contentDiv
         */
        padding: [15, 32, 61, 15],
        /**
         * Relative position in the lower right corner
         */
        relativePosition: OpenLayers.Position.BOTTOM_RIGHT,
        /**
         * The x and y offset needed position the stem image over the marker.
         */
        offset: new OpenLayers.Pixel(55, -10),
        /**
         * size - {Array} width and height of the clipping div (accepts 'auto' values)
         * anchor - {Array} top, right, bottom, left values to position the clipping div
         * position - {<OpenLayers.Pixel>} left and top position of the image, 
         *     in order to show a specific part of the global popup image
         */
        blocks: [
            { // top-left
                size: ['auto', 'auto'],
                anchor: [0, 38, 68, 0],
                position: new OpenLayers.Pixel(0, 0)
            },
            { //top-right
                size: [38, 'auto'],
                anchor: [0, 0, 68, null],
                position: new OpenLayers.Pixel(-638, 0)
            },
            { //bottom-left
                size: ['auto', 39],
                anchor: [null, 97, 29, 0],
                position: new OpenLayers.Pixel(0, -629)
            },
            { // stem
                size: [97, 68],
                anchor: [null, 0, 0, null],
                position: new OpenLayers.Pixel(0, -668)
            }
        ]
    },
    
    div: null,
    contentDiv: null,
    
    /**
     * APIMethod: onLoad
     * {Function} Optional function to be called when the popup is fully drawn.
     */
    onLoad: function() {onePopup = true;},
    
    /**
     * APIMethod: onClose
     * {Function} Optional function to be called when close button is clicked.
     */
    onClose: function() {onePopup = false},
    
    /**
     * Property: decorated
     * {Boolean} tells if popup is already decorated, if true decorate() only updates sizes
     */
    decorated: false,
    
    /** 
     * Constructor: OpenLayers.Popup.FramedBubble
     * 
     * Parameters:
     * id - {String}
     * lonlat - {<OpenLayers.LonLat>}
     * size - {<OpenLayers.Size>}
     * contentHTML - {String}
     * anchor - {Object} Object to which we'll anchor the popup. Must expose 
     *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
     *     (Note that this is generally an <OpenLayers.Icon>).
     * closeBox - {Boolean}
     * closeBoxCallback - {Function} Function to be called on closeBox click.
     */
    initialize:function(id, lonlat, size, contentHTML, anchor, closeBox,
                        closeBoxCallback) {
        
        if (id == null) {
            id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
        }

        this.id = id;
        this.lonlat = lonlat;
        this.size = (size != null) ? size 
                                  : new OpenLayers.Size(
                                                   OpenLayers.Popup.WIDTH,
                                                   OpenLayers.Popup.HEIGHT);
        
        if (contentHTML != null) { 
             this.contentHTML = contentHTML;
        }
     
        // compute the div size (ie. popup with bubble)
        
        this.div = OpenLayers.Util.createDiv(this.id + "Frame", null, null,
                                             null, "absolute");
        this.div.className = "olBubbleFrame";
        
        this.contentDiv = OpenLayers.Util.createDiv(id, null, null, 
                                                    null, "absolute", null,
                                                    "hidden");
        // TODO: when popup is created by a feature using createPopup(), the overflow
        // is reset to auto, which causes display flickr when popup is outside the map viewport
        this.contentDiv.className = 'olPopupContent';
        
        // determine actual render dimensions
        this.size = this.getRenderedDimensions();
        
        this.contentDiv.style.left = this.bubble.padding[3] + "px";
        this.contentDiv.style.top = this.bubble.padding[0] + "px";
        
        if (closeBox) {
            // TODO : we should probably use the bubble properties
            
            // close icon
            var oBtnClose = document.createElement("A");
            oBtnClose.id = "olPopupCloseButton";
            oBtnClose.className = "close";
            oBtnClose.style.position = "absolute";
            oBtnClose.title = "close popup";
            oBtnClose.href = "#";
            this.div.appendChild(oBtnClose);
            
            var closePopup = closeBoxCallback ||
            function(e){
                this.hide();
                OpenLayers.Event.stop(e);
            };
            OpenLayers.Event.observe(oBtnClose, "click",
                OpenLayers.Function.bindAsEventListener(closePopup, this));
        }        
    },

    /** 
     * APIMethod: destroy
     * nullify references to prevent circular references and memory leaks
     */
    destroy: function() {
        if (this.map != null) {
            this.map.removePopup(this);
        }
        this.div = null;
        this.contentDiv = null;
        this.map = null;
    },

    /** 
     * Method: draw
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     * 
     * Returns:
     * {DOMElement} Reference to a div that contains the drawn popup.
     */
    draw: function(px) {
    	if(onePopup == false) {
			this.setSize(this.size);
			this.setContentHTML();
			this.onLoad();
			
			/*
			 * by Pierre GIRAUD: I would prefer something like if (!viewable) { .. doSomething .. }
			 */
			this.shiftMapIfNotViewable();
			  
			return this.div;
		}
    },

    /**
     * Method: moveTo
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>} the top and left position of the popup div. 
     */
    moveTo: function(px) {
        px = this.calculateRelativePosition(px);
        
        this.div.style.left = px.x + "px";
        this.div.style.top = px.y + "px";
    },
    
    /** 
     * Method: updatePosition
     * if the popup has a lonlat and its map member set, 
     * then have it move itself to its proper position
     */
    updatePosition: function() {
        this.shiftMapIfNotViewable();
        
        if ((this.lonlat) && (this.map)) {
            var px = this.map.getLayerPxFromLonLat(this.lonlat);
            if (px) {
                this.moveTo(px);           
            }
        }
    },

    /** 
     * APIMethod: Set the size of the popup. 
     *     Given size is intended to be used for the content.
     *     This function also makes sure the popup does not get sized larger than
     *     the viewport.
     * 
     * Parameters:
     * size {OpenLayers.Size} - size of the content of the popup
     */
    setSize: function(size) {
        size = this.constrainSizeToViewPort(size);
        
        // pgiraud: to my point of view, the popup given size correspond
        // to the size of the content, it's easier to user to set this size
        // (to show an image in the popup for example)
        
        // first set the contentDiv style with the given size
        this.contentDiv.style.width = size.w + "px";
        this.contentDiv.style.height = size.h + "px";
        
        // then compute the size of the frame using the bubble properties
        var w = size.w + this.bubble.padding[1] + this.bubble.padding[3];
        var h = size.h + this.bubble.padding[0] + this.bubble.padding[2];
        size.w = w;
        size.h = h;
        this.frameSize = size;
        
        this.div.style.width = this.frameSize.w + "px";
        this.div.style.height = this.frameSize.h + "px";
        
        this.updatePosition();
        
        this.decorate();
    },
    
    /**
     * Method: getRenderedDimensions
     * Renders the contentHTML offscreen to determine actual dimensions for
     *     popup sizing. As we need layout to determine dimensions the content
     *     is rendered -9999px to the left and absolute to ensure the scrollbars do not flicker
     *     
     * Returns:
     * {OpenLayers.Size}
     */
    getRenderedDimensions : function() {
        
        // create temp container div with restricted size
        var container = document.createElement("div");
        container.className = 'olBubbleFrame';
        container.style.overflow="";
        container.style.position = "absolute";
        container.style.left="-9999px";
            
        // create temp content div and assign content
        var content = document.createElement("div");
        content.innerHTML = this.contentHTML;
        content.className = 'olPopupContent';
        
        // add content to restricted container 
        container.appendChild(content);
        
        // append container to body for rendering
        document.body.appendChild(container);
        
        // calculate scroll width of content and add corners and shadow width
        var w = parseInt(content.scrollWidth);//
        
        // update container width to allow height to adjust
        container.style.width = w + "px";
        
        // capture height and add shadow and corner image widths
        var h = parseInt(content.scrollHeight);//
            
        // remove elements
        document.body.removeChild(container);
        
        // cleanup
        container = null;
        content = null;
        
        // prevent the popup from being smaller than a given minimal size
        if (w < this.bubble.minSize.w) {
            w = this.bubble.minSize.w;
        }
        if (h < this.bubble.minSize.h) {
            h = this.bubble.minSize.h;
        }
        
        return new OpenLayers.Size(w, h);
    },
    
    /**
     * Method: constrainSizeToViewPort
     * Ensure that popup size doesn't get larger than the map viewport size
     * 
     * Returns:
     * {OpenLayers.Size}
     */
    constrainSizeToViewPort: function(size) {
        size = size.clone();
        
        // Restrict the size if it's greater than the map's viewport.
        var mapSize = this.map.size;
        
        // Calculate the difference between the container and the content
        // and scale the content accordingly.
        var heightDifference = size.h - 
            (mapSize.h - 
             this.bubble.margin.top - 
             this.bubble.margin.bottom);
        
        if (heightDifference > 0) {
            size.h -= heightDifference;
        }
        
        var widthDifference = size.w - 
            (mapSize.w - 
             this.bubble.margin.left - 
             this.bubble.margin.right +
             OpenLayers.Popup.SCROLL_BAR_WIDTH);
        
        if (widthDifference > 0) {
            size.w -= widthDifference;
        } else if (size.w + OpenLayers.Popup.SCROLL_BAR_WIDTH < mapSize.w){
            size.w += OpenLayers.Popup.SCROLL_BAR_WIDTH;
        }
        
        return size;
    },
    
    /**
     * Method: shiftMapIfNotViewable
     * Shifts the map (pan) if the popup isn't totaly viewable (includes bubble.margin)
     * TODO use the panTo (animated method)
     */
    shiftMapIfNotViewable : function() {
        
        var mapSize = this.map.getSize();
        var popupPosition = this.calculateRelativePosition(this.map.getViewPortPxFromLonLat(this.lonlat), this.frameSize);
    
        // Get the current center before any shift.
        var center = this.map.getViewPortPxFromLonLat(this.map.getCenter());
        var newCenter = center.clone();
        
        var top = popupPosition.y;
        var right = popupPosition.x + this.frameSize.w;
        var bottom = popupPosition.y + this.frameSize.h;
        var left = popupPosition.x;
        
        // TODO: check race condition
        // what if left is not visible and right is not visible ?
        
        // If left is not visible...
        if (left < this.bubble.margin.left) {
            newCenter.x += left - this.bubble.margin.left;
        }
         
        // If right is not visible...
        if (right > mapSize.w - this.bubble.margin.right) {
            newCenter.x += parseInt(right - mapSize.w + this.bubble.margin.right);
        }
        
        // If top is not visible...
        if (top < this.bubble.margin.top) {
            newCenter.y += top - this.bubble.margin.top;
        }
        
        // If bottom is not visible...
        if (bottom > mapSize.h - this.bubble.margin.bottom) {
            newCenter.y += parseInt(bottom - mapSize.h + this.bubble.margin.bottom);
        }
        
//        this.map.shiftToViewPortPx(newCenter);
        var dx = newCenter.x - center.x;
        var dy = newCenter.y - center.y;
        
        // TODO: this will be the place to call panTo (animated panning)
        this.map.pan(dx, dy);
    },
    
    /**
     * Method: calculateRelativePosition
     * 
     * Calculate the position of the popup (or what the popup should be)
     * given a starting pixel position and a size value.
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>} The top and left position of the popup div.
     * 
     * Returns: 
     * {<OpenLayers.Pixel>} The new top and left position of the popup div.
     */
    calculateRelativePosition: function(px) {
        px = px.clone();
        
        px.x += this.bubble.offset.x;
        px.y += this.bubble.offset.y;
        
        if (this.bubble.relativePosition == OpenLayers.Position.TOP_RIGHT ||
            this.bubble.relativePosition == OpenLayers.Position.BOTTOM_RIGHT) {
            px.x -= this.frameSize.w;
        }
        
        if (this.bubble.relativePosition == OpenLayers.Position.BOTTOM_LEFT ||
            this.bubble.relativePosition == OpenLayers.Position.BOTTOM_RIGHT) {
            px.y -= this.frameSize.h;
        }
        
        return px; 
    },
    
    /**
     * Method: setContentHTML
     * Allows the user to set the HTML content of the popup.
     *
     * Parameters:
     * contentHTML - {String} HTML for the div.
     */
    setContentHTML:function(contentHTML) {
        if (contentHTML != null) {
            this.contentHTML = contentHTML;
        }
        
        if (this.contentDiv != null) {
            this.contentDiv.innerHTML = this.contentHTML;
        }    
    },
    
    /****************************
     *        Styling           *
     ****************************/
    
    /**
     * Method: decorate
     * Append the bubble images (corners) to the frame
     */
    decorate: function() {
        var src = this.bubble.image.src;
        var blocks = this.bubble.blocks;
        for (var i in blocks) {
            var block = blocks[i];
            var divId = this.id + '_FrameDecorationDiv_' + i;
            var imgId = this.id + '_FrameDecorationImg_' + i;
            
            var div, image;
            if (this.decorated) {
                div = OpenLayers.Util.getElement(divId);
                image = OpenLayers.Util.getElement(imgId);
            } else {
                div = OpenLayers.Util.createDiv(divId, null, null, null, "absolute", null, "hidden", null);
                image = OpenLayers.Util.createAlphaImageDiv(imgId, null, this.bubble.image.size, src, "absolute", null, null, null);
                div.appendChild(image);
                this.div.appendChild(div);
            }
            
            // adjust sizes
            var t = block.anchor[0];
            var r = block.anchor[1];
            var b = block.anchor[2];
            var l = block.anchor[3];
            
            var w = (block.size[0] == 'auto') ?
                 this.frameSize.w - (r + l) : block.size[0];
            var h = (block.size[1] == 'auto') ?
                 this.frameSize.h - (b + t) : block.size[1];
                 
            div.style.width = parseInt(w) + 'px';
            
            div.style.height = h + 'px';

            div.style.top = (t != null) ? t + 'px' : '';
            div.style.right = (r != null) ? r + 'px' : '';            
            div.style.bottom = (b != null) ? b + 'px' : '';
            div.style.left = (l != null) ? l + 'px' : '';
            
            image.style.left = block.position.x + 'px';
            image.style.top = block.position.y + 'px';
            
        }
        this.div.appendChild(this.contentDiv);
        this.decorated = true;
    },

    /**
     * APIMethod: visible
     *
     * Returns:      
     * {Boolean} Boolean indicating whether or not the popup is visible
     */
    visible: function() {
        return OpenLayers.Element.visible(this.div);
    },

    /**
     * APIMethod: toggle
     * Toggles visibility of the popup.
     */
    toggle: function() {
        OpenLayers.Element.toggle(this.div);
    },

    /**
     * APIMethod: show
     * Makes the popup visible.
     */
    show: function() {
        OpenLayers.Element.show(this.div);
    },

    /**
     * APIMethod: hide
     * Makes the popup invisible.
     */
    hide: function() {
        if (this.map) {
			this.map.removePopup(this);
		}
        OpenLayers.Element.hide(this.div);
        this.onClose();
    },

    /**
     * APIMethod: setBackgroundColor
     * Kept here for compatibility with possible old application code
     */
    setBackgroundColor:function(color) {},  
    
    /**
     * APIMethod: setOpacity
     * Sets the opacity of the popup.
     * 
     * Parameters:
     * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
     */
    setOpacity:function(opacity) { 
        if (opacity != undefined) {
            this.opacity = opacity; 
        }
        
        if (this.div != null) {
            // for Mozilla and Safari
            this.div.style.opacity = this.opacity;

            // for IE
            this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
        }
    },
    
    /**
     * Method: setBorder
     * Kept here for compatibility with possible old application code
     */
    setBorder:function() {},      

    CLASS_NAME: "OpenLayers.Popup.FramedBubble"
});

