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

"use strict";

var { FileUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/FileUtils.sys.mjs"
);
var Pipe = Components.Constructor("@mozilla.org/pipe;1", "nsIPipe", "init");

add_task(async function () {
  initTestDevToolsServer();
  add_test_bulk_actor();

  dump(" ################# JSON REPLY\n");
  await test_bulk_request_cs(socket_transport, "jsonReply", "json");
  await test_bulk_request_cs(local_transport, "jsonReply", "json");

  dump(" ################# BULK ECHO\n");
  await test_bulk_request_cs(socket_transport, "bulkEcho", "bulk");
  await test_bulk_request_cs(local_transport, "bulkEcho", "bulk");

  dump(" ################# BULK ECHO WITH BUFFER\n");
  await test_bulk_request_cs(socket_transport, "bulkEchoWithBuffer", "bulk");
  await test_bulk_request_cs(local_transport, "bulkEchoWithBuffer", "bulk");

  dump(" ################# BULK REPLY\n");
  await test_json_request_cs(socket_transport, "bulkReply", "bulk");
  await test_json_request_cs(local_transport, "bulkReply", "bulk");

  DevToolsServer.destroy();
});

/*** Sample Bulk Actor ***/
const protocol = require("resource://devtools/shared/protocol.js");
const { Arg, RetVal } = protocol;
const { Actor } = require("resource://devtools/shared/protocol/Actor.js");

const testBulkSpec = protocol.generateActorSpec({
  typeName: "testBulk",

  methods: {
    bulkEcho: {
      request: protocol.BULK_REQUEST,
      response: protocol.BULK_RESPONSE,
    },
    bulkEchoWithBuffer: {
      request: protocol.BULK_REQUEST,
      response: protocol.BULK_RESPONSE,
    },
    bulkReply: {
      request: { foo: Arg(0, "number") },
      response: protocol.BULK_RESPONSE,
    },
    jsonReply: {
      request: protocol.BULK_REQUEST,
      response: { allDone: RetVal("number") },
    },
  },
});

class TestBulkFront extends protocol.FrontClassWithSpec(testBulkSpec) {
  formAttributeName = "testBulk";
  form(form) {
    this.actor = form.actor;
  }
}
protocol.registerFront(TestBulkFront);

class TestBulkActor extends Actor {
  constructor(conn) {
    super(conn, testBulkSpec);
  }

  // Receives data as bulk and respond as bulk
  async bulkEcho({ length, copyTo }, startBulkResponse) {
    Assert.equal(length, really_long().length);

    const { copyFrom } = await startBulkResponse(length);

    // We'll just echo back the same thing
    const pipe = new Pipe(true, true, 0, 0, null);
    copyTo(pipe.outputStream).then(() => {
      pipe.outputStream.close();
    });
    copyFrom(pipe.inputStream).then(() => {
      pipe.inputStream.close();
    });
  }

  async bulkEchoWithBuffer({ length, copyToBuffer }, startBulkResponse) {
    Assert.equal(length, really_long().length);

    const { copyFromBuffer } = await startBulkResponse(length);

    // We'll just echo back the same thing
    const buffer = new ArrayBuffer(length);
    await copyToBuffer(buffer);
    await copyFromBuffer(buffer);
  }

  // Receives data as json and respond as bulk
  async bulkReply({ foo }, startBulkResponse) {
    Assert.equal(foo, 42);

    const { copyFrom } = await startBulkResponse(really_long().length);
    const input = await new Promise(resolve => {
      NetUtil.asyncFetch(
        {
          uri: NetUtil.newURI(getTestTempFile("bulk-input")),
          loadUsingSystemPrincipal: true,
        },
        resolve
      );
    });
    await copyFrom(input);
    input.close();
  }

  // Receives data as bulk and response as json
  async jsonReply({ length, copyTo }) {
    Assert.equal(length, really_long().length);

    const outputFile = getTestTempFile("bulk-output", true);
    outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));

    const output = FileUtils.openSafeFileOutputStream(outputFile);

    await copyTo(output);
    FileUtils.closeSafeFileOutputStream(output);
    await verify_files();

    return 24;
  }
}

function add_test_bulk_actor() {
  ActorRegistry.addGlobalActor(
    {
      constructorName: "TestBulkActor",
      constructorFun: TestBulkActor,
    },
    "testBulk"
  );
}

/*** Reply Handlers ***/

var replyHandlers = {
  async json(reply) {
    // Receive JSON reply from server
    Assert.equal(reply, 24, "The returned JSON value is correct");
  },

  async bulk({ length, copyTo }) {
    // Receive bulk data reply from server
    Assert.equal(length, really_long().length);

    const outputFile = getTestTempFile("bulk-output", true);
    outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));

    const output = FileUtils.openSafeFileOutputStream(outputFile);

    await copyTo(output);
    FileUtils.closeSafeFileOutputStream(output);

    await verify_files();
  },
};

/*** Tests ***/

var test_bulk_request_cs = async function (
  transportFactory,
  frontMethod,
  replyType
) {
  // Ensure test files are not present from a failed run
  cleanup_files();
  writeTestTempFile("bulk-input", really_long());

  let serverResolve;
  const serverDeferred = new Promise(resolve => {
    serverResolve = resolve;
  });

  let bulkCopyResolve;
  const bulkCopyDeferred = new Promise(resolve => {
    bulkCopyResolve = resolve;
  });

  const transport = await transportFactory();

  const client = new DevToolsClient(transport);
  await client.connect();
  await client.mainRoot.rootForm;

  function bulkSendReadyCallback({ copyFrom }) {
    NetUtil.asyncFetch(
      {
        uri: NetUtil.newURI(getTestTempFile("bulk-input")),
        loadUsingSystemPrincipal: true,
      },
      input => {
        copyFrom(input).then(() => {
          input.close();
          bulkCopyResolve();
        });
      }
    );
  }

  const front = await client.mainRoot.getFront("testBulk");
  const response = await front[frontMethod](
    { length: really_long().length },
    bulkSendReadyCallback
  );

  // Set up reply handling for this type
  await replyHandlers[replyType](response);

  const connectionListener = type => {
    if (type === "closed") {
      DevToolsServer.off("connectionchange", connectionListener);
      serverResolve();
    }
  };
  DevToolsServer.on("connectionchange", connectionListener);

  client.close();
  transport.close();

  await Promise.all([bulkCopyDeferred, serverDeferred]);
};

var test_json_request_cs = async function (
  transportFactory,
  frontMethod,
  replyType
) {
  // Ensure test files are not present from a failed run
  cleanup_files();
  writeTestTempFile("bulk-input", really_long());

  let serverResolve;
  const serverDeferred = new Promise(resolve => {
    serverResolve = resolve;
  });

  const transport = await transportFactory();

  const client = new DevToolsClient(transport);
  await client.connect();
  await client.mainRoot.rootForm;
  const front = await client.mainRoot.getFront("testBulk");
  const response = await front[frontMethod]({ foo: 42 });

  await replyHandlers[replyType](response);

  const connectionListener = type => {
    if (type === "closed") {
      DevToolsServer.off("connectionchange", connectionListener);
      serverResolve();
    }
  };
  DevToolsServer.on("connectionchange", connectionListener);
  client.close();
  transport.close();

  return serverDeferred;
};

/*** Test Utils ***/

function verify_files() {
  const reallyLong = really_long();

  const inputFile = getTestTempFile("bulk-input");
  const outputFile = getTestTempFile("bulk-output");

  Assert.equal(inputFile.fileSize, reallyLong.length);
  Assert.equal(outputFile.fileSize, reallyLong.length);

  // Ensure output file contents actually match
  return new Promise(resolve => {
    NetUtil.asyncFetch(
      {
        uri: NetUtil.newURI(getTestTempFile("bulk-output")),
        loadUsingSystemPrincipal: true,
      },
      input => {
        const outputData = NetUtil.readInputStreamToString(
          input,
          reallyLong.length
        );
        // Avoid Assert.strictEqual here so we don't log the contents
        // eslint-disable-next-line mozilla/no-comparison-or-assignment-inside-ok
        Assert.ok(outputData === reallyLong);
        input.close();
        resolve();
      }
    );
  }).then(cleanup_files);
}

function cleanup_files() {
  const inputFile = getTestTempFile("bulk-input", true);
  if (inputFile.exists()) {
    inputFile.remove(false);
  }

  const outputFile = getTestTempFile("bulk-output", true);
  if (outputFile.exists()) {
    outputFile.remove(false);
  }
}
