'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _index = require('../../index.js');

var _index2 = _interopRequireDefault(_index);

var _layertype = require('../../layertype.js');

var _layertype2 = _interopRequireDefault(_layertype);

var _tilestate = require('../../tilestate.js');

var _tilestate2 = _interopRequireDefault(_tilestate);

var _dom = require('../../dom.js');

var _dom2 = _interopRequireDefault(_dom);

var _events = require('../../events.js');

var _events2 = _interopRequireDefault(_events);

var _eventtype = require('../../events/eventtype.js');

var _eventtype2 = _interopRequireDefault(_eventtype);

var _rbush = require('rbush');

var _rbush2 = _interopRequireDefault(_rbush);

var _extent = require('../../extent.js');

var _extent2 = _interopRequireDefault(_extent);

var _vectortilerendertype = require('../../layer/vectortilerendertype.js');

var _vectortilerendertype2 = _interopRequireDefault(_vectortilerendertype);

var _proj = require('../../proj.js');

var _proj2 = _interopRequireDefault(_proj);

var _units = require('../../proj/units.js');

var _units2 = _interopRequireDefault(_units);

var _replaytype = require('../../render/replaytype.js');

var _replaytype2 = _interopRequireDefault(_replaytype);

var _canvas = require('../../render/canvas.js');

var _canvas2 = _interopRequireDefault(_canvas);

var _replaygroup = require('../../render/canvas/replaygroup.js');

var _replaygroup2 = _interopRequireDefault(_replaygroup);

var _replay = require('../../render/replay.js');

var _replay2 = _interopRequireDefault(_replay);

var _type = require('../type.js');

var _type2 = _interopRequireDefault(_type);

var _tilelayer = require('../canvas/tilelayer.js');

var _tilelayer2 = _interopRequireDefault(_tilelayer);

var _vector = require('../vector.js');

var _vector2 = _interopRequireDefault(_vector);

var _transform = require('../../transform.js');

var _transform2 = _interopRequireDefault(_transform);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

/**
 * @constructor
 * @extends {ol.renderer.canvas.TileLayer}
 * @param {ol.layer.VectorTile} layer VectorTile layer.
 * @api
 */
var _ol_renderer_canvas_VectorTileLayer_ = function (layer) {

  /**
   * @type {CanvasRenderingContext2D}
   */
  this.context = null;

  _tilelayer2.default.call(this, layer);

  /**
   * Declutter tree.
   * @private
     */
  this.declutterTree_ = layer.getDeclutter() ? (0, _rbush2.default)(9) : null;

  /**
   * @private
   * @type {boolean}
   */
  this.dirty_ = false;

  /**
   * @private
   * @type {number}
   */
  this.renderedLayerRevision_;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.tmpTransform_ = _transform2.default.create();

  // Use lower resolution for pure vector rendering. Closest resolution otherwise.
  this.zDirection = layer.getRenderMode() == _vectortilerendertype2.default.VECTOR ? 1 : 0;

  _events2.default.listen(_canvas2.default.labelCache, _eventtype2.default.CLEAR, this.handleFontsChanged_, this);
};

_index2.default.inherits(_ol_renderer_canvas_VectorTileLayer_, _tilelayer2.default);

/**
 * Determine if this renderer handles the provided layer.
 * @param {ol.renderer.Type} type The renderer type.
 * @param {ol.layer.Layer} layer The candidate layer.
 * @return {boolean} The renderer can render the layer.
 */
_ol_renderer_canvas_VectorTileLayer_['handles'] = function (type, layer) {
  return type === _type2.default.CANVAS && layer.getType() === _layertype2.default.VECTOR_TILE;
};

/**
 * Create a layer renderer.
 * @param {ol.renderer.Map} mapRenderer The map renderer.
 * @param {ol.layer.Layer} layer The layer to be rendererd.
 * @return {ol.renderer.canvas.VectorTileLayer} The layer renderer.
 */
_ol_renderer_canvas_VectorTileLayer_['create'] = function (mapRenderer, layer) {
  return new _ol_renderer_canvas_VectorTileLayer_( /** @type {ol.layer.VectorTile} */layer);
};

/**
 * @const
 * @type {!Object.<string, Array.<ol.render.ReplayType>>}
 */
_ol_renderer_canvas_VectorTileLayer_.IMAGE_REPLAYS = {
  'image': [_replaytype2.default.POLYGON, _replaytype2.default.CIRCLE, _replaytype2.default.LINE_STRING, _replaytype2.default.IMAGE, _replaytype2.default.TEXT],
  'hybrid': [_replaytype2.default.POLYGON, _replaytype2.default.LINE_STRING]
};

/**
 * @const
 * @type {!Object.<string, Array.<ol.render.ReplayType>>}
 */
_ol_renderer_canvas_VectorTileLayer_.VECTOR_REPLAYS = {
  'image': [_replaytype2.default.DEFAULT],
  'hybrid': [_replaytype2.default.IMAGE, _replaytype2.default.TEXT, _replaytype2.default.DEFAULT],
  'vector': _replay2.default.ORDER
};

/**
 * @inheritDoc
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.disposeInternal = function () {
  _events2.default.unlisten(_canvas2.default.labelCache, _eventtype2.default.CLEAR, this.handleFontsChanged_, this);
  _tilelayer2.default.prototype.disposeInternal.call(this);
};

/**
 * @inheritDoc
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.prepareFrame = function (frameState, layerState) {
  var layer = this.getLayer();
  var layerRevision = layer.getRevision();
  if (this.renderedLayerRevision_ != layerRevision) {
    this.renderedTiles.length = 0;
    var renderMode = layer.getRenderMode();
    if (!this.context && renderMode != _vectortilerendertype2.default.VECTOR) {
      this.context = _dom2.default.createCanvasContext2D();
    }
    if (this.context && renderMode == _vectortilerendertype2.default.VECTOR) {
      this.context = null;
    }
  }
  this.renderedLayerRevision_ = layerRevision;
  return _tilelayer2.default.prototype.prepareFrame.apply(this, arguments);
};

/**
 * @param {ol.VectorImageTile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.createReplayGroup_ = function (tile, frameState) {
  var layer = this.getLayer();
  var pixelRatio = frameState.pixelRatio;
  var projection = frameState.viewState.projection;
  var revision = layer.getRevision();
  var renderOrder = /** @type {ol.RenderOrderFunction} */
  layer.getRenderOrder() || null;

  var replayState = tile.getReplayState(layer);
  if (!replayState.dirty && replayState.renderedRevision == revision && replayState.renderedRenderOrder == renderOrder) {
    return;
  }

  var source = /** @type {ol.source.VectorTile} */layer.getSource();
  var sourceTileGrid = source.getTileGrid();
  var tileGrid = source.getTileGridForProjection(projection);
  var resolution = tileGrid.getResolution(tile.tileCoord[0]);
  var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);

  var zIndexKeys = {};
  for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
    var sourceTile = tile.getTile(tile.tileKeys[t]);
    if (sourceTile.getState() == _tilestate2.default.ERROR) {
      continue;
    }

    var sourceTileCoord = sourceTile.tileCoord;
    var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
    var sharedExtent = _extent2.default.getIntersection(tileExtent, sourceTileExtent);
    var bufferedExtent = _extent2.default.equals(sourceTileExtent, sharedExtent) ? null : _extent2.default.buffer(sharedExtent, layer.getRenderBuffer() * resolution);
    var tileProjection = sourceTile.getProjection();
    var reproject = false;
    if (!_proj2.default.equivalent(projection, tileProjection)) {
      reproject = true;
      sourceTile.setProjection(projection);
    }
    replayState.dirty = false;
    var replayGroup = new _replaygroup2.default(0, sharedExtent, resolution, pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer());
    var squaredTolerance = _vector2.default.getSquaredTolerance(resolution, pixelRatio);

    /**
     * @param {ol.Feature|ol.render.Feature} feature Feature.
     * @this {ol.renderer.canvas.VectorTileLayer}
     */
    var renderFeature = function (feature) {
      var styles;
      var styleFunction = feature.getStyleFunction();
      if (styleFunction) {
        styles = styleFunction.call( /** @type {ol.Feature} */feature, resolution);
      } else {
        styleFunction = layer.getStyleFunction();
        if (styleFunction) {
          styles = styleFunction(feature, resolution);
        }
      }
      if (styles) {
        var dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup);
        this.dirty_ = this.dirty_ || dirty;
        replayState.dirty = replayState.dirty || dirty;
      }
    };

    var features = sourceTile.getFeatures();
    if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
      features.sort(renderOrder);
    }
    var feature;
    for (var i = 0, ii = features.length; i < ii; ++i) {
      feature = features[i];
      if (reproject) {
        if (tileProjection.getUnits() == _units2.default.TILE_PIXELS) {
          // projected tile extent
          tileProjection.setWorldExtent(sourceTileExtent);
          // tile extent in tile pixel space
          tileProjection.setExtent(sourceTile.getExtent());
        }
        feature.getGeometry().transform(tileProjection, projection);
      }
      if (!bufferedExtent || _extent2.default.intersects(bufferedExtent, feature.getGeometry().getExtent())) {
        renderFeature.call(this, feature);
      }
    }
    replayGroup.finish();
    for (var r in replayGroup.getReplays()) {
      zIndexKeys[r] = true;
    }
    sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), replayGroup);
  }
  replayState.renderedRevision = revision;
  replayState.renderedRenderOrder = renderOrder;
};

/**
 * @inheritDoc
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.drawTileImage = function (tile, frameState, layerState, x, y, w, h, gutter, transition) {
  var vectorImageTile = /** @type {ol.VectorImageTile} */tile;
  this.createReplayGroup_(vectorImageTile, frameState);
  if (this.context) {
    this.renderTileImage_(vectorImageTile, frameState, layerState);
    _tilelayer2.default.prototype.drawTileImage.apply(this, arguments);
  }
};

/**
 * @inheritDoc
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.forEachFeatureAtCoordinate = function (coordinate, frameState, hitTolerance, callback, thisArg) {
  var resolution = frameState.viewState.resolution;
  var rotation = frameState.viewState.rotation;
  hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
  var layer = this.getLayer();
  /** @type {Object.<string, boolean>} */
  var features = {};

  /** @type {Array.<ol.VectorImageTile>} */
  var renderedTiles = this.renderedTiles;

  var source = /** @type {ol.source.VectorTile} */layer.getSource();
  var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
  var bufferedExtent, found;
  var i, ii, replayGroup;
  var tile, tileCoord, tileExtent;
  for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
    tile = renderedTiles[i];
    tileCoord = tile.wrappedTileCoord;
    tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
    bufferedExtent = _extent2.default.buffer(tileExtent, hitTolerance * resolution, bufferedExtent);
    if (!_extent2.default.containsCoordinate(bufferedExtent, coordinate)) {
      continue;
    }
    for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
      var sourceTile = tile.getTile(tile.tileKeys[t]);
      if (sourceTile.getState() == _tilestate2.default.ERROR) {
        continue;
      }
      replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString());
      found = found || replayGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {},
      /**
       * @param {ol.Feature|ol.render.Feature} feature Feature.
       * @return {?} Callback result.
       */
      function (feature) {
        var key = _index2.default.getUid(feature).toString();
        if (!(key in features)) {
          features[key] = true;
          return callback.call(thisArg, feature, layer);
        }
      }, null);
    }
  }
  return found;
};

/**
 * @param {ol.VectorTile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 * @return {ol.Transform} transform Transform.
 * @private
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.getReplayTransform_ = function (tile, frameState) {
  var layer = this.getLayer();
  var source = /** @type {ol.source.VectorTile} */layer.getSource();
  var tileGrid = source.getTileGrid();
  var tileCoord = tile.tileCoord;
  var tileResolution = tileGrid.getResolution(tileCoord[0]);
  var viewState = frameState.viewState;
  var pixelRatio = frameState.pixelRatio;
  var renderResolution = viewState.resolution / pixelRatio;
  var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
  var center = viewState.center;
  var origin = _extent2.default.getTopLeft(tileExtent);
  var size = frameState.size;
  var offsetX = Math.round(pixelRatio * size[0] / 2);
  var offsetY = Math.round(pixelRatio * size[1] / 2);
  return _transform2.default.compose(this.tmpTransform_, offsetX, offsetY, tileResolution / renderResolution, tileResolution / renderResolution, viewState.rotation, (origin[0] - center[0]) / tileResolution, (center[1] - origin[1]) / tileResolution);
};

/**
 * @param {ol.events.Event} event Event.
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.handleFontsChanged_ = function (event) {
  var layer = this.getLayer();
  if (layer.getVisible() && this.renderedLayerRevision_ !== undefined) {
    layer.changed();
  }
};

/**
 * Handle changes in image style state.
 * @param {ol.events.Event} event Image style change event.
 * @private
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.handleStyleImageChange_ = function (event) {
  this.renderIfReadyAndVisible();
};

/**
 * @inheritDoc
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.postCompose = function (context, frameState, layerState) {
  var layer = this.getLayer();
  var declutterReplays = layer.getDeclutter() ? {} : null;
  var source = /** @type {ol.source.VectorTile} */layer.getSource();
  var renderMode = layer.getRenderMode();
  var replayTypes = _ol_renderer_canvas_VectorTileLayer_.VECTOR_REPLAYS[renderMode];
  var pixelRatio = frameState.pixelRatio;
  var rotation = frameState.viewState.rotation;
  var size = frameState.size;
  var offsetX, offsetY;
  if (rotation) {
    offsetX = Math.round(pixelRatio * size[0] / 2);
    offsetY = Math.round(pixelRatio * size[1] / 2);
    _canvas2.default.rotateAtOffset(context, -rotation, offsetX, offsetY);
  }
  if (declutterReplays) {
    this.declutterTree_.clear();
  }
  var tiles = this.renderedTiles;
  var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
  var clips = [];
  var zs = [];
  for (var i = tiles.length - 1; i >= 0; --i) {
    var tile = /** @type {ol.VectorImageTile} */tiles[i];
    if (tile.getState() == _tilestate2.default.ABORT) {
      continue;
    }
    var tileCoord = tile.tileCoord;
    var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] - tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
    var transform = undefined;
    for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
      var sourceTile = tile.getTile(tile.tileKeys[t]);
      if (sourceTile.getState() == _tilestate2.default.ERROR) {
        continue;
      }
      var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString());
      if (renderMode != _vectortilerendertype2.default.VECTOR && !replayGroup.hasReplays(replayTypes)) {
        continue;
      }
      if (!transform) {
        transform = this.getTransform(frameState, worldOffset);
      }
      var currentZ = sourceTile.tileCoord[0];
      var currentClip = replayGroup.getClipCoords(transform);
      context.save();
      context.globalAlpha = layerState.opacity;
      // Create a clip mask for regions in this low resolution tile that are
      // already filled by a higher resolution tile
      for (var j = 0, jj = clips.length; j < jj; ++j) {
        var clip = clips[j];
        if (currentZ < zs[j]) {
          context.beginPath();
          // counter-clockwise (outer ring) for current tile
          context.moveTo(currentClip[0], currentClip[1]);
          context.lineTo(currentClip[2], currentClip[3]);
          context.lineTo(currentClip[4], currentClip[5]);
          context.lineTo(currentClip[6], currentClip[7]);
          // clockwise (inner ring) for higher resolution tile
          context.moveTo(clip[6], clip[7]);
          context.lineTo(clip[4], clip[5]);
          context.lineTo(clip[2], clip[3]);
          context.lineTo(clip[0], clip[1]);
          context.clip();
        }
      }
      replayGroup.replay(context, transform, rotation, {}, replayTypes, declutterReplays);
      context.restore();
      clips.push(currentClip);
      zs.push(currentZ);
    }
  }
  if (declutterReplays) {
    _replaygroup2.default.replayDeclutter(declutterReplays, context, rotation);
  }
  if (rotation) {
    _canvas2.default.rotateAtOffset(context, rotation,
    /** @type {number} */offsetX, /** @type {number} */offsetY);
  }
  _tilelayer2.default.prototype.postCompose.apply(this, arguments);
};

/**
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
 *     styles.
 * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.renderFeature = function (feature, squaredTolerance, styles, replayGroup) {
  if (!styles) {
    return false;
  }
  var loading = false;
  if (Array.isArray(styles)) {
    for (var i = 0, ii = styles.length; i < ii; ++i) {
      loading = _vector2.default.renderFeature(replayGroup, feature, styles[i], squaredTolerance, this.handleStyleImageChange_, this) || loading;
    }
  } else {
    loading = _vector2.default.renderFeature(replayGroup, feature, styles, squaredTolerance, this.handleStyleImageChange_, this);
  }
  return loading;
};

/**
 * @param {ol.VectorImageTile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @private
 */
_ol_renderer_canvas_VectorTileLayer_.prototype.renderTileImage_ = function (tile, frameState, layerState) {
  var layer = this.getLayer();
  var replayState = tile.getReplayState(layer);
  var revision = layer.getRevision();
  var replays = _ol_renderer_canvas_VectorTileLayer_.IMAGE_REPLAYS[layer.getRenderMode()];
  if (replays && replayState.renderedTileRevision !== revision) {
    replayState.renderedTileRevision = revision;
    var tileCoord = tile.wrappedTileCoord;
    var z = tileCoord[0];
    var pixelRatio = frameState.pixelRatio;
    var source = /** @type {ol.source.VectorTile} */layer.getSource();
    var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
    var resolution = tileGrid.getResolution(z);
    var context = tile.getContext(layer);
    var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection);
    context.canvas.width = size[0];
    context.canvas.height = size[1];
    var tileExtent = tileGrid.getTileCoordExtent(tileCoord);
    for (var i = 0, ii = tile.tileKeys.length; i < ii; ++i) {
      var sourceTile = tile.getTile(tile.tileKeys[i]);
      if (sourceTile.getState() == _tilestate2.default.ERROR) {
        continue;
      }
      var pixelScale = pixelRatio / resolution;
      var transform = _transform2.default.reset(this.tmpTransform_);
      _transform2.default.scale(transform, pixelScale, -pixelScale);
      _transform2.default.translate(transform, -tileExtent[0], -tileExtent[3]);
      var replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString());
      replayGroup.replay(context, transform, 0, {}, replays);
    }
  }
};
exports.default = _ol_renderer_canvas_VectorTileLayer_;
module.exports = exports['default'];