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

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

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.ShadowManager
 * 
 * Manages SVG shadow elements.
 * 
 * @author Zhang Wenli
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */
function hasShadow(style) {
  // TODO: textBoxShadowBlur is not supported yet
  return style && (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY || style.textShadowBlur || style.textShadowOffsetX || style.textShadowOffsetY);
}
/**
 * @method constructor ShadowManager
 * 
 * Manages SVG shadow elements.
 *
 * @param   {Number}     qrId    qrenderer instance id
 * @param   {SVGElement} svgRoot root of SVG document
 */


var ShadowManager =
/*#__PURE__*/
function (_Definable) {
  _inherits(ShadowManager, _Definable);

  function ShadowManager(qrId, svgRoot) {
    _classCallCheck(this, ShadowManager);

    return _possibleConstructorReturn(this, _getPrototypeOf(ShadowManager).call(this, qrId, svgRoot, ['filter'], '__filter_in_use__', '_shadowDom'));
  }
  /**
   * Create new shadow DOM for fill or stroke if not exist,
   * but will not update shadow if exists.
   *
   * @param {SvgElement}  svgElement   SVG element to paint
   * @param {Displayable} displayable  qrenderer displayable element
   */


  _createClass(ShadowManager, [{
    key: "addWithoutUpdate",
    value: function addWithoutUpdate(svgElement, displayable) {
      if (displayable && hasShadow(displayable.style)) {
        // Create dom in <defs> if not exists
        var dom;

        if (displayable._shadowDom) {
          // Gradient exists
          dom = displayable._shadowDom;
          var defs = this.getDefs(true);

          if (!defs.contains(displayable._shadowDom)) {
            // _shadowDom is no longer in defs, recreate
            this.addDom(dom);
          }
        } else {
          // New dom
          dom = this.add(displayable);
        }

        this.markUsed(displayable);
        var id = dom.getAttribute('id');
        svgElement.style.filter = 'url(#' + id + ')';
      }
    }
    /**
     * Add a new shadow tag in <defs>
     *
     * @param {Displayable} displayable  qrenderer displayable element
     * @return {SVGFilterElement} created DOM
     */

  }, {
    key: "add",
    value: function add(displayable) {
      var dom = this.createElement('filter'); // Set dom id with shadow id, since each shadow instance
      // will have no more than one dom element.
      // id may exists before for those dirty elements, in which case
      // id should remain the same, and other attributes should be
      // updated.

      displayable._shadowDomId = displayable._shadowDomId || this.nextId++;
      dom.setAttribute('id', 'qr' + this._qrId + '-shadow-' + displayable._shadowDomId);
      this.updateDom(displayable, dom);
      this.addDom(dom);
      return dom;
    }
    /**
     * Update shadow.
     *
     * @param {Displayable} displayable  qrenderer displayable element
     */

  }, {
    key: "update",
    value: function update(svgElement, displayable) {
      var style = displayable.style;

      if (hasShadow(style)) {
        var that = this;
        Definable.prototype.update.call(this, displayable, function () {
          that.updateDom(displayable, displayable._shadowDom);
        });
      } else {
        // Remove shadow
        this.remove(svgElement, displayable);
      }
    }
    /**
     * Remove DOM and clear parent filter
     */

  }, {
    key: "remove",
    value: function remove(svgElement, displayable) {
      if (displayable._shadowDomId != null) {
        this.removeDom(svgElement);
        svgElement.style.filter = '';
      }
    }
    /**
     * Update shadow dom
     *
     * @param {Displayable} displayable  qrenderer displayable element
     * @param {SVGFilterElement} dom DOM to update
     */

  }, {
    key: "updateDom",
    value: function updateDom(displayable, dom) {
      var domChild = dom.getElementsByTagName('feDropShadow');

      if (domChild.length === 0) {
        domChild = this.createElement('feDropShadow');
      } else {
        domChild = domChild[0];
      }

      var style = displayable.style;
      var scaleX = displayable.scale ? displayable.scale[0] || 1 : 1;
      var scaleY = displayable.scale ? displayable.scale[1] || 1 : 1; // TODO: textBoxShadowBlur is not supported yet

      var offsetX;
      var offsetY;
      var blur;
      var color;

      if (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY) {
        offsetX = style.shadowOffsetX || 0;
        offsetY = style.shadowOffsetY || 0;
        blur = style.shadowBlur;
        color = style.shadowColor;
      } else if (style.textShadowBlur) {
        offsetX = style.textShadowOffsetX || 0;
        offsetY = style.textShadowOffsetY || 0;
        blur = style.textShadowBlur;
        color = style.textShadowColor;
      } else {
        // Remove shadow
        this.removeDom(dom, style);
        return;
      }

      domChild.setAttribute('dx', offsetX / scaleX);
      domChild.setAttribute('dy', offsetY / scaleY);
      domChild.setAttribute('flood-color', color); // Divide by two here so that it looks the same as in canvas
      // See: https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowblur

      var stdDx = blur / 2 / scaleX;
      var stdDy = blur / 2 / scaleY;
      var stdDeviation = stdDx + ' ' + stdDy;
      domChild.setAttribute('stdDeviation', stdDeviation); // Fix filter clipping problem

      dom.setAttribute('x', '-100%');
      dom.setAttribute('y', '-100%');
      dom.setAttribute('width', Math.ceil(blur / 2 * 200) + '%');
      dom.setAttribute('height', Math.ceil(blur / 2 * 200) + '%');
      dom.appendChild(domChild); // Store dom element in shadow, to avoid creating multiple
      // dom instances for the same shadow element

      displayable._shadowDom = dom;
    }
    /**
     * Mark a single shadow to be used
     *
     * @param {Displayable} displayable displayable element
     */

  }, {
    key: "markUsed",
    value: function markUsed(displayable) {
      if (displayable._shadowDom) {
        Definable.prototype.markUsed.call(this, displayable._shadowDom);
      }
    }
  }]);

  return ShadowManager;
}(Definable);

var _default = ShadowManager;
module.exports = _default;