/* 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");

function run_test() {
  initTestDevToolsServer();
  add_test_bulk_actor();

  add_task(async function () {
    await test_bulk_request_cs(socket_transport, "jsonReply", "json");
    await test_bulk_request_cs(local_transport, "jsonReply", "json");
    await test_bulk_request_cs(socket_transport, "bulkEcho", "bulk");
    await test_bulk_request_cs(local_transport, "bulkEcho", "bulk");
    await test_json_request_cs(socket_transport, "bulkReply", "bulk");
    await test_json_request_cs(local_transport, "bulkReply", "bulk");
    DevToolsServer.destroy();
  });

  run_next_test();
}

/** * Sample Bulk Actor ***/
const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
class TestBulkActor extends Actor {
  constructor(conn) {
    super(conn, { typeName: "testBulk", methods: [] });

    this.requestTypes = {
      bulkEcho: this.bulkEcho,
      bulkReply: this.bulkReply,
      jsonReply: this.jsonReply,
    };
  }

  bulkEcho({ actor, type, length, copyTo }) {
    Assert.equal(length, really_long().length);
    this.conn
      .startBulkSend({
        actor,
        type,
        length,
      })
      .then(({ copyFrom }) => {
        // 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();
        });
      });
  }

  bulkReply({ to, type }) {
    this.conn
      .startBulkSend({
        actor: to,
        type,
        length: really_long().length,
      })
      .then(({ copyFrom }) => {
        NetUtil.asyncFetch(
          {
            uri: NetUtil.newURI(getTestTempFile("bulk-input")),
            loadUsingSystemPrincipal: true,
          },
          input => {
            copyFrom(input).then(() => {
              input.close();
            });
          }
        );
      });
  }

  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);

    return copyTo(output)
      .then(() => {
        FileUtils.closeSafeFileOutputStream(output);
        return verify_files();
      })
      .then(() => {
        return { allDone: true };
      }, do_throw);
  }
}

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

/** * Reply Handlers ***/

var replyHandlers = {
  json(request) {
    // Receive JSON reply from server
    return new Promise(resolve => {
      request.on("json-reply", reply => {
        Assert.ok(reply.allDone);
        resolve();
      });
    });
  },

  bulk(request) {
    // Receive bulk data reply from server
    return new Promise(resolve => {
      request.on("bulk-reply", ({ 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);

        copyTo(output).then(() => {
          FileUtils.closeSafeFileOutputStream(output);
          resolve(verify_files());
        });
      });
    });
  },
};

/** * Tests ***/

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

  let clientResolve;
  const clientDeferred = new Promise(resolve => {
    clientResolve = resolve;
  });

  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);
  client.connect().then(() => {
    client.mainRoot.rootForm.then(clientResolve);
  });

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

  clientDeferred
    .then(response => {
      const request = client.startBulkRequest({
        actor: response.testBulk,
        type: actorType,
        length: really_long().length,
      });

      // Send bulk data to server
      request.on("bulk-send-ready", bulkSendReadyCallback);

      // Set up reply handling for this type
      replyHandlers[replyType](request).then(() => {
        client.close();
        transport.close();
      });
    })
    .catch(do_throw);

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

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

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

  let clientResolve;
  const clientDeferred = new Promise(resolve => {
    clientResolve = resolve;
  });

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

  const transport = await transportFactory();

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

  clientDeferred
    .then(response => {
      const request = client.request({
        to: response.testBulk,
        type: actorType,
      });

      // Set up reply handling for this type
      replyHandlers[replyType](request).then(() => {
        client.close();
        transport.close();
      });
    })
    .catch(do_throw);

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

  return Promise.all([clientDeferred, 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 do_check_eq here so we don't log the contents
        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);
  }
}
