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

// The path to the top level directory.
const depth = "../../../../";

var testGenerator;
var testHarnessGenerator;
var workerScriptPaths = [];

loadScript("dom/quota/test/common/mochitest.js");

function loadScript(path) {
  const url = new URL(depth + path, window.location.href);
  SpecialPowers.Services.scriptloader.loadSubScript(url.href, this);
}

function loadWorkerScript(path) {
  const url = new URL(depth + path, window.location.href);
  workerScriptPaths.push(url.href);
}

function* testHarnessSteps() {
  function nextTestHarnessStep(val) {
    testHarnessGenerator.next(val);
  }

  info("Enabling storage testing");

  enableStorageTesting().then(nextTestHarnessStep);
  yield undefined;

  info("Pushing preferences");

  SpecialPowers.pushPrefEnv(
    {
      set: [["dom.storageManager.prompt.testing", true]],
    },
    nextTestHarnessStep
  );
  yield undefined;

  info("Clearing old databases");

  clearAllDatabases(nextTestHarnessStep);
  yield undefined;

  info("Running test in given scopes");

  if (workerScriptPaths.length) {
    info("Running test in a worker");

    let workerScriptBlob = new Blob(["(" + workerScript.toString() + ")();"], {
      type: "text/javascript",
    });
    let workerScriptURL = URL.createObjectURL(workerScriptBlob);

    let worker = new Worker(workerScriptURL);

    worker.onerror = function (event) {
      ok(false, "Worker had an error: " + event.message);
      worker.terminate();
      nextTestHarnessStep();
    };

    worker.onmessage = function (event) {
      let message = event.data;
      switch (message.op) {
        case "ok":
          ok(message.condition, message.name + " - " + message.diag);
          break;

        case "todo":
          todo(message.condition, message.name, message.diag);
          break;

        case "info":
          info(message.msg);
          break;

        case "ready":
          worker.postMessage({ op: "load", files: workerScriptPaths });
          break;

        case "loaded":
          worker.postMessage({ op: "start" });
          break;

        case "done":
          ok(true, "Worker finished");
          nextTestHarnessStep();
          break;

        case "clearAllDatabases":
          clearAllDatabases(function () {
            worker.postMessage({ op: "clearAllDatabasesDone" });
          });
          break;

        default:
          ok(
            false,
            "Received a bad message from worker: " + JSON.stringify(message)
          );
          nextTestHarnessStep();
      }
    };

    URL.revokeObjectURL(workerScriptURL);

    yield undefined;

    worker.terminate();
    worker = null;

    clearAllDatabases(nextTestHarnessStep);
    yield undefined;
  }

  info("Running test in main thread");

  // Now run the test script in the main thread.
  if (testSteps.constructor.name === "AsyncFunction") {
    SimpleTest.registerCleanupFunction(async function () {
      await requestFinished(clearAllDatabases());
    });

    add_task(testSteps);
  } else {
    testGenerator = testSteps();
    testGenerator.next();

    yield undefined;
  }
}

if (!window.runTest) {
  window.runTest = function () {
    SimpleTest.waitForExplicitFinish();
    testHarnessGenerator = testHarnessSteps();
    testHarnessGenerator.next();
  };
}

function finishTest() {
  SimpleTest.executeSoon(function () {
    clearAllDatabases(function () {
      SimpleTest.finish();
    });
  });
}

function grabArgAndContinueHandler(arg) {
  testGenerator.next(arg);
}

function continueToNextStep() {
  SimpleTest.executeSoon(function () {
    testGenerator.next();
  });
}

function continueToNextStepSync() {
  testGenerator.next();
}

function workerScript() {
  "use strict";

  self.testGenerator = null;

  self.repr = function (_thing_) {
    if (typeof _thing_ == "undefined") {
      return "undefined";
    }

    let str;

    try {
      str = _thing_ + "";
    } catch (e) {
      return "[" + typeof _thing_ + "]";
    }

    if (typeof _thing_ == "function") {
      str = str.replace(/^\s+/, "");
      let idx = str.indexOf("{");
      if (idx != -1) {
        str = str.substr(0, idx) + "{...}";
      }
    }

    return str;
  };

  self.ok = function (_condition_, _name_, _diag_) {
    self.postMessage({
      op: "ok",
      condition: !!_condition_,
      name: _name_,
      diag: _diag_,
    });
  };

  self.is = function (_a_, _b_, _name_) {
    let pass = _a_ == _b_;
    let diag = pass ? "" : "got " + repr(_a_) + ", expected " + repr(_b_);
    ok(pass, _name_, diag);
  };

  self.isnot = function (_a_, _b_, _name_) {
    let pass = _a_ != _b_;
    let diag = pass ? "" : "didn't expect " + repr(_a_) + ", but got it";
    ok(pass, _name_, diag);
  };

  self.todo = function (_condition_, _name_, _diag_) {
    self.postMessage({
      op: "todo",
      condition: !!_condition_,
      name: _name_,
      diag: _diag_,
    });
  };

  self.info = function (_msg_) {
    self.postMessage({ op: "info", msg: _msg_ });
  };

  self.executeSoon = function (_fun_) {
    var channel = new MessageChannel();
    channel.port1.postMessage("");
    channel.port2.onmessage = function (event) {
      _fun_();
    };
  };

  self.finishTest = function () {
    self.postMessage({ op: "done" });
  };

  self.grabArgAndContinueHandler = function (_arg_) {
    testGenerator.next(_arg_);
  };

  self.continueToNextStep = function () {
    executeSoon(function () {
      testGenerator.next();
    });
  };

  self.continueToNextStepSync = function () {
    testGenerator.next();
  };

  self._clearAllDatabasesCallback = undefined;
  self.clearAllDatabases = function (_callback_) {
    self._clearAllDatabasesCallback = _callback_;
    self.postMessage({ op: "clearAllDatabases" });
  };

  self.onerror = function (_message_, _file_, _line_) {
    ok(
      false,
      "Worker: uncaught exception [" +
        _file_ +
        ":" +
        _line_ +
        "]: '" +
        _message_ +
        "'"
    );
    self.finishTest();
    self.close();
    return true;
  };

  self.onmessage = function (_event_) {
    let message = _event_.data;
    switch (message.op) {
      case "load":
        info("Worker: loading " + JSON.stringify(message.files));
        self.importScripts(message.files);
        self.postMessage({ op: "loaded" });
        break;

      case "start":
        executeSoon(function () {
          info("Worker: starting tests");
          testGenerator = testSteps();
          testGenerator.next();
        });
        break;

      case "clearAllDatabasesDone":
        info("Worker: all databases are cleared");
        if (self._clearAllDatabasesCallback) {
          self._clearAllDatabasesCallback();
        }
        break;

      default:
        throw new Error(
          "Received a bad message from parent: " + JSON.stringify(message)
        );
    }
  };

  self.postMessage({ op: "ready" });
}
