/* 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/. */

"use strict";

const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  DevToolsWorker: "resource://devtools/shared/worker/worker.sys.mjs",
});

const WORKER_URL =
  "resource://devtools/shared/heapsnapshot/HeapAnalyses.worker.js";
var workerCounter = 0;

/**
 * A HeapAnalysesClient instance provides a developer-friendly interface for
 * interacting with a HeapAnalysesWorker. This enables users to be ignorant of
 * the message passing protocol used to communicate with the worker. The
 * HeapAnalysesClient owns the worker, and terminating the worker is done by
 * terminating the client (see the `destroy` method).
 */
const HeapAnalysesClient = (module.exports = function () {
  this._worker = new lazy.DevToolsWorker(WORKER_URL, {
    name: `HeapAnalyses-${workerCounter++}`,
    verbose: DevToolsUtils.dumpv.wantVerbose,
  });
});

/**
 * Destroy the worker, causing it to release its resources (such as heap
 * snapshots it has deserialized and read into memory). The client is no longer
 * usable after calling this method.
 */
HeapAnalysesClient.prototype.destroy = function () {
  this._worker.destroy();
  this._worker = null;
};

/**
 * Tell the worker to read into memory the heap snapshot at the given file
 * path. This is a prerequisite for asking the worker to perform various
 * analyses on a heap snapshot.
 *
 * @param {string} snapshotFilePath
 *
 * @returns Promise
 *          The promise is fulfilled if the heap snapshot is successfully
 *          deserialized and read into memory. The promise is rejected if that
 *          does not happen, eg due to a bad file path or malformed heap
 *          snapshot file.
 */
HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
  return this._worker.performTask("readHeapSnapshot", { snapshotFilePath });
};

/**
 * Tell the worker to delete all references to the snapshot and dominator trees
 * linked to the provided snapshot file path.
 *
 * @param {string} snapshotFilePath
 * @return Promise<undefined>
 */
HeapAnalysesClient.prototype.deleteHeapSnapshot = function (snapshotFilePath) {
  return this._worker.performTask("deleteHeapSnapshot", { snapshotFilePath });
};

/**
 * Request the creation time given a snapshot file path. Returns `null`
 * if snapshot does not exist.
 *
 * @param {string} snapshotFilePath
 *        The path to the snapshot.
 * @return {number?}
 *        The unix timestamp of the creation time of the snapshot, or null if
 *        snapshot does not exist.
 */
HeapAnalysesClient.prototype.getCreationTime = function (snapshotFilePath) {
  return this._worker.performTask("getCreationTime", snapshotFilePath);
};

/*** Censuses *****************************************************************/

/**
 * Ask the worker to perform a census analysis on the heap snapshot with the
 * given path. The heap snapshot at the given path must have already been read
 * into memory by the worker (see `readHeapSnapshot`).
 *
 * @param {string} snapshotFilePath
 *
 * @param {object} censusOptions
 *        A structured-cloneable object specifying the requested census's
 *        breakdown. See the "takeCensus" section of
 *        `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
 *
 * @param {object} requestOptions
 *        An object specifying options of this worker request.
 *        - {Boolean} asTreeNode
 *          Whether or not the census is returned as a CensusTreeNode,
 *          or just a breakdown report. Defaults to false.
 *          @see `devtools/shared/heapsnapshot/census-tree-node.js`
 *        - {Boolean} asInvertedTreeNode
 *          Whether or not the census is returned as an inverted
 *          CensusTreeNode. Defaults to false.
 *        - {String} filter
 *          A filter string to prune the resulting tree with. Only applies if
 *          either asTreeNode or asInvertedTreeNode is true.
 *
 * @returns Promise<Object>
 *          An object with the following properties:
 *          - report:
 *            The report generated by the given census breakdown, or a
 *            CensusTreeNode generated by the given census breakdown if
 *            `asTreeNode` is true.
 *          - parentMap:
 *            The result of calling CensusUtils.createParentMap on the generated
 *            report. Only exists if asTreeNode or asInvertedTreeNode are set.
 */
HeapAnalysesClient.prototype.takeCensus = function (
  snapshotFilePath,
  censusOptions,
  requestOptions = {}
) {
  return this._worker.performTask("takeCensus", {
    snapshotFilePath,
    censusOptions,
    requestOptions,
  });
};

/**
 * Get the individual nodes that correspond to the given census report leaf
 * indices.
 *
 * @param {object} opts
 *        An object with the following properties:
 *        - {DominatorTreeId} dominatorTreeId: The id of the dominator tree.
 *        - {Set<Number>} indices: The indices of the census report leaves we
 *          would like to get the individuals for.
 *        - {Object} censusBreakdown: The breakdown used to generate the census.
 *        - {Object} labelBreakdown: The breakdown we would like to use when
 *          labeling the resulting nodes.
 *        - {Number} maxRetainingPaths: The maximum number of retaining paths to
 *          compute for each node.
 *        - {Number} maxIndividuals: The maximum number of individual nodes to
 *          return.
 *
 * @returns {Promise<object>}
 *          A promise of an object with the following properties:
 *          - {Array<DominatorTreeNode>} nodes: An array of `DominatorTreeNode`s
 *            with their shortest paths attached, and without any dominator tree
 *            child/parent information attached. The results are sorted by
 *            retained size.
 */
HeapAnalysesClient.prototype.getCensusIndividuals = function (opts) {
  return this._worker.performTask("getCensusIndividuals", opts);
};

/**
 * Request that the worker take a census on the heap snapshots with the given
 * paths and then return the difference between them. Both heap snapshots must
 * have already been read into memory by the worker (see `readHeapSnapshot`).
 *
 * @param {string} firstSnapshotFilePath
 *        The first snapshot file path.
 *
 * @param {string} secondSnapshotFilePath
 *        The second snapshot file path.
 *
 * @param {object} censusOptions
 *        A structured-cloneable object specifying the requested census's
 *        breakdown. See the "takeCensus" section of
 *        `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
 *
 * @param {object} requestOptions
 *        An object specifying options for this request.
 *        - {Boolean} asTreeNode
 *          Whether the resulting delta report should be converted to a census
 *          tree node before returned. Defaults to false.
 *        - {Boolean} asInvertedTreeNode
 *          Whether or not the census is returned as an inverted
 *          CensusTreeNode. Defaults to false.
 *        - {String} filter
 *          A filter string to prune the resulting tree with. Only applies if
 *          either asTreeNode or asInvertedTreeNode is true.
 *
 * @returns Promise<Object>
 *          - delta:
 *            The delta report generated by diffing the two census reports, or a
 *            CensusTreeNode generated from the delta report if
 *            `requestOptions.asTreeNode` was true.
 *          - parentMap:
 *            The result of calling CensusUtils.createParentMap on the generated
 *            delta. Only exists if asTreeNode or asInvertedTreeNode are set.
 */
HeapAnalysesClient.prototype.takeCensusDiff = function (
  firstSnapshotFilePath,
  secondSnapshotFilePath,
  censusOptions,
  requestOptions = {}
) {
  return this._worker.performTask("takeCensusDiff", {
    firstSnapshotFilePath,
    secondSnapshotFilePath,
    censusOptions,
    requestOptions,
  });
};

/*** Dominator Trees **********************************************************/

/**
 * Compute the dominator tree of the heap snapshot loaded from the given file
 * path. Returns the id of the computed dominator tree.
 *
 * @param {string} snapshotFilePath
 *
 * @returns {Promise<DominatorTreeId>}
 */
HeapAnalysesClient.prototype.computeDominatorTree = function (
  snapshotFilePath
) {
  return this._worker.performTask("computeDominatorTree", snapshotFilePath);
};

/**
 * Get the initial, partial view of the dominator tree with the given id.
 *
 * @param {object} opts
 *        An object specifying options for this request.
 *        - {DominatorTreeId} dominatorTreeId
 *          The id of the dominator tree.
 *        - {Object} breakdown
 *          The breakdown used to generate node labels.
 *        - {Number} maxDepth
 *          The maximum depth to traverse down the tree to create this initial
 *          view.
 *        - {Number} maxSiblings
 *          The maximum number of siblings to visit within each traversed node's
 *          children.
 *        - {Number} maxRetainingPaths
 *          The maximum number of retaining paths to find for each node.
 *
 * @returns {Promise<DominatorTreeNode>}
 */
HeapAnalysesClient.prototype.getDominatorTree = function (opts) {
  return this._worker.performTask("getDominatorTree", opts);
};

/**
 * Get a subset of a nodes children in the dominator tree.
 *
 * @param {object} opts
 *        An object specifying options for this request.
 *        - {DominatorTreeId} dominatorTreeId
 *          The id of the dominator tree.
 *        - {NodeId} nodeId
 *          The id of the node whose children are being found.
 *        - {Object} breakdown
 *          The breakdown used to generate node labels.
 *        - {Number} startIndex
 *          The starting index within the full set of immediately dominated
 *          children of the children being requested. Children are always sorted
 *          by greatest to least retained size.
 *        - {Number} maxCount
 *          The maximum number of children to return.
 *        - {Number} maxRetainingPaths
 *          The maximum number of retaining paths to find for each node.
 *
 * @returns {Promise<object>}
 *          A promise of an object with the following properties:
 *          - {Array<DominatorTreeNode>} nodes
 *            The requested nodes that are immediately dominated by the node
 *            identified by `opts.nodeId`.
 *          - {Boolean} moreChildrenAvailable
 *            True iff there are more children available after the returned
 *            nodes.
 *          - {Array<NodeId>} path
 *            The path through the tree from the root to these node's parent, eg
 *            [root's id, child of root's id, child of child of root's id, ..., `nodeId`].
 */
HeapAnalysesClient.prototype.getImmediatelyDominated = function (opts) {
  return this._worker.performTask("getImmediatelyDominated", opts);
};
