'use strict';

const common = require('../common.js');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/');

function readKey(name) {
  return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8');
}

function readKeyPair(publicKeyName, privateKeyName) {
  return {
    publicKey: readKey(publicKeyName),
    privateKey: readKey(privateKeyName),
  };
}

const keyFixtures = {
  ec: readKeyPair('ec_p256_public', 'ec_p256_private'),
  rsa: readKeyPair('rsa_public_2048', 'rsa_private_2048'),
  ed25519: readKeyPair('ed25519_public', 'ed25519_private'),
};

const data = crypto.randomBytes(256);

let pems;
let keyObjects;

const bench = common.createBenchmark(main, {
  keyType: ['rsa', 'ec', 'ed25519'],
  mode: ['sync', 'async', 'async-parallel'],
  keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'],
  n: [1e3],
}, {
  combinationFilter(p) {
    // "keyObject.unique" allows to compare the result with "keyObject" to
    // assess whether mutexes over the key material impact the operation
    return p.keyFormat !== 'keyObject.unique' ||
      (p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel');
  },
});

function measureSync(n, digest, signature, publicKey, keys) {
  bench.start();
  for (let i = 0; i < n; ++i) {
    crypto.verify(
      digest,
      data,
      publicKey || keys[i],
      signature);
  }
  bench.end(n);
}

function measureAsync(n, digest, signature, publicKey, keys) {
  let remaining = n;
  function done() {
    if (--remaining === 0)
      bench.end(n);
    else
      one();
  }

  function one() {
    crypto.verify(
      digest,
      data,
      publicKey || keys[n - remaining],
      signature,
      done);
  }
  bench.start();
  one();
}

function measureAsyncParallel(n, digest, signature, publicKey, keys) {
  let remaining = n;
  function done() {
    if (--remaining === 0)
      bench.end(n);
  }
  bench.start();
  for (let i = 0; i < n; ++i) {
    crypto.verify(
      digest,
      data,
      publicKey || keys[i],
      signature,
      done);
  }
}

function main({ n, mode, keyFormat, keyType }) {
  pems ||= [...Buffer.alloc(n)].map(() => keyFixtures[keyType].publicKey);
  keyObjects ||= pems.map(crypto.createPublicKey);

  let publicKey, keys, digest;

  switch (keyType) {
    case 'rsa':
    case 'ec':
      digest = 'sha256';
      break;
    case 'ed25519':
      break;
    default:
      throw new Error('not implemented');
  }

  switch (keyFormat) {
    case 'keyObject':
      publicKey = keyObjects[0];
      break;
    case 'pem':
      publicKey = pems[0];
      break;
    case 'jwk': {
      publicKey = { key: keyObjects[0].export({ format: 'jwk' }), format: 'jwk' };
      break;
    }
    case 'der': {
      publicKey = { key: keyObjects[0].export({ format: 'der', type: 'spki' }), format: 'der', type: 'spki' };
      break;
    }
    case 'keyObject.unique':
      keys = keyObjects;
      break;
    default:
      throw new Error('not implemented');
  }


  const { privateKey } = keyFixtures[keyType];
  const signature = crypto.sign(digest, data, privateKey);

  switch (mode) {
    case 'sync':
      measureSync(n, digest, signature, publicKey, keys);
      break;
    case 'async':
      measureAsync(n, digest, signature, publicKey, keys);
      break;
    case 'async-parallel':
      measureAsyncParallel(n, digest, signature, publicKey, keys);
      break;
    default:
      throw new Error('not implemented');
  }
}
