/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

function CCAnalyzer() {
}

CCAnalyzer.prototype = {
  clear: function () {
    this.callback = null;
    this.processingCount = 0;
    this.graph = {};
    this.roots = [];
    this.garbage = [];
    this.edges = [];
    this.listener = null;
  },

  run: function (aCallback) {
    this.clear();
    this.callback = aCallback;

    this.listener = Cc["@mozilla.org/cycle-collector-logger;1"].
      createInstance(Ci.nsICycleCollectorListener);

    this.listener.disableLog = true;
    this.listener.wantAfterProcessing = true;

    this.runCC(3);
  },

  runCC: function (aCounter) {
    let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
        getInterface(Ci.nsIDOMWindowUtils);

    if (aCounter > 1) {
      utils.garbageCollect();
      setTimeout(this.runCC.bind(this, aCounter - 1), 0);
    } else {
      utils.garbageCollect(this.listener);
      this.processLog();
    }
  },

  processLog: function () {
    // Process entire heap step by step in 5K chunks
    for (let i = 0; i < 5000; i++) {
      if (!this.listener.processNext(this)) {
        this.callback();
        this.clear();
        return;
      }
    }

    // Next chunk on timeout.
    setTimeout(this.processLog.bind(this), 0);
  },

  noteRefCountedObject: function (aAddress, aRefCount, aObjectDescription) {
    let o = this.ensureObject(aAddress);
    o.address = aAddress;
    o.refcount = aRefCount;
    o.name = aObjectDescription;
  },

  noteGCedObject: function (aAddress, aMarked, aObjectDescription) {
    let o = this.ensureObject(aAddress);
    o.address = aAddress;
    o.gcmarked = aMarked;
    o.name = aObjectDescription;
  },

  noteEdge: function (aFromAddress, aToAddress, aEdgeName) {
    let fromObject = this.ensureObject(aFromAddress);
    let toObject = this.ensureObject(aToAddress);
    fromObject.edges.push({name: aEdgeName, to: toObject});
    toObject.owners.push({name: aEdgeName, from: fromObject});

    this.edges.push({
      name: aEdgeName,
      from: fromObject,
      to: toObject
    });
  },

  describeRoot: function (aAddress, aKnownEdges) {
    let o = this.ensureObject(aAddress);
    o.root = true;
    o.knownEdges = aKnownEdges;
    this.roots.push(o);
  },

  describeGarbage: function (aAddress) {
    let o = this.ensureObject(aAddress);
    o.garbage = true;
    this.garbage.push(o);
  },

  ensureObject: function (aAddress) {
    if (!this.graph[aAddress])
      this.graph[aAddress] = new CCObject();

    return this.graph[aAddress];
  },

  find: function (aText) {
    let result = [];
    for (let address in this.graph) {
      let o = this.graph[address];
      if (!o.garbage && o.name.indexOf(aText) >= 0)
        result.push(o);
    }
    return result;
  }
};

function CCObject() {
  this.name = "";
  this.address = null;
  this.refcount = 0;
  this.gcmarked = false;
  this.root = false;
  this.garbage = false;
  this.knownEdges = 0;
  this.edges = [];
  this.owners = [];
}
