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

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

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

var colorTool = require("../../core/utils/color_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.GradientManager
 * 
 * Manages SVG gradient elements.
 * 
 * @author Zhang Wenli
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */
var GradientManager =
/*#__PURE__*/
function (_Definable) {
  _inherits(GradientManager, _Definable);

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

    return _possibleConstructorReturn(this, _getPrototypeOf(GradientManager).call(this, qrId, svgRoot, ['linearGradient', 'radialGradient'], '__gradient_in_use__'));
  }
  /**
   * @method addWithoutUpdate
   * Create new gradient DOM for fill or stroke if not exist,
   * but will not update gradient if exists.
   *
   * @param {SvgElement}  svgElement   SVG element to paint
   * @param {Displayable} displayable  qrenderer displayable element
   */


  _createClass(GradientManager, [{
    key: "addWithoutUpdate",
    value: function addWithoutUpdate(svgElement, displayable) {
      if (displayable && displayable.style) {
        var that = this;
        dataUtil.each(['fill', 'stroke'], function (fillOrStroke) {
          if (displayable.style[fillOrStroke] && (displayable.style[fillOrStroke].type === 'linear' || displayable.style[fillOrStroke].type === 'radial')) {
            var gradient = displayable.style[fillOrStroke];
            var defs = that.getDefs(true); // Create dom in <defs> if not exists

            var dom;

            if (gradient._dom) {
              // Gradient exists
              dom = gradient._dom;

              if (!defs.contains(gradient._dom)) {
                // _dom is no longer in defs, recreate
                that.addDom(dom);
              }
            } else {
              // New dom
              dom = that.add(gradient);
            }

            that.markUsed(displayable);
            var id = dom.getAttribute('id');
            svgElement.setAttribute(fillOrStroke, 'url(#' + id + ')');
          }
        });
      }
    }
    /**
     * @method add
     * 
     * Add a new gradient tag in <defs>
     *
     * @param   {Gradient} gradient qr gradient instance
     * @return {SVGLinearGradientElement | SVGRadialGradientElement} created DOM
     */

  }, {
    key: "add",
    value: function add(gradient) {
      var dom;

      if (gradient.type === 'linear') {
        dom = this.createElement('linearGradient');
      } else if (gradient.type === 'radial') {
        dom = this.createElement('radialGradient');
      } else {
        console.log('Illegal gradient type.');
        return null;
      } // Set dom id with gradient id, since each gradient 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.


      gradient.id = gradient.id || this.nextId++;
      dom.setAttribute('id', "qr".concat(this._qrId, "-gradient-").concat(gradient.id));
      this.updateDom(gradient, dom);
      this.addDom(dom);
      return dom;
    }
    /**
     * @method update
     * 
     * Update gradient.
     *
     * @param {Gradient} gradient qr gradient instance
     */

  }, {
    key: "update",
    value: function update(gradient) {
      var that = this;
      Definable.prototype.update.call(this, gradient, function () {
        var type = gradient.type;
        var tagName = gradient._dom.tagName;

        if (type === 'linear' && tagName === 'linearGradient' || type === 'radial' && tagName === 'radialGradient') {
          // Gradient type is not changed, update gradient
          that.updateDom(gradient, gradient._dom);
        } else {
          // Remove and re-create if type is changed
          that.removeDom(gradient);
          that.add(gradient);
        }
      });
    }
    /**
     * @method updateDom
     * 
     * Update gradient dom
     *
     * @param {Gradient} gradient qr gradient instance
     * @param {SVGLinearGradientElement | SVGRadialGradientElement} dom
     *                            DOM to update
     */

  }, {
    key: "updateDom",
    value: function updateDom(gradient, dom) {
      if (gradient.type === 'linear') {
        dom.setAttribute('x1', gradient.x);
        dom.setAttribute('y1', gradient.y);
        dom.setAttribute('x2', gradient.x2);
        dom.setAttribute('y2', gradient.y2);
      } else if (gradient.type === 'radial') {
        dom.setAttribute('cx', gradient.x);
        dom.setAttribute('cy', gradient.y);
        dom.setAttribute('r', gradient.r);
      } else {
        console.log('Illegal gradient type.');
        return;
      }

      if (gradient.global) {
        // x1, x2, y1, y2 in range of 0 to canvas width or height
        dom.setAttribute('gradientUnits', 'userSpaceOnUse');
      } else {
        // x1, x2, y1, y2 in range of 0 to 1
        dom.setAttribute('gradientUnits', 'objectBoundingBox');
      } // Remove color stops if exists


      dom.innerHTML = ''; // Add color stops

      var colors = gradient.colorStops;

      for (var i = 0, len = colors.length; i < len; ++i) {
        var stop = this.createElement('stop');
        stop.setAttribute('offset', colors[i].offset * 100 + '%');
        var color = colors[i].color;

        if (color.indexOf('rgba' > -1)) {
          // Fix Safari bug that stop-color not recognizing alpha #9014
          var opacity = colorTool.parse(color)[3];
          var hex = colorTool.toHex(color); // stop-color cannot be color, since:
          // The opacity value used for the gradient calculation is the
          // *product* of the value of stop-opacity and the opacity of the
          // value of stop-color.
          // See https://www.w3.org/TR/SVG2/pservers.html#StopOpacityProperty

          stop.setAttribute('stop-color', '#' + hex);
          stop.setAttribute('stop-opacity', opacity);
        } else {
          stop.setAttribute('stop-color', colors[i].color);
        }

        dom.appendChild(stop);
      } // Store dom element in gradient, to avoid creating multiple
      // dom instances for the same gradient element


      gradient._dom = dom;
    }
    /**
     * @method markUsed
     * 
     * Mark a single gradient to be used
     *
     * @param {Displayable} displayable displayable element
     */

  }, {
    key: "markUsed",
    value: function markUsed(displayable) {
      if (displayable.style) {
        var gradient = displayable.style.fill;

        if (gradient && gradient._dom) {
          Definable.prototype.markUsed.call(this, gradient._dom);
        }

        gradient = displayable.style.stroke;

        if (gradient && gradient._dom) {
          Definable.prototype.markUsed.call(this, gradient._dom);
        }
      }
    }
  }]);

  return GradientManager;
}(Definable);

var _default = GradientManager;
module.exports = _default;