/**
 * Texture Atlas for the sprites.
 * It uses zrender for 2d element management and rendering
 * @module echarts-gl/util/ZRTextureAtlasSurface
 */
// TODO Expand.
import * as echarts from 'echarts/lib/echarts';
import Texture2D from 'claygl/src/Texture2D';

function ZRTextureAtlasSurfaceNode(zr, offsetX, offsetY, width, height, gap, dpr) {
  this._zr = zr;
  /**
   * Current cursor x
   * @type {number}
   * @private
   */

  this._x = 0;
  /**
   * Current cursor y
   * @type {number}
   */

  this._y = 0;
  this._rowHeight = 0;
  /**
   * width without dpr.
   * @type {number}
   * @private
   */

  this.width = width;
  /**
   * height without dpr.
   * @type {number}
   * @private
   */

  this.height = height;
  /**
   * offsetX without dpr
   * @type {number}
   */

  this.offsetX = offsetX;
  /**
   * offsetY without dpr
   * @type {number}
   */

  this.offsetY = offsetY;
  this.dpr = dpr;
  this.gap = gap;
}

ZRTextureAtlasSurfaceNode.prototype = {
  constructor: ZRTextureAtlasSurfaceNode,
  clear: function () {
    this._x = 0;
    this._y = 0;
    this._rowHeight = 0;
  },

  /**
   * Add shape to atlas
   * @param {module:zrender/graphic/Displayable} shape
   * @param {number} width
   * @param {number} height
   * @return {Array}
   */
  add: function (el, width, height) {
    // FIXME Text element not consider textAlign and textVerticalAlign.
    // TODO, inner text, shadow
    var rect = el.getBoundingRect(); // FIXME aspect ratio

    if (width == null) {
      width = rect.width;
    }

    if (height == null) {
      height = rect.height;
    }

    width *= this.dpr;
    height *= this.dpr;

    this._fitElement(el, width, height); // var aspect = el.scale[1] / el.scale[0];
    // Adjust aspect ratio to make the text more clearly
    // FIXME If height > width, width is useless ?
    // width = height * aspect;
    // el.position[0] *= aspect;
    // el.scale[0] = el.scale[1];


    var x = this._x;
    var y = this._y;
    var canvasWidth = this.width * this.dpr;
    var canvasHeight = this.height * this.dpr;
    var gap = this.gap;

    if (x + width + gap > canvasWidth) {
      // Change a new row
      x = this._x = 0;
      y += this._rowHeight + gap;
      this._y = y; // Reset row height

      this._rowHeight = 0;
    }

    this._x += width + gap;
    this._rowHeight = Math.max(this._rowHeight, height);

    if (y + height + gap > canvasHeight) {
      // There is no space anymore
      return null;
    } // Shift the el


    el.x += this.offsetX * this.dpr + x;
    el.y += this.offsetY * this.dpr + y;

    this._zr.add(el);

    var coordsOffset = [this.offsetX / this.width, this.offsetY / this.height];
    var coords = [[x / canvasWidth + coordsOffset[0], y / canvasHeight + coordsOffset[1]], [(x + width) / canvasWidth + coordsOffset[0], (y + height) / canvasHeight + coordsOffset[1]]];
    return coords;
  },

  /**
   * Fit element size by correct its position and scaling
   * @param {module:zrender/graphic/Displayable} el
   * @param {number} spriteWidth
   * @param {number} spriteHeight
   */
  _fitElement: function (el, spriteWidth, spriteHeight) {
    // TODO, inner text, shadow
    var rect = el.getBoundingRect();
    var scaleX = spriteWidth / rect.width;
    var scaleY = spriteHeight / rect.height;
    el.x = -rect.x * scaleX;
    el.y = -rect.y * scaleY;
    el.scaleX = scaleX;
    el.scaleY = scaleY;
    el.update();
  }
};
/**
 * constructor
 * @alias module:echarts-gl/util/ZRTextureAtlasSurface
 * @param {number} opt.width
 * @param {number} opt.height
 * @param {number} opt.devicePixelRatio
 * @param {number} opt.gap Gap for safe.
 * @param {Function} opt.onupdate
 */

function ZRTextureAtlasSurface(opt) {
  opt = opt || {};
  opt.width = opt.width || 512;
  opt.height = opt.height || 512;
  opt.devicePixelRatio = opt.devicePixelRatio || 1;
  opt.gap = opt.gap == null ? 2 : opt.gap;
  var canvas = document.createElement('canvas');
  canvas.width = opt.width * opt.devicePixelRatio;
  canvas.height = opt.height * opt.devicePixelRatio;
  this._canvas = canvas;
  this._texture = new Texture2D({
    image: canvas,
    flipY: false
  });
  var self = this;
  /**
   * zrender instance in the Chart
   * @type {zrender~ZRender}
   */

  this._zr = echarts.zrender.init(canvas);
  var oldRefreshImmediately = this._zr.refreshImmediately;

  this._zr.refreshImmediately = function () {
    oldRefreshImmediately.call(this);

    self._texture.dirty();

    self.onupdate && self.onupdate();
  };

  this._dpr = opt.devicePixelRatio;
  /**
   * Texture coords map for each sprite image
   * @type {Object}
   */

  this._coords = {};
  this.onupdate = opt.onupdate;
  this._gap = opt.gap; // Left sub atlas.

  this._textureAtlasNodes = [new ZRTextureAtlasSurfaceNode(this._zr, 0, 0, opt.width, opt.height, this._gap, this._dpr)];
  this._nodeWidth = opt.width;
  this._nodeHeight = opt.height;
  this._currentNodeIdx = 0;
}

ZRTextureAtlasSurface.prototype = {
  /**
   * Clear the texture atlas
   */
  clear: function () {
    for (var i = 0; i < this._textureAtlasNodes.length; i++) {
      this._textureAtlasNodes[i].clear();
    }

    this._currentNodeIdx = 0;

    this._zr.clear();

    this._coords = {};
  },

  /**
   * @return {number}
   */
  getWidth: function () {
    return this._width;
  },

  /**
   * @return {number}
   */
  getHeight: function () {
    return this._height;
  },

  /**
   * @return {number}
   */
  getTexture: function () {
    return this._texture;
  },

  /**
   * @return {number}
   */
  getDevicePixelRatio: function () {
    return this._dpr;
  },
  getZr: function () {
    return this._zr;
  },
  _getCurrentNode: function () {
    return this._textureAtlasNodes[this._currentNodeIdx];
  },
  _expand: function () {
    this._currentNodeIdx++;

    if (this._textureAtlasNodes[this._currentNodeIdx]) {
      // Use the node created previously.
      return this._textureAtlasNodes[this._currentNodeIdx];
    }

    var maxSize = 4096 / this._dpr;
    var textureAtlasNodes = this._textureAtlasNodes;
    var nodeLen = textureAtlasNodes.length;
    var offsetX = nodeLen * this._nodeWidth % maxSize;

    var offsetY = Math.floor(nodeLen * this._nodeWidth / maxSize) * this._nodeHeight;

    if (offsetY >= maxSize) {
      // Failed if image is too large.
      if (process.env.NODE_ENV !== 'production') {
        console.error('Too much labels. Some will be ignored.');
      }

      return;
    }

    var width = (offsetX + this._nodeWidth) * this._dpr;
    var height = (offsetY + this._nodeHeight) * this._dpr;

    try {
      // Resize will error in node.
      this._zr.resize({
        width: width,
        height: height
      });
    } catch (e) {
      this._canvas.width = width;
      this._canvas.height = height;
    }

    var newNode = new ZRTextureAtlasSurfaceNode(this._zr, offsetX, offsetY, this._nodeWidth, this._nodeHeight, this._gap, this._dpr);

    this._textureAtlasNodes.push(newNode);

    return newNode;
  },
  add: function (el, width, height) {
    if (this._coords[el.id]) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn('Element already been add');
      }

      return this._coords[el.id];
    }

    var coords = this._getCurrentNode().add(el, width, height);

    if (!coords) {
      var newNode = this._expand();

      if (!newNode) {
        // To maximum
        return;
      }

      coords = newNode.add(el, width, height);
    }

    this._coords[el.id] = coords;
    return coords;
  },

  /**
   * Get coord scale after texture atlas is expanded.
   * @return {Array.<number>}
   */
  getCoordsScale: function () {
    var dpr = this._dpr;
    return [this._nodeWidth / this._canvas.width * dpr, this._nodeHeight / this._canvas.height * dpr];
  },

  /**
   * Get texture coords of sprite image
   * @param  {string} id Image id
   * @return {Array}
   */
  getCoords: function (id) {
    return this._coords[id];
  },
  dispose: function () {
    this._zr.dispose();
  }
};
export default ZRTextureAtlasSurface;