/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const PREF_DISABLED = "pdfjs.disabled";
const PREF_MIGRATION_VERSION = "pdfjs.migrationVersion";
const PREF_PREVIOUS_ACTION = "pdfjs.previousHandler.preferredAction";
const PREF_PREVIOUS_ASK = "pdfjs.previousHandler.alwaysAskBeforeHandling";
const PREF_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
const PDF_CONTENT_TYPE = "application/pdf";

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

var Svc = {};
XPCOMUtils.defineLazyServiceGetter(
  Svc,
  "mime",
  "@mozilla.org/mime;1",
  "nsIMIMEService"
);
XPCOMUtils.defineLazyServiceGetter(
  Svc,
  "handlerService",
  "@mozilla.org/uriloader/handler-service;1",
  "nsIHandlerService"
);

// We're supposed to get this type of thing from the OS, and generally we do.
// But doing so is expensive, so on startup paths we can use this to make the
// handler service get and store the Right Thing (it just goes into a JSON
// file) and avoid the performance issues.
const gPdfFakeHandlerInfo = {
  QueryInterface: ChromeUtils.generateQI(["nsIMIMEInfo"]),
  getFileExtensions() {
    return ["pdf"];
  },
  possibleApplicationHandlers: Cc["@mozilla.org/array;1"].createInstance(
    Ci.nsIMutableArray
  ),
  extensionExists(ext) {
    return ext == "pdf";
  },
  alwaysAskBeforeHandling: false,
  preferredAction: Ci.nsIHandlerInfo.handleInternally,
  type: PDF_CONTENT_TYPE,
};

export var PdfJs = {
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
  _initialized: false,
  _cachedIsDefault: true,

  init: function init(isNewProfile) {
    if (
      Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT
    ) {
      throw new Error(
        "PdfJs.init should only get called in the parent process."
      );
    }
    this.initPrefs();
    this.checkIsDefault(isNewProfile);
  },

  initPrefs: function initPrefs() {
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    if (this._prefDisabled) {
      this._unbecomeHandler();
    } else {
      this._migrate();
    }

    // Listen for when a different pdf handler is chosen.
    Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED);
  },

  uninit: function uninit() {
    if (this._initialized) {
      Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED);
      this._initialized = false;
    }
  },

  _migrate: function migrate() {
    const VERSION = 2;
    var currentVersion = Services.prefs.getIntPref(PREF_MIGRATION_VERSION, 0);
    if (currentVersion >= VERSION) {
      return;
    }
    // Make pdf.js the default pdf viewer on the first migration.
    if (currentVersion < 1) {
      this._becomeHandler();
    }
    if (currentVersion < 2) {
      // cleaning up of unused database preference (see #3994)
      Services.prefs.clearUserPref("pdfjs.database");
    }
    Services.prefs.setIntPref(PREF_MIGRATION_VERSION, VERSION);
  },

  _becomeHandler: function _becomeHandler() {
    // Normally, this happens in the first run at some point, where the
    // handler service doesn't have any info on user preferences yet.
    // Don't bother storing old defaults in this case, as they're
    // meaningless anyway.
    if (!Svc.handlerService.exists(gPdfFakeHandlerInfo)) {
      // Store the requisite info into the DB, and nothing else:
      Svc.handlerService.store(gPdfFakeHandlerInfo);
    } else {
      let handlerInfo = Svc.mime.getFromTypeAndExtension(
        PDF_CONTENT_TYPE,
        "pdf"
      );
      let prefs = Services.prefs;
      if (
        handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally &&
        handlerInfo.alwaysAskBeforeHandling !== false
      ) {
        // Store the previous settings of preferredAction and
        // alwaysAskBeforeHandling in case we need to revert them in a hotfix that
        // would turn pdf.js off.
        prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction);
        prefs.setBoolPref(
          PREF_PREVIOUS_ASK,
          handlerInfo.alwaysAskBeforeHandling
        );
      }

      // Change and save mime handler settings.
      handlerInfo.alwaysAskBeforeHandling = false;
      handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
      Svc.handlerService.store(handlerInfo);
    }
  },

  _unbecomeHandler: function _unbecomeHandler() {
    let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf");
    if (handlerInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally) {
      // If PDFJS is disabled, but we're still marked to handleInternally,
      // either put it back to what it was, or remove it.
      if (Services.prefs.prefHasUserValue(PREF_PREVIOUS_ACTION)) {
        handlerInfo.preferredAction =
          Services.prefs.getIntPref(PREF_PREVIOUS_ACTION);
        handlerInfo.alwaysAskBeforeHandling =
          Services.prefs.getBoolPref(PREF_PREVIOUS_ASK);
        Svc.handlerService.store(handlerInfo);
      } else {
        Svc.handlerService.remove(handlerInfo);
        // Clear migration pref so the handler comes back if reenabled
        Services.prefs.clearUserPref(PREF_MIGRATION_VERSION);
      }
    }
  },

  /**
   * @param isNewProfile used to decide whether we need to check the
   *                     handler service to see if the user configured
   *                     pdfs differently. If we're on a new profile,
   *                     there's no need to check.
   */
  _isDefault(isNewProfile) {
    let { processType, PROCESS_TYPE_DEFAULT } = Services.appinfo;
    if (processType !== PROCESS_TYPE_DEFAULT) {
      throw new Error(
        "isDefault should only get called in the parent process."
      );
    }

    if (this._prefDisabled) {
      return false;
    }

    // Don't bother with the handler service on a new profile:
    if (isNewProfile) {
      return true;
    }
    // Check if the 'application/pdf' preview handler is configured properly.
    let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf");
    return (
      !handlerInfo.alwaysAskBeforeHandling &&
      handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally
    );
  },

  checkIsDefault(isNewProfile) {
    this._cachedIsDefault = this._isDefault(isNewProfile);
    Services.prefs.setBoolPref(
      PREF_ISDEFAULT_CACHE_STATE,
      this._cachedIsDefault
    );
  },

  cachedIsDefault() {
    return this._cachedIsDefault;
  },

  // nsIObserver
  observe() {
    this.checkIsDefault();
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  PdfJs,
  "_prefDisabled",
  PREF_DISABLED,
  false,
  () => PdfJs.checkIsDefault()
);
