"use strict";

AddonTestUtils.init(this);

const ECHO_BODY = String.raw`
  import struct
  import sys

  stdin = getattr(sys.stdin, 'buffer', sys.stdin)
  stdout = getattr(sys.stdout, 'buffer', sys.stdout)

  while True:
    rawlen = stdin.read(4)
    if len(rawlen) == 0:
      sys.exit(0)
    msglen = struct.unpack('@I', rawlen)[0]
    msg = stdin.read(msglen)

    stdout.write(struct.pack('@I', msglen))
    stdout.write(msg)
`;

const SCRIPTS = [
  {
    name: "echo",
    description: "a native app that echoes back messages it receives",
    script: ECHO_BODY.replace(/^ {2}/gm, ""),
  },
];

function loadTestExtension({ background }) {
  return ExtensionTestUtils.loadExtension({
    background,
    manifest: {
      browser_specific_settings: { gecko: { id: ID } },
      permissions: ["nativeMessaging"],
    },
  });
}

add_setup(async () => {
  await ExtensionTestUtils.startAddonManager();
  await setupHosts(SCRIPTS);
});

// Regression test for https://bugzilla.mozilla.org/show_bug.cgi?id=1979546
add_task(async function test_many_connectNative_calls() {
  let extension = loadTestExtension({
    async background() {
      // Prior to bug 1979546 being fixed, the test got stuck on Windows at 16.
      // Let's verify that things run smoothly even if we launch "many" more,
      // e.g. 70 (arbitrarily chosen, higher than 64).
      const NUMBER_OF_CONCURRENT_NATIVE_MESSAGING_APPS = 70;

      const openPorts = [];
      const remainingMsgs = new Set();
      const firstMessagePromises = [];
      for (let i = 0; i < NUMBER_OF_CONCURRENT_NATIVE_MESSAGING_APPS; ++i) {
        const dummyMsg = `pingpong-${i}`;
        const port = browser.runtime.connectNative("echo");
        openPorts[i] = port;
        remainingMsgs.add(i);
        firstMessagePromises[i] = new Promise(resolve => {
          port.onMessage.addListener(msg => {
            browser.test.assertEq(dummyMsg, msg, `Echoed back message ${i}`);
            remainingMsgs.delete(i);
            browser.test.log(`Remaining: ${Array.from(remainingMsgs)}`);
            resolve();
          });
          port.onDisconnect.addListener(() => {
            if (remainingMsgs.delete(i)) {
              // If the program somehow exits before it responded, note the
              // failure and continue (do not stay stuck).
              browser.test.fail(`onDisconnect fired before onMessage: ${i}`);
              resolve();
            } else {
              // onDisconnect should not fire when we call port.disconnect()
              // below.
              browser.test.fail(`Unexpected port.onDisconnect: ${i}`);
            }
          });
        });
        port.postMessage(dummyMsg);
      }
      browser.test.log(`Awaiting replies to: ${Array.from(remainingMsgs)}`);
      await Promise.all(firstMessagePromises);

      browser.test.log("Now verifying sendNativeMessage behavior");
      browser.test.assertEq(
        await browser.runtime.sendNativeMessage("echo", "one_off_msg"),
        "one_off_msg",
        "sendNativeMessage can still roundtrip after so many connectNative"
      );
      for (const port of openPorts) {
        port.disconnect();
      }
      browser.test.sendMessage("done");
    },
  });
  await extension.startup();
  await extension.awaitMessage("done");
  info("Waiting for all echo processes to have exit");
  await waitForSubprocessExit();
  await extension.unload();
});
