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

// Portions of this file are originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT

"use strict";

/* import-globals-from /testing/mochitest/tests/SimpleTest/WorkerSimpleTest.js */
importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js");

importScripts("file_ioutils_test_fixtures.js");

self.onmessage = async function () {
  const tmpDir = await PathUtils.getTempDir();

  // IOUtils functionality is the same when called from the main thread, or a
  // web worker. These tests are a modified subset of the main thread tests, and
  // serve as a confidence check that the implementation is thread-safe.
  await test_api_is_available_on_worker();
  await test_full_read_and_write();
  await test_move_file();
  await test_copy_file();
  await test_make_directory();

  finish();
  info("test_ioutils_worker.xhtml: Test finished");

  async function test_api_is_available_on_worker() {
    ok(self.IOUtils, "IOUtils is present in web workers");
  }

  async function test_full_read_and_write() {
    // Write a file.
    const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_numbers.tmp");
    const bytes = Uint8Array.of(...new Array(50).keys());
    const bytesWritten = await IOUtils.write(tmpFileName, bytes);
    is(bytesWritten, 50, "IOUtils::write can write entire byte array to file");

    // Read it back.
    let fileContents = await IOUtils.read(tmpFileName);
    ok(
      _deepEqual(bytes, fileContents) && bytes.length == fileContents.length,
      "IOUtils::read can read back entire file"
    );

    const tooManyBytes = bytes.length + 1;
    fileContents = await IOUtils.read(tmpFileName, { maxBytes: tooManyBytes });
    ok(
      _deepEqual(bytes, fileContents) && fileContents.length == bytes.length,
      "IOUtils::read can read entire file when requested maxBytes is too large"
    );

    await cleanup(tmpFileName);
  }

  async function test_move_file() {
    const src = PathUtils.join(tmpDir, "test_move_file_src.tmp");
    const dest = PathUtils.join(tmpDir, "test_move_file_dest.tmp");
    const bytes = Uint8Array.of(...new Array(50).keys());
    await IOUtils.write(src, bytes);

    await IOUtils.move(src, dest);
    ok(
      !(await fileExists(src)) && (await fileExists(dest)),
      "IOUtils::move can move files from a worker"
    );

    await cleanup(dest);
  }

  async function test_copy_file() {
    const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_orig.tmp");
    const destFileName = PathUtils.join(tmpDir, "test_ioutils_copy.tmp");
    await createFile(tmpFileName, "original");

    await IOUtils.copy(tmpFileName, destFileName);
    ok(
      (await fileExists(tmpFileName)) &&
        (await fileHasTextContents(destFileName, "original")),
      "IOUtils::copy can copy source to dest in same directory"
    );

    await cleanup(tmpFileName, destFileName);
  }

  async function test_make_directory() {
    const dir = PathUtils.join(tmpDir, "test_make_dir.tmp.d");
    await IOUtils.makeDirectory(dir);
    const stat = await IOUtils.stat(dir);
    is(
      stat.type,
      "directory",
      "IOUtils::makeDirectory can make a new directory from a worker"
    );

    await cleanup(dir);
  }
};

// This is copied from the ObjectUtils module, as it is difficult to translate
// file_ioutils_test_fixtures.js into a ES module and have it used in non-module
// contexts.

// ... Start of previously MIT-licensed code.
// This deepEqual implementation is originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT

function _deepEqual(a, b) {
  // The numbering below refers to sections in the CommonJS spec.

  // 7.1 All identical values are equivalent, as determined by ===.
  if (a === b) {
    return true;
    // 7.2 If the b value is a Date object, the a value is
    // equivalent if it is also a Date object that refers to the same time.
  }
  let aIsDate = instanceOf(a, "Date");
  let bIsDate = instanceOf(b, "Date");
  if (aIsDate || bIsDate) {
    if (!aIsDate || !bIsDate) {
      return false;
    }
    if (isNaN(a.getTime()) && isNaN(b.getTime())) {
      return true;
    }
    return a.getTime() === b.getTime();
    // 7.3 If the b value is a RegExp object, the a value is
    // equivalent if it is also a RegExp object with the same source and
    // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
  }
  let aIsRegExp = instanceOf(a, "RegExp");
  let bIsRegExp = instanceOf(b, "RegExp");
  if (aIsRegExp || bIsRegExp) {
    return (
      aIsRegExp &&
      bIsRegExp &&
      a.source === b.source &&
      a.global === b.global &&
      a.multiline === b.multiline &&
      a.lastIndex === b.lastIndex &&
      a.ignoreCase === b.ignoreCase
    );
    // 7.4 Other pairs that do not both pass typeof value == "object",
    // equivalence is determined by ==.
  }
  if (typeof a != "object" || typeof b != "object") {
    return a == b;
  }
  // 7.5 For all other Object pairs, including Array objects, equivalence is
  // determined by having the same number of owned properties (as verified
  // with Object.prototype.hasOwnProperty.call), the same set of keys
  // (although not necessarily the same order), equivalent values for every
  // corresponding key, and an identical 'prototype' property. Note: this
  // accounts for both named and indexed properties on Arrays.
  return objEquiv(a, b);
}

function instanceOf(object, type) {
  return Object.prototype.toString.call(object) == "[object " + type + "]";
}

function isUndefinedOrNull(value) {
  return value === null || value === undefined;
}

function isArguments(object) {
  return instanceOf(object, "Arguments");
}

function objEquiv(a, b) {
  if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
    return false;
  }
  // An identical 'prototype' property.
  if ((a.prototype || undefined) != (b.prototype || undefined)) {
    return false;
  }
  // Object.keys may be broken through screwy arguments passing. Converting to
  // an array solves the problem.
  if (isArguments(a)) {
    if (!isArguments(b)) {
      return false;
    }
    a = Array.prototype.slice.call(a);
    b = Array.prototype.slice.call(b);
    return _deepEqual(a, b);
  }
  let ka, kb;
  try {
    ka = Object.keys(a);
    kb = Object.keys(b);
  } catch (e) {
    // Happens when one is a string literal and the other isn't
    return false;
  }
  // Having the same number of owned properties (keys incorporates
  // hasOwnProperty)
  if (ka.length != kb.length) {
    return false;
  }
  // The same set of keys (although not necessarily the same order),
  ka.sort();
  kb.sort();
  // Equivalent values for every corresponding key, and possibly expensive deep
  // test
  for (let key of ka) {
    if (!_deepEqual(a[key], b[key])) {
      return false;
    }
  }
  return true;
}

// ... End of previously MIT-licensed code.
