goog.provide('ee.FloatTileOverlay');

goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.net.XmlHttp');
goog.require('goog.structs.Map');
goog.require('earthengine_api.javascript.abstractoverlay');


/**
 * A google.maps.MapType implementation used to display Earth Engine tiles in
 * Float32Array format.
 * @export
 * @ignore
 * @deprecated Use ee.layers.BinaryOverlay instead.
 */
ee.FloatTileOverlay =
    class extends earthengine_api.javascript.abstractoverlay
                      .AbstractOverlay {
  /**
   * @param {string} url The URL for fetching floating point tiles.
   * @param {string} mapId The map ID for fetching floating point tiles.
   * @param {string} token The temporary token for fetching tiles.
   */
  constructor(url, mapId, token) {
    super(url, mapId, token);

    this.tileSize = new google.maps.Size(
        ee.FloatTileOverlay.TILE_EDGE_LENGTH,

        ee.FloatTileOverlay.TILE_EDGE_LENGTH);

    /**
     * The set of loaded floating point buffer tiles. The keys are the
     * coordinates of the tiles, and the values are the corresponding
     * Float32Arrays.
     * @private {?goog.structs.Map<!google.maps.Point, !Float32Array>}
     */
    this.floatTiles_ = new goog.structs.Map();

    /**
     * The floating point buffer tile DIV elements returned by getTile().
     * The keys are the coordinates of the tiles, and the values are the
     * corresponding DIV elements.
     * @private {?goog.structs.Map<!google.maps.Point, !Element>}
     */
    this.floatTileDivs_ = new goog.structs.Map();
  }

  /** @override */
  getTile(coord, zoom, ownerDocument) {
    const tileId = this.getTileId(coord, zoom);
    const src = [this.url, tileId].join('/') + '?token=' + this.token;
    const uniqueTileId = [tileId, this.tileCounter, this.token].join('/');
    this.tilesLoading.push(uniqueTileId);
    this.tileCounter += 1;

    const div = goog.dom.createDom(goog.dom.TagName.DIV);

    this.loadFloatTile_(src, coord, uniqueTileId, div);
    this.dispatchTileEvent_();

    // The Maps API expects a div for the tile. We don't actually want to render
    // the floating point tiles as a visible layer, so we return an empty div.
    return div;
  }

  /**
   * Requests a floating point tile from the provided URL.
   * @param {string} tileUrl Tile URL
   * @param {!google.maps.Point} coord Coordinates of the floating tile
   * @param {string} tileId Unique tile ID
   * @param {!Element} div The corresponding DIV element.
   * @private
   */
  loadFloatTile_(tileUrl, coord, tileId, div) {
    let tileRequest = goog.net.XmlHttp();
    tileRequest.open('GET', tileUrl, true);
    tileRequest.responseType = 'arraybuffer';
    tileRequest.onreadystatechange = goog.bind(function() {
      if (tileRequest.readyState === XMLHttpRequest.DONE &&
          tileRequest.status === 200) {
        const tileResponse = /** @type {!Float32Array} */ (tileRequest.response);
        if (tileResponse) {
          let floatBuffer = new Float32Array(tileResponse);
          this.handleFloatTileLoaded_(floatBuffer, coord, tileId, div);
        } else {
          this.tilesFailed.add(tileId);
          throw new Error('Unable to request floating point array buffers.');
        }
      }
    }, this);
    tileRequest.send();
  }

  /**
   * Handles float tile loaded events by storing the tile data and dispatching
   * a tile event.
   * @param {!Float32Array} floatTile Successfully requested float tile
   * @param {!google.maps.Point} coord Coordinate of the floating tile
   * @param {string} tileId Unique tile ID
   * @param {!Element} div The corresponding DIV element.
   * @private
   */
  handleFloatTileLoaded_(floatTile, coord, tileId, div) {
    this.floatTiles_.set(coord, floatTile);
    this.floatTileDivs_.set(coord, div);
    goog.array.remove(this.tilesLoading, tileId);
    this.dispatchTileEvent_();
  }

  /**
   * Returns the map of all visible floating tiles and the corresponding
   * coordinates.
   * @return {?goog.structs.Map}
   */
  getAllFloatTiles() {
    return this.floatTiles_;
  }

  /**
   * Returns the map of all the floating tile divs that are visible and
   * the corresponding coordinates.
   * @return {?goog.structs.Map}
   */
  getAllFloatTileDivs() {
    return this.floatTileDivs_;
  }

  /** @return {number} The number of tiles successfully loaded. */
  getLoadedFloatTilesCount() {
    return this.floatTiles_.size;
  }

  /**
   * Dispatches an event about a change in the number of outstanding tiles.
   * @private
   */
  dispatchTileEvent_() {
    this.dispatchEvent(
        new earthengine_api.javascript.abstractoverlay
            .TileEvent(this.tilesLoading.length));
  }

  /** @override */
  disposeInternal() {
    this.floatTiles_ = null;
    this.floatTileDivs_ = null;

    super.disposeInternal();
  }
};


/**
 * The tile edge length of a float overlay tile.
 * @const {number}
 */
ee.FloatTileOverlay.TILE_EDGE_LENGTH = 256;
