var classUtil = require("../core/utils/class_util");

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

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

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

var extend = _data_structure_util.extend;

function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

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; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

/**
 * @class qrenderer.graphic.Group
 * 
 * - Group is a container, it's not visible.
 * - Group can have child nodes, not the other Element types.
 * - The transformations applied to Group will apply to its children too.
 * 
 * - Group 是一个容器，本身不可见。
 * - Group 可以插入子节点，其它类型不能。
 * - Group 上的变换也会被应用到子节点上。
 * 
 *      @example small frame
 *      let Group = require('qrenderer/Group');
 *      let Circle = require('qrenderer/graphic/shape/Circle');
 *      let g = new Group();
 *      g.position[0] = 100;
 *      g.position[1] = 100;
 *      g.add(new Circle({
 *          style: {
 *              x: 100,
 *              y: 100,
 *              r: 20,
 *          }
 *      }));
 *      qr.add(g);
 */
var Group =
/*#__PURE__*/
function (_Element) {
  _inherits(Group, _Element);

  /**
   * @method constructor Group
   */
  function Group() {
    var _this;

    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

    _classCallCheck(this, Group);

    _this = _possibleConstructorReturn(this, _getPrototypeOf(Group).call(this, options));
    /**
     * @private
     * @property _children
     */

    _this._children = [];
    /**
     * @private
     * @property __storage
     */

    _this.__storage = null;
    /**
     * @private
     * @property __dirty
     */

    _this.__dirty = true;
    /**
     * @property isGroup
     */

    _this.isGroup = true;
    /**
     * @property {String}
     */

    _this.type = 'group';
    /**
     * @property {Boolean} 所有子孙元素是否响应鼠标事件
     */

    _this.silent = false;
    classUtil.copyOwnProperties(_assertThisInitialized(_this), options);
    return _this;
  }
  /**
   * @method children
   * @return {Array<Element>}
   */


  _createClass(Group, [{
    key: "children",
    value: function children() {
      return this._children.slice();
    }
    /**
     * @method childAt
     * 获取指定 index 的儿子节点
     * @param  {Number} idx
     * @return {Element}
     */

  }, {
    key: "childAt",
    value: function childAt(idx) {
      return this._children[idx];
    }
    /**
     * @method childOfName
     * 获取指定名字的儿子节点
     * @param  {String} name
     * @return {Element}
     */

  }, {
    key: "childOfName",
    value: function childOfName(name) {
      var children = this._children;

      for (var i = 0; i < children.length; i++) {
        if (children[i].name === name) {
          return children[i];
        }
      }
    }
    /**
     * @method childCount
     * @return {Number}
     */

  }, {
    key: "childCount",
    value: function childCount() {
      return this._children.length;
    }
    /**
     * @method add
     * 添加子节点到最后
     * @param {Element} child
     */

  }, {
    key: "add",
    value: function add(child) {
      if (child && child !== this && child.parent !== this) {
        this._children.push(child);

        this._doAdd(child);
      }

      return this;
    }
    /**
     * @method addBefore
     * 添加子节点在 nextSibling 之前
     * @param {Element} child
     * @param {Element} nextSibling
     */

  }, {
    key: "addBefore",
    value: function addBefore(child, nextSibling) {
      if (child && child !== this && child.parent !== this && nextSibling && nextSibling.parent === this) {
        var children = this._children;
        var idx = children.indexOf(nextSibling);

        if (idx >= 0) {
          children.splice(idx, 0, child);

          this._doAdd(child);
        }
      }

      return this;
    }
    /**
     * @private
     * @method _doAdd
     * @param {*} child 
     */

  }, {
    key: "_doAdd",
    value: function _doAdd(child) {
      if (child.parent) {
        child.parent.remove(child);
      }

      child.parent = this; //把子节点的 parent 属性指向自己，在事件冒泡的时候会使用 parent 属性。

      var storage = this.__storage;
      var qr = this.__qr;

      if (storage && storage !== child.__storage) {
        storage.addToStorage(child);

        if (child instanceof Group) {
          child.addChildrenToStorage(storage);
        }
      }

      qr && qr.refresh();
    }
    /**
     * @method remove
     * 移除子节点
     * @param {Element} child
     */

  }, {
    key: "remove",
    value: function remove(child) {
      var qr = this.__qr;
      var storage = this.__storage;
      var children = this._children;
      var idx = dataUtil.indexOf(children, child);

      if (idx < 0) {
        return this;
      }

      children.splice(idx, 1);
      child.parent = null;

      if (storage) {
        storage.delFromStorage(child);

        if (child instanceof Group) {
          child.delChildrenFromStorage(storage);
        }
      }

      qr && qr.refresh();
      return this;
    }
    /**
     * @method removeAll
     * 移除所有子节点
     */

  }, {
    key: "removeAll",
    value: function removeAll() {
      var children = this._children;
      var storage = this.__storage;
      var child;
      var i;

      for (i = 0; i < children.length; i++) {
        child = children[i];

        if (storage) {
          storage.delFromStorage(child);

          if (child instanceof Group) {
            child.delChildrenFromStorage(storage);
          }
        }

        child.parent = null;
      }

      children.length = 0;
      return this;
    }
    /**
     * @method eachChild
     * 遍历所有子节点
     * @param  {Function} cb
     * @param  {Object}   context
     */

  }, {
    key: "eachChild",
    value: function eachChild(cb, context) {
      var children = this._children;

      for (var i = 0; i < children.length; i++) {
        var child = children[i];
        cb.call(context, child, i);
      }

      return this;
    }
    /**
     * @method traverse
     * 深度优先遍历所有子孙节点
     * @param  {Function} cb
     * @param  {Object}   context
     */

  }, {
    key: "traverse",
    value: function traverse(cb, context) {
      for (var i = 0; i < this._children.length; i++) {
        var child = this._children[i];
        cb.call(context, child);

        if (child.type === 'group') {
          child.traverse(cb, context);
        }
      }

      return this;
    }
    /**
     * @method addChildrenToStorage
     * @param {Storage} storage 
     */

  }, {
    key: "addChildrenToStorage",
    value: function addChildrenToStorage(storage) {
      for (var i = 0; i < this._children.length; i++) {
        var child = this._children[i];
        storage.addToStorage(child);

        if (child instanceof Group) {
          child.addChildrenToStorage(storage);
        }
      }
    }
    /**
     * @method delChildrenFromStorage
     * @param {Storage} storage 
     */

  }, {
    key: "delChildrenFromStorage",
    value: function delChildrenFromStorage(storage) {
      for (var i = 0; i < this._children.length; i++) {
        var child = this._children[i];
        storage.delFromStorage(child);

        if (child instanceof Group) {
          child.delChildrenFromStorage(storage);
        }
      }
    }
    /**
     * @method dirty
     * @return {Group}
     */

  }, {
    key: "dirty",
    value: function dirty() {
      this.__dirty = true;
      this.__qr && this.__qr.refresh();
      return this;
    }
    /**
     * @method getBoundingRect
     * @return {BoundingRect}
     */

  }, {
    key: "getBoundingRect",
    value: function getBoundingRect(includeChildren) {
      // TODO Caching
      var rect = null;
      var tmpRect = new BoundingRect(0, 0, 0, 0);
      var children = includeChildren || this._children;
      var tmpMat = [];

      for (var i = 0; i < children.length; i++) {
        var child = children[i];

        if (child.ignore || child.invisible) {
          continue;
        }

        var childRect = child.getBoundingRect();
        var transform = child.getLocalTransform(tmpMat); // TODO
        // The boundingRect cacluated by transforming original
        // rect may be bigger than the actual bundingRect when rotation
        // is used. (Consider a circle rotated aginst its center, where
        // the actual boundingRect should be the same as that not be
        // rotated.) But we can not find better approach to calculate
        // actual boundingRect yet, considering performance.

        if (transform) {
          tmpRect.copy(childRect);
          tmpRect.applyTransform(transform);
          rect = rect || tmpRect.clone();
          rect.union(tmpRect);
        } else {
          rect = rect || childRect.clone();
          rect.union(childRect);
        }
      }

      return rect || tmpRect;
    }
  }]);

  return Group;
}(Element);

var _default = Group;
module.exports = _default;