'use strict';

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

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

var _index2 = _interopRequireDefault(_index);

var _imagecanvas = require('../imagecanvas.js');

var _imagecanvas2 = _interopRequireDefault(_imagecanvas);

var _tilequeue = require('../tilequeue.js');

var _tilequeue2 = _interopRequireDefault(_tilequeue);

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

var _dom2 = _interopRequireDefault(_dom);

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

var _events2 = _interopRequireDefault(_events);

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

var _event2 = _interopRequireDefault(_event);

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

var _eventtype2 = _interopRequireDefault(_eventtype);

var _pixelworks = require('pixelworks');

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

var _extent2 = _interopRequireDefault(_extent);

var _image = require('../layer/image.js');

var _image2 = _interopRequireDefault(_image);

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

var _tile2 = _interopRequireDefault(_tile);

var _obj = require('../obj.js');

var _obj2 = _interopRequireDefault(_obj);

var _imagelayer = require('../renderer/canvas/imagelayer.js');

var _imagelayer2 = _interopRequireDefault(_imagelayer);

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

var _tilelayer2 = _interopRequireDefault(_tilelayer);

var _image3 = require('../source/image.js');

var _image4 = _interopRequireDefault(_image3);

var _rasteroperationtype = require('../source/rasteroperationtype.js');

var _rasteroperationtype2 = _interopRequireDefault(_rasteroperationtype);

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

var _state2 = _interopRequireDefault(_state);

var _tile3 = require('../source/tile.js');

var _tile4 = _interopRequireDefault(_tile3);

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

var _transform2 = _interopRequireDefault(_transform);

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

/**
 * @classdesc
 * A source that transforms data from any number of input sources using an
 * {@link ol.RasterOperation} function to transform input pixel values into
 * output pixel values.
 *
 * @constructor
 * @extends {ol.source.Image}
 * @fires ol.source.Raster.Event
 * @param {olx.source.RasterOptions} options Options.
 * @api
 */
var _ol_source_Raster_ = function (options) {

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

  /**
   * @private
   * @type {ol.source.RasterOperationType}
   */
  this.operationType_ = options.operationType !== undefined ? options.operationType : _rasteroperationtype2.default.PIXEL;

  /**
   * @private
   * @type {number}
   */
  this.threads_ = options.threads !== undefined ? options.threads : 1;

  /**
   * @private
   * @type {Array.<ol.renderer.canvas.Layer>}
   */
  this.renderers_ = _ol_source_Raster_.createRenderers_(options.sources);

  for (var r = 0, rr = this.renderers_.length; r < rr; ++r) {
    _events2.default.listen(this.renderers_[r], _eventtype2.default.CHANGE, this.changed, this);
  }

  /**
   * @private
   * @type {ol.TileQueue}
   */
  this.tileQueue_ = new _tilequeue2.default(function () {
    return 1;
  }, this.changed.bind(this));

  var layerStatesArray = _ol_source_Raster_.getLayerStatesArray_(this.renderers_);
  var layerStates = {};
  for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) {
    layerStates[_index2.default.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
  }

  /**
   * The most recently requested frame state.
   * @type {olx.FrameState}
   * @private
   */
  this.requestedFrameState_;

  /**
   * The most recently rendered image canvas.
   * @type {ol.ImageCanvas}
   * @private
   */
  this.renderedImageCanvas_ = null;

  /**
   * The most recently rendered revision.
   * @type {number}
   */
  this.renderedRevision_;

  /**
   * @private
   * @type {olx.FrameState}
   */
  this.frameState_ = {
    animate: false,
    coordinateToPixelTransform: _transform2.default.create(),
    extent: null,
    focus: null,
    index: 0,
    layerStates: layerStates,
    layerStatesArray: layerStatesArray,
    logos: {},
    pixelRatio: 1,
    pixelToCoordinateTransform: _transform2.default.create(),
    postRenderFunctions: [],
    size: [0, 0],
    skippedFeatureUids: {},
    tileQueue: this.tileQueue_,
    time: Date.now(),
    usedTiles: {},
    viewState: /** @type {olx.ViewState} */{
      rotation: 0
    },
    viewHints: [],
    wantedTiles: {}
  };

  _image4.default.call(this, {});

  if (options.operation !== undefined) {
    this.setOperation(options.operation, options.lib);
  }
};

_index2.default.inherits(_ol_source_Raster_, _image4.default);

/**
 * Set the operation.
 * @param {ol.RasterOperation} operation New operation.
 * @param {Object=} opt_lib Functions that will be available to operations run
 *     in a worker.
 * @api
 */
_ol_source_Raster_.prototype.setOperation = function (operation, opt_lib) {
  this.worker_ = new _pixelworks.Processor({
    operation: operation,
    imageOps: this.operationType_ === _rasteroperationtype2.default.IMAGE,
    queue: 1,
    lib: opt_lib,
    threads: this.threads_
  });
  this.changed();
};

/**
 * Update the stored frame state.
 * @param {ol.Extent} extent The view extent (in map units).
 * @param {number} resolution The view resolution.
 * @param {ol.proj.Projection} projection The view projection.
 * @return {olx.FrameState} The updated frame state.
 * @private
 */
_ol_source_Raster_.prototype.updateFrameState_ = function (extent, resolution, projection) {

  var frameState = /** @type {olx.FrameState} */_obj2.default.assign({}, this.frameState_);

  frameState.viewState = /** @type {olx.ViewState} */_obj2.default.assign({}, frameState.viewState);

  var center = _extent2.default.getCenter(extent);

  frameState.extent = extent.slice();
  frameState.focus = center;
  frameState.size[0] = Math.round(_extent2.default.getWidth(extent) / resolution);
  frameState.size[1] = Math.round(_extent2.default.getHeight(extent) / resolution);
  frameState.time = Date.now();
  frameState.animate = false;

  var viewState = frameState.viewState;
  viewState.center = center;
  viewState.projection = projection;
  viewState.resolution = resolution;
  return frameState;
};

/**
 * Determine if all sources are ready.
 * @return {boolean} All sources are ready.
 * @private
 */
_ol_source_Raster_.prototype.allSourcesReady_ = function () {
  var ready = true;
  var source;
  for (var i = 0, ii = this.renderers_.length; i < ii; ++i) {
    source = this.renderers_[i].getLayer().getSource();
    if (source.getState() !== _state2.default.READY) {
      ready = false;
      break;
    }
  }
  return ready;
};

/**
 * @inheritDoc
 */
_ol_source_Raster_.prototype.getImage = function (extent, resolution, pixelRatio, projection) {
  if (!this.allSourcesReady_()) {
    return null;
  }

  var frameState = this.updateFrameState_(extent, resolution, projection);
  this.requestedFrameState_ = frameState;

  // check if we can't reuse the existing ol.ImageCanvas
  if (this.renderedImageCanvas_) {
    var renderedResolution = this.renderedImageCanvas_.getResolution();
    var renderedExtent = this.renderedImageCanvas_.getExtent();
    if (resolution !== renderedResolution || !_extent2.default.equals(extent, renderedExtent)) {
      this.renderedImageCanvas_ = null;
    }
  }

  if (!this.renderedImageCanvas_ || this.getRevision() !== this.renderedRevision_) {
    this.processSources_();
  }

  frameState.tileQueue.loadMoreTiles(16, 16);

  if (frameState.animate) {
    requestAnimationFrame(this.changed.bind(this));
  }

  return this.renderedImageCanvas_;
};

/**
 * Start processing source data.
 * @private
 */
_ol_source_Raster_.prototype.processSources_ = function () {
  var frameState = this.requestedFrameState_;
  var len = this.renderers_.length;
  var imageDatas = new Array(len);
  for (var i = 0; i < len; ++i) {
    var imageData = _ol_source_Raster_.getImageData_(this.renderers_[i], frameState, frameState.layerStatesArray[i]);
    if (imageData) {
      imageDatas[i] = imageData;
    } else {
      return;
    }
  }

  var data = {};
  this.dispatchEvent(new _ol_source_Raster_.Event(_ol_source_Raster_.EventType_.BEFOREOPERATIONS, frameState, data));
  this.worker_.process(imageDatas, data, this.onWorkerComplete_.bind(this, frameState));
};

/**
 * Called when pixel processing is complete.
 * @param {olx.FrameState} frameState The frame state.
 * @param {Error} err Any error during processing.
 * @param {ImageData} output The output image data.
 * @param {Object} data The user data.
 * @private
 */
_ol_source_Raster_.prototype.onWorkerComplete_ = function (frameState, err, output, data) {
  if (err || !output) {
    return;
  }

  // do nothing if extent or resolution changed
  var extent = frameState.extent;
  var resolution = frameState.viewState.resolution;
  if (resolution !== this.requestedFrameState_.viewState.resolution || !_extent2.default.equals(extent, this.requestedFrameState_.extent)) {
    return;
  }

  var context;
  if (this.renderedImageCanvas_) {
    context = this.renderedImageCanvas_.getImage().getContext('2d');
  } else {
    var width = Math.round(_extent2.default.getWidth(extent) / resolution);
    var height = Math.round(_extent2.default.getHeight(extent) / resolution);
    context = _dom2.default.createCanvasContext2D(width, height);
    this.renderedImageCanvas_ = new _imagecanvas2.default(extent, resolution, 1, context.canvas);
  }
  context.putImageData(output, 0, 0);

  this.changed();
  this.renderedRevision_ = this.getRevision();

  this.dispatchEvent(new _ol_source_Raster_.Event(_ol_source_Raster_.EventType_.AFTEROPERATIONS, frameState, data));
};

/**
 * Get image data from a renderer.
 * @param {ol.renderer.canvas.Layer} renderer Layer renderer.
 * @param {olx.FrameState} frameState The frame state.
 * @param {ol.LayerState} layerState The layer state.
 * @return {ImageData} The image data.
 * @private
 */
_ol_source_Raster_.getImageData_ = function (renderer, frameState, layerState) {
  if (!renderer.prepareFrame(frameState, layerState)) {
    return null;
  }
  var width = frameState.size[0];
  var height = frameState.size[1];
  if (!_ol_source_Raster_.context_) {
    _ol_source_Raster_.context_ = _dom2.default.createCanvasContext2D(width, height);
  } else {
    var canvas = _ol_source_Raster_.context_.canvas;
    if (canvas.width !== width || canvas.height !== height) {
      _ol_source_Raster_.context_ = _dom2.default.createCanvasContext2D(width, height);
    } else {
      _ol_source_Raster_.context_.clearRect(0, 0, width, height);
    }
  }
  renderer.composeFrame(frameState, layerState, _ol_source_Raster_.context_);
  return _ol_source_Raster_.context_.getImageData(0, 0, width, height);
};

/**
 * A reusable canvas context.
 * @type {CanvasRenderingContext2D}
 * @private
 */
_ol_source_Raster_.context_ = null;

/**
 * Get a list of layer states from a list of renderers.
 * @param {Array.<ol.renderer.canvas.Layer>} renderers Layer renderers.
 * @return {Array.<ol.LayerState>} The layer states.
 * @private
 */
_ol_source_Raster_.getLayerStatesArray_ = function (renderers) {
  return renderers.map(function (renderer) {
    return renderer.getLayer().getLayerState();
  });
};

/**
 * Create renderers for all sources.
 * @param {Array.<ol.source.Source>} sources The sources.
 * @return {Array.<ol.renderer.canvas.Layer>} Array of layer renderers.
 * @private
 */
_ol_source_Raster_.createRenderers_ = function (sources) {
  var len = sources.length;
  var renderers = new Array(len);
  for (var i = 0; i < len; ++i) {
    renderers[i] = _ol_source_Raster_.createRenderer_(sources[i]);
  }
  return renderers;
};

/**
 * Create a renderer for the provided source.
 * @param {ol.source.Source} source The source.
 * @return {ol.renderer.canvas.Layer} The renderer.
 * @private
 */
_ol_source_Raster_.createRenderer_ = function (source) {
  var renderer = null;
  if (source instanceof _tile4.default) {
    renderer = _ol_source_Raster_.createTileRenderer_(source);
  } else if (source instanceof _image4.default) {
    renderer = _ol_source_Raster_.createImageRenderer_(source);
  }
  return renderer;
};

/**
 * Create an image renderer for the provided source.
 * @param {ol.source.Image} source The source.
 * @return {ol.renderer.canvas.Layer} The renderer.
 * @private
 */
_ol_source_Raster_.createImageRenderer_ = function (source) {
  var layer = new _image2.default({ source: source });
  return new _imagelayer2.default(layer);
};

/**
 * Create a tile renderer for the provided source.
 * @param {ol.source.Tile} source The source.
 * @return {ol.renderer.canvas.Layer} The renderer.
 * @private
 */
_ol_source_Raster_.createTileRenderer_ = function (source) {
  var layer = new _tile2.default({ source: source });
  return new _tilelayer2.default(layer);
};

/**
 * @classdesc
 * Events emitted by {@link ol.source.Raster} instances are instances of this
 * type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.source.RasterEvent}
 * @param {string} type Type.
 * @param {olx.FrameState} frameState The frame state.
 * @param {Object} data An object made available to operations.
 */
_ol_source_Raster_.Event = function (type, frameState, data) {
  _event2.default.call(this, type);

  /**
   * The raster extent.
   * @type {ol.Extent}
   * @api
   */
  this.extent = frameState.extent;

  /**
   * The pixel resolution (map units per pixel).
   * @type {number}
   * @api
   */
  this.resolution = frameState.viewState.resolution / frameState.pixelRatio;

  /**
   * An object made available to all operations.  This can be used by operations
   * as a storage object (e.g. for calculating statistics).
   * @type {Object}
   * @api
   */
  this.data = data;
};
_index2.default.inherits(_ol_source_Raster_.Event, _event2.default);

/**
 * @override
 */
_ol_source_Raster_.prototype.getImageInternal = function () {
  return null; // not implemented
};

/**
 * @enum {string}
 * @private
 */
_ol_source_Raster_.EventType_ = {
  /**
   * Triggered before operations are run.
   * @event ol.source.Raster.Event#beforeoperations
   * @api
   */
  BEFOREOPERATIONS: 'beforeoperations',

  /**
   * Triggered after operations are run.
   * @event ol.source.Raster.Event#afteroperations
   * @api
   */
  AFTEROPERATIONS: 'afteroperations'
};
exports.default = _ol_source_Raster_;
module.exports = exports['default'];