/* 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 { Actor } = require("resource://devtools/shared/protocol.js");
const {
  networkParentSpec,
} = require("resource://devtools/shared/specs/network-parent.js");

const {
  TYPES: { NETWORK_EVENT },
  getResourceWatcher,
} = require("resource://devtools/server/actors/resources/index.js");

/**
 * This actor manages all network functionality running
 * in the parent process.
 *
 * @class
 */
class NetworkParentActor extends Actor {
  constructor(watcherActor) {
    super(watcherActor.conn, networkParentSpec);
    this.watcherActor = watcherActor;
  }

  // Caches the throttling data so that on clearing the
  // current network throttling it can be reset to the previous.
  defaultThrottleData = undefined;

  isEqual(next, current) {
    // If both objects, check all entries
    if (current && next && next == current) {
      return Object.entries(current).every(([k, v]) => {
        return next[k] === v;
      });
    }
    return false;
  }

  get networkEventWatcher() {
    return getResourceWatcher(this.watcherActor, NETWORK_EVENT);
  }

  setNetworkThrottling(throttleData) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }

    if (throttleData !== null) {
      throttleData = {
        latencyMean: throttleData.latency,
        latencyMax: throttleData.latency,
        downloadBPSMean: throttleData.downloadThroughput,
        downloadBPSMax: throttleData.downloadThroughput,
        uploadBPSMean: throttleData.uploadThroughput,
        uploadBPSMax: throttleData.uploadThroughput,
      };
    }

    const currentThrottleData = this.networkEventWatcher.getThrottleData();
    if (this.isEqual(throttleData, currentThrottleData)) {
      return;
    }

    if (this.defaultThrottleData === undefined) {
      this.defaultThrottleData = currentThrottleData;
    }

    this.networkEventWatcher.setThrottleData(throttleData);
  }

  getNetworkThrottling() {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    const throttleData = this.networkEventWatcher.getThrottleData();
    if (!throttleData) {
      return null;
    }
    return {
      downloadThroughput: throttleData.downloadBPSMax,
      uploadThroughput: throttleData.uploadBPSMax,
      latency: throttleData.latencyMax,
    };
  }

  clearNetworkThrottling() {
    if (this.defaultThrottleData !== undefined) {
      this.setNetworkThrottling(this.defaultThrottleData);
    }
  }

  setSaveRequestAndResponseBodies(save) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    this.networkEventWatcher.setSaveRequestAndResponseBodies(save);
  }

  /**
   * Sets the urls to block.
   *
   * @param Array urls
   *         The response packet - stack trace.
   */
  setBlockedUrls(urls) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    this.networkEventWatcher.setBlockedUrls(urls);
    return {};
  }

  /**
   * Returns the urls that are block
   */
  getBlockedUrls() {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    return this.networkEventWatcher.getBlockedUrls();
  }

  /**
   * Blocks the requests based on the filters
   *
   * @param {object} filters
   */
  blockRequest(filters) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    this.networkEventWatcher.blockRequest(filters);
  }

  /**
   * Unblocks requests based on the filters
   *
   * @param {object} filters
   */
  unblockRequest(filters) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    this.networkEventWatcher.unblockRequest(filters);
  }

  setPersist(enabled) {
    // We will always call this method, even if we are still using legacy listener.
    // Do not throw, we will always persist in that deprecated codepath.
    if (!this.networkEventWatcher) {
      return;
    }
    this.networkEventWatcher.setPersist(enabled);
  }

  override(url, path) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    this.networkEventWatcher.override(url, path);
    return {};
  }

  removeOverride(url) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    this.networkEventWatcher.removeOverride(url);
  }

  setLocalModeMappings(mappings) {
    if (!this.networkEventWatcher) {
      throw new Error("Not listening for network events");
    }
    this.networkEventWatcher.setLocalModeMappings(mappings);
  }

  setBodyLimit(bodyLimit) {
    // We will always call this method, even if we are still using legacy listener.
    // Do not throw, we will always persist in that deprecated codepath.
    if (!this.networkEventWatcher) {
      return;
    }
    this.networkEventWatcher.setBodyLimit(bodyLimit);
  }
}

exports.NetworkParentActor = NetworkParentActor;
