/*
 * 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 validator = require('./input-validator');
var constants = require('./constants');
var codec = require('scytl-codec');
var forge = require('node-forge');

var BigInteger = forge.jsbn.BigInteger;

module.exports = SecureRandomGenerator;

/**
 * @class SecureRandomGenerator
 * @classdesc The secure random generator API. To instantiate this object, use
 * the method {@link SecureRandomService.newRandomGenerator}.
 * @hideconstructor
 * @param {Prng}
 *            prng The pseudo-random number generator (PRNG) to use.
 * @param {Object}
 *            [options] An object containing optional arguments.
 * @param {number}
 *            [options.maxNumBytes=512] The maximum number of bytes that can be
 *            randomly generated per call.
 * @param {number}
 *            [options.maxNumDigits=512] The maximum number of BigInteger digits
 *            that can be randomly generated per call.
 */
function SecureRandomGenerator(prng, options) {
  options = options || {};

  var maxNumBytes_ =
      options.maxNumBytes || constants.SECURE_RANDOM_MAX_NUM_BYTES;
  var maxNumDigits_ =
      options.maxNumDigits || constants.SECURE_RANDOM_MAX_NUM_DIGITS;

  /**
   * Generates some random bytes.
   *
   * @function nextBytes
   * @memberof SecureRandomGenerator
   * @param {number}
   *            numBytes The number of bytes to generate.
   * @return {Uint8Array} The random bytes.
   * @throws {Error}
   *             If the input data validation fails.
   */
  this.nextBytes = function(numBytes) {
    checkData(numBytes, maxNumBytes_, 'Number of bytes to randomly generate');

    return prng.nextBytes(numBytes);
  };

  /**
   * Generates a random positive BigInteger object, with a specified maximum
   * number of bits.
   *
   * @function nextBigInteger
   * @memberof SecureRandomGenerator
   * @param {number}
   *            maxNumBits The maximum number of bits in the BigInteger
   *            object.
   * @returns {BigInteger} The random BigInteger object.
   * @throws {Error}
   *             If the input data validation fails.
   */
  this.nextBigInteger = function(maxNumBits) {
    validator.checkIsPositiveNumber(
        maxNumBits,
        'Maximum number of bits in BigInteger to randomly generate');

    // Find minimum number of bytes needed for maximum number of bits.
    var numBytes = Math.ceil(maxNumBits / 8);

    var numExcessBits = 8 - (maxNumBits % 8);

    var randomBytes = prng.nextBytes(numBytes);
    var randomByteArray = Array.apply([], randomBytes);
    randomByteArray.unshift(0);
    var generatedBigInteger = codec.bytesToBigInteger(randomBytes);

    return generatedBigInteger.shiftRight(numExcessBits);
  };

  /**
   * Generates a random positive BigInteger object, with a specified maximum
   * number of digits.
   *
   * @function nextBigIntegerByDigits
   * @memberof SecureRandomGenerator
   * @param {number}
   *            numDigits The maximum number of digits in the BigInteger
   *            object value.
   * @returns {BigInteger} The random BigInteger object.
   * @throws {Error}
   *             If the input data validation fails.
   */
  this.nextBigIntegerByDigits = function(numDigits) {
    checkData(
        numDigits, maxNumDigits_,
        'Number of digits in BigInteger to randomly generate');

    // Variable n is largest possible number for given number of digits. For
    // instance, for 3 digits, n would be 999.
    var n = ((new BigInteger('10')).pow(numDigits)).subtract(BigInteger.ONE);

    // Get number of bits needed to represent n.
    var numBits = n.bitLength();

    // Get minimum number of bytes needed to represent n.
    var numBytes = Math.ceil(numBits / 8);

    // Continuously generate numbers until number is found that is smaller than
    // n. Note: This will take just one round if number of bits is a multiple
    // of 8.
    var generatedBigInteger;
    do {
      var randomBytes = prng.nextBytes(numBytes);
      var randomByteArray = Array.apply([], randomBytes);
      randomByteArray.unshift(0);
      generatedBigInteger = codec.bytesToBigInteger(randomBytes);
    } while ((generatedBigInteger.compareTo(BigInteger.ZERO) <= 0) ||
             (generatedBigInteger.compareTo(n) > 0));

    return generatedBigInteger;
  };

  /**
   * Implementation of the method <code>getBytesSync</code> so that this random
   * generator can be used directly by Forge.
   *
   * @function getBytesSync
   * @memberof SecureRandomGenerator
   * @private
   * @param {number}
   *            numBytes The number of bytes to generate.
   * @returns {string} The random bytes, in binary encoded format.
   */
  this.getBytesSync = function(numBytes) {
    return codec.binaryEncode(this.nextBytes(numBytes));
  };

  function checkData(num, max, label) {
    validator.checkIsPositiveNumber(num, label);

    if (num > max) {
      throw new Error(
          'Expected ' + label + ' to be less than or equal to ' + max +
          ' ; Found: ' + num);
    }
  }
}
