"use strict";

/* globals AppConstants, FileUtils */
/* exported getSubprocessCount, setupHosts, waitForSubprocessExit */

ChromeUtils.defineESModuleGetters(this, {
  MockRegistry: "resource://testing-common/MockRegistry.sys.mjs",
});
if (AppConstants.platform == "win") {
  ChromeUtils.defineESModuleGetters(this, {
    SubprocessImpl: "resource://gre/modules/subprocess/subprocess_win.sys.mjs",
  });
} else {
  ChromeUtils.defineESModuleGetters(this, {
    SubprocessImpl: "resource://gre/modules/subprocess/subprocess_unix.sys.mjs",
  });
}

const { Subprocess } = ChromeUtils.importESModule(
  "resource://gre/modules/Subprocess.sys.mjs"
);

// It's important that we use a space in this directory name to make sure we
// correctly handle executing batch files with spaces in their path.
let tmpDir = FileUtils.getDir("TmpD", ["Native Messaging"]);
tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

const TYPE_SLUG =
  AppConstants.platform === "linux"
    ? "native-messaging-hosts"
    : "NativeMessagingHosts";

add_setup(async function setup() {
  await IOUtils.makeDirectory(PathUtils.join(tmpDir.path, TYPE_SLUG));
});

registerCleanupFunction(async () => {
  await IOUtils.remove(tmpDir.path, { recursive: true });
});

function getPath(filename) {
  return PathUtils.join(tmpDir.path, TYPE_SLUG, filename);
}

const ID = "native@tests.mozilla.org";

async function setupHosts(scripts) {
  const pythonPath = await Subprocess.pathSearch(Services.env.get("PYTHON"));

  async function writeManifest(script, scriptPath, path) {
    let body = `#!${pythonPath} -u\n${script.script}`;

    await IOUtils.writeUTF8(scriptPath, body);
    await IOUtils.setPermissions(scriptPath, 0o755);

    let manifest = {
      name: script.name,
      description: script.description,
      path,
      type: "stdio",
      allowed_extensions: [ID],
    };

    // Optionally, allow the test to change the manifest before writing.
    script._hookModifyManifest?.(manifest);

    let manifestPath = getPath(`${script.name}.json`);
    await IOUtils.writeJSON(manifestPath, manifest);

    return manifestPath;
  }

  switch (AppConstants.platform) {
    case "macosx":
    case "linux": {
      let dirProvider = {
        getFile(property) {
          if (property == "XREUserNativeManifests") {
            return tmpDir.clone();
          } else if (property == "XRESysNativeManifests") {
            return tmpDir.clone();
          }
          return null;
        },
      };

      Services.dirsvc.registerProvider(dirProvider);
      registerCleanupFunction(() => {
        Services.dirsvc.unregisterProvider(dirProvider);
      });

      for (let script of scripts) {
        let path = getPath(`${script.name}.py`);

        await writeManifest(script, path, path);
      }
      break;
    }

    case "win": {
      const REGKEY = String.raw`Software\Mozilla\NativeMessagingHosts`;

      let registry = new MockRegistry();
      registerCleanupFunction(() => {
        registry.shutdown();
      });

      for (let script of scripts) {
        let { scriptExtension = "bat" } = script;

        // It's important that we use a space in this filename. See directory
        // name comment above.
        let batPath = getPath(`batch ${script.name}.${scriptExtension}`);
        let scriptPath = getPath(`${script.name}.py`);

        let batBody = `@ECHO OFF\n"${pythonPath}" -u "${scriptPath}" %*\n`;
        await IOUtils.writeUTF8(batPath, batBody);

        let manifestPath = await writeManifest(script, scriptPath, batPath);

        registry.setValue(
          Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
          `${REGKEY}\\${script.name}`,
          "",
          manifestPath
        );
      }
      break;
    }

    default:
      ok(
        false,
        `Native messaging is not supported on ${AppConstants.platform}`
      );
  }
}

function getSubprocessCount() {
  return SubprocessImpl.Process.getWorker()
    .call("getProcesses", [])
    .then(result => result.size);
}
function waitForSubprocessExit() {
  return SubprocessImpl.Process.getWorker()
    .call("waitForNoProcesses", [])
    .then(() => {
      // Return to the main event loop to give IO handlers enough time to consume
      // their remaining buffered input.
      return new Promise(resolve => setTimeout(resolve, 0));
    });
}
