'use strict';

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

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

var _index2 = _interopRequireDefault(_index);

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

var _array2 = _interopRequireDefault(_array);

var _css = require('../../css.js');

var _css2 = _interopRequireDefault(_css);

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

var _dom2 = _interopRequireDefault(_dom);

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

var _events2 = _interopRequireDefault(_events);

var _has = require('../../has.js');

var _has2 = _interopRequireDefault(_has);

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

var _layer2 = _interopRequireDefault(_layer);

var _event = require('../../render/event.js');

var _event2 = _interopRequireDefault(_event);

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

var _eventtype2 = _interopRequireDefault(_eventtype);

var _immediate = require('../../render/webgl/immediate.js');

var _immediate2 = _interopRequireDefault(_immediate);

var _map = require('../map.js');

var _map2 = _interopRequireDefault(_map);

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

var _type2 = _interopRequireDefault(_type);

var _state = require('../../source/state.js');

var _state2 = _interopRequireDefault(_state);

var _lrucache = require('../../structs/lrucache.js');

var _lrucache2 = _interopRequireDefault(_lrucache);

var _priorityqueue = require('../../structs/priorityqueue.js');

var _priorityqueue2 = _interopRequireDefault(_priorityqueue);

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

var _webgl2 = _interopRequireDefault(_webgl);

var _context = require('../../webgl/context.js');

var _context2 = _interopRequireDefault(_context);

var _contexteventtype = require('../../webgl/contexteventtype.js');

var _contexteventtype2 = _interopRequireDefault(_contexteventtype);

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

/**
 * @constructor
 * @extends {ol.renderer.Map}
 * @param {Element} container Container.
 * @param {ol.PluggableMap} map Map.
 * @api
 */
// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)

var _ol_renderer_webgl_Map_ = function (container, map) {
  _map2.default.call(this, container, map);

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = /** @type {HTMLCanvasElement} */
  document.createElement('CANVAS');
  this.canvas_.style.width = '100%';
  this.canvas_.style.height = '100%';
  this.canvas_.style.display = 'block';
  this.canvas_.className = _css2.default.CLASS_UNSELECTABLE;
  container.insertBefore(this.canvas_, container.childNodes[0] || null);

  /**
   * @private
   * @type {number}
   */
  this.clipTileCanvasWidth_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.clipTileCanvasHeight_ = 0;

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.clipTileContext_ = _dom2.default.createCanvasContext2D();

  /**
   * @private
   * @type {boolean}
   */
  this.renderedVisible_ = true;

  /**
   * @private
   * @type {WebGLRenderingContext}
   */
  this.gl_ = _webgl2.default.getContext(this.canvas_, {
    antialias: true,
    depth: true,
    failIfMajorPerformanceCaveat: true,
    preserveDrawingBuffer: false,
    stencil: true
  });

  /**
   * @private
   * @type {ol.webgl.Context}
   */
  this.context_ = new _context2.default(this.canvas_, this.gl_);

  _events2.default.listen(this.canvas_, _contexteventtype2.default.LOST, this.handleWebGLContextLost, this);
  _events2.default.listen(this.canvas_, _contexteventtype2.default.RESTORED, this.handleWebGLContextRestored, this);

  /**
   * @private
   * @type {ol.structs.LRUCache.<ol.WebglTextureCacheEntry|null>}
   */
  this.textureCache_ = new _lrucache2.default();

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.focus_ = null;

  /**
   * @private
   * @type {ol.structs.PriorityQueue.<Array>}
   */
  this.tileTextureQueue_ = new _priorityqueue2.default(
  /**
   * @param {Array.<*>} element Element.
   * @return {number} Priority.
   * @this {ol.renderer.webgl.Map}
   */
  function (element) {
    var tileCenter = /** @type {ol.Coordinate} */element[1];
    var tileResolution = /** @type {number} */element[2];
    var deltaX = tileCenter[0] - this.focus_[0];
    var deltaY = tileCenter[1] - this.focus_[1];
    return 65536 * Math.log(tileResolution) + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
  }.bind(this),
  /**
   * @param {Array.<*>} element Element.
   * @return {string} Key.
   */
  function (element) {
    return (/** @type {ol.Tile} */element[0].getKey()
    );
  });

  /**
   * @param {ol.PluggableMap} map Map.
   * @param {?olx.FrameState} frameState Frame state.
   * @return {boolean} false.
   * @this {ol.renderer.webgl.Map}
   */
  this.loadNextTileTexture_ = function (map, frameState) {
    if (!this.tileTextureQueue_.isEmpty()) {
      this.tileTextureQueue_.reprioritize();
      var element = this.tileTextureQueue_.dequeue();
      var tile = /** @type {ol.Tile} */element[0];
      var tileSize = /** @type {ol.Size} */element[3];
      var tileGutter = /** @type {number} */element[4];
      this.bindTileTexture(tile, tileSize, tileGutter, _webgl2.default.LINEAR, _webgl2.default.LINEAR);
    }
    return false;
  }.bind(this);

  /**
   * @private
   * @type {number}
   */
  this.textureCacheFrameMarkerCount_ = 0;

  this.initializeGL_();
};

_index2.default.inherits(_ol_renderer_webgl_Map_, _map2.default);

/**
 * Determine if this renderer handles the provided layer.
 * @param {ol.renderer.Type} type The renderer type.
 * @return {boolean} The renderer can render the layer.
 */
_ol_renderer_webgl_Map_['handles'] = function (type) {
  return _has2.default.WEBGL && type === _type2.default.WEBGL;
};

/**
 * Create the map renderer.
 * @param {Element} container Container.
 * @param {ol.PluggableMap} map Map.
 * @return {ol.renderer.webgl.Map} The map renderer.
 */
_ol_renderer_webgl_Map_['create'] = function (container, map) {
  return new _ol_renderer_webgl_Map_(container, map);
};

/**
 * @param {ol.Tile} tile Tile.
 * @param {ol.Size} tileSize Tile size.
 * @param {number} tileGutter Tile gutter.
 * @param {number} magFilter Mag filter.
 * @param {number} minFilter Min filter.
 */
_ol_renderer_webgl_Map_.prototype.bindTileTexture = function (tile, tileSize, tileGutter, magFilter, minFilter) {
  var gl = this.getGL();
  var tileKey = tile.getKey();
  if (this.textureCache_.containsKey(tileKey)) {
    var textureCacheEntry = this.textureCache_.get(tileKey);
    gl.bindTexture(_webgl2.default.TEXTURE_2D, textureCacheEntry.texture);
    if (textureCacheEntry.magFilter != magFilter) {
      gl.texParameteri(_webgl2.default.TEXTURE_2D, _webgl2.default.TEXTURE_MAG_FILTER, magFilter);
      textureCacheEntry.magFilter = magFilter;
    }
    if (textureCacheEntry.minFilter != minFilter) {
      gl.texParameteri(_webgl2.default.TEXTURE_2D, _webgl2.default.TEXTURE_MIN_FILTER, minFilter);
      textureCacheEntry.minFilter = minFilter;
    }
  } else {
    var texture = gl.createTexture();
    gl.bindTexture(_webgl2.default.TEXTURE_2D, texture);
    if (tileGutter > 0) {
      var clipTileCanvas = this.clipTileContext_.canvas;
      var clipTileContext = this.clipTileContext_;
      if (this.clipTileCanvasWidth_ !== tileSize[0] || this.clipTileCanvasHeight_ !== tileSize[1]) {
        clipTileCanvas.width = tileSize[0];
        clipTileCanvas.height = tileSize[1];
        this.clipTileCanvasWidth_ = tileSize[0];
        this.clipTileCanvasHeight_ = tileSize[1];
      } else {
        clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]);
      }
      clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]);
      gl.texImage2D(_webgl2.default.TEXTURE_2D, 0, _webgl2.default.RGBA, _webgl2.default.RGBA, _webgl2.default.UNSIGNED_BYTE, clipTileCanvas);
    } else {
      gl.texImage2D(_webgl2.default.TEXTURE_2D, 0, _webgl2.default.RGBA, _webgl2.default.RGBA, _webgl2.default.UNSIGNED_BYTE, tile.getImage());
    }
    gl.texParameteri(_webgl2.default.TEXTURE_2D, _webgl2.default.TEXTURE_MAG_FILTER, magFilter);
    gl.texParameteri(_webgl2.default.TEXTURE_2D, _webgl2.default.TEXTURE_MIN_FILTER, minFilter);
    gl.texParameteri(_webgl2.default.TEXTURE_2D, _webgl2.default.TEXTURE_WRAP_S, _webgl2.default.CLAMP_TO_EDGE);
    gl.texParameteri(_webgl2.default.TEXTURE_2D, _webgl2.default.TEXTURE_WRAP_T, _webgl2.default.CLAMP_TO_EDGE);
    this.textureCache_.set(tileKey, {
      texture: texture,
      magFilter: magFilter,
      minFilter: minFilter
    });
  }
};

/**
 * @param {ol.render.EventType} type Event type.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
_ol_renderer_webgl_Map_.prototype.dispatchComposeEvent_ = function (type, frameState) {
  var map = this.getMap();
  if (map.hasListener(type)) {
    var context = this.context_;

    var extent = frameState.extent;
    var size = frameState.size;
    var viewState = frameState.viewState;
    var pixelRatio = frameState.pixelRatio;

    var resolution = viewState.resolution;
    var center = viewState.center;
    var rotation = viewState.rotation;

    var vectorContext = new _immediate2.default(context, center, resolution, rotation, size, extent, pixelRatio);
    var composeEvent = new _event2.default(type, vectorContext, frameState, null, context);
    map.dispatchEvent(composeEvent);
  }
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_Map_.prototype.disposeInternal = function () {
  var gl = this.getGL();
  if (!gl.isContextLost()) {
    this.textureCache_.forEach(
    /**
     * @param {?ol.WebglTextureCacheEntry} textureCacheEntry
     *     Texture cache entry.
     */
    function (textureCacheEntry) {
      if (textureCacheEntry) {
        gl.deleteTexture(textureCacheEntry.texture);
      }
    });
  }
  this.context_.dispose();
  _map2.default.prototype.disposeInternal.call(this);
};

/**
 * @param {ol.PluggableMap} map Map.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
_ol_renderer_webgl_Map_.prototype.expireCache_ = function (map, frameState) {
  var gl = this.getGL();
  var textureCacheEntry;
  while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > _index2.default.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
    textureCacheEntry = this.textureCache_.peekLast();
    if (!textureCacheEntry) {
      if (+this.textureCache_.peekLastKey() == frameState.index) {
        break;
      } else {
        --this.textureCacheFrameMarkerCount_;
      }
    } else {
      gl.deleteTexture(textureCacheEntry.texture);
    }
    this.textureCache_.pop();
  }
};

/**
 * @return {ol.webgl.Context} The context.
 */
_ol_renderer_webgl_Map_.prototype.getContext = function () {
  return this.context_;
};

/**
 * @return {WebGLRenderingContext} GL.
 */
_ol_renderer_webgl_Map_.prototype.getGL = function () {
  return this.gl_;
};

/**
 * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
 */
_ol_renderer_webgl_Map_.prototype.getTileTextureQueue = function () {
  return this.tileTextureQueue_;
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_Map_.prototype.getType = function () {
  return _type2.default.WEBGL;
};

/**
 * @param {ol.events.Event} event Event.
 * @protected
 */
_ol_renderer_webgl_Map_.prototype.handleWebGLContextLost = function (event) {
  event.preventDefault();
  this.textureCache_.clear();
  this.textureCacheFrameMarkerCount_ = 0;

  var renderers = this.getLayerRenderers();
  for (var id in renderers) {
    var renderer = /** @type {ol.renderer.webgl.Layer} */renderers[id];
    renderer.handleWebGLContextLost();
  }
};

/**
 * @protected
 */
_ol_renderer_webgl_Map_.prototype.handleWebGLContextRestored = function () {
  this.initializeGL_();
  this.getMap().render();
};

/**
 * @private
 */
_ol_renderer_webgl_Map_.prototype.initializeGL_ = function () {
  var gl = this.gl_;
  gl.activeTexture(_webgl2.default.TEXTURE0);
  gl.blendFuncSeparate(_webgl2.default.SRC_ALPHA, _webgl2.default.ONE_MINUS_SRC_ALPHA, _webgl2.default.ONE, _webgl2.default.ONE_MINUS_SRC_ALPHA);
  gl.disable(_webgl2.default.CULL_FACE);
  gl.disable(_webgl2.default.DEPTH_TEST);
  gl.disable(_webgl2.default.SCISSOR_TEST);
  gl.disable(_webgl2.default.STENCIL_TEST);
};

/**
 * @param {ol.Tile} tile Tile.
 * @return {boolean} Is tile texture loaded.
 */
_ol_renderer_webgl_Map_.prototype.isTileTextureLoaded = function (tile) {
  return this.textureCache_.containsKey(tile.getKey());
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_Map_.prototype.renderFrame = function (frameState) {

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

  if (gl.isContextLost()) {
    return false;
  }

  if (!frameState) {
    if (this.renderedVisible_) {
      this.canvas_.style.display = 'none';
      this.renderedVisible_ = false;
    }
    return false;
  }

  this.focus_ = frameState.focus;

  this.textureCache_.set((-frameState.index).toString(), null);
  ++this.textureCacheFrameMarkerCount_;

  this.dispatchComposeEvent_(_eventtype2.default.PRECOMPOSE, frameState);

  /** @type {Array.<ol.LayerState>} */
  var layerStatesToDraw = [];
  var layerStatesArray = frameState.layerStatesArray;
  _array2.default.stableSort(layerStatesArray, _map2.default.sortByZIndex);

  var viewResolution = frameState.viewState.resolution;
  var i, ii, layerRenderer, layerState;
  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
    layerState = layerStatesArray[i];
    if (_layer2.default.visibleAtResolution(layerState, viewResolution) && layerState.sourceState == _state2.default.READY) {
      layerRenderer = /** @type {ol.renderer.webgl.Layer} */this.getLayerRenderer(layerState.layer);
      if (layerRenderer.prepareFrame(frameState, layerState, context)) {
        layerStatesToDraw.push(layerState);
      }
    }
  }

  var width = frameState.size[0] * frameState.pixelRatio;
  var height = frameState.size[1] * frameState.pixelRatio;
  if (this.canvas_.width != width || this.canvas_.height != height) {
    this.canvas_.width = width;
    this.canvas_.height = height;
  }

  gl.bindFramebuffer(_webgl2.default.FRAMEBUFFER, null);

  gl.clearColor(0, 0, 0, 0);
  gl.clear(_webgl2.default.COLOR_BUFFER_BIT);
  gl.enable(_webgl2.default.BLEND);
  gl.viewport(0, 0, this.canvas_.width, this.canvas_.height);

  for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) {
    layerState = layerStatesToDraw[i];
    layerRenderer = /** @type {ol.renderer.webgl.Layer} */this.getLayerRenderer(layerState.layer);
    layerRenderer.composeFrame(frameState, layerState, context);
  }

  if (!this.renderedVisible_) {
    this.canvas_.style.display = '';
    this.renderedVisible_ = true;
  }

  this.calculateMatrices2D(frameState);

  if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > _index2.default.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
    frameState.postRenderFunctions.push(
    /** @type {ol.PostRenderFunction} */this.expireCache_.bind(this));
  }

  if (!this.tileTextureQueue_.isEmpty()) {
    frameState.postRenderFunctions.push(this.loadNextTileTexture_);
    frameState.animate = true;
  }

  this.dispatchComposeEvent_(_eventtype2.default.POSTCOMPOSE, frameState);

  this.scheduleRemoveUnusedLayerRenderers(frameState);
  this.scheduleExpireIconCache(frameState);
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_Map_.prototype.forEachFeatureAtCoordinate = function (coordinate, frameState, hitTolerance, callback, thisArg, layerFilter, thisArg2) {
  var result;

  if (this.getGL().isContextLost()) {
    return false;
  }

  var viewState = frameState.viewState;

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (_layer2.default.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg2, layer)) {
      var layerRenderer = this.getLayerRenderer(layer);
      result = layerRenderer.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg);
      if (result) {
        return result;
      }
    }
  }
  return undefined;
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_Map_.prototype.hasFeatureAtCoordinate = function (coordinate, frameState, hitTolerance, layerFilter, thisArg) {
  var hasFeature = false;

  if (this.getGL().isContextLost()) {
    return false;
  }

  var viewState = frameState.viewState;

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (_layer2.default.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg, layer)) {
      var layerRenderer = this.getLayerRenderer(layer);
      hasFeature = layerRenderer.hasFeatureAtCoordinate(coordinate, frameState);
      if (hasFeature) {
        return true;
      }
    }
  }
  return hasFeature;
};

/**
 * @inheritDoc
 */
_ol_renderer_webgl_Map_.prototype.forEachLayerAtPixel = function (pixel, frameState, callback, thisArg, layerFilter, thisArg2) {
  if (this.getGL().isContextLost()) {
    return false;
  }

  var viewState = frameState.viewState;
  var result;

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (_layer2.default.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg, layer)) {
      var layerRenderer = /** @type {ol.renderer.webgl.Layer} */this.getLayerRenderer(layer);
      result = layerRenderer.forEachLayerAtPixel(pixel, frameState, callback, thisArg);
      if (result) {
        return result;
      }
    }
  }
  return undefined;
};
exports.default = _ol_renderer_webgl_Map_;
module.exports = exports['default'];