/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
ChromeUtils.import("resource://services-common/async.js");
ChromeUtils.import("resource://services-sync/addonsreconciler.js");
ChromeUtils.import("resource://services-sync/engines/addons.js");
ChromeUtils.import("resource://services-sync/service.js");
ChromeUtils.import("resource://services-sync/util.js");

loadAddonTestFunctions();
startupManager();

function makeAddonsReconciler() {
  const log = Service.engineManager.get("addons")._log;
  const queueCaller = Async.asyncQueueCaller(log);
  return new AddonsReconciler(queueCaller);
}

add_task(async function setup() {
  Svc.Prefs.set("engine.addons", true);
  await Service.engineManager.register(AddonsEngine);
});

add_task(async function test_defaults() {
  _("Ensure new objects have reasonable defaults.");

  let reconciler = makeAddonsReconciler();
  await reconciler.ensureStateLoaded();

  Assert.ok(!reconciler._listening);
  Assert.equal("object", typeof(reconciler.addons));
  Assert.equal(0, Object.keys(reconciler.addons).length);
  Assert.equal(0, reconciler._changes.length);
  Assert.equal(0, reconciler._listeners.length);
});

add_task(async function test_load_state_empty_file() {
  _("Ensure loading from a missing file results in defaults being set.");

  let reconciler = makeAddonsReconciler();
  await reconciler.ensureStateLoaded();

  let loaded = await reconciler.loadState();
  Assert.ok(!loaded);

  Assert.equal("object", typeof(reconciler.addons));
  Assert.equal(0, Object.keys(reconciler.addons).length);
  Assert.equal(0, reconciler._changes.length);
});

add_task(async function test_install_detection() {
  _("Ensure that add-on installation results in appropriate side-effects.");

  let reconciler = makeAddonsReconciler();
  await reconciler.ensureStateLoaded();
  reconciler.startListening();

  let before = new Date();
  let addon = await installAddon("test_bootstrap1_1");
  let after = new Date();

  Assert.equal(1, Object.keys(reconciler.addons).length);
  Assert.ok(addon.id in reconciler.addons);
  let record = reconciler.addons[addon.id];

  const KEYS = ["id", "guid", "enabled", "installed", "modified", "type",
                "scope", "foreignInstall"];
  for (let key of KEYS) {
    Assert.ok(key in record);
    Assert.notEqual(null, record[key]);
  }

  Assert.equal(addon.id, record.id);
  Assert.equal(addon.syncGUID, record.guid);
  Assert.ok(record.enabled);
  Assert.ok(record.installed);
  Assert.ok(record.modified >= before && record.modified <= after);
  Assert.equal("extension", record.type);
  Assert.ok(!record.foreignInstall);

  Assert.equal(1, reconciler._changes.length);
  let change = reconciler._changes[0];
  Assert.ok(change[0] >= before && change[1] <= after);
  Assert.equal(CHANGE_INSTALLED, change[1]);
  Assert.equal(addon.id, change[2]);

  await uninstallAddon(addon);
});

add_task(async function test_uninstall_detection() {
  _("Ensure that add-on uninstallation results in appropriate side-effects.");

  let reconciler = makeAddonsReconciler();
  await reconciler.ensureStateLoaded();
  reconciler.startListening();

  reconciler._addons = {};
  reconciler._changes = [];

  let addon = await installAddon("test_bootstrap1_1");
  let id = addon.id;

  reconciler._changes = [];
  await uninstallAddon(addon, reconciler);

  Assert.equal(1, Object.keys(reconciler.addons).length);
  Assert.ok(id in reconciler.addons);

  let record = reconciler.addons[id];
  Assert.ok(!record.installed);

  Assert.equal(1, reconciler._changes.length);
  let change = reconciler._changes[0];
  Assert.equal(CHANGE_UNINSTALLED, change[1]);
  Assert.equal(id, change[2]);
});

add_task(async function test_load_state_future_version() {
  _("Ensure loading a file from a future version results in no data loaded.");

  const FILENAME = "TEST_LOAD_STATE_FUTURE_VERSION";

  let reconciler = makeAddonsReconciler();
  await reconciler.ensureStateLoaded();

  // First we populate our new file.
  let state = {version: 100, addons: {foo: {}}, changes: [[1, 1, "foo"]]};

  // jsonSave() expects an object with ._log, so we give it a reconciler
  // instance.
  await Utils.jsonSave(FILENAME, reconciler, state);

  let loaded = await reconciler.loadState(FILENAME);
  Assert.ok(!loaded);

  Assert.equal("object", typeof(reconciler.addons));
  Assert.equal(0, Object.keys(reconciler.addons).length);
  Assert.equal(0, reconciler._changes.length);
});

add_task(async function test_prune_changes_before_date() {
  _("Ensure that old changes are pruned properly.");

  let reconciler = makeAddonsReconciler();
  await reconciler.ensureStateLoaded();
  reconciler._changes = [];

  let now = new Date();
  const HOUR_MS = 1000 * 60 * 60;

  _("Ensure pruning an empty changes array works.");
  reconciler.pruneChangesBeforeDate(now);
  Assert.equal(0, reconciler._changes.length);

  let old = new Date(now.getTime() - HOUR_MS);
  let young = new Date(now.getTime() - 1000);
  reconciler._changes.push([old, CHANGE_INSTALLED, "foo"]);
  reconciler._changes.push([young, CHANGE_INSTALLED, "bar"]);
  Assert.equal(2, reconciler._changes.length);

  _("Ensure pruning with an old time won't delete anything.");
  let threshold = new Date(old.getTime() - 1);
  reconciler.pruneChangesBeforeDate(threshold);
  Assert.equal(2, reconciler._changes.length);

  _("Ensure pruning a single item works.");
  threshold = new Date(young.getTime() - 1000);
  reconciler.pruneChangesBeforeDate(threshold);
  Assert.equal(1, reconciler._changes.length);
  Assert.notEqual(undefined, reconciler._changes[0]);
  Assert.equal(young, reconciler._changes[0][0]);
  Assert.equal("bar", reconciler._changes[0][2]);

  _("Ensure pruning all changes works.");
  reconciler._changes.push([old, CHANGE_INSTALLED, "foo"]);
  reconciler.pruneChangesBeforeDate(now);
  Assert.equal(0, reconciler._changes.length);
});
