'use strict';

const EventEmitter = require('events').EventEmitter;
const v8Util = process.atomBinding('v8_util');

class ObjectsRegistry extends EventEmitter {
  constructor() {
    super();

    this.setMaxListeners(Number.MAX_VALUE);
    this.nextId = 0;

    // Stores all objects by ref-counting.
    // (id) => {object, count}
    this.storage = {};

    // Stores the IDs of objects referenced by WebContents.
    // (webContentsId) => {(id) => (count)}
    this.owners = {};
  }

  // Register a new object, the object would be kept referenced until you release
  // it explicitly.
  add(webContentsId, obj) {
    var base, base1, id;
    id = this.saveToStorage(obj);

    // Remember the owner.
    if ((base = this.owners)[webContentsId] == null) {
      base[webContentsId] = {};
    }
    if ((base1 = this.owners[webContentsId])[id] == null) {
      base1[id] = 0;
    }
    this.owners[webContentsId][id]++;

    // Returns object's id
    return id;
  }

  // Get an object according to its ID.
  get(id) {
    var ref;
    return (ref = this.storage[id]) != null ? ref.object : void 0;
  }

  // Dereference an object according to its ID.
  remove(webContentsId, id) {
    var pointer;
    this.dereference(id, 1);

    // Also reduce the count in owner.
    pointer = this.owners[webContentsId];
    if (pointer == null) {
      return;
    }
    --pointer[id];
    if (pointer[id] === 0) {
      return delete pointer[id];
    }
  }

  // Clear all references to objects refrenced by the WebContents.
  clear(webContentsId) {
    var count, id, ref;
    this.emit("clear-" + webContentsId);
    if (this.owners[webContentsId] == null) {
      return;
    }
    ref = this.owners[webContentsId];
    for (id in ref) {
      count = ref[id];
      this.dereference(id, count);
    }
    return delete this.owners[webContentsId];
  }

  // Private: Saves the object into storage and assigns an ID for it.
  saveToStorage(object) {
    var id;
    id = v8Util.getHiddenValue(object, 'atomId');
    if (!id) {
      id = ++this.nextId;
      this.storage[id] = {
        count: 0,
        object: object
      };
      v8Util.setHiddenValue(object, 'atomId', id);
    }
    ++this.storage[id].count;
    return id;
  }

  // Private: Dereference the object from store.
  dereference(id, count) {
    var pointer;
    pointer = this.storage[id];
    if (pointer == null) {
      return;
    }
    pointer.count -= count;
    if (pointer.count === 0) {
      v8Util.deleteHiddenValue(pointer.object, 'atomId');
      return delete this.storage[id];
    }
  }
}

module.exports = new ObjectsRegistry;