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

let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});

function run_test() {
  run_next_test();
}

function readFile(file) {
  let stream = Cc['@mozilla.org/network/file-input-stream;1']
               .createInstance(Ci.nsIFileInputStream);
  stream.init(file, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);

  let sis = Cc["@mozilla.org/scriptableinputstream;1"]
            .createInstance(Ci.nsIScriptableInputStream);
  sis.init(stream);
  let contents = sis.read(file.fileSize);
  sis.close();
  return contents;
}

function checkDirectoryContains(dir, files) {
  print("checking " + dir.path + " - should contain " + Object.keys(files));
  let seen = new Set();
  let enumerator = dir.directoryEntries;
  while (enumerator.hasMoreElements()) {
    let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
    print("found file: " + file.path);
    Assert.ok(file.leafName in files, file.leafName + " exists, but shouldn't");

    let expectedContents = files[file.leafName];
    if (typeof expectedContents != "string") {
      // it's a subdir - recurse!
      Assert.ok(file.isDirectory(), "should be a subdir");
      let newDir = dir.clone();
      newDir.append(file.leafName);
      checkDirectoryContains(newDir, expectedContents);
    } else {
      Assert.ok(!file.isDirectory(), "should be a regular file");
      let contents = readFile(file);
      Assert.equal(contents, expectedContents);
    }
    seen.add(file.leafName);
  }
  let missing = [x for (x in files) if (!seen.has(x))];
  Assert.deepEqual(missing, [], "no missing files in " + dir.path);
}

function getTestDirs() {
  // we make a directory structure in a temp dir which mirrors what we are
  // testing.
  let tempDir = do_get_tempdir();
  let srcDir = tempDir.clone();
  srcDir.append("test_source_dir");
  srcDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

  let targetDir = tempDir.clone();
  targetDir.append("test_target_dir");
  targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

  // no need to cleanup these dirs - the xpcshell harness will do it for us.
  return [srcDir, targetDir];
}

function writeToFile(dir, leafName, contents) {
  let file = dir.clone();
  file.append(leafName);

  let outputStream = FileUtils.openFileOutputStream(file);
  outputStream.write(contents, contents.length);
  outputStream.close();
}

function promiseMigrator(name, srcDir, targetDir) {
  let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=firefox"]
                 .createInstance(Ci.nsISupports)
                 .wrappedJSObject;
  let migrators = migrator._getResourcesInternal(srcDir, targetDir);
  for (let m of migrators) {
    if (m.name == name) {
      return new Promise((resolve, reject) => {
        m.migrate(resolve);
      });
    }
  }
  throw new Error("failed to find the " + name + " migrator");
}

function promiseFHRMigrator(srcDir, targetDir) {
  return promiseMigrator("healthreporter", srcDir, targetDir);
}

add_task(function* test_empty() {
  let [srcDir, targetDir] = getTestDirs();
  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true with empty directories");
  // check both are empty
  checkDirectoryContains(srcDir, {});
  checkDirectoryContains(targetDir, {});
});

add_task(function* test_just_sqlite() {
  let [srcDir, targetDir] = getTestDirs();

  let contents = "hello there\n\n";
  writeToFile(srcDir, "healthreport.sqlite", contents);

  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true with sqlite file copied");

  checkDirectoryContains(targetDir, {
    "healthreport.sqlite": contents,
  });
});

add_task(function* test_sqlite_extras() {
  let [srcDir, targetDir] = getTestDirs();

  let contents_sqlite = "hello there\n\n";
  writeToFile(srcDir, "healthreport.sqlite", contents_sqlite);

  let contents_wal = "this is the wal\n\n";
  writeToFile(srcDir, "healthreport.sqlite-wal", contents_wal);

  // and the -shm - this should *not* be copied.
  writeToFile(srcDir, "healthreport.sqlite-shm", "whatever");

  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true with sqlite file copied");

  checkDirectoryContains(targetDir, {
    "healthreport.sqlite": contents_sqlite,
    "healthreport.sqlite-wal": contents_wal,
  });
});

add_task(function* test_sqlite_healthreport_not_dir() {
  let [srcDir, targetDir] = getTestDirs();

  let contents = "hello there\n\n";
  writeToFile(srcDir, "healthreport.sqlite", contents);
  writeToFile(srcDir, "healthreport", "I'm a file but should be a directory");

  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true even though the directory was a file");
  // We should have only the sqlite file
  checkDirectoryContains(targetDir, {
    "healthreport.sqlite": contents,
  });
});

add_task(function* test_sqlite_healthreport_empty() {
  let [srcDir, targetDir] = getTestDirs();

  let contents = "hello there\n\n";
  writeToFile(srcDir, "healthreport.sqlite", contents);

  // create an empty 'healthreport' subdir.
  let subDir = srcDir.clone();
  subDir.append("healthreport");
  subDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true");

  // we should end up with the .sqlite file and an empty subdir in the target.
  checkDirectoryContains(targetDir, {
    "healthreport.sqlite": contents,
    "healthreport": {},
  });
});

add_task(function* test_sqlite_healthreport_contents() {
  let [srcDir, targetDir] = getTestDirs();

  let contents = "hello there\n\n";
  writeToFile(srcDir, "healthreport.sqlite", contents);

  // create an empty 'healthreport' subdir.
  let subDir = srcDir.clone();
  subDir.append("healthreport");
  subDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

  writeToFile(subDir, "file1", "this is file 1");
  writeToFile(subDir, "file2", "this is file 2");

  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true");

  // we should end up with the .sqlite file and an empty subdir in the target.
  checkDirectoryContains(targetDir, {
    "healthreport.sqlite": contents,
    "healthreport": {
      "file1": "this is file 1",
      "file2": "this is file 2",
    },
  });
});

add_task(function* test_datareporting_empty() {
  let [srcDir, targetDir] = getTestDirs();

  // create an empty 'datareporting' subdir.
  let subDir = srcDir.clone();
  subDir.append("datareporting");
  subDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true");

  // we should end up with nothing at all in the destination - state.json was
  // missing so we didn't even create the target dir.
  checkDirectoryContains(targetDir, {});
});

add_task(function* test_datareporting_many() {
  let [srcDir, targetDir] = getTestDirs();

  // create an empty 'datareporting' subdir.
  let subDir = srcDir.clone();
  subDir.append("datareporting");
  subDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

  writeToFile(subDir, "state.json", "should be copied");
  writeToFile(subDir, "something.else", "should not");

  let ok = yield promiseFHRMigrator(srcDir, targetDir);
  Assert.ok(ok, "callback should have been true");

  checkDirectoryContains(targetDir, {
    "datareporting" : {
      "state.json": "should be copied",
    }
  });
});

add_task(function* test_times_migration() {
  let [srcDir, targetDir] = getTestDirs();

  // create a times.json in the source directory.
  let contents = JSON.stringify({created: 1234});
  writeToFile(srcDir, "times.json", contents);

  let earliest = Date.now();
  let ok = yield promiseMigrator("times", srcDir, targetDir);
  Assert.ok(ok, "callback should have been true");
  let latest = Date.now();

  let timesFile = targetDir.clone();
  timesFile.append("times.json");

  let raw = readFile(timesFile);
  let times = JSON.parse(raw);
  Assert.ok(times.reset >= earliest && times.reset <= latest);
  // and it should have left the creation time alone.
  Assert.equal(times.created, 1234);
});
