'use strict';

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

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

var _index2 = _interopRequireDefault(_index);

var _tile = require('../tile.js');

var _tile2 = _interopRequireDefault(_tile);

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

var _tilestate2 = _interopRequireDefault(_tilestate);

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

var _events2 = _interopRequireDefault(_events);

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

var _eventtype2 = _interopRequireDefault(_eventtype);

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

var _extent2 = _interopRequireDefault(_extent);

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

var _math2 = _interopRequireDefault(_math);

var _reproj = require('../reproj.js');

var _reproj2 = _interopRequireDefault(_reproj);

var _triangulation = require('../reproj/triangulation.js');

var _triangulation2 = _interopRequireDefault(_triangulation);

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

/**
 * @classdesc
 * Class encapsulating single reprojected tile.
 * See {@link ol.source.TileImage}.
 *
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.proj.Projection} sourceProj Source projection.
 * @param {ol.tilegrid.TileGrid} sourceTileGrid Source tile grid.
 * @param {ol.proj.Projection} targetProj Target projection.
 * @param {ol.tilegrid.TileGrid} targetTileGrid Target tile grid.
 * @param {ol.TileCoord} tileCoord Coordinate of the tile.
 * @param {ol.TileCoord} wrappedTileCoord Coordinate of the tile wrapped in X.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} gutter Gutter of the source tiles.
 * @param {ol.ReprojTileFunctionType} getTileFunction
 *     Function returning source tiles (z, x, y, pixelRatio).
 * @param {number=} opt_errorThreshold Acceptable reprojection error (in px).
 * @param {boolean=} opt_renderEdges Render reprojection edges.
 */
var _ol_reproj_Tile_ = function (sourceProj, sourceTileGrid, targetProj, targetTileGrid, tileCoord, wrappedTileCoord, pixelRatio, gutter, getTileFunction, opt_errorThreshold, opt_renderEdges) {
  _tile2.default.call(this, tileCoord, _tilestate2.default.IDLE);

  /**
   * @private
   * @type {boolean}
   */
  this.renderEdges_ = opt_renderEdges !== undefined ? opt_renderEdges : false;

  /**
   * @private
   * @type {number}
   */
  this.pixelRatio_ = pixelRatio;

  /**
   * @private
   * @type {number}
   */
  this.gutter_ = gutter;

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = null;

  /**
   * @private
   * @type {ol.tilegrid.TileGrid}
   */
  this.sourceTileGrid_ = sourceTileGrid;

  /**
   * @private
   * @type {ol.tilegrid.TileGrid}
   */
  this.targetTileGrid_ = targetTileGrid;

  /**
   * @private
   * @type {ol.TileCoord}
   */
  this.wrappedTileCoord_ = wrappedTileCoord ? wrappedTileCoord : tileCoord;

  /**
   * @private
   * @type {!Array.<ol.Tile>}
   */
  this.sourceTiles_ = [];

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.sourcesListenerKeys_ = null;

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

  var targetExtent = targetTileGrid.getTileCoordExtent(this.wrappedTileCoord_);
  var maxTargetExtent = this.targetTileGrid_.getExtent();
  var maxSourceExtent = this.sourceTileGrid_.getExtent();

  var limitedTargetExtent = maxTargetExtent ? _extent2.default.getIntersection(targetExtent, maxTargetExtent) : targetExtent;

  if (_extent2.default.getArea(limitedTargetExtent) === 0) {
    // Tile is completely outside range -> EMPTY
    // TODO: is it actually correct that the source even creates the tile ?
    this.state = _tilestate2.default.EMPTY;
    return;
  }

  var sourceProjExtent = sourceProj.getExtent();
  if (sourceProjExtent) {
    if (!maxSourceExtent) {
      maxSourceExtent = sourceProjExtent;
    } else {
      maxSourceExtent = _extent2.default.getIntersection(maxSourceExtent, sourceProjExtent);
    }
  }

  var targetResolution = targetTileGrid.getResolution(this.wrappedTileCoord_[0]);

  var targetCenter = _extent2.default.getCenter(limitedTargetExtent);
  var sourceResolution = _reproj2.default.calculateSourceResolution(sourceProj, targetProj, targetCenter, targetResolution);

  if (!isFinite(sourceResolution) || sourceResolution <= 0) {
    // invalid sourceResolution -> EMPTY
    // probably edges of the projections when no extent is defined
    this.state = _tilestate2.default.EMPTY;
    return;
  }

  var errorThresholdInPixels = opt_errorThreshold !== undefined ? opt_errorThreshold : _index2.default.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;

  /**
   * @private
   * @type {!ol.reproj.Triangulation}
   */
  this.triangulation_ = new _triangulation2.default(sourceProj, targetProj, limitedTargetExtent, maxSourceExtent, sourceResolution * errorThresholdInPixels);

  if (this.triangulation_.getTriangles().length === 0) {
    // no valid triangles -> EMPTY
    this.state = _tilestate2.default.EMPTY;
    return;
  }

  this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution);
  var sourceExtent = this.triangulation_.calculateSourceExtent();

  if (maxSourceExtent) {
    if (sourceProj.canWrapX()) {
      sourceExtent[1] = _math2.default.clamp(sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]);
      sourceExtent[3] = _math2.default.clamp(sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]);
    } else {
      sourceExtent = _extent2.default.getIntersection(sourceExtent, maxSourceExtent);
    }
  }

  if (!_extent2.default.getArea(sourceExtent)) {
    this.state = _tilestate2.default.EMPTY;
  } else {
    var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(sourceExtent, this.sourceZ_);

    for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) {
      for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) {
        var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio);
        if (tile) {
          this.sourceTiles_.push(tile);
        }
      }
    }

    if (this.sourceTiles_.length === 0) {
      this.state = _tilestate2.default.EMPTY;
    }
  }
};

_index2.default.inherits(_ol_reproj_Tile_, _tile2.default);

/**
 * @inheritDoc
 */
_ol_reproj_Tile_.prototype.disposeInternal = function () {
  if (this.state == _tilestate2.default.LOADING) {
    this.unlistenSources_();
  }
  _tile2.default.prototype.disposeInternal.call(this);
};

/**
 * Get the HTML Canvas element for this tile.
 * @return {HTMLCanvasElement} Canvas.
 */
_ol_reproj_Tile_.prototype.getImage = function () {
  return this.canvas_;
};

/**
 * @private
 */
_ol_reproj_Tile_.prototype.reproject_ = function () {
  var sources = [];
  this.sourceTiles_.forEach(function (tile, i, arr) {
    if (tile && tile.getState() == _tilestate2.default.LOADED) {
      sources.push({
        extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord),
        image: tile.getImage()
      });
    }
  }, this);
  this.sourceTiles_.length = 0;

  if (sources.length === 0) {
    this.state = _tilestate2.default.ERROR;
  } else {
    var z = this.wrappedTileCoord_[0];
    var size = this.targetTileGrid_.getTileSize(z);
    var width = typeof size === 'number' ? size : size[0];
    var height = typeof size === 'number' ? size : size[1];
    var targetResolution = this.targetTileGrid_.getResolution(z);
    var sourceResolution = this.sourceTileGrid_.getResolution(this.sourceZ_);

    var targetExtent = this.targetTileGrid_.getTileCoordExtent(this.wrappedTileCoord_);
    this.canvas_ = _reproj2.default.render(width, height, this.pixelRatio_, sourceResolution, this.sourceTileGrid_.getExtent(), targetResolution, targetExtent, this.triangulation_, sources, this.gutter_, this.renderEdges_);

    this.state = _tilestate2.default.LOADED;
  }
  this.changed();
};

/**
 * @inheritDoc
 */
_ol_reproj_Tile_.prototype.load = function () {
  if (this.state == _tilestate2.default.IDLE) {
    this.state = _tilestate2.default.LOADING;
    this.changed();

    var leftToLoad = 0;

    this.sourcesListenerKeys_ = [];
    this.sourceTiles_.forEach(function (tile, i, arr) {
      var state = tile.getState();
      if (state == _tilestate2.default.IDLE || state == _tilestate2.default.LOADING) {
        leftToLoad++;

        var sourceListenKey;
        sourceListenKey = _events2.default.listen(tile, _eventtype2.default.CHANGE, function (e) {
          var state = tile.getState();
          if (state == _tilestate2.default.LOADED || state == _tilestate2.default.ERROR || state == _tilestate2.default.EMPTY) {
            _events2.default.unlistenByKey(sourceListenKey);
            leftToLoad--;
            if (leftToLoad === 0) {
              this.unlistenSources_();
              this.reproject_();
            }
          }
        }, this);
        this.sourcesListenerKeys_.push(sourceListenKey);
      }
    }, this);

    this.sourceTiles_.forEach(function (tile, i, arr) {
      var state = tile.getState();
      if (state == _tilestate2.default.IDLE) {
        tile.load();
      }
    });

    if (leftToLoad === 0) {
      setTimeout(this.reproject_.bind(this), 0);
    }
  }
};

/**
 * @private
 */
_ol_reproj_Tile_.prototype.unlistenSources_ = function () {
  this.sourcesListenerKeys_.forEach(_events2.default.unlistenByKey);
  this.sourcesListenerKeys_ = null;
};
exports.default = _ol_reproj_Tile_;
module.exports = exports['default'];