import Benchmark from 'benchmark';
import { readToEnd } from '@openpgp/web-stream-tools';
import * as openpgp from 'openpgp';

const wrapAsync = func => ({
  fn: async deferred => {
    await func().catch(onError);
    deferred.resolve();
  },
  defer: true
});

const onError = err => {
  // eslint-disable-next-line no-console
  console.error('The time benchmark tests failed by throwing the following error:');
  // eslint-disable-next-line no-console
  console.error(err);
  // eslint-disable-next-line no-process-exit
  process.exit(1);
};

/**
 * Time benchmark tests.
 * NB: each test will be run multiple times, so any input must be consumable multiple times.
 */
(async () => {
  const suite = new Benchmark.Suite();
  const { armoredKey, privateKey, publicKey, armoredEncryptedMessage, armoredSignedMessage } = await getTestData();
  function* largeDataGenerator({ chunk, numberOfChunks }) {
    for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
      yield chunk;
    }
  }

  const streamFromGenerator = it => new ReadableStream({
    pull: controller => {
      const { value, done } = it.next();
      if (done) {
        controller.close();
      } else {
        controller.enqueue(value);
      }
    }
  });

  suite.add('openpgp.readKey', wrapAsync(async () => {
    await openpgp.readKey({ armoredKey });
  }));

  suite.add('openpgp.readMessage', wrapAsync(async () => {
    await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
  }));

  suite.add('openpgp.generateKey', wrapAsync(async () => {
    await openpgp.generateKey({ userIDs: { email: 'test@test.it' } });
  }));

  suite.add('openpgp.encrypt', wrapAsync(async () => {
    const message = await openpgp.createMessage({ text: 'plaintext' });
    await openpgp.encrypt({ message, encryptionKeys: publicKey });
  }));

  suite.add('openpgp.sign', wrapAsync(async () => {
    const message = await openpgp.createMessage({ text: 'plaintext' });
    await openpgp.sign({ message, signingKeys: privateKey });
  }));

  suite.add('openpgp.sign (stream)', wrapAsync(async () => {
    const inputStream = streamFromGenerator(largeDataGenerator({ chunk: new Uint8Array(10000), numberOfChunks: 10 }));
    const message = await openpgp.createMessage({ binary: inputStream });
    const signed = await openpgp.sign({ message, signingKeys: privateKey });

    await readToEnd(signed);
  }));

  suite.add('openpgp.decrypt', wrapAsync(async () => {
    const message = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
    await openpgp.decrypt({ message, decryptionKeys: privateKey });
  }));

  suite.add('openpgp.verify', wrapAsync(async () => {
    const message = await openpgp.readMessage({ armoredMessage: armoredSignedMessage });
    await openpgp.verify({ message, verificationKeys: publicKey, expectSigned: true });
  }));

  suite.on('cycle', event => {
    // Output benchmark result by converting benchmark result to string
    // eslint-disable-next-line no-console
    console.log(String(event.target));
  });

  suite.run({ 'async': true });
})();

async function getTestData() {
  const armoredKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----

xVgEYS4KIRYJKwYBBAHaRw8BAQdAOl5Ij0p8llEOLqalwRM8+YWKXELm+Zl1
arT2orL/42MAAP9SQBdl+A/i4AtIOr33rn6OKzmXQ2EQH0xoSPJcVxX7BA5U
zRR0ZXN0IDx0ZXN0QHRlc3QuY29tPsKMBBAWCgAdBQJhLgohBAsJBwgDFQgK
BBYAAgECGQECGwMCHgEAIQkQ2RFo4G/cGHQWIQRL9hTrZduw8+42e1rZEWjg
b9wYdEi3AP91NftBKXLfcMRz/g540cQ/0+ax8pvsiqFSb+Sqz87YPwEAkoYK
8I9rVAlVABIhy/g7ZStHu/u0zsPbiquZFKoVLgPHXQRhLgohEgorBgEEAZdV
AQUBAQdAqY5VZYX6axscpfVN3EED83T3WO3+Hzxfq31dXJXKrRkDAQgHAAD/
an6zziN/Aw0ruIxuZTjmkYriDW34hys8F2nRR23PO6gPjsJ4BBgWCAAJBQJh
LgohAhsMACEJENkRaOBv3Bh0FiEES/YU62XbsPPuNnta2RFo4G/cGHQjlgEA
gbOEmauiq2avut4e7pSJ98t50zai2dzNies1OpqTU58BAM1pWI99FxM6thX9
aDa+Qhz0AxhA9P+3eQCXYTZR7CEE
=LPl8
-----END PGP PRIVATE KEY BLOCK-----`;

  const privateKey = await openpgp.readKey({ armoredKey });
  const publicKey = privateKey.toPublic();
  const plaintextMessage = await openpgp.createMessage({ text: 'plaintext' });
  const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, encryptionKeys: publicKey });
  const armoredSignedMessage = await openpgp.sign({ message: await openpgp.createMessage({ text: 'plaintext' }), signingKeys: privateKey });

  return {
    armoredKey,
    privateKey,
    publicKey,
    armoredEncryptedMessage,
    armoredSignedMessage
  };
}
