/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @package
 * @supported IE 10+ and other browsers. IE 8 and IE 9 could be supported by
 * by making anti-clobbering support optional.
 */

goog.module('goog.html.sanitizer.ElementWeakMap');
goog.module.declareLegacyNamespace();

var noclobber = goog.require('goog.html.sanitizer.noclobber');

// We also need to check if WeakMap has been polyfilled, because we want to use
// ElementWeakMap instead of the polyfill.
/** @const {boolean} */
var NATIVE_WEAKMAP_SUPPORTED = typeof WeakMap != 'undefined' &&
    WeakMap.toString().indexOf('[native code]') != -1;

/** @const {string} */
var DATA_ATTRIBUTE_NAME_PREFIX = 'data-elementweakmap-index-';

// Increased every time a new ElementWeakMap is constructed, to guarantee
// that each weakmap uses a different attribute name.
var weakMapCount = 0;

/**
 * A weakmap-like implementation for browsers that don't support native WeakMap.
 * It uses a data attribute on the key element for O(1) lookups.
 * @template T
 * @constructor
 */
var ElementWeakMap = function() {
  /** @private {!Array<!Element>} */
  this.keys_ = [];

  /** @private {!Array<!T>} */
  this.values_ = [];

  /** @private @const {string} */
  this.dataAttributeName_ = DATA_ATTRIBUTE_NAME_PREFIX + weakMapCount++;
};

/**
 * Stores a `elementKey` -> `value` mapping.
 * @param {!Element} elementKey
 * @param {!T} value
 * @return {!ElementWeakMap}
 */
ElementWeakMap.prototype.set = function(elementKey, value) {
  if (noclobber.hasElementAttribute(elementKey, this.dataAttributeName_)) {
    var itemIndex = parseInt(
        noclobber.getElementAttribute(elementKey, this.dataAttributeName_), 10);
    this.values_[itemIndex] = value;
  } else {
    var itemIndex = this.values_.push(value) - 1;
    noclobber.setElementAttribute(
        elementKey, this.dataAttributeName_, itemIndex.toString());
    this.keys_.push(elementKey);
  }
  return this;
};

/**
 * Gets the value previously stored for `elementKey`, or undefined if no
 * value was stored for such key.
 * @param {!Element} elementKey
 * @return {!Element|undefined}
 */
ElementWeakMap.prototype.get = function(elementKey) {
  if (!noclobber.hasElementAttribute(elementKey, this.dataAttributeName_)) {
    return undefined;
  }
  var itemIndex = parseInt(
      noclobber.getElementAttribute(elementKey, this.dataAttributeName_), 10);
  return this.values_[itemIndex];
};

/** Clears the map. */
ElementWeakMap.prototype.clear = function() {
  this.keys_.forEach(function(el) {
    noclobber.removeElementAttribute(el, this.dataAttributeName_);
  }, this);
  this.keys_ = [];
  this.values_ = [];
};

/**
 * Returns either this weakmap adapter or the native weakmap implmentation, if
 * available.
 * @return {!ElementWeakMap|!WeakMap}
 */
ElementWeakMap.newWeakMap = function() {
  return NATIVE_WEAKMAP_SUPPORTED ? new WeakMap() : new ElementWeakMap();
};

exports = ElementWeakMap;
