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

const kEnabledPref = "browser.laterrun.enabled";
const kPagePrefRoot = "browser.laterrun.pages.";
// Number of sessions we've been active in
const kSessionCountPref = "browser.laterrun.bookkeeping.sessionCount";
// Time the profile was created at in seconds:
const kProfileCreationTime = "browser.laterrun.bookkeeping.profileCreationTime";
// Time the update was applied at in seconds:
const kUpdateAppliedTime = "browser.laterrun.bookkeeping.updateAppliedTime";

// After 50 sessions or 1 month since install, assume we will no longer be
// interested in showing anything to "new" users
const kSelfDestructSessionLimit = 50;
const kSelfDestructHoursLimit = 31 * 24;

class Page {
  constructor({
    pref,
    minimumHoursSinceInstall,
    minimumSessionCount,
    requireBoth,
    url,
  }) {
    this.pref = pref;
    this.minimumHoursSinceInstall = minimumHoursSinceInstall || 0;
    this.minimumSessionCount = minimumSessionCount || 1;
    this.requireBoth = requireBoth || false;
    this.url = url;
  }

  get hasRun() {
    return Services.prefs.getBoolPref(this.pref + "hasRun", false);
  }

  applies(sessionInfo) {
    if (this.hasRun) {
      return false;
    }
    if (this.requireBoth) {
      return (
        sessionInfo.sessionCount >= this.minimumSessionCount &&
        sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall
      );
    }
    return (
      sessionInfo.sessionCount >= this.minimumSessionCount ||
      sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall
    );
  }
}

export let LaterRun = {
  get ENABLE_REASON_NEW_PROFILE() {
    return 1;
  },
  get ENABLE_REASON_UPDATE_APPLIED() {
    return 2;
  },

  init(reason) {
    if (!this.enabled) {
      return;
    }

    if (reason == this.ENABLE_REASON_NEW_PROFILE) {
      // If this is the first run, set the time we were installed
      if (
        Services.prefs.getPrefType(kProfileCreationTime) ==
        Ci.nsIPrefBranch.PREF_INVALID
      ) {
        // We need to store seconds in order to fit within int prefs.
        Services.prefs.setIntPref(
          kProfileCreationTime,
          Math.floor(Date.now() / 1000)
        );
      }
      this.sessionCount++;
    } else if (reason == this.ENABLE_REASON_UPDATE_APPLIED) {
      Services.prefs.setIntPref(
        kUpdateAppliedTime,
        Math.floor(Services.startup.getStartupInfo().start.getTime() / 1000)
      );
    }

    if (
      this.hoursSinceInstall > kSelfDestructHoursLimit ||
      this.sessionCount > kSelfDestructSessionLimit
    ) {
      this.selfDestruct();
    }
  },

  // The enabled, hoursSinceInstall and sessionCount properties mirror the
  // preferences system, and are here for convenience.
  get enabled() {
    return Services.prefs.getBoolPref(kEnabledPref, false);
  },

  enable(reason) {
    if (!this.enabled) {
      Services.prefs.setBoolPref(kEnabledPref, true);
      this.init(reason);
    }
  },

  get hoursSinceInstall() {
    let installStampSec = Services.prefs.getIntPref(
      kProfileCreationTime,
      Date.now() / 1000
    );
    return Math.floor((Date.now() / 1000 - installStampSec) / 3600);
  },

  get hoursSinceUpdate() {
    let updateStampSec = Services.prefs.getIntPref(kUpdateAppliedTime, 0);
    return Math.floor((Date.now() / 1000 - updateStampSec) / 3600);
  },

  get sessionCount() {
    if (this._sessionCount) {
      return this._sessionCount;
    }
    return (this._sessionCount = Services.prefs.getIntPref(
      kSessionCountPref,
      0
    ));
  },

  set sessionCount(val) {
    this._sessionCount = val;
    Services.prefs.setIntPref(kSessionCountPref, val);
  },

  // Because we don't want to keep incrementing this indefinitely for no reason,
  // we will turn ourselves off after a set amount of time/sessions (see top of
  // file).
  selfDestruct() {
    Services.prefs.setBoolPref(kEnabledPref, false);
  },

  // Create an array of Page objects based on the currently set prefs
  readPages() {
    // Enumerate all the pages.
    let allPrefsForPages = Services.prefs.getChildList(kPagePrefRoot);
    let pageDataStore = new Map();
    for (let pref of allPrefsForPages) {
      let [slug, prop] = pref.substring(kPagePrefRoot.length).split(".");
      if (!pageDataStore.has(slug)) {
        pageDataStore.set(slug, {
          pref: pref.substring(0, pref.length - prop.length),
        });
      }
      if (prop == "requireBoth" || prop == "hasRun") {
        pageDataStore.get(slug)[prop] = Services.prefs.getBoolPref(pref, false);
      } else if (prop == "url") {
        pageDataStore.get(slug)[prop] = Services.prefs.getStringPref(pref, "");
      } else {
        pageDataStore.get(slug)[prop] = Services.prefs.getIntPref(pref, 0);
      }
    }
    let rv = [];
    for (let [, pageData] of pageDataStore) {
      if (pageData.url) {
        let uri = null;
        try {
          let urlString = Services.urlFormatter.formatURL(pageData.url.trim());
          uri = Services.io.newURI(urlString);
        } catch (ex) {
          console.error(
            "Invalid LaterRun page URL ",
            pageData.url,
            " ignored."
          );
          continue;
        }
        if (!uri.schemeIs("https")) {
          console.error("Insecure LaterRun page URL ", uri.spec, " ignored.");
        } else {
          pageData.url = uri.spec;
          rv.push(new Page(pageData));
        }
      }
    }
    return rv;
  },

  // Return a URL for display as a 'later run' page if its criteria are matched,
  // or null otherwise.
  // NB: will only return one page at a time; if multiple pages match, it's up
  // to the preference service which one gets shown first, and the next one
  // will be shown next startup instead.
  getURL() {
    if (!this.enabled) {
      return null;
    }
    let pages = this.readPages();
    let page = pages.find(p => p.applies(this));
    if (page) {
      Services.prefs.setBoolPref(page.pref + "hasRun", true);
      return page.url;
    }
    return null;
  },
};

LaterRun.init();
