var dataUtil = require("./core/utils/data_structure_util");

var canvasUtil = require("./core/utils/canvas_util");

var BoundingRect = require("./graphic/transform/BoundingRect");

var CanvasLayer = require("./CanvasLayer");

var Image = require("./graphic/Image");

var guid = require("./core/utils/guid");

var timsort = require("./core/utils/timsort");

var requestAnimationFrame = require("./animation/utils/request_animation_frame");

var _constants = require("./graphic/constants");

var mathRandom = _constants.mathRandom;
var mathMax = _constants.mathMax;

var _config = require("./config");

var devicePixelRatio = _config.devicePixelRatio;

var env = require("./core/env");

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/**
 * @class qrenderer.canvas.CanvasPainter
 * 这是基于 canvas 接口的 CanvasPainter 类
 * @see 基于 SVG 接口的 SVGPainter 类在 svg 目录下
 */
var HOVER_LAYER_QLEVEL = 1e5;
var CANVAS_QLEVEL = 314159;
var EL_AFTER_INCREMENTAL_INC = 0.01;
var INCREMENTAL_INC = 0.001;

var CanvasPainter =
/*#__PURE__*/
function () {
  /**
   * @method constructor
   * @param {HTMLDomElement|Canvas|Context} host 
   * This can be a HTMLDomElement like a DIV, or a Canvas instance, 
   * or Context for Wechat mini-program.
   * 
   * 此属性可以是 HTMLDomElement ，比如 DIV 标签；也可以是 Canvas 实例；或者是 Context 实例，因为在某些
   * 运行环境中，不能获得 Canvas 实例的引用，只能获得 Context。
   * @param {Storage} storage
   * @param {Object} options
   */
  function CanvasPainter(host, storage) {
    var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

    _classCallCheck(this, CanvasPainter);

    this.options = dataUtil.extend({}, options);
    /**
     * @property {String} type
     */

    this.type = 'canvas';
    /**
     * @property {Number} dpr
     */

    this.dpr = this.options.devicePixelRatio || devicePixelRatio;
    /**
     * @property {HTMLDomElement|Canvas|Context} host 
     * This can be a HTMLDomElement like a DIV, or a Canvas instance, 
     * or Context for Wechat mini-program.
     * 
     * 此属性可以是 HTMLDomElement ，比如 DIV 标签；也可以是 Canvas 实例；或者是 Context 实例，因为在某些
     * 运行环境中，不能获得 Canvas 实例的引用，只能获得 Context。
     */

    this.host = host; // There is no style attribute on element in nodejs.

    if (this.host.style) {
      this.host.style['-webkit-tap-highlight-color'] = 'transparent';
      this.host.style['-webkit-user-select'] = this.host.style['user-select'] = this.host.style['-webkit-touch-callout'] = 'none';
      host.innerHTML = '';
    }
    /**
     * @private
     * @property {HTMLDomElement|Canvas|Context} _host 
     * This can be a HTMLDomElement like a DIV, or a Canvas instance, 
     * or Context for Wechat mini-program. In browser environment, this._host is 
     * a div which is created by QuarkRenderer automaticly, 
     * in other environments, this._host equals this.host.
     * 
     * 此属性可以是 HTMLDomElement ，比如 DIV 标签；也可以是 Canvas 实例；或者是 Context 实例，因为在某些
     * 运行环境中，不能获得 Canvas 实例的引用，只能获得 Context。在浏览器环境中，this._host 是 QuarkRenderer
     * 自己自动创建的 div 层，在其它环境中，this._host 等于 this.root。
     */


    this._host = null;
    /**
     * @property {Storage} storage
     */

    this.storage = storage;
    /**
     * @property {Array<Number>}
     * @private
     */

    var qlevelList = this._qlevelList = [];
    /**
     * @private
     * @property {Object<String, CanvasLayer>} layers
     */

    var layers = this._layers = {};
    /**
     * @private
     * @property {Object<String, Object>} _layerConfig
     */

    this._layerConfig = {};
    /**
     * @private
     * @property _needsManuallyCompositing
     * qrenderer will do compositing when host is a canvas and have multiple zlevels.
     */

    this._needsManuallyCompositing = false;
    /**
     * @private
     * @property {CanvasLayer} _hoverlayer
     */

    this._hoverlayer = null;
    /**
     * @private
     * @property {Array} _hoverElements
     */

    this._hoverElements = [];
    this._tmpRect = new BoundingRect(0, 0, 0, 0);
    this._viewRect = new BoundingRect(0, 0, 0, 0);
    /**
     * @property {Boolean} _singleCanvas
     * In node environment using node-canvas
     * @private
     */

    this._singleCanvas = !this.host.nodeName || this.host.nodeName.toUpperCase() === 'CANVAS'; //The code below is used to compatible with various runtime environments like browser, node-canvas, and Wechat mini-program.

    if (this._singleCanvas) {
      var width = this.host.width;
      var height = this.host.height;

      if (this.options.width != null) {
        width = this.options.width;
      }

      if (this.options.height != null) {
        height = this.options.height;
      }

      this.dpr = this.options.devicePixelRatio || 1; // Use canvas width and height directly

      this.host.width = width * this.dpr;
      this.host.height = height * this.dpr;
      this._width = width;
      this._height = height; // Create layer if only one given canvas
      // Device can be specified to create a high dpi image.

      var mainLayer = new CanvasLayer(this.host, this._width, this._height, this.dpr);
      mainLayer.__builtin__ = true;
      mainLayer.initContext(); // FIXME Use canvas width and height
      // mainLayer.resize(width, height);

      layers[CANVAS_QLEVEL] = mainLayer;
      mainLayer.qlevel = CANVAS_QLEVEL; // Not use common qlevel.

      qlevelList.push(CANVAS_QLEVEL);
      this._host = this.host; // Here, this._host equals this.host.
    } else {
      this._width = this._getSize(0);
      this._height = this._getSize(1);
      var canvasContainer = this.createDomRoot( // Craete a new div inside the host element.
      this._width, this._height);
      this._host = canvasContainer; // In this case, this._host is different from this.host.

      this.host.appendChild(canvasContainer);
    }
  }
  /**
   * @method getHost
   * Do NOT use this method in Wechat mini-program, because we can not get HTMLElement 
   * nor canvas instance.
   * @return {HTMLDivElement}
   */


  _createClass(CanvasPainter, [{
    key: "getHost",
    value: function getHost() {
      return this._host;
    }
    /**
     * @method getViewportRootOffset
     * Do NOT use this method in Wechat mini-program, because we can not get HTMLElement 
     * nor canvas instance.
     * @return {Object}
     */

  }, {
    key: "getViewportRootOffset",
    value: function getViewportRootOffset() {
      var host = this.getHost();

      if (host) {
        return {
          offsetLeft: host.offsetLeft || 0,
          offsetTop: host.offsetTop || 0
        };
      }
    }
    /**
     * @method
     * 刷新
     * @param {Boolean} [paintAll=false] 是否强制绘制所有displayable
     */

  }, {
    key: "refresh",
    value: function refresh(paintAll) {
      var list = this.storage.getDisplayList(true);
      var qlevelList = this._qlevelList;
      this._redrawId = mathRandom();

      this._paintList(list, paintAll, this._redrawId); // Paint custum layers


      for (var i = 0; i < qlevelList.length; i++) {
        var z = qlevelList[i];
        var layer = this._layers[z];

        if (!layer.__builtin__ && layer.refresh) {
          var clearColor = i === 0 ? this._backgroundColor : null;
          layer.refresh(clearColor);
        }
      }

      this.refreshHover();
      return this;
    }
    /**
     * @method addHover
     * 
     * @param {*} el 
     * @param {*} hoverStyle 
     */

  }, {
    key: "addHover",
    value: function addHover(el, hoverStyle) {
      if (el.__hoverMir) {
        return;
      }

      var elMirror = new el.constructor({
        style: el.style,
        shape: el.shape,
        z: el.z,
        z2: el.z2,
        silent: el.silent
      });
      elMirror.__from = el;
      el.__hoverMir = elMirror;
      hoverStyle && elMirror.setStyle(hoverStyle);

      this._hoverElements.push(elMirror);

      return elMirror;
    }
    /**
     * @method removeHover
     * @param {*} el 
     */

  }, {
    key: "removeHover",
    value: function removeHover(el) {
      var elMirror = el.__hoverMir;
      var hoverElements = this._hoverElements;
      var idx = dataUtil.indexOf(hoverElements, elMirror);

      if (idx >= 0) {
        hoverElements.splice(idx, 1);
      }

      el.__hoverMir = null;
    }
    /**
     * @method clearHover
     * @param {*} el 
     */

  }, {
    key: "clearHover",
    value: function clearHover(el) {
      var hoverElements = this._hoverElements;

      for (var i = 0; i < hoverElements.length; i++) {
        var from = hoverElements[i].__from;

        if (from) {
          from.__hoverMir = null;
        }
      }

      hoverElements.length = 0;
    }
    /**
     * @method refreshHover
     */

  }, {
    key: "refreshHover",
    value: function refreshHover() {
      var hoverElements = this._hoverElements;
      var len = hoverElements.length;
      var hoverLayer = this._hoverlayer;
      hoverLayer && hoverLayer.clear();

      if (!len) {
        return;
      }

      timsort(hoverElements, this.storage.displayableSortFunc); // Use a extream large qlevel
      // FIXME?

      if (!hoverLayer) {
        hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_QLEVEL);
      }

      var scope = {};
      hoverLayer.ctx.save();

      for (var i = 0; i < len;) {
        var el = hoverElements[i];
        var originalEl = el.__from; // Original el is removed
        // PENDING

        if (!(originalEl && originalEl.__qr)) {
          hoverElements.splice(i, 1);
          originalEl.__hoverMir = null;
          len--;
          continue;
        }

        i++; // Use transform
        // FIXME style and shape ?

        if (!originalEl.invisible) {
          el.transform = originalEl.transform;
          el.invTransform = originalEl.invTransform;
          el.__clipPaths = originalEl.__clipPaths; // el.

          this._doPaintEl(el, hoverLayer, true, scope);
        }
      }

      hoverLayer.ctx.restore();
    }
    /**
     * @method getHoverLayer
     */

  }, {
    key: "getHoverLayer",
    value: function getHoverLayer() {
      return this.getLayer(HOVER_LAYER_QLEVEL);
    }
    /**
     * @method _paintList
     * @param {*} list 
     * @param {*} paintAll 
     * @param {*} redrawId 
     */

  }, {
    key: "_paintList",
    value: function _paintList(list, paintAll, redrawId) {
      //如果 redrawId 不一致，说明下一个动画帧已经到来，这里就会直接跳过去，相当于跳过了一帧
      if (this._redrawId !== redrawId) {
        return;
      }

      paintAll = paintAll || false;

      this._updateLayerStatus(list);

      var finished = this._doPaintList(list, paintAll);

      if (this._needsManuallyCompositing) {
        this._compositeManually();
      } //如果在一帧的时间内没有绘制完，在下一帧继续绘制。
      //当前本机的测试值，1000 个元素同时进行动画，可以在 16ms 的时间中绘制完成。


      if (!finished) {
        var self = this;
        requestAnimationFrame(function () {
          self._paintList(list, paintAll, redrawId);
        });
      }
    }
    /**
     * @method _compositeManually
     */

  }, {
    key: "_compositeManually",
    value: function _compositeManually() {
      var ctx = this.getLayer(CANVAS_QLEVEL).ctx;
      var width = this._host.width;
      var height = this._host.height;
      ctx.clearRect(0, 0, width, height); // PENDING, If only builtin layer?

      this.eachBuiltinLayer(function (layer) {
        if (layer.virtual) {
          ctx.drawImage(layer.canvasInstance, 0, 0, width, height);
        }
      });
    }
    /**
     * @method _doPaintList
     */

  }, {
    key: "_doPaintList",
    value: function _doPaintList(list, paintAll) {
      var layerList = [];

      for (var zi = 0; zi < this._qlevelList.length; zi++) {
        var qlevel = this._qlevelList[zi];
        var layer = this._layers[qlevel];

        if (layer.__builtin__ && layer !== this._hoverlayer && (layer.__dirty || paintAll)) {
          layerList.push(layer);
        }
      }

      var finished = true;

      for (var k = 0; k < layerList.length; k++) {
        var _layer = layerList[k];
        var ctx = _layer.ctx;
        var scope = {};
        ctx.save();
        var start = paintAll ? _layer.__startIndex : _layer.__drawIndex;
        var useTimer = !paintAll && _layer.incremental && Date.now;
        var startTime = useTimer && Date.now();
        var clearColor = _layer.qlevel === this._qlevelList[0] ? this._backgroundColor : null; // All elements in this layer are cleared.

        if (_layer.__startIndex === _layer.__endIndex) {
          _layer.clear(false, clearColor);
        } else if (start === _layer.__startIndex) {
          var firstEl = list[start];

          if (!firstEl.incremental || !firstEl.notClear || paintAll) {
            _layer.clear(false, clearColor);
          }
        }

        if (start === -1) {
          console.error('For some unknown reason. drawIndex is -1');
          start = _layer.__startIndex;
        }

        var i = start;

        for (; i < _layer.__endIndex; i++) {
          var el = list[i];

          this._doPaintEl(el, _layer, paintAll, scope);

          el.__dirty = el.__dirtyText = false;

          if (useTimer) {
            // Date.now can be executed in 13,025,305 ops/second.
            var dTime = Date.now() - startTime; // Give 15 millisecond to draw.
            // The rest elements will be drawn in the next frame.
            // 这里的时间计算非常重要，如果 15ms 的时间内没有能绘制完所有元素，则跳出，等待下一帧继续绘制
            // 但是 15ms 的时间依然是有限的，如果元素的数量非常巨大，例如有 1000 万个，还是会卡顿。

            if (dTime > 15) {
              break;
            }
          }
        }

        _layer.__drawIndex = i;

        if (_layer.__drawIndex < _layer.__endIndex) {
          finished = false;
        }

        if (scope.prevElClipPaths) {
          // Needs restore the state. If last drawn element is in the clipping area.
          ctx.restore();
        }

        ctx.restore();
      }

      if (env.wxa) {
        // Flush for weixin application
        dataUtil.each(this._layers, function (layer) {
          if (layer && layer.ctx && layer.ctx.draw) {
            layer.ctx.draw();
          }
        });
      }

      return finished;
    }
    /**
     * @method _doPaintEl
     * 绘制一个元素
     * @param {*} el 
     * @param {*} currentLayer 
     * @param {*} forcePaint 
     * @param {*} scope 
     */

  }, {
    key: "_doPaintEl",
    value: function _doPaintEl(el, currentLayer, forcePaint, scope) {
      var ctx = currentLayer.ctx;
      var m = el.transform;

      if ((currentLayer.__dirty || forcePaint) && // Ignore invisible element
      !el.invisible // Ignore transparent element
      && el.style.opacity !== 0 // Ignore scale 0 element, in some environment like node-canvas
      // Draw a scale 0 element can cause all following draw wrong
      // And setTransform with scale 0 will cause set back transform failed.
      && !(m && !m[0] && !m[3]) // Ignore culled element
      && !(el.culling && this.isDisplayableCulled(el, this._width, this._height))) {
        var clipPaths = el.__clipPaths;
        var prevElClipPaths = scope.prevElClipPaths; // Optimize when clipping on group with several elements

        if (!prevElClipPaths || this.isClipPathChanged(clipPaths, prevElClipPaths)) {
          // If has previous clipping state, restore from it
          if (prevElClipPaths) {
            ctx.restore();
            scope.prevElClipPaths = null; // Reset prevEl since context has been restored

            scope.prevEl = null;
          } // New clipping state


          if (clipPaths) {
            ctx.save();
            this.doClip(clipPaths, ctx);
            scope.prevElClipPaths = clipPaths;
          }
        }

        el.beforeBrush && el.beforeBrush(ctx);
        el.brush(ctx, scope.prevEl || null);
        scope.prevEl = el;
        el.afterBrush && el.afterBrush(ctx);
      }
    }
    /**
     * @method getLayer
     * 获取 qlevel 所在层，如果不存在则会创建一个新的层
     * @param {Number} qlevel
     * @param {Boolean} virtual Virtual layer will not be inserted into dom.
     * @return {CanvasLayer}
     */

  }, {
    key: "getLayer",
    value: function getLayer(qlevel, virtual) {
      if (this._singleCanvas && !this._needsManuallyCompositing) {
        qlevel = CANVAS_QLEVEL;
      }

      var layer = this._layers[qlevel];

      if (!layer) {
        // Create a new layer
        layer = new CanvasLayer('qr_' + qlevel, this._width, this._height, this.dpr);
        layer.qlevel = qlevel;
        layer.__builtin__ = true;

        if (this._layerConfig[qlevel]) {
          dataUtil.merge(layer, this._layerConfig[qlevel], true);
        }

        if (virtual) {
          layer.virtual = virtual;
        }

        this.insertLayer(qlevel, layer); // Context is created after dom inserted to document
        // Or excanvas will get 0px clientWidth and clientHeight

        layer.initContext();
      }

      return layer;
    }
    /**
     * @method insertLayer
     * Insert layer dynamicly during runtime.
     * Do NOT use this method in Wechat mini-program, because we can neither get HTMLElement 
     * nor canvas instance.
     * @param {*} qlevel 
     * @param {*} layer 
     */

  }, {
    key: "insertLayer",
    value: function insertLayer(qlevel, layer) {
      var layersMap = this._layers;
      var qlevelList = this._qlevelList;
      var len = qlevelList.length;
      var prevLayer = null;
      var i = -1;

      if (layersMap[qlevel]) {
        console.log('ZLevel ' + qlevel + ' has been used already');
        return;
      } // Check if is a valid layer


      if (!this.isLayerValid(layer)) {
        console.log('CanvasLayer of qlevel ' + qlevel + ' is not valid');
        return;
      }

      if (len > 0 && qlevel > qlevelList[0]) {
        for (i = 0; i < len - 1; i++) {
          if (qlevelList[i] < qlevel && qlevelList[i + 1] > qlevel) {
            break;
          }
        }

        prevLayer = layersMap[qlevelList[i]];
      }

      qlevelList.splice(i + 1, 0, qlevel);
      layersMap[qlevel] = layer; // Vitual layer will not directly show on the screen.
      // (It can be a WebGL layer and assigned to a QImage element)
      // But it still under management of qrenderer.

      if (!layer.virtual) {
        if (prevLayer) {
          var prevDom = prevLayer.canvasInstance;

          if (prevDom.nextSibling) {
            this._host.insertBefore(layer.canvasInstance, prevDom.nextSibling);
          } else {
            this._host.appendChild(layer.canvasInstance);
          }
        } else {
          if (this._host.firstChild) {
            this._host.insertBefore(layer.canvasInstance, this._host.firstChild);
          } else {
            this._host.appendChild(layer.canvasInstance);
          }
        }
      }
    }
    /**
     * @method delLayer
     * 删除指定层
     * @param {Number} qlevel 层所在的zlevel
     */

  }, {
    key: "delLayer",
    value: function delLayer(qlevel) {
      var layers = this._layers;
      var qlevelList = this._qlevelList;
      var layer = layers[qlevel];

      if (!layer) {
        return;
      }

      if (layer.canvasInstance) {
        layer.canvasInstance.parentNode.removeChild(layer.canvasInstance);
      }

      delete layers[qlevel];
      qlevelList.splice(dataUtil.indexOf(qlevelList, qlevel), 1);
    }
    /**
     * @private
     * @method eachLayer
     * Iterate each layer
     * @param {Function} cb 
     * @param {Object} context 
     */

  }, {
    key: "eachLayer",
    value: function eachLayer(cb, context) {
      var qlevelList = this._qlevelList;
      var z;
      var i;

      for (i = 0; i < qlevelList.length; i++) {
        z = qlevelList[i];
        cb.call(context, this._layers[z], z);
      }
    }
    /**
     * @private
     * @method eachBuiltinLayer
     * Iterate each buildin layer
     * @param {Function} cb 
     * @param {Object} context 
     */

  }, {
    key: "eachBuiltinLayer",
    value: function eachBuiltinLayer(cb, context) {
      var qlevelList = this._qlevelList;
      var layer;
      var z;
      var i;

      for (i = 0; i < qlevelList.length; i++) {
        z = qlevelList[i];
        layer = this._layers[z];

        if (layer.__builtin__) {
          cb.call(context, layer, z);
        }
      }
    }
    /**
     * @private
     * @method eachOtherLayer
     * Iterate each other layer except buildin layer
     * @param {Function} cb 
     * @param {Object} context 
     */

  }, {
    key: "eachOtherLayer",
    value: function eachOtherLayer(cb, context) {
      var qlevelList = this._qlevelList;
      var layer;
      var z;
      var i;

      for (i = 0; i < qlevelList.length; i++) {
        z = qlevelList[i];
        layer = this._layers[z];

        if (!layer.__builtin__) {
          cb.call(context, layer, z);
        }
      }
    }
    /**
     * @method getLayers
     * 获取所有已创建的层
     * @param {Array<CanvasLayer>} [prevLayer]
     */

  }, {
    key: "getLayers",
    value: function getLayers() {
      return this._layers;
    }
    /**
     * @private
     * @method _updateLayerStatus
     * @param {*} list 
     */

  }, {
    key: "_updateLayerStatus",
    value: function _updateLayerStatus(list) {
      this.eachBuiltinLayer(function (layer, z) {
        layer.__dirty = layer.__used = false;
      });

      function updatePrevLayer(idx) {
        if (prevLayer) {
          if (prevLayer.__endIndex !== idx) {
            prevLayer.__dirty = true;
          }

          prevLayer.__endIndex = idx;
        }
      }

      if (this._singleCanvas) {
        for (var _i = 1; _i < list.length; _i++) {
          var el = list[_i];

          if (el.qlevel !== list[_i - 1].qlevel || el.incremental) {
            this._needsManuallyCompositing = true;
            break;
          }
        }
      }

      var prevLayer = null;
      var incrementalLayerCount = 0;
      var i = 0;

      for (; i < list.length; i++) {
        var _el = list[i];
        var qlevel = _el.qlevel;
        var layer = void 0; // PENDING If change one incremental element style ?
        // TODO Where there are non-incremental elements between incremental elements.

        if (_el.incremental) {
          layer = this.getLayer(qlevel + INCREMENTAL_INC, this._needsManuallyCompositing);
          layer.incremental = true;
          incrementalLayerCount = 1;
        } else {
          layer = this.getLayer(qlevel + (incrementalLayerCount > 0 ? EL_AFTER_INCREMENTAL_INC : 0), this._needsManuallyCompositing);
        }

        if (!layer.__builtin__) {
          console.log('ZLevel ' + qlevel + ' has been used by unkown layer ' + layer.id);
        }

        if (layer !== prevLayer) {
          layer.__used = true;

          if (layer.__startIndex !== i) {
            layer.__dirty = true;
          }

          layer.__startIndex = i;

          if (!layer.incremental) {
            layer.__drawIndex = i;
          } else {
            // Mark layer draw index needs to update.
            layer.__drawIndex = -1;
          }

          updatePrevLayer(i);
          prevLayer = layer;
        }

        if (_el.__dirty) {
          layer.__dirty = true;

          if (layer.incremental && layer.__drawIndex < 0) {
            // Start draw from the first dirty element.
            layer.__drawIndex = i;
          }
        }
      }

      updatePrevLayer(i);
      this.eachBuiltinLayer(function (layer, z) {
        // Used in last frame but not in this frame. Needs clear
        if (!layer.__used && layer.getElementCount() > 0) {
          layer.__dirty = true;
          layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0;
        } // For incremental layer. In case start index changed and no elements are dirty.


        if (layer.__dirty && layer.__drawIndex < 0) {
          layer.__drawIndex = layer.__startIndex;
        }
      });
    }
    /**
     * @method clear
     * 清除hover层外所有内容
     */

  }, {
    key: "clear",
    value: function clear() {
      this.eachBuiltinLayer(this._clearLayer);
      return this;
    }
    /**
     * @private
     * @method _clearLayer
     */

  }, {
    key: "_clearLayer",
    value: function _clearLayer(layer) {
      layer.clear();
    }
    /**
     * @method setBackgroundColor
     */

  }, {
    key: "setBackgroundColor",
    value: function setBackgroundColor(backgroundColor) {
      this._backgroundColor = backgroundColor;
    }
    /**
     * @method configLayer
     * 修改指定zlevel的绘制参数
     *
     * @param {String} qlevel
     * @param {Object} [config] 配置对象
     * @param {String} [config.clearColor=0] 每次清空画布的颜色
     * @param {String} [config.motionBlur=false] 是否开启动态模糊
     * @param {Number} [config.lastFrameAlpha=0.7] 在开启动态模糊的时候使用，与上一帧混合的alpha值，值越大尾迹越明显
     */

  }, {
    key: "configLayer",
    value: function configLayer(qlevel, config) {
      if (config) {
        var layerConfig = this._layerConfig;

        if (!layerConfig[qlevel]) {
          layerConfig[qlevel] = config;
        } else {
          dataUtil.merge(layerConfig[qlevel], config, true);
        }

        for (var i = 0; i < this._qlevelList.length; i++) {
          var _zlevel = this._qlevelList[i];

          if (_zlevel === qlevel || _zlevel === qlevel + EL_AFTER_INCREMENTAL_INC) {
            var layer = this._layers[_zlevel];
            dataUtil.merge(layer, layerConfig[qlevel], true);
          }
        }
      }
    }
    /**
     * @method resize
     * 区域大小变化后重绘
     * @param {Number} width
     * @param {Number} height
     */

  }, {
    key: "resize",
    value: function resize(width, height) {
      if (!this._host.style) {
        // Maybe in node or worker or Wechat
        if (width == null || height == null) {
          return;
        }

        this._width = width;
        this._height = height;
        this.getLayer(CANVAS_QLEVEL).resize(width, height);
      } else {
        var domRoot = this._host;
        domRoot.style.display = 'none'; // Save input w/h

        var options = this.options;
        width != null && (options.width = width);
        height != null && (options.height = height);
        width = this._getSize(0);
        height = this._getSize(1);
        domRoot.style.display = ''; // 优化没有实际改变的resize

        if (this._width !== width || height !== this._height) {
          domRoot.style.width = width + 'px';
          domRoot.style.height = height + 'px';

          for (var id in this._layers) {
            if (this._layers.hasOwnProperty(id)) {
              this._layers[id].resize(width, height);
            }
          }

          dataUtil.each(this._progressiveLayers, function (layer) {
            layer.resize(width, height);
          });
          this.refresh(true);
        }

        this._width = width;
        this._height = height;
      }

      return this;
    }
    /**
     * @method clearLayer
     * 清除单独的一个层
     * @param {Number} qlevel
     */

  }, {
    key: "clearLayer",
    value: function clearLayer(qlevel) {
      var layer = this._layers[qlevel];

      if (layer) {
        layer.clear();
      }
    }
    /**
     * @method dispose
     * 释放
     */

  }, {
    key: "dispose",
    value: function dispose() {
      this.host.innerHTML = '';
      this.host = null;
      this.storage = null;
      this._host = null;
      this._layers = null;
    }
    /**
     * @method getRenderedCanvas
     * Get canvas which has all thing rendered.
     * Do NOT use this method in Wechat mini-program, because we can not get HTMLElement 
     * nor canvas instance.
     * @param {Object} [options]
     * @param {String} [options.backgroundColor]
     * @param {Number} [options.pixelRatio]
     */

  }, {
    key: "getRenderedCanvas",
    value: function getRenderedCanvas() {
      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

      if (this._singleCanvas && !this._compositeManually) {
        return this._layers[CANVAS_QLEVEL].canvasInstance;
      }

      var imageLayer = new CanvasLayer('image', this._width, this._height, options.pixelRatio || this.dpr);
      imageLayer.initContext();
      imageLayer.clear(false, options.backgroundColor || this._backgroundColor);

      if (options.pixelRatio <= this.dpr) {
        this.refresh();
        var width = imageLayer.canvasInstance.width;
        var height = imageLayer.canvasInstance.height;
        var ctx = imageLayer.ctx;
        this.eachLayer(function (layer) {
          if (layer.__builtin__) {
            ctx.drawImage(layer.canvasInstance, 0, 0, width, height);
          } else if (layer.renderToCanvas) {
            imageLayer.ctx.save();
            layer.renderToCanvas(imageLayer.ctx);
            imageLayer.ctx.restore();
          }
        });
      } else {
        // PENDING, echarts-gl and incremental rendering.
        var scope = {};
        var displayList = this.storage.getDisplayList(true);

        for (var i = 0; i < displayList.length; i++) {
          var el = displayList[i];

          this._doPaintEl(el, imageLayer, true, scope);
        }
      }

      return imageLayer.canvasInstance;
    }
    /**
     * @method getWidth
     * 获取绘图区域宽度
     * @return {Number}
     */

  }, {
    key: "getWidth",
    value: function getWidth() {
      return this._width;
    }
    /**
     * @method getHeight
     * 获取绘图区域高度
     * @return {Number}
     */

  }, {
    key: "getHeight",
    value: function getHeight() {
      return this._height;
    }
    /**
     * @method _getSize
     * Do NOT use this method in Wechat mini-program, because we can not get HTMLElement 
     * nor canvas instance.
     * @param {*} whIdx 
     */

  }, {
    key: "_getSize",
    value: function _getSize(whIdx) {
      var options = this.options;
      var wh = ['width', 'height'][whIdx];
      var cwh = ['clientWidth', 'clientHeight'][whIdx];
      var plt = ['paddingLeft', 'paddingTop'][whIdx];
      var prb = ['paddingRight', 'paddingBottom'][whIdx];

      if (options[wh] != null && options[wh] !== 'auto') {
        return parseFloat(options[wh]);
      } // IE8 does not support getComputedStyle, but it use VML.


      var stl = document.defaultView.getComputedStyle(this.host);
      return (this.host[cwh] || dataUtil.parseInt10(stl[wh]) || dataUtil.parseInt10(this.host.style[wh])) - (dataUtil.parseInt10(stl[plt]) || 0) - (dataUtil.parseInt10(stl[prb]) || 0) | 0;
    }
    /**
     * @method pathToImage
     * @param {*} path 
     * @param {*} dpr 
     */

  }, {
    key: "pathToImage",
    value: function pathToImage(path, dpr) {
      dpr = dpr || this.dpr;
      var canvas = canvasUtil.createCanvas(); //创建隐藏的 canvas，在内存中。

      var ctx = canvasUtil.getContext(canvas);
      var rect = path.getBoundingRect();
      var style = path.style;
      var shadowBlurSize = style.shadowBlur * dpr;
      var shadowOffsetX = style.shadowOffsetX * dpr;
      var shadowOffsetY = style.shadowOffsetY * dpr;
      var lineWidth = style.hasStroke() ? style.lineWidth : 0;
      var leftMargin = mathMax(lineWidth / 2, -shadowOffsetX + shadowBlurSize);
      var rightMargin = mathMax(lineWidth / 2, shadowOffsetX + shadowBlurSize);
      var topMargin = mathMax(lineWidth / 2, -shadowOffsetY + shadowBlurSize);
      var bottomMargin = mathMax(lineWidth / 2, shadowOffsetY + shadowBlurSize);
      var width = rect.width + leftMargin + rightMargin;
      var height = rect.height + topMargin + bottomMargin;
      canvas.width = width * dpr;
      canvas.height = height * dpr;
      ctx.scale(dpr, dpr);
      ctx.clearRect(0, 0, width, height);
      ctx.dpr = dpr;
      var pathTransform = {
        position: path.position,
        rotation: path.rotation,
        scale: path.scale
      };
      path.position = [leftMargin - rect.x, topMargin - rect.y];
      path.rotation = 0;
      path.scale = [1, 1];
      path.updateTransform();

      if (path) {
        path.brush(ctx);
      }

      var ImageShape = Image;
      var imgShape = new ImageShape({
        style: {
          x: 0,
          y: 0,
          image: canvas
        }
      });

      if (pathTransform.position != null) {
        imgShape.position = path.position = pathTransform.position;
      }

      if (pathTransform.rotation != null) {
        imgShape.rotation = path.rotation = pathTransform.rotation;
      }

      if (pathTransform.scale != null) {
        imgShape.scale = path.scale = pathTransform.scale;
      }

      return imgShape;
    }
    /**
     * @private
     * @method isLayerValid
     * @param {*} layer 
     */

  }, {
    key: "isLayerValid",
    value: function isLayerValid(layer) {
      if (!layer) {
        return false;
      }

      if (layer.__builtin__) {
        return true;
      }

      if (typeof layer.resize !== 'function' || typeof layer.refresh !== 'function') {
        return false;
      }

      return true;
    }
    /**
     * @private
     * @method isDisplayableCulled
     * @param {*} el 
     * @param {*} width 
     * @param {*} height 
     */

  }, {
    key: "isDisplayableCulled",
    value: function isDisplayableCulled(el, width, height) {
      this._tmpRect.copy(el.getBoundingRect());

      if (el.transform) {
        this._tmpRect.applyTransform(el.transform);
      }

      this._viewRect.width = width;
      this._viewRect.height = height;
      return !this._tmpRect.intersect(this._viewRect);
    }
    /**
     * @private
     * @method isClipPathChanged
     * @param {*} clipPaths 
     * @param {*} prevClipPaths 
     */

  }, {
    key: "isClipPathChanged",
    value: function isClipPathChanged(clipPaths, prevClipPaths) {
      // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
      if (clipPaths === prevClipPaths) {
        return false;
      }

      if (!clipPaths || !prevClipPaths || clipPaths.length !== prevClipPaths.length) {
        return true;
      }

      for (var i = 0; i < clipPaths.length; i++) {
        if (clipPaths[i] !== prevClipPaths[i]) {
          return true;
        }
      }

      return false;
    }
    /**
     * @private
     * @method doClip
     * @param {*} clipPaths 
     * @param {*} ctx 
     */

  }, {
    key: "doClip",
    value: function doClip(clipPaths, ctx) {
      clipPaths.forEach(function (clipPath, index) {
        clipPath.setTransform(ctx);
        ctx.beginPath();
        clipPath.buildPath(ctx, clipPath.shape);
        ctx.clip();
        clipPath.restoreTransform(ctx);
      });
    }
    /**
     * @private
     * @method createDomRoot
     * 在浏览器环境中，不会直接在传入的 dom 节点内部创建 canvas 标签，而是再内嵌一层div。
     * 目的是加上一些必须的 CSS 样式，方便实现特定的功能。
     * @param {Number} width 
     * @param {Number} height 
     */

  }, {
    key: "createDomRoot",
    value: function createDomRoot(width, height) {
      var domRoot = document.createElement('div'); // IOS13 safari probably has a compositing bug (z order of the canvas and the consequent
      // dom does not act as expected) when some of the parent dom has
      // `-webkit-overflow-scrolling: touch;` and the webpage is longer than one screen and
      // the canvas is not at the top part of the page.
      // Check `https://bugs.webkit.org/show_bug.cgi?id=203681` for more details. We remove
      // this `overflow:hidden` to avoid the bug.
      // 'overflow:hidden',

      domRoot.style.cssText = ['position:relative', 'width:' + width + 'px', 'height:' + height + 'px', 'padding:0', 'margin:0', 'border-width:0'].join(';') + ';';
      return domRoot;
    }
  }]);

  return CanvasPainter;
}();

module.exports = CanvasPainter;