'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 _tilerange = require('../../tilerange.js');

var _tilerange2 = _interopRequireDefault(_tilerange);

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

var _tilestate2 = _interopRequireDefault(_tilestate);

var _array = require('../../array.js');

var _array2 = _interopRequireDefault(_array);

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

var _extent2 = _interopRequireDefault(_extent);

var _math = require('../../math.js');

var _math2 = _interopRequireDefault(_math);

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

var _type2 = _interopRequireDefault(_type);

var _layer = require('../webgl/layer.js');

var _layer2 = _interopRequireDefault(_layer);

var _tilelayershader = require('../webgl/tilelayershader.js');

var _tilelayershader2 = _interopRequireDefault(_tilelayershader);

var _locations = require('../webgl/tilelayershader/locations.js');

var _locations2 = _interopRequireDefault(_locations);

var _size = require('../../size.js');

var _size2 = _interopRequireDefault(_size);

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

var _transform2 = _interopRequireDefault(_transform);

var _webgl = require('../../webgl.js');

var _webgl2 = _interopRequireDefault(_webgl);

var _buffer = require('../../webgl/buffer.js');

var _buffer2 = _interopRequireDefault(_buffer);

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

/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
 * @param {ol.layer.Tile} tileLayer Tile layer.
 * @api
 */
var _ol_renderer_webgl_TileLayer_ = function (mapRenderer, tileLayer) {

  _layer2.default.call(this, mapRenderer, tileLayer);

  /**
   * @private
   * @type {ol.webgl.Fragment}
   */
  this.fragmentShader_ = _tilelayershader2.default.fragment;

  /**
   * @private
   * @type {ol.webgl.Vertex}
   */
  this.vertexShader_ = _tilelayershader2.default.vertex;

  /**
   * @private
   * @type {ol.renderer.webgl.tilelayershader.Locations}
   */
  this.locations_ = null;

  /**
   * @private
   * @type {ol.webgl.Buffer}
   */
  this.renderArrayBuffer_ = new _buffer2.default([0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0]);

  /**
   * @private
   * @type {ol.TileRange}
   */
  this.renderedTileRange_ = null;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.renderedFramebufferExtent_ = null;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = -1;

  /**
   * @private
   * @type {ol.Size}
   */
  this.tmpSize_ = [0, 0];
}; // FIXME large resolutions lead to too large framebuffers :-(
// FIXME animated shaders! check in redraw

_index2.default.inherits(_ol_renderer_webgl_TileLayer_, _layer2.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_webgl_TileLayer_['handles'] = function (type, layer) {
  return type === _type2.default.WEBGL && layer.getType() === _layertype2.default.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.webgl.TileLayer} The layer renderer.
 */
_ol_renderer_webgl_TileLayer_['create'] = function (mapRenderer, layer) {
  return new _ol_renderer_webgl_TileLayer_(
  /** @type {ol.renderer.webgl.Map} */mapRenderer,
  /** @type {ol.layer.Tile} */layer);
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_TileLayer_.prototype.disposeInternal = function () {
  var context = this.mapRenderer.getContext();
  context.deleteBuffer(this.renderArrayBuffer_);
  _layer2.default.prototype.disposeInternal.call(this);
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_TileLayer_.prototype.createLoadedTileFinder = function (source, projection, tiles) {
  var mapRenderer = this.mapRenderer;

  return (
    /**
     * @param {number} zoom Zoom level.
     * @param {ol.TileRange} tileRange Tile range.
     * @return {boolean} The tile range is fully loaded.
     */
    function (zoom, tileRange) {
      function callback(tile) {
        var loaded = mapRenderer.isTileTextureLoaded(tile);
        if (loaded) {
          if (!tiles[zoom]) {
            tiles[zoom] = {};
          }
          tiles[zoom][tile.tileCoord.toString()] = tile;
        }
        return loaded;
      }
      return source.forEachLoadedTile(projection, zoom, tileRange, callback);
    }
  );
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_TileLayer_.prototype.handleWebGLContextLost = function () {
  _layer2.default.prototype.handleWebGLContextLost.call(this);
  this.locations_ = null;
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_TileLayer_.prototype.prepareFrame = function (frameState, layerState, context) {

  var mapRenderer = this.mapRenderer;
  var gl = context.getGL();

  var viewState = frameState.viewState;
  var projection = viewState.projection;

  var tileLayer = /** @type {ol.layer.Tile} */this.getLayer();
  var tileSource = tileLayer.getSource();
  var tileGrid = tileSource.getTileGridForProjection(projection);
  var z = tileGrid.getZForResolution(viewState.resolution);
  var tileResolution = tileGrid.getResolution(z);

  var tilePixelSize = tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
  var pixelRatio = tilePixelSize[0] / _size2.default.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0];
  var tilePixelResolution = tileResolution / pixelRatio;
  var tileGutter = tileSource.getTilePixelRatio(pixelRatio) * tileSource.getGutter(projection);

  var center = viewState.center;
  var extent = frameState.extent;
  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);

  var framebufferExtent;
  if (this.renderedTileRange_ && this.renderedTileRange_.equals(tileRange) && this.renderedRevision_ == tileSource.getRevision()) {
    framebufferExtent = this.renderedFramebufferExtent_;
  } else {

    var tileRangeSize = tileRange.getSize();

    var maxDimension = Math.max(tileRangeSize[0] * tilePixelSize[0], tileRangeSize[1] * tilePixelSize[1]);
    var framebufferDimension = _math2.default.roundUpToPowerOfTwo(maxDimension);
    var framebufferExtentDimension = tilePixelResolution * framebufferDimension;
    var origin = tileGrid.getOrigin(z);
    var minX = origin[0] + tileRange.minX * tilePixelSize[0] * tilePixelResolution;
    var minY = origin[1] + tileRange.minY * tilePixelSize[1] * tilePixelResolution;
    framebufferExtent = [minX, minY, minX + framebufferExtentDimension, minY + framebufferExtentDimension];

    this.bindFramebuffer(frameState, framebufferDimension);
    gl.viewport(0, 0, framebufferDimension, framebufferDimension);

    gl.clearColor(0, 0, 0, 0);
    gl.clear(_webgl2.default.COLOR_BUFFER_BIT);
    gl.disable(_webgl2.default.BLEND);

    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
    context.useProgram(program);
    if (!this.locations_) {
      this.locations_ = new _locations2.default(gl, program);
    }

    context.bindBuffer(_webgl2.default.ARRAY_BUFFER, this.renderArrayBuffer_);
    gl.enableVertexAttribArray(this.locations_.a_position);
    gl.vertexAttribPointer(this.locations_.a_position, 2, _webgl2.default.FLOAT, false, 16, 0);
    gl.enableVertexAttribArray(this.locations_.a_texCoord);
    gl.vertexAttribPointer(this.locations_.a_texCoord, 2, _webgl2.default.FLOAT, false, 16, 8);
    gl.uniform1i(this.locations_.u_texture, 0);

    /**
     * @type {Object.<number, Object.<string, ol.Tile>>}
     */
    var tilesToDrawByZ = {};
    tilesToDrawByZ[z] = {};

    var findLoadedTiles = this.createLoadedTileFinder(tileSource, projection, tilesToDrawByZ);

    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
    var allTilesLoaded = true;
    var tmpExtent = _extent2.default.createEmpty();
    var tmpTileRange = new _tilerange2.default(0, 0, 0, 0);
    var childTileRange, drawable, fullyLoaded, tile, tileState;
    var x, y, tileExtent;
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {

        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
        if (layerState.extent !== undefined) {
          // ignore tiles outside layer extent
          tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
          if (!_extent2.default.intersects(tileExtent, layerState.extent)) {
            continue;
          }
        }
        tileState = tile.getState();
        drawable = tileState == _tilestate2.default.LOADED || tileState == _tilestate2.default.EMPTY || tileState == _tilestate2.default.ERROR && !useInterimTilesOnError;
        if (!drawable) {
          tile = tile.getInterimTile();
        }
        tileState = tile.getState();
        if (tileState == _tilestate2.default.LOADED) {
          if (mapRenderer.isTileTextureLoaded(tile)) {
            tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
            continue;
          }
        } else if (tileState == _tilestate2.default.EMPTY || tileState == _tilestate2.default.ERROR && !useInterimTilesOnError) {
          continue;
        }

        allTilesLoaded = false;
        fullyLoaded = tileGrid.forEachTileCoordParentTileRange(tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
        if (!fullyLoaded) {
          childTileRange = tileGrid.getTileCoordChildTileRange(tile.tileCoord, tmpTileRange, tmpExtent);
          if (childTileRange) {
            findLoadedTiles(z + 1, childTileRange);
          }
        }
      }
    }

    /** @type {Array.<number>} */
    var zs = Object.keys(tilesToDrawByZ).map(Number);
    zs.sort(_array2.default.numberSafeCompareFunction);
    var u_tileOffset = new Float32Array(4);
    var i, ii, tileKey, tilesToDraw;
    for (i = 0, ii = zs.length; i < ii; ++i) {
      tilesToDraw = tilesToDrawByZ[zs[i]];
      for (tileKey in tilesToDraw) {
        tile = tilesToDraw[tileKey];
        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
        u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) / framebufferExtentDimension;
        u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) / framebufferExtentDimension;
        u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) / framebufferExtentDimension - 1;
        u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) / framebufferExtentDimension - 1;
        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
        mapRenderer.bindTileTexture(tile, tilePixelSize, tileGutter * pixelRatio, _webgl2.default.LINEAR, _webgl2.default.LINEAR);
        gl.drawArrays(_webgl2.default.TRIANGLE_STRIP, 0, 4);
      }
    }

    if (allTilesLoaded) {
      this.renderedTileRange_ = tileRange;
      this.renderedFramebufferExtent_ = framebufferExtent;
      this.renderedRevision_ = tileSource.getRevision();
    } else {
      this.renderedTileRange_ = null;
      this.renderedFramebufferExtent_ = null;
      this.renderedRevision_ = -1;
      frameState.animate = true;
    }
  }

  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
  var tileTextureQueue = mapRenderer.getTileTextureQueue();
  this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, tileLayer.getPreload(),
  /**
   * @param {ol.Tile} tile Tile.
   */
  function (tile) {
    if (tile.getState() == _tilestate2.default.LOADED && !mapRenderer.isTileTextureLoaded(tile) && !tileTextureQueue.isKeyQueued(tile.getKey())) {
      tileTextureQueue.enqueue([tile, tileGrid.getTileCoordCenter(tile.tileCoord), tileGrid.getResolution(tile.tileCoord[0]), tilePixelSize, tileGutter * pixelRatio]);
    }
  }, this);
  this.scheduleExpireCache(frameState, tileSource);
  this.updateLogos(frameState, tileSource);

  var texCoordMatrix = this.texCoordMatrix;
  _transform2.default.reset(texCoordMatrix);
  _transform2.default.translate(texCoordMatrix, (Math.round(center[0] / tileResolution) * tileResolution - framebufferExtent[0]) / (framebufferExtent[2] - framebufferExtent[0]), (Math.round(center[1] / tileResolution) * tileResolution - framebufferExtent[1]) / (framebufferExtent[3] - framebufferExtent[1]));
  if (viewState.rotation !== 0) {
    _transform2.default.rotate(texCoordMatrix, viewState.rotation);
  }
  _transform2.default.scale(texCoordMatrix, frameState.size[0] * viewState.resolution / (framebufferExtent[2] - framebufferExtent[0]), frameState.size[1] * viewState.resolution / (framebufferExtent[3] - framebufferExtent[1]));
  _transform2.default.translate(texCoordMatrix, -0.5, -0.5);

  return true;
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_TileLayer_.prototype.forEachLayerAtPixel = function (pixel, frameState, callback, thisArg) {
  if (!this.framebuffer) {
    return undefined;
  }

  var pixelOnMapScaled = [pixel[0] / frameState.size[0], (frameState.size[1] - pixel[1]) / frameState.size[1]];

  var pixelOnFrameBufferScaled = _transform2.default.apply(this.texCoordMatrix, pixelOnMapScaled.slice());
  var pixelOnFrameBuffer = [pixelOnFrameBufferScaled[0] * this.framebufferDimension, pixelOnFrameBufferScaled[1] * this.framebufferDimension];

  var gl = this.mapRenderer.getContext().getGL();
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
  var imageData = new Uint8Array(4);
  gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);

  if (imageData[3] > 0) {
    return callback.call(thisArg, this.getLayer(), imageData);
  } else {
    return undefined;
  }
};
exports.default = _ol_renderer_webgl_TileLayer_;
module.exports = exports['default'];