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

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  CLIENT_NOT_CONFIGURED: "resource://services-sync/constants.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "syncUsername",
  "services.sync.username"
);

/**
 * Sync's XPCOM service.
 *
 * It is named "Weave" for historical reasons.
 *
 * It's worth noting how Sync is lazily loaded. We register a timer that
 * loads Sync a few seconds after app startup. This is so Sync does not
 * adversely affect application start time.
 *
 * If Sync is not configured, no extra Sync code is loaded. If an
 * external component (say the UI) needs to interact with Sync, it
 * should use the promise-base function whenLoaded() - something like the
 * following:
 *
 * // 1. Grab a handle to the Sync XPCOM service.
 * let service = Cc["@mozilla.org/weave/service;1"]
 *                 .getService(Components.interfaces.nsISupports)
 *                 .wrappedJSObject;
 *
 * // 2. Use the .then method of the promise.
 * service.whenLoaded().then(() => {
 *   // You are free to interact with "Weave." objects.
 *   return;
 * });
 *
 * And that's it!  However, if you really want to avoid promises and do it
 * old-school, then
 *
 * // 1. Get a reference to the service as done in (1) above.
 *
 * // 2. Check if the service has been initialized.
 * if (service.ready) {
 *   // You are free to interact with "Weave." objects.
 *   return;
 * }
 *
 * // 3. Install "ready" listener.
 * Services.obs.addObserver(function onReady() {
 *   Services.obs.removeObserver(onReady, "weave:service:ready");
 *
 *   // You are free to interact with "Weave." objects.
 * }, "weave:service:ready", false);
 *
 * // 4. Trigger loading of Sync.
 * service.ensureLoaded();
 */
export function WeaveService() {
  this.wrappedJSObject = this;
  this.ready = false;
}

WeaveService.prototype = {
  classID: Components.ID("{74b89fb0-f200-4ae8-a3ec-dd164117f6de}"),

  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  get Weave() {
    const { Weave } = ChromeUtils.importESModule(
      "resource://services-sync/main.sys.mjs"
    );
    return Weave;
  },

  ensureLoaded() {
    // Side-effect of accessing the service is that it is instantiated.
    this.Weave.Service;
  },

  whenLoaded() {
    if (this.ready) {
      return Promise.resolve();
    }
    let onReadyPromise = new Promise(resolve => {
      Services.obs.addObserver(function onReady() {
        Services.obs.removeObserver(onReady, "weave:service:ready");
        resolve();
      }, "weave:service:ready");
    });
    this.ensureLoaded();
    return onReadyPromise;
  },

  init() {
    // Force Weave service to load if it hasn't triggered from overlays
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this.timer.initWithCallback(
      {
        notify: () => {
          let isConfigured = false;
          // We only load more if it looks like Sync is configured.
          if (this.enabled) {
            // We have an associated FxAccount. So, do a more thorough check.
            // This will import a number of modules and thus increase memory
            // accordingly. We could potentially copy code performed by
            // this check into this file if our above code is yielding too
            // many false positives.
            var { Weave } = ChromeUtils.importESModule(
              "resource://services-sync/main.sys.mjs"
            );
            isConfigured =
              Weave.Status.checkSetup() != lazy.CLIENT_NOT_CONFIGURED;
          }
          if (isConfigured) {
            this.ensureLoaded();
          }
        },
      },
      10000,
      Ci.nsITimer.TYPE_ONE_SHOT
    );
  },

  /**
   * Whether Sync appears to be enabled.
   *
   * This returns true if we have an associated FxA account and Sync is enabled.
   *
   * It does *not* perform a robust check to see if the client is working.
   * For that, you'll want to check Weave.Status.checkSetup().
   */
  get enabled() {
    return (
      !!lazy.syncUsername &&
      Services.prefs.getBoolPref("identity.fxaccounts.enabled")
    );
  },
};

export function AboutWeaveLog() {}
AboutWeaveLog.prototype = {
  classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"),

  QueryInterface: ChromeUtils.generateQI([
    "nsIAboutModule",
    "nsISupportsWeakReference",
  ]),

  getURIFlags() {
    return 0;
  },

  newChannel(aURI, aLoadInfo) {
    let dir = lazy.FileUtils.getDir("ProfD", ["weave", "logs"]);
    try {
      dir.create(Ci.nsIFile.DIRECTORY_TYPE, lazy.FileUtils.PERMS_DIRECTORY);
    } catch (ex) {
      if (ex.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
        throw ex;
      }
      // Ignore the exception due to a directory that already exists.
    }
    let uri = Services.io.newFileURI(dir);
    let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);

    channel.originalURI = aURI;

    // Ensure that the about page has the same privileges as a regular directory
    // view. That way links to files can be opened. make sure we use the correct
    // origin attributes when creating the principal for accessing the
    // about:sync-log data.
    let principal = Services.scriptSecurityManager.createContentPrincipal(
      uri,
      aLoadInfo.originAttributes
    );

    channel.owner = principal;
    return channel;
  },
};