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

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

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

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

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 _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

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

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.svg.helper.ClippathManager
 * 
 * Manages SVG clipPath elements.
 * 
 * @author Zhang Wenli
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */
var ClippathManager =
/*#__PURE__*/
function (_Definable) {
  _inherits(ClippathManager, _Definable);

  /**
   * @method constructor ClippathManager
   * @param   {Number}     qrId    qrenderer instance id
   * @param   {SVGElement} svgRoot root of SVG document
   */
  function ClippathManager(qrId, svgRoot) {
    _classCallCheck(this, ClippathManager);

    return _possibleConstructorReturn(this, _getPrototypeOf(ClippathManager).call(this, qrId, svgRoot, 'clipPath', '__clippath_in_use__'));
  }
  /**
   * @method update
   * Update clipPath.
   *
   * @param {Displayable} displayable displayable element
   */


  _createClass(ClippathManager, [{
    key: "update",
    value: function update(displayable) {
      var svgEl = this.getSvgElement(displayable);

      if (svgEl) {
        this.updateDom(svgEl, displayable.__clipPaths, false);
      }

      var textEl = this.getTextSvgElement(displayable);

      if (textEl) {
        // Make another clipPath for text, since it's transform
        // matrix is not the same with svgElement
        this.updateDom(textEl, displayable.__clipPaths, true);
      }

      this.markUsed(displayable);
    }
    /**
     * @method updateDom
     * Create an SVGElement of displayable and create a <clipPath> of its
     * clipPath
     *
     * @param {Displayable} parentEl  parent element
     * @param {ClipPath[]}  clipPaths clipPaths of parent element
     * @param {boolean}     isText    if parent element is Text
     */

  }, {
    key: "updateDom",
    value: function updateDom(parentEl, clipPaths, isText) {
      if (clipPaths && clipPaths.length > 0) {
        // Has clipPath, create <clipPath> with the first clipPath
        var defs = this.getDefs(true);
        var clipPath = clipPaths[0];
        var clipPathEl;
        var id;
        var dom = isText ? '_textDom' : '_dom';

        if (clipPath[dom]) {
          // Use a dom that is already in <defs>
          id = clipPath[dom].getAttribute('id');
          clipPathEl = clipPath[dom]; // Use a dom that is already in <defs>

          if (!defs.contains(clipPathEl)) {
            // This happens when set old clipPath that has
            // been previously removed
            defs.appendChild(clipPathEl);
          }
        } else {
          // New <clipPath>
          id = 'qr' + this._qrId + '-clip-' + this.nextId;
          ++this.nextId;
          clipPathEl = this.createElement('clipPath');
          clipPathEl.setAttribute('id', id);
          defs.appendChild(clipPathEl);
          clipPath[dom] = clipPathEl;
        } // Build path and add to <clipPath>


        var svgProxy = this.getSvgProxy(clipPath);

        if (clipPath.transform && clipPath.parent.invTransform && !isText) {
          /**
           * If a clipPath has a parent with transform, the transform
           * of parent should not be considered when setting transform
           * of clipPath. So we need to transform back from parent's
           * transform, which is done by multiplying parent's inverse
           * transform.
           */
          // Store old transform
          var transform = Array.prototype.slice.call(clipPath.transform); // Transform back from parent, and brush path

          matrix.mul(clipPath.transform, clipPath.parent.invTransform, clipPath.transform);
          svgProxy.brush(clipPath); // Set back transform of clipPath

          clipPath.transform = transform;
        } else {
          svgProxy.brush(clipPath);
        }

        var pathEl = this.getSvgElement(clipPath);
        clipPathEl.innerHTML = '';
        /**
         * Use `cloneNode()` here to appendChild to multiple parents,
         * which may happend when Text and other shapes are using the same
         * clipPath. Since Text will create an extra clipPath DOM due to
         * different transform rules.
         */

        clipPathEl.appendChild(pathEl.cloneNode());
        parentEl.setAttribute('clip-path', 'url(#' + id + ')');

        if (clipPaths.length > 1) {
          // Make the other clipPaths recursively
          this.updateDom(clipPathEl, clipPaths.slice(1), isText);
        }
      } else {
        // No clipPath
        if (parentEl) {
          parentEl.setAttribute('clip-path', 'none');
        }
      }
    }
    /**
     * @method markUsed
     * 
     * Mark a single clipPath to be used
     *
     * @param {Displayable} displayable displayable element
     */

  }, {
    key: "markUsed",
    value: function markUsed(displayable) {
      var that = this; // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.

      if (displayable.__clipPaths) {
        dataUtil.each(displayable.__clipPaths, function (clipPath) {
          if (clipPath._dom) {
            Definable.prototype.markUsed.call(that, clipPath._dom);
          }

          if (clipPath._textDom) {
            Definable.prototype.markUsed.call(that, clipPath._textDom);
          }
        });
      }
    }
  }]);

  return ClippathManager;
}(Definable);

var _default = ClippathManager;
module.exports = _default;