AFRAME.registerComponent("networked-counter", {
  schema: {
    max: { default: 3 },
    ttl: { default: 120 }
  },

  init: function() {
    this.count = 0;
    this.queue = {};
    this.timeouts = {};
  },

  getCount: function() {
    return queue.length;
  },

  getMax: function() {
    return this.data.max;
  },

  getTtl: function() {
    return this.data.ttl;
  },

  register: function(networkedEl) {
    if (this.data.max <= 0) {
      return;
    }

    const id = this._getNetworkId(networkedEl);
    if (this.queue.hasOwnProperty(id)) {
      return;
    }

    const now = Date.now();
    const onGrabHandler = this._onGrabbed.bind(this, id);
    const onReleaseHandler = this._onReleased.bind(this, id);
    this.queue[id] = {
      ts: now,
      el: networkedEl,
      onGrabHandler: onGrabHandler,
      onReleaseHandler: onReleaseHandler
    };

    networkedEl.addEventListener("grab-start", onGrabHandler);
    networkedEl.addEventListener("grab-end", onReleaseHandler);

    this.count++;

    if (!this._isCurrentlyGrabbed(id)) {
      this._addTimeout(id);
    }

    this._removeOldest();
  },

  deregister: function(networkedEl) {
    const id = this._getNetworkId(networkedEl);
    if (this.queue.hasOwnProperty(id)) {
      const item = this.queue[id];
      networkedEl.removeEventListener("grab-start", item.onGrabHandler);
      networkedEl.removeEventListener("grab-end", item.onReleaseHandler);

      delete this.queue[id];

      this._removeTimeout(id);
      delete this.timeouts[id];

      this.count--;
    }
  },

  _onGrabbed: function(id, e) {
    this._removeTimeout(id);
  },

  _onReleased: function(id, e) {
    this._removeTimeout(id);
    this._addTimeout(id);
  },

  _removeOldest: function() {
    if (this.count > this.data.max) {
      let oldest = null, ts = Number.MAX_VALUE;
      Object.keys(this.queue).forEach(function(id) {
        const expiration = this.queue[id].ts + this.data.ttl * 1000;
        if (this.queue[id].ts < ts && !this._isCurrentlyGrabbed(id)) {
          oldest = this.queue[id];
          ts = this.queue[id].ts;
        }
      }, this);
      if (ts > 0) {
        this.deregister(oldest.el);
        this._destroy(oldest.el);
      }
    }
  },

  _isCurrentlyGrabbed: function(id) {
    const networkedEl = this.queue[id].el;
    return networkedEl.is("grabbed");
  },

  _addTimeout: function(id) {
    const timeout = this.data.ttl * 1000;
    this.timeouts[id] = setTimeout(() => {

      const el = this.queue[id].el;
      this.deregister(el);
      this._destroy(el);
    }, timeout);
  },

  _removeTimeout: function(id) {
    if (this.timeouts.hasOwnProperty(id)) {
      clearTimeout(this.timeouts[id]);
    }
  },

  _destroy: function(networkedEl) {
    networkedEl.parentNode.removeChild(networkedEl);
  },

  _getNetworkId: function(networkedEl) {
    return networkedEl.components.networked.data.networkId;
  }
});