L.ImageOverlay.WMS = L.ImageOverlay.extend({
    defaultWmsParams: {
        service: 'WMS',
        request: 'GetMap',
        version: '1.1.1',
        layers: '',
        styles: '',
        format: 'image/jpeg',
        transparent: false,
        tiled: false,
        identify: true
    },
    initialize: function(url, options) {
        this._baseUrl = url;

        var wmsParams = L.Util.extend({}, this.defaultWmsParams);

        if (options.detectRetina && L.Browser.retina) {
            wmsParams.width = wmsParams.height = this.options.tileSize * 2;
        } else {
            wmsParams.width = wmsParams.height = this.options.tileSize;
        }

        for (var i in options) {
            // all keys that are not ImageOverlay options go to WMS params
            if (!this.options.hasOwnProperty(i)) {
                wmsParams[i] = options[i];
            }
        }

        this.wmsParams = wmsParams;

        L.Util.setOptions(this, options);
    },
    onAdd: function(map) {
        this._bounds = map.getBounds();
        this._map = map;

        var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
        this.wmsParams[projectionKey] = this.wmsParams[projectionKey] || map.options.crs.code;

        map.on("moveend", this._reset, this);

        L.ImageOverlay.prototype.onAdd.call(this, map);
    },
    _updateUrl: function() {
        var map = this._map,
                bounds = this._bounds,
                zoom = map.getZoom(),
                crs = map.options.crs,
                topLeft = map.latLngToLayerPoint(bounds.getNorthWest()),
                mapSize = map.latLngToLayerPoint(bounds.getSouthEast()).subtract(topLeft),
                nw = crs.project(bounds.getNorthWest()),
                se = crs.project(bounds.getSouthEast()),
                bbox = [nw.x, se.y, se.x, nw.y].join(','),
                urlParams = {width: mapSize.x, height: mapSize.y, bbox: bbox},
        url = this._baseUrl + L.Util.getParamString(L.Util.extend({}, this.wmsParams, urlParams));

        this._url = url;
    },
    _updateImagePosition: function() {
        // The original reset function really just sets the position and size, so rename it for clarity.
        L.ImageOverlay.prototype._reset.call(this);
    },
    _reset: function() {
        if (!this._map.hasLayer(this))
            return; // fred: do not load if invisible

        this._bounds = this._map.getBounds();

        this._updateUrl();
        L.Util.extend(this._image, {
            src: this._url
        });
    },
    _onImageLoad: function() {
        this.fire('load');

        // Only update the image position after the image has loaded.
        // This the old image from visibly shifting before the new image loads.
        this._updateImagePosition();
    },
    getFeatureInfo: function(point, latlng, layers, callback) {
        // Request WMS GetFeatureInfo and call callback with results
        // (split from identify() to faciliate use outside of map events)
        var params = this.getFeatureInfoParams(point, layers),
                url = this._url + L.Util.getParamString(params, this._url);

        this.showWaiting();
        ajax.call(this, url, done);

        function done(result) {
            this.hideWaiting();
            var text = this.parseFeatureInfo(result, url);
            callback.call(this, latlng, text);
        }
    },
    getEvents: function() {
        if (this.options.identify) {
            return {'click': this.identify};
        } else {
            return {};
        }
    }  ,
    identify: function(evt) {
        // Identify map features in response to map clicks. To customize this
        // behavior, create a class extending L.WMS.Source and override one or
        // more of the following hook functions.

        var layers = this.getIdentifyLayers();
        if (!layers.length) {
            return;
        }
        this.getFeatureInfo(
                evt.containerPoint, evt.latlng, layers,
                this.showFeatureInfo
                );
    },
    getIdentifyLayers: function() {
        // Hook to determine which layers to identify
        if (this.options.identifyLayers)
            return this.options.identifyLayers;
        return Object.keys(this._subLayers);
    },
    getFeatureInfoParams: function(point, layers) {
        // Hook to generate parameters for WMS service GetFeatureInfo request
        var wmsParams, overlay;
        if (this.options.tiled) {
            // Create overlay instance to leverage updateWmsParams
            overlay = L.WMS.overlay(this._url, this.options);
            overlay.updateWmsParams(this._map);
            wmsParams = overlay.wmsParams;
            wmsParams.layers = layers.join(',');
        } else {
            // Use existing overlay
            wmsParams = this._overlay.wmsParams;
        }
        var infoParams = {
            'request': 'GetFeatureInfo',
            'query_layers': layers.join(','),
            'X': point.x,
            'Y': point.y
        };
        return L.extend({}, wmsParams, infoParams);
    },
    parseFeatureInfo: function(result, url) {
        // Hook to handle parsing AJAX response
        if (result == "error") {
            // AJAX failed, possibly due to CORS issues.
            // Try loading content in <iframe>.
            result = "<iframe src='" + url + "' style='border:none'>";
        }
        return result;
    },
    showFeatureInfo: function(latlng, info) {
        if (!this._map) {
            return;
        }
        this._map.openPopup(info, latlng);
    },
    showWaiting: function() {
        if (!this._map)
            return;
        this._map._container.style.cursor = "progress";
    },
    hideWaiting: function() {
        if (!this._map)
            return;
        this._map._container.style.cursor = "default";
    }




});

L.imageOverlay.wms = function(url, options) {
    return new L.ImageOverlay.WMS(url, options);
};