/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
/* jshint node:true */
'use strict';

var SecureRandomGenerator = require('./generator');
var PrivateKeyCollector = require('./entropy/collectors/privatekey');
var EntropyManager = require('./entropy/manager');
var Prng = require('./prng');
var constants = require('./constants');

module.exports = SecureRandomService;

/**
 * @class SecureRandomService
 * @classdesc The secure random service API. To instantiate this object, use the
 *            method {@link newService}.
 * @hideconstructor
 * @param {Object}
 *            [options] An object containing optional arguments.
 * @param {Uint8Array}
 *            [options.prngSeed=Internally generated] The seed to initialize the
 *            PRNG.
 * @param {number}
 *            [options.maxEntropyCounter=256] The amount of entropy to
 *            accumulate before stopping collectors. It must be a positive
 *            number.
 * @param {number}
 *            [options.hashUpdaterInterval=1000] The interval (in milliseconds)
 *            at which entropy data is to be hashed. It must be a positive
 *            number.
 * @param {Object}
 *            [options.windowObject=Built-in window object] The
 *            <code>window</code> object provided to the collectors.
 * @param {Object}
 *            [options.cryptoApi=Built-in cryptographic library] The
 *            cryptographic library supplied to the WebKit collector (if the
 *            latter is being used).
 * @param {Object}
 *            [options.privateKeyData=Not used] The private key collector data.
 * @param {string}
 *            options.privateKeyData.privateKey The private key, in PEM format.
 * @param {number}
 *            options.privateKeyData.pinEntropy The PIN entropy of the private
 *            key. It must be a positive number.
 * @param {boolean}
 *            [options.noDefaultCollectors=false] If <code>true</code>, no
 *            default entropy collectors are to be used.
 */
function SecureRandomService(options) {
  ////////////////////////////////// PRIVATE //////////////////////////////////

  var prng_;
  var entropyManager_;

  function init(options) {
    options = options || {};

    if (options.prngSeed) {
      prng_ = new Prng(options.prngSeed);
    }

    entropyManager_ = new EntropyManager(options);

    if (options.privateKeyData) {
      var privateKey = options.privateKeyData.privateKey;
      var pinEntropy = options.privateKeyData.pinEntropy;

      entropyManager_.addCollector(new PrivateKeyCollector(
          entropyManager_.collectEntropy, privateKey, pinEntropy));
    }

    entropyManager_.startCollectors();
    entropyManager_.stopCollectors();
  }

  function getPrng() {
    if (!prng_) {
      prng_ = new Prng(entropyManager_.getEntropyHash());
    }

    return prng_;
  }

  /////////////////////////////////// PUBLIC ///////////////////////////////////

  /**
   * Creates a new SecureRandomGenerator object, that uses the PRNG of this
   * service. This object can be used for all supported types of random
   * generation.
   *
   * @function newRandomGenerator
   * @memberof SecureRandomService
   * @param {Object}
   *            [options] An object containing optional arguments.
   * @param {number}
   *            [options.maxNumBytes=512] The maximum number of bytes that can
   *            randomly generated per method call.
   * @param {number}
   *            [options.maxNumDigits=512] The maximum number of BigInteger
   *            digits that can randomly generated per method call.
   * @returns {SecureRandomGenerator} The new SecureRandomGenerator object.
   */
  this.newRandomGenerator = function(options) {
    return new SecureRandomGenerator(getPrng(), options);
  };

  /**
   * Generates a random seed that can be used to initialize the PRNG of a new
   * SecureRandomService object.
   *
   * @function nextSeed
   * @memberof SecureRandomService
   * @param {number}
   *            [numBytes=32] The number of bytes in the seed.
   * @returns {Uint8Array} The random seed.
   */
  this.nextSeed = function(numBytes) {
    numBytes = numBytes || constants.SECURE_RANDOM_DEFAULT_NUM_SEED_BYTES;

    return getPrng().nextBytes(numBytes);
  };

  /**
   * Manually starts the entropy collection. This method has no effect if
   * entropy is already being collected.
   *
   * @function startEntropyCollection
   * @memberof SecureRandomService
   * @param {Function}
   *            [callback=No callback] A callback function that will be
   *            executed if the maximum amount of entropy required is
   *            collected.
   */
  this.startEntropyCollection = function(callback) {
    entropyManager_.startCollectors(callback);
  };

  /**
   * Manually stops the entropy collection and resets the PRNG with the
   * collected entropy.
   *
   * @function stopEntropyCollection
   * @memberof SecureRandomService
   * @returns {boolean} <code>true</code> if the maximum amount of entropy
   *          required has been collected, <code>false</code> otherwise.
   */
  this.stopEntropyCollection = function() {
    var maxEntropyCollected = entropyManager_.stopCollectors();
    prng_ = new Prng(entropyManager_.getEntropyHash());

    return maxEntropyCollected;
  };

  /**
   * Retrieves the amount of entropy collected, as a percentage of the maximum
   * amount of entropy required.
   *
   * @function getEntropyPercentage
   * @memberof SecureRandomService
   * @returns {number} The percentage of entropy collected.
   */
  this.getEntropyPercentage = function() {
    return entropyManager_.getEntropyPercentage();
  };

  //////////////////////////////// CONSTRUCTOR ////////////////////////////////

  // Initialize the object.
  init(options);
}
