/**
 * Painter绘图模块
 * @module zrender/Painter
 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
 *         errorrik (errorrik@gmail.com)
 *         pissang (https://www.github.com/pissang)
 */
module.exports = function (require) {
  'use strict';

  var config = require('./config');

  var util = require('./tool/util'); // var vec2 = require('./tool/vector');


  var log = require('./tool/log'); // var matrix = require('./tool/matrix');


  var BaseLoadingEffect = require('./loadingEffect/Base');

  var Layer = require('./Layer'); // 返回false的方法，用于避免页面被选中


  function returnFalse() {
    return false;
  } // 什么都不干的空方法


  function doNothing() {}

  function isLayerValid(layer) {
    if (!layer) {
      return false;
    }

    if (layer.isBuildin) {
      return true;
    }

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

    return true;
  }
  /**
   * @alias module:zrender/Painter
   * @constructor
   * @param {HTMLElement} root 绘图容器
   * @param {module:zrender/Storage} storage
   */


  var Painter = function (root, storage) {
    /**
     * 绘图容器
     * @type {HTMLElement}
     */
    this.root = root;
    root.style['-webkit-tap-highlight-color'] = 'transparent';
    root.style['-webkit-user-select'] = 'none';
    root.style['user-select'] = 'none';
    root.style['-webkit-touch-callout'] = 'none';
    /**
     * @type {module:zrender/Storage}
     */

    this.storage = storage;
    root.innerHTML = '';
    this._width = this._getWidth(); // 宽，缓存记录

    this._height = this._getHeight(); // 高，缓存记录

    var domRoot = document.createElement('div');
    this._domRoot = domRoot; // domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬

    domRoot.style.position = 'relative';
    domRoot.style.overflow = 'hidden';
    domRoot.style.width = this._width + 'px';
    domRoot.style.height = this._height + 'px';
    root.appendChild(domRoot);
    this._layers = {};
    this._zlevelList = [];
    this._layerConfig = {};
    this._loadingEffect = new BaseLoadingEffect({});
    this.shapeToImage = this._createShapeToImageProcessor(); // 创建各层canvas
    // 背景

    this._bgDom = document.createElement('div');
    this._bgDom.style.cssText = ['position:absolute;left:0px;top:0px;width:', this._width, 'px;height:', this._height + 'px;', '-webkit-user-select:none;user-select;none;', '-webkit-touch-callout:none;'].join('');

    this._bgDom.setAttribute('data-zr-dom-id', 'bg');

    this._bgDom.className = config.elementClassName;
    domRoot.appendChild(this._bgDom);
    this._bgDom.onselectstart = returnFalse; // 高亮

    var hoverLayer = new Layer('_zrender_hover_', this);
    this._layers['hover'] = hoverLayer;
    domRoot.appendChild(hoverLayer.dom);
    hoverLayer.initContext();
    hoverLayer.dom.onselectstart = returnFalse;
    hoverLayer.dom.style['-webkit-user-select'] = 'none';
    hoverLayer.dom.style['user-select'] = 'none';
    hoverLayer.dom.style['-webkit-touch-callout'] = 'none'; // Will be injected by zrender instance

    this.refreshNextFrame = null;
  };
  /**
   * 首次绘图，创建各种dom和context
   * 
   * @param {Function} callback 绘画结束后的回调函数
   */


  Painter.prototype.render = function (callback) {
    if (this.isLoading()) {
      this.hideLoading();
    } // TODO


    this.refresh(callback, true);
    return this;
  };
  /**
   * 刷新
   * @param {Function} callback 刷新结束后的回调函数
   * @param {boolean} paintAll 强制绘制所有shape
   */


  Painter.prototype.refresh = function (callback, paintAll) {
    var list = this.storage.getShapeList(true);

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


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

      if (!layer.isBuildin && layer.refresh) {
        layer.refresh();
      }
    }

    if (typeof callback == 'function') {
      callback();
    }

    return this;
  };

  Painter.prototype._preProcessLayer = function (layer) {
    layer.unusedCount++;
    layer.updateTransform();
  };

  Painter.prototype._postProcessLayer = function (layer) {
    layer.dirty = false; // 删除过期的层
    // PENDING
    // if (layer.unusedCount >= 500) {
    //     this.delLayer(z);
    // }

    if (layer.unusedCount == 1) {
      layer.clear();
    }
  };

  Painter.prototype._paintList = function (list, paintAll) {
    if (typeof paintAll == 'undefined') {
      paintAll = false;
    }

    this._updateLayerStatus(list);

    var currentLayer;
    var currentZLevel;
    var ctx;
    this.eachBuildinLayer(this._preProcessLayer); // var invTransform = [];

    for (var i = 0, l = list.length; i < l; i++) {
      var shape = list[i]; // Change draw layer

      if (currentZLevel !== shape.zlevel) {
        if (currentLayer) {
          if (currentLayer.needTransform) {
            ctx.restore();
          }

          ctx.flush && ctx.flush();
        }

        currentZLevel = shape.zlevel;
        currentLayer = this.getLayer(currentZLevel);

        if (!currentLayer.isBuildin) {
          log('ZLevel ' + currentZLevel + ' has been used by unkown layer ' + currentLayer.id);
        }

        ctx = currentLayer.ctx; // Reset the count

        currentLayer.unusedCount = 0;

        if (currentLayer.dirty || paintAll) {
          currentLayer.clear();
        }

        if (currentLayer.needTransform) {
          ctx.save();
          currentLayer.setTransform(ctx);
        }
      }

      if ((currentLayer.dirty || paintAll) && !shape.invisible) {
        if (!shape.onbrush || shape.onbrush && !shape.onbrush(ctx, false)) {
          if (config.catchBrushException) {
            try {
              shape.brush(ctx, false, this.refreshNextFrame);
            } catch (error) {
              log(error, 'brush error of ' + shape.type, shape);
            }
          } else {
            shape.brush(ctx, false, this.refreshNextFrame);
          }
        }
      }

      shape.__dirty = false;
    }

    if (currentLayer) {
      if (currentLayer.needTransform) {
        ctx.restore();
      }

      ctx.flush && ctx.flush();
    }

    this.eachBuildinLayer(this._postProcessLayer);
  };
  /**
   * 获取 zlevel 所在层，如果不存在则会创建一个新的层
   * @param {number} zlevel
   * @return {module:zrender/Layer}
   */


  Painter.prototype.getLayer = function (zlevel) {
    var layer = this._layers[zlevel];

    if (!layer) {
      // Create a new layer
      layer = new Layer(zlevel, this);
      layer.isBuildin = true;

      if (this._layerConfig[zlevel]) {
        util.merge(layer, this._layerConfig[zlevel], true);
      }

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

      layer.initContext();
    }

    return layer;
  };

  Painter.prototype.insertLayer = function (zlevel, layer) {
    if (this._layers[zlevel]) {
      log('ZLevel ' + zlevel + ' has been used already');
      return;
    } // Check if is a valid layer


    if (!isLayerValid(layer)) {
      log('Layer of zlevel ' + zlevel + ' is not valid');
      return;
    }

    var len = this._zlevelList.length;
    var prevLayer = null;
    var i = -1;

    if (len > 0 && zlevel > this._zlevelList[0]) {
      for (i = 0; i < len - 1; i++) {
        if (this._zlevelList[i] < zlevel && this._zlevelList[i + 1] > zlevel) {
          break;
        }
      }

      prevLayer = this._layers[this._zlevelList[i]];
    }

    this._zlevelList.splice(i + 1, 0, zlevel);

    var prevDom = prevLayer ? prevLayer.dom : this._bgDom;

    if (prevDom.nextSibling) {
      prevDom.parentNode.insertBefore(layer.dom, prevDom.nextSibling);
    } else {
      prevDom.parentNode.appendChild(layer.dom);
    }

    this._layers[zlevel] = layer;
  }; // Iterate each layer


  Painter.prototype.eachLayer = function (cb, context) {
    for (var i = 0; i < this._zlevelList.length; i++) {
      var z = this._zlevelList[i];
      cb.call(context, this._layers[z], z);
    }
  }; // Iterate each buildin layer


  Painter.prototype.eachBuildinLayer = function (cb, context) {
    for (var i = 0; i < this._zlevelList.length; i++) {
      var z = this._zlevelList[i];
      var layer = this._layers[z];

      if (layer.isBuildin) {
        cb.call(context, layer, z);
      }
    }
  }; // Iterate each other layer except buildin layer


  Painter.prototype.eachOtherLayer = function (cb, context) {
    for (var i = 0; i < this._zlevelList.length; i++) {
      var z = this._zlevelList[i];
      var layer = this._layers[z];

      if (!layer.isBuildin) {
        cb.call(context, layer, z);
      }
    }
  };
  /**
   * 获取所有已创建的层
   * @param {Array.<module:zrender/Layer>} [prevLayer]
   */


  Painter.prototype.getLayers = function () {
    return this._layers;
  };

  Painter.prototype._updateLayerStatus = function (list) {
    var layers = this._layers;
    var elCounts = {};
    this.eachBuildinLayer(function (layer, z) {
      elCounts[z] = layer.elCount;
      layer.elCount = 0;
    });

    for (var i = 0, l = list.length; i < l; i++) {
      var shape = list[i];
      var zlevel = shape.zlevel;
      var layer = layers[zlevel];

      if (layer) {
        layer.elCount++; // 已经被标记为需要刷新

        if (layer.dirty) {
          continue;
        }

        layer.dirty = shape.__dirty;
      }
    } // 层中的元素数量有发生变化


    this.eachBuildinLayer(function (layer, z) {
      if (elCounts[z] !== layer.elCount) {
        layer.dirty = true;
      }
    });
  };
  /**
   * 指定的图形列表
   * @param {Array.<module:zrender/shape/Base>} shapeList 需要更新的图形元素列表
   * @param {Function} [callback] 视图更新后回调函数
   */


  Painter.prototype.refreshShapes = function (shapeList, callback) {
    for (var i = 0, l = shapeList.length; i < l; i++) {
      var shape = shapeList[i];
      shape.modSelf();
    }

    this.refresh(callback);
    return this;
  };
  /**
   * 设置loading特效
   * 
   * @param {Object} loadingEffect loading特效
   * @return {Painter}
   */


  Painter.prototype.setLoadingEffect = function (loadingEffect) {
    this._loadingEffect = loadingEffect;
    return this;
  };
  /**
   * 清除hover层外所有内容
   */


  Painter.prototype.clear = function () {
    this.eachBuildinLayer(this._clearLayer);
    return this;
  };

  Painter.prototype._clearLayer = function (layer) {
    layer.clear();
  };
  /**
   * 修改指定zlevel的绘制参数
   * 
   * @param {string} zlevel
   * @param {Object} config 配置对象
   * @param {string} [config.clearColor=0] 每次清空画布的颜色
   * @param {string} [config.motionBlur=false] 是否开启动态模糊
   * @param {number} [config.lastFrameAlpha=0.7]
   *                 在开启动态模糊的时候使用，与上一帧混合的alpha值，值越大尾迹越明显
   * @param {Array.<number>} [position] 层的平移
   * @param {Array.<number>} [rotation] 层的旋转
   * @param {Array.<number>} [scale] 层的缩放
   * @param {boolean} [zoomable=false] 层是否支持鼠标缩放操作
   * @param {boolean} [panable=false] 层是否支持鼠标平移操作
   */


  Painter.prototype.modLayer = function (zlevel, config) {
    if (config) {
      if (!this._layerConfig[zlevel]) {
        this._layerConfig[zlevel] = config;
      } else {
        util.merge(this._layerConfig[zlevel], config, true);
      }

      var layer = this._layers[zlevel];

      if (layer) {
        util.merge(layer, this._layerConfig[zlevel], true);
      }
    }
  };
  /**
   * 删除指定层
   * @param {number} zlevel 层所在的zlevel
   */


  Painter.prototype.delLayer = function (zlevel) {
    var layer = this._layers[zlevel];

    if (!layer) {
      return;
    } // Save config


    this.modLayer(zlevel, {
      position: layer.position,
      rotation: layer.rotation,
      scale: layer.scale
    });
    layer.dom.parentNode.removeChild(layer.dom);
    delete this._layers[zlevel];

    this._zlevelList.splice(util.indexOf(this._zlevelList, zlevel), 1);
  };
  /**
   * 刷新hover层
   */


  Painter.prototype.refreshHover = function () {
    this.clearHover();
    var list = this.storage.getHoverShapes(true);

    for (var i = 0, l = list.length; i < l; i++) {
      this._brushHover(list[i]);
    }

    var ctx = this._layers.hover.ctx;
    ctx.flush && ctx.flush();
    this.storage.delHover();
    return this;
  };
  /**
   * 清除hover层所有内容
   */


  Painter.prototype.clearHover = function () {
    var hover = this._layers.hover;
    hover && hover.clear();
    return this;
  };
  /**
   * 显示loading
   * 
   * @param {Object=} loadingEffect loading效果对象
   */


  Painter.prototype.showLoading = function (loadingEffect) {
    this._loadingEffect && this._loadingEffect.stop();
    loadingEffect && this.setLoadingEffect(loadingEffect);

    this._loadingEffect.start(this);

    this.loading = true;
    return this;
  };
  /**
   * loading结束
   */


  Painter.prototype.hideLoading = function () {
    this._loadingEffect.stop();

    this.clearHover();
    this.loading = false;
    return this;
  };
  /**
   * loading结束判断
   */


  Painter.prototype.isLoading = function () {
    return this.loading;
  };
  /**
   * 区域大小变化后重绘
   */


  Painter.prototype.resize = function () {
    var domRoot = this._domRoot;
    domRoot.style.display = 'none';

    var width = this._getWidth();

    var height = this._getHeight();

    domRoot.style.display = ''; // 优化没有实际改变的resize

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

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

      this.refresh(null, true);
    }

    return this;
  };
  /**
   * 清除单独的一个层
   * @param {number} zLevel
   */


  Painter.prototype.clearLayer = function (zLevel) {
    var layer = this._layers[zLevel];

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


  Painter.prototype.dispose = function () {
    if (this.isLoading()) {
      this.hideLoading();
    }

    this.root.innerHTML = '';
    this.root = this.storage = this._domRoot = this._layers = null;
  };

  Painter.prototype.getDomHover = function () {
    return this._layers.hover.dom;
  };
  /**
   * 图像导出
   * @param {string} type
   * @param {string} [backgroundColor='#fff'] 背景色
   * @return {string} 图片的Base64 url
   */


  Painter.prototype.toDataURL = function (type, backgroundColor, args) {
    if (window['G_vmlCanvasManager']) {
      return null;
    }

    var imageLayer = new Layer('image', this);

    this._bgDom.appendChild(imageLayer.dom);

    imageLayer.initContext();
    var ctx = imageLayer.ctx;
    imageLayer.clearColor = backgroundColor || '#fff';
    imageLayer.clear();
    var self = this; // 升序遍历，shape上的zlevel指定绘画图层的z轴层叠

    this.storage.iterShape(function (shape) {
      if (!shape.invisible) {
        if (!shape.onbrush // 没有onbrush
        // 有onbrush并且调用执行返回false或undefined则继续粉刷
        || shape.onbrush && !shape.onbrush(ctx, false)) {
          if (config.catchBrushException) {
            try {
              shape.brush(ctx, false, self.refreshNextFrame);
            } catch (error) {
              log(error, 'brush error of ' + shape.type, shape);
            }
          } else {
            shape.brush(ctx, false, self.refreshNextFrame);
          }
        }
      }
    }, {
      normal: 'up',
      update: true
    });
    var image = imageLayer.dom.toDataURL(type, args);
    ctx = null;

    this._bgDom.removeChild(imageLayer.dom);

    return image;
  };
  /**
   * 获取绘图区域宽度
   */


  Painter.prototype.getWidth = function () {
    return this._width;
  };
  /**
   * 获取绘图区域高度
   */


  Painter.prototype.getHeight = function () {
    return this._height;
  };

  Painter.prototype._getWidth = function () {
    var root = this.root;
    var stl = root.currentStyle || document.defaultView.getComputedStyle(root);
    return ((root.clientWidth || parseInt(stl.width, 10)) - parseInt(stl.paddingLeft, 10) // 请原谅我这比较粗暴
    - parseInt(stl.paddingRight, 10)).toFixed(0) - 0;
  };

  Painter.prototype._getHeight = function () {
    var root = this.root;
    var stl = root.currentStyle || document.defaultView.getComputedStyle(root);
    return ((root.clientHeight || parseInt(stl.height, 10)) - parseInt(stl.paddingTop, 10) // 请原谅我这比较粗暴
    - parseInt(stl.paddingBottom, 10)).toFixed(0) - 0;
  };

  Painter.prototype._brushHover = function (shape) {
    var ctx = this._layers.hover.ctx;

    if (!shape.onbrush // 没有onbrush
    // 有onbrush并且调用执行返回false或undefined则继续粉刷
    || shape.onbrush && !shape.onbrush(ctx, true)) {
      var layer = this.getLayer(shape.zlevel);

      if (layer.needTransform) {
        ctx.save();
        layer.setTransform(ctx);
      } // Retina 优化


      if (config.catchBrushException) {
        try {
          shape.brush(ctx, true, this.refreshNextFrame);
        } catch (error) {
          log(error, 'hoverBrush error of ' + shape.type, shape);
        }
      } else {
        shape.brush(ctx, true, this.refreshNextFrame);
      }

      if (layer.needTransform) {
        ctx.restore();
      }
    }
  };

  Painter.prototype._shapeToImage = function (id, shape, width, height, devicePixelRatio) {
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    canvas.style.width = width + 'px';
    canvas.style.height = height + 'px';
    canvas.setAttribute('width', width * devicePixelRatio);
    canvas.setAttribute('height', height * devicePixelRatio);
    ctx.clearRect(0, 0, width * devicePixelRatio, height * devicePixelRatio);
    var shapeTransform = {
      position: shape.position,
      rotation: shape.rotation,
      scale: shape.scale
    };
    shape.position = [0, 0, 0];
    shape.rotation = 0;
    shape.scale = [1, 1];

    if (shape) {
      shape.brush(ctx, false);
    }

    var ImageShape = require('./shape/Image');

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

    if (shapeTransform.position != null) {
      imgShape.position = shape.position = shapeTransform.position;
    }

    if (shapeTransform.rotation != null) {
      imgShape.rotation = shape.rotation = shapeTransform.rotation;
    }

    if (shapeTransform.scale != null) {
      imgShape.scale = shape.scale = shapeTransform.scale;
    }

    return imgShape;
  };

  Painter.prototype._createShapeToImageProcessor = function () {
    if (window['G_vmlCanvasManager']) {
      return doNothing;
    }

    var me = this;
    return function (id, e, width, height) {
      return me._shapeToImage(id, e, width, height, config.devicePixelRatio);
    };
  };

  return Painter;
}(require);