var _core = require("../core");

var createElement = _core.createElement;

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

var Path = require("../../graphic/Path");

var QImage = require("../../graphic/Image");

var QText = require("../../graphic/Text");

var _graphic = require("../graphic");

var svgPath = _graphic.path;
var svgImage = _graphic.image;
var svgText = _graphic.text;

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

/**
 * @class qrenderer.svg.helper.Definable
 * 
 * Manages elements that can be defined in <defs> in SVG,
 *       e.g., gradients, clip path, etc.
 * @author Zhang Wenli
 * @docauthor 大漠穷秋 damoqiongqiu@126.com
 */
var MARK_UNUSED = '0';
var MARK_USED = '1';

var Definable =
/*#__PURE__*/
function () {
  /**
   * @method constructor Definable
   * 
   * Manages elements that can be defined in <defs> in SVG,
   * e.g., gradients, clip path, etc.
   *
   * @param {Number}          qrId      qrenderer instance id
   * @param {SVGElement}      svgRoot   root of SVG document
   * @param {String|String[]} tagNames  possible tag names
   * @param {String}          markLabel label name to make if the element
   *                                    is used
   */
  function Definable(qrId, svgRoot, tagNames, markLabel, domName) {
    _classCallCheck(this, Definable);

    this._qrId = qrId;
    this._svgRoot = svgRoot;
    this._tagNames = typeof tagNames === 'string' ? [tagNames] : tagNames;
    this._markLabel = markLabel;
    this._domName = domName || '_dom';
    this.nextId = 0;
    this.createElement = createElement;
  }
  /**
   * @method getDefs
   * 
   * Get the <defs> tag for svgRoot; optionally creates one if not exists.
   *
   * @param {Boolean} isForceCreating if need to create when not exists
   * @return {SVGDefsElement} SVG <defs> element, null if it doesn't exist and isForceCreating is false
   */


  _createClass(Definable, [{
    key: "getDefs",
    value: function getDefs(isForceCreating) {
      var svgRoot = this._svgRoot;

      var defs = this._svgRoot.getElementsByTagName('defs');

      if (defs.length === 0) {
        // Not exist
        if (isForceCreating) {
          defs = svgRoot.insertBefore(this.createElement('defs'), // Create new tag
          svgRoot.firstChild // Insert in the front of svg
          );

          if (!defs.contains) {
            // IE doesn't support contains method
            defs.contains = function (el) {
              var children = defs.children;

              if (!children) {
                return false;
              }

              for (var i = children.length - 1; i >= 0; --i) {
                if (children[i] === el) {
                  return true;
                }
              }

              return false;
            };
          }

          return defs;
        } else {
          return null;
        }
      } else {
        return defs[0];
      }
    }
    /**
     * @method update
     * 
     * Update DOM element if necessary.
     *
     * @param {Object|String} element style element. e.g., for gradient, it may be '#ccc' or {type: 'linear', ...}
     * @param {Function|undefined} onUpdate update callback
     */

  }, {
    key: "update",
    value: function update(element, onUpdate) {
      if (!element) {
        return;
      }

      var defs = this.getDefs(false);

      if (element[this._domName] && defs.contains(element[this._domName])) {
        // Update DOM
        if (typeof onUpdate === 'function') {
          onUpdate(element);
        }
      } else {
        // No previous dom, create new
        var dom = this.add(element);

        if (dom) {
          element[this._domName] = dom;
        }
      }
    }
    /**
     * @method addDom
     * 
     * Add gradient dom to defs
     *
     * @param {SVGElement} dom DOM to be added to <defs>
     */

  }, {
    key: "addDom",
    value: function addDom(dom) {
      var defs = this.getDefs(true);
      defs.appendChild(dom);
    }
    /**
     * @method removeDom
     * 
     * Remove DOM of a given element.
     *
     * @param {SVGElement} element element to remove dom
     */

  }, {
    key: "removeDom",
    value: function removeDom(element) {
      var defs = this.getDefs(false);

      if (defs && element[this._domName]) {
        defs.removeChild(element[this._domName]);
        element[this._domName] = null;
      }
    }
    /**
     * @method getDoms
     * 
     * Get DOMs of this element.
     *
     * @return {HTMLDomElement} doms of this defineable elements in <defs>
     */

  }, {
    key: "getDoms",
    value: function getDoms() {
      var defs = this.getDefs(false);

      if (!defs) {
        // No dom when defs is not defined
        return [];
      }

      var doms = [];
      dataUtil.each(this._tagNames, function (tagName) {
        var tags = defs.getElementsByTagName(tagName); // Note that tags is HTMLCollection, which is array-like
        // rather than real array.
        // So `doms.concat(tags)` add tags as one object.

        doms = doms.concat([].slice.call(tags));
      });
      return doms;
    }
    /**
     * @method markAllUnused
     * 
     * Mark DOMs to be unused before painting, and clear unused ones at the end
     * of the painting.
     */

  }, {
    key: "markAllUnused",
    value: function markAllUnused() {
      var doms = this.getDoms();
      var that = this;
      dataUtil.each(doms, function (dom) {
        dom[that._markLabel] = MARK_UNUSED;
      });
    }
    /**
     * @method markUsed
     * 
     * Mark a single DOM to be used.
     *
     * @param {SVGElement} dom DOM to mark
     */

  }, {
    key: "markUsed",
    value: function markUsed(dom) {
      if (dom) {
        dom[this._markLabel] = MARK_USED;
      }
    }
    /**
     * @method removeUnused
     * 
     * Remove unused DOMs defined in <defs>
     */

  }, {
    key: "removeUnused",
    value: function removeUnused() {
      var defs = this.getDefs(false);

      if (!defs) {
        // Nothing to remove
        return;
      }

      var doms = this.getDoms();
      var that = this;
      dataUtil.each(doms, function (dom) {
        if (dom[that._markLabel] !== MARK_USED) {
          // Remove gradient
          defs.removeChild(dom);
        }
      });
    }
    /**
     * @method getSvgProxy
     * 
     * Get SVG proxy.
     *
     * @param {Displayable} displayable displayable element
     * @return {Path|Image|Text} svg proxy of given element
     */

  }, {
    key: "getSvgProxy",
    value: function getSvgProxy(displayable) {
      if (displayable instanceof Path) {
        return svgPath;
      } else if (displayable instanceof QImage) {
        return svgImage;
      } else if (displayable instanceof QText) {
        return svgText;
      } else {
        return svgPath;
      }
    }
    /**
     * @method getTextSvgElement
     * 
     * Get text SVG element.
     *
     * @param {Displayable} displayable displayable element
     * @return {SVGElement} SVG element of text
     */

  }, {
    key: "getTextSvgElement",
    value: function getTextSvgElement(displayable) {
      return displayable.__textSvgEl;
    }
    /**
     * @method getSvgElement
     * 
     * Get SVG element.
     *
     * @param {Displayable} displayable displayable element
     * @return {SVGElement} SVG element
     */

  }, {
    key: "getSvgElement",
    value: function getSvgElement(displayable) {
      return displayable.__svgEl;
    }
  }]);

  return Definable;
}();

var _default = Definable;
module.exports = _default;