goog.provide('ol.Popup');

goog.require('ol.Map');
goog.require('ol.Loc');
goog.require('ol.Feature');


/**
 * @export
 * @constructor
 * @param {ol.Map} map the map on which the popup is placed.
 * @param {ol.Loc|ol.Feature=} opt_anchor the anchor object for the popup.
 * @param {string=} opt_placement the placement of the arrow on the popup.
 * @param {boolean=} opt_close include a close button on the popup
 */
ol.Popup = function(map, opt_anchor, opt_placement, opt_close) {

    /**
     * @private
     * @type {ol.Map}
     */
    this.map_ = map;
    
    /**
     * @private
     * @type {ol.Loc|ol.Feature|undefined}
     */
    this.anchor_ = opt_anchor;
    
    /**
     * can be 'top','bottom','right','left','auto'
     * TODO: 'auto' not yet implemented
     * @private
     * @type {!string}
     */
     this.placement_ = goog.isDefAndNotNull(opt_placement)?opt_placement:'top';
    
    /**
     * include a close button on the popup - defaults to true.
     * @private
     * @type {boolean|undefined}
     */
    this.closeButton_ = goog.isDefAndNotNull(opt_close) ? opt_close : true;
    
    /**
     * @private
     * @type {string|undefined}
     */
    this.content_ = undefined;
    
    /**
     * @private
     * @type {string|undefined}
     */
    this.template_ = undefined;
    
    /**
     * @private
     * @type {Element}
     */
    this.container_ = null;
    
    /**
     * @private
     * @type {number}
     */
    this.arrowOffset_ = 30; //FIXME: set this from CSS dynamically somehow?
    
    /**
     * if the CSS sets either width or height assume the app is specifying the
     * size of the popup, if not auto size the popup.
     * @private
     * @type {boolean}
     */
    this.autoSize_ = true;
    
};

/**
 * @const
 */
ol.Popup.CLASS_NAME = 'ol-popup';

/**
 * @return {ol.Map} Projection.
 */
ol.Popup.prototype.getMap = function() {
    return this.map_;
};

/**
 * @param {ol.Map} map the map object to hold this popup.
 */
ol.Popup.prototype.setMap = function(map) {
    this.map_ = map;
};

/**
 * @return {ol.Feature|ol.Loc|undefined} the anchor .
 */
ol.Popup.prototype.getAnchor = function() {
    return this.anchor_;
};

/**
 * @param {ol.Feature|ol.Loc} anchor the anchor location to place this popup.
 */
ol.Popup.prototype.setAnchor = function(anchor) {
    this.anchor_ = anchor;
};


/**
 * @return {string|undefined} the placement value relative to the anchor.
 */
ol.Popup.prototype.getPlacement = function() {
    return this.placement_;
};

/**
 * @param {string} placement where to place this popup relative to the anchor.
 */
ol.Popup.prototype.setPlacement = function(placement) {
    if (!goog.isNull(this.container_)) {
        goog.dom.classes.remove(this.container_, 
                               ol.Popup.CLASS_NAME+'-'+this.placement_);
        goog.dom.classes.add(this.container_,ol.Popup.CLASS_NAME+'-'+placement);
    }
    this.placement_ = placement;
};


/**
 * @return {string|undefined} static content to be displayed in the popup (HTML)
 */
ol.Popup.prototype.getContent = function() {
    return this.content_;
};

/**
 * @param {string} content the content to be displayed this popup.
 */
ol.Popup.prototype.setContent = function(content) {
    this.content_ = content;
};


/**
 * @private
 * @returns {string} generates the content
 */
ol.Popup.prototype.generateContent_ = function() {
    //set the content
    if ( goog.isDefAndNotNull(this.content_) ) {
        return this.content_;
    } else {
        if ( goog.isDefAndNotNull(this.template_) && 
             goog.isDefAndNotNull(this.anchor_) &&
            (this.anchor_ instanceof ol.Feature)) {
            //set content from feature attributes on the template
            //TODO: this.setContent(template.apply(this.anchor_.getAttributes()));
            return this.template_; //stub to return something
        } else {
            ol.error('ol.Popup unabale to generate any content');
            return '<p>no content</p>';
        }
    }
};


/**
 * @return {string|undefined} the anchor .
 */
ol.Popup.prototype.getTemplate = function() {
    return this.template_;
};

/**
 * @param {string} template the map object to hold this popup.
 */
ol.Popup.prototype.setTemplate = function(template) {
    this.template_ = template;
};

/**
 * Open the popup.
 * @param {ol.Feature|ol.Loc} opt_arg feature or location for the anchor
 */
ol.Popup.prototype.doOpen = function(opt_arg) {
    if (goog.isDef(opt_arg)) {
        this.setAnchor(opt_arg);
    }
    
    //create popup container if it's not created already
    if (goog.isNull(this.container_)) {
        this.container_ = goog.dom.createElement('div');
        goog.dom.classes.add(this.container_, 
            ol.Popup.CLASS_NAME, ol.Popup.CLASS_NAME+'-'+this.placement_);
        
        //see if the style class sets width or height
        if (goog.style.getStyle(this.container_, 'width').length>0 ||
            goog.style.getStyle(this.container_, 'height').length>0 ) {
            this.autoSize_ = false;
        }
        
        if (this.closeButton_) {
            var closeButton = goog.dom.createElement('div');
            goog.dom.appendChild(this.container_, closeButton);
            goog.dom.classes.add(closeButton,  ol.Popup.CLASS_NAME+'-close');
        }
        this.map_.getEvents().register('click', this.clickHandler, this);
        goog.dom.appendChild(this.map_.getMapOverlay(), this.container_);
    }
    
    this.childContent_=goog.dom.htmlToDocumentFragment(this.generateContent_());
    goog.dom.appendChild(this.container_, this.childContent_);

    if (this.autoSize_) {
        this.registerImageListeners();
    }
    
    this.setAnchorOffset_();
};

ol.Popup.prototype.setAnchorOffset_ = function() {
    
    if (goog.isNull(this.container_.parentNode)) {
        //this means the popup has already been closed, nothing to do here
        //which might happen while waiting for images to load
        return;
    }
    
    if (!goog.isDefAndNotNull(this.anchor_)) {
        //must have an anchor when trying to set the position
        ol.error("ol.Popup must have an anchor to set the position");
        return;
    }
    
    //position the element
    if (this.anchor_ instanceof ol.Feature) {
        this.pos_ = this.anchor_.getGeometry().getCentroid();
    } else {
        this.pos_ = new ol.geom.Point(this.anchor_.getX(), this.anchor_.getY());
    }
    var pos = /** @type {ol.Loc} */ (this.pos_);
    var popupPosPx = this.map_.getPixelForLoc(pos);
    var popupSize = goog.style.getSize(this.container_);
    
    switch(this.placement_) {
        default:
        case 'auto':
            //TODO: switch based on map quadrant
            break;
        case 'top':
        case 'bottom':
            popupPosPx[0] -= popupSize.width / 2.0;

            if (this.placement_ == "bottom") {
                popupPosPx[1] -= popupSize.height + this.arrowOffset_;
            } else {
                popupPosPx[1] += this.arrowOffset_;
            }
            break;
        case 'left':
        case 'right':
            popupPosPx[1] -= popupSize.height / 2.0;

            if (this.placement_ == "right") {
                popupPosPx[0] -= popupSize.width + this.arrowOffset_;
            } else {
                popupPosPx[0] += this.arrowOffset_;
            }
            break;
    }
    this.moveTo_(popupPosPx);
    
};

/**
 * registerImageListeners
 * Called when an image contained by the popup loaded. this function
 *     updates the popup size, then unregisters the image load listener.
 */   
ol.Popup.prototype.registerImageListeners = function() {

    // As the images load, this function will call setAnchorOffset_() to 
    // resize the popup to fit the content div (which presumably is now
    // bigger than when the image was not loaded).
    // 
    //cycle through the images and if their size is 0x0, that means that 
    // they haven't been loaded yet, so we attach the listener, which 
    // will fire when the images finish loading and will resize the 
    // popup accordingly to its new size.
    var images = this.container_.getElementsByTagName("img");
    for (var i = 0, len = images.length; i < len; i++) {
        var img = images[i];
        if (img.width == 0 || img.height == 0) {
            goog.events.listenOnce(img, 'load', 
                                goog.bind(this.setAnchorOffset_, this));
        }    
    } 
};


/**
 * @param px - {goog.} the top and left position of the popup div. 
 */
ol.Popup.prototype.moveTo_ = function(px) {
    if (goog.isDefAndNotNull(px)) {
        goog.style.setPosition(this.container_, px[0], px[1]);
    }
};

/**
 * Click handler
 * @param {Event} evt the event generated by a click
 */
ol.Popup.prototype.clickHandler = function(evt) {
    var target = /** @type {Node} */ evt.target;
    if (goog.dom.classes.has(target,ol.Popup.CLASS_NAME+'-close')) {
        this.close();
    }
};

/**
 * Clean up.
 * @export
 */
ol.Popup.prototype.close = function() {
    goog.dom.removeChildren(this.container_);
    goog.dom.removeNode(this.container_);
};

/**
 * Clean up.
 * @export
 */
ol.Popup.prototype.destroy = function() {
    for (var key in this) {
        delete this[key];
    }
};
