goog.provide('ol.render.webgl.ReplayGroup');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.render.replay');
goog.require('ol.render.ReplayGroup');
goog.require('ol.render.webgl.CircleReplay');
goog.require('ol.render.webgl.ImageReplay');
goog.require('ol.render.webgl.LineStringReplay');
goog.require('ol.render.webgl.PolygonReplay');
goog.require('ol.render.webgl.TextReplay');


if (ol.ENABLE_WEBGL) {

  /**
   * @constructor
   * @extends {ol.render.ReplayGroup}
   * @param {number} tolerance Tolerance.
   * @param {ol.Extent} maxExtent Max extent.
   * @param {number=} opt_renderBuffer Render buffer.
   * @struct
   */
  ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) {
    ol.render.ReplayGroup.call(this);

    /**
     * @type {ol.Extent}
     * @private
     */
    this.maxExtent_ = maxExtent;

    /**
     * @type {number}
     * @private
     */
    this.tolerance_ = tolerance;

    /**
     * @type {number|undefined}
     * @private
     */
    this.renderBuffer_ = opt_renderBuffer;

    /**
     * @private
     * @type {!Object.<string,
     *        Object.<ol.render.ReplayType, ol.render.webgl.Replay>>}
     */
    this.replaysByZIndex_ = {};

  };
  ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);


  /**
   * @param {ol.webgl.Context} context WebGL context.
   * @return {function()} Delete resources function.
   */
  ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) {
    var functions = [];
    var zKey;
    for (zKey in this.replaysByZIndex_) {
      var replays = this.replaysByZIndex_[zKey];
      var replayKey;
      for (replayKey in replays) {
        functions.push(
            replays[replayKey].getDeleteResourcesFunction(context));
      }
    }
    return function() {
      var length = functions.length;
      var result;
      for (var i = 0; i < length; i++) {
        result = functions[i].apply(this, arguments);
      }
      return result;
    };
  };


  /**
   * @param {ol.webgl.Context} context Context.
   */
  ol.render.webgl.ReplayGroup.prototype.finish = function(context) {
    var zKey;
    for (zKey in this.replaysByZIndex_) {
      var replays = this.replaysByZIndex_[zKey];
      var replayKey;
      for (replayKey in replays) {
        replays[replayKey].finish(context);
      }
    }
  };


  /**
   * @inheritDoc
   */
  ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {
    var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
    var replays = this.replaysByZIndex_[zIndexKey];
    if (replays === undefined) {
      replays = {};
      this.replaysByZIndex_[zIndexKey] = replays;
    }
    var replay = replays[replayType];
    if (replay === undefined) {
      /**
       * @type {Function}
       */
      var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType];
      replay = new Constructor(this.tolerance_, this.maxExtent_);
      replays[replayType] = replay;
    }
    return replay;
  };


  /**
   * @inheritDoc
   */
  ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
    return ol.obj.isEmpty(this.replaysByZIndex_);
  };


  /**
   * @param {ol.webgl.Context} context Context.
   * @param {ol.Coordinate} center Center.
   * @param {number} resolution Resolution.
   * @param {number} rotation Rotation.
   * @param {ol.Size} size Size.
   * @param {number} pixelRatio Pixel ratio.
   * @param {number} opacity Global opacity.
   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
   *  to skip.
   */
  ol.render.webgl.ReplayGroup.prototype.replay = function(context,
      center, resolution, rotation, size, pixelRatio,
      opacity, skippedFeaturesHash) {
    /** @type {Array.<number>} */
    var zs = Object.keys(this.replaysByZIndex_).map(Number);
    zs.sort(ol.array.numberSafeCompareFunction);

    var i, ii, j, jj, replays, replay;
    for (i = 0, ii = zs.length; i < ii; ++i) {
      replays = this.replaysByZIndex_[zs[i].toString()];
      for (j = 0, jj = ol.render.replay.ORDER.length; j < jj; ++j) {
        replay = replays[ol.render.replay.ORDER[j]];
        if (replay !== undefined) {
          replay.replay(context,
              center, resolution, rotation, size, pixelRatio,
              opacity, skippedFeaturesHash,
              undefined, false);
        }
      }
    }
  };


  /**
   * @private
   * @param {ol.webgl.Context} context Context.
   * @param {ol.Coordinate} center Center.
   * @param {number} resolution Resolution.
   * @param {number} rotation Rotation.
   * @param {ol.Size} size Size.
   * @param {number} pixelRatio Pixel ratio.
   * @param {number} opacity Global opacity.
   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
   *  to skip.
   * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
   * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
   * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
   *  this extent are checked.
   * @return {T|undefined} Callback result.
   * @template T
   */
  ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context,
      center, resolution, rotation, size, pixelRatio, opacity,
      skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) {
    /** @type {Array.<number>} */
    var zs = Object.keys(this.replaysByZIndex_).map(Number);
    zs.sort(function(a, b) {
      return b - a;
    });

    var i, ii, j, replays, replay, result;
    for (i = 0, ii = zs.length; i < ii; ++i) {
      replays = this.replaysByZIndex_[zs[i].toString()];
      for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) {
        replay = replays[ol.render.replay.ORDER[j]];
        if (replay !== undefined) {
          result = replay.replay(context,
              center, resolution, rotation, size, pixelRatio, opacity,
              skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent);
          if (result) {
            return result;
          }
        }
      }
    }
    return undefined;
  };


  /**
   * @param {ol.Coordinate} coordinate Coordinate.
   * @param {ol.webgl.Context} context Context.
   * @param {ol.Coordinate} center Center.
   * @param {number} resolution Resolution.
   * @param {number} rotation Rotation.
   * @param {ol.Size} size Size.
   * @param {number} pixelRatio Pixel ratio.
   * @param {number} opacity Global opacity.
   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
   *  to skip.
   * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback.
   * @return {T|undefined} Callback result.
   * @template T
   */
  ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
      coordinate, context, center, resolution, rotation, size, pixelRatio,
      opacity, skippedFeaturesHash,
      callback) {
    var gl = context.getGL();
    gl.bindFramebuffer(
        gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());


    /**
     * @type {ol.Extent}
     */
    var hitExtent;
    if (this.renderBuffer_ !== undefined) {
      // build an extent around the coordinate, so that only features that
      // intersect this extent are checked
      hitExtent = ol.extent.buffer(
          ol.extent.createOrUpdateFromCoordinate(coordinate),
          resolution * this.renderBuffer_);
    }

    return this.replayHitDetection_(context,
        coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_,
        pixelRatio, opacity, skippedFeaturesHash,
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @return {?} Callback result.
         */
        function(feature) {
          var imageData = new Uint8Array(4);
          gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);

          if (imageData[3] > 0) {
            var result = callback(feature);
            if (result) {
              return result;
            }
          }
        }, true, hitExtent);
  };


  /**
   * @param {ol.Coordinate} coordinate Coordinate.
   * @param {ol.webgl.Context} context Context.
   * @param {ol.Coordinate} center Center.
   * @param {number} resolution Resolution.
   * @param {number} rotation Rotation.
   * @param {ol.Size} size Size.
   * @param {number} pixelRatio Pixel ratio.
   * @param {number} opacity Global opacity.
   * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
   *  to skip.
   * @return {boolean} Is there a feature at the given coordinate?
   */
  ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function(
      coordinate, context, center, resolution, rotation, size, pixelRatio,
      opacity, skippedFeaturesHash) {
    var gl = context.getGL();
    gl.bindFramebuffer(
        gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());

    var hasFeature = this.replayHitDetection_(context,
        coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_,
        pixelRatio, opacity, skippedFeaturesHash,
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @return {boolean} Is there a feature?
         */
        function(feature) {
          var imageData = new Uint8Array(4);
          gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);
          return imageData[3] > 0;
        }, false);

    return hasFeature !== undefined;
  };

  /**
   * @const
   * @private
   * @type {Array.<number>}
   */
  ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1];

  /**
   * @const
   * @private
   * @type {Object.<ol.render.ReplayType,
   *                function(new: ol.render.webgl.Replay, number,
   *                ol.Extent)>}
   */
  ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_ = {
    'Circle': ol.render.webgl.CircleReplay,
    'Image': ol.render.webgl.ImageReplay,
    'LineString': ol.render.webgl.LineStringReplay,
    'Polygon': ol.render.webgl.PolygonReplay,
    'Text': ol.render.webgl.TextReplay
  };

}
