/*
 * 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
 */
cryptolib.modules.primitives = cryptolib.modules.primitives || {};

/**
 * @namespace primitives/securerandom
 */
cryptolib.modules.primitives.securerandom = function(box) {
  'use strict';

  box.primitives = box.primitives || {};
  box.primitives.securerandom = {};
  /**
   * A module that holds secure random functionalities.
   *
   * @exports primitives/securerandom
   */
  box.primitives.securerandom.factory = {};

  var policies = {
    secureRandom: {provider: box.policies.primitives.secureRandom.provider}
  };

  var converters, exceptions;

  cryptolib('commons', function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
  });

  /**
   * A factory class for creating random generators.
   *
   * @class
   */
  box.primitives.securerandom.factory.SecureRandomFactory = function() {

  };

  box.primitives.securerandom.factory.SecureRandomFactory.prototype = {
    /**
     * Instantiates a random byte generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoRandomBytes: function() {

      try {
        if (policies.secureRandom.provider ===
            Config.primitives.secureRandom.provider.SCYTL) {
          return this.getCryptoScytlRandomBytes();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoRandomBytes could not be obtained.', error);
      }

    },

    /**
     * Instantiates a Scytl random byte generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoScytlRandomBytes: function() {
      return new CryptoScytlRandomBytes();
    },

    /**
     * Instantiates a random integer generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoRandomInteger: function() {

      try {
        if (policies.secureRandom.provider ===
            Config.primitives.secureRandom.provider.SCYTL) {
          return this.getCryptoScytlRandomInteger();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoRandomBytes could not be obtained.', error);
      }

    },

    /**
     * Instantiates a Scytl random integer generator.
     *
     * @function
     * @returns {primitives/securerandom.CryptoScytlRandomBytes}
     */
    getCryptoScytlRandomInteger: function() {
      return new CryptoScytlRandomInteger();
    }
  };

  var validate = function(length) {
    try {
      if (typeof length !== 'number') {
        throw new exceptions.CryptoLibException(
            'The given length is not a number');
      }
      if (length < 1) {
        throw new exceptions.CryptoLibException(
            'The given length should be a strictly positive integer.');
      }
      if (length > box.MAXIMUM_LENGTH_TO_GENERATE_DATA) {
        throw new exceptions.CryptoLibException(
            'The given length should be lower than or equal to ' +
            box.MAXIMUM_LENGTH_TO_GENERATE_DATA);
      }
    } catch (error) {
      throw new exceptions.CryptoLibException(
          'Random generation failed.', error);
    }
  };

  /**
   * The SCYTL Bytes Random Generator
   *
   * @class
   * @memberof primitives/securerandom
   */
  function CryptoScytlRandomBytes() {}

  CryptoScytlRandomBytes.prototype = {

    /**
     * Generates secure random bytes.
     *
     * @function
     * @param {number}
     *            numBytes number of bytes to be generated..
     * @returns {string} secure random string.
     */
    nextRandom: function(length) {

      validate(length);

      var randomBytes = box.prng.generate(length);

      return randomBytes.toString();
    },

    /**
     * Method to be compliant with the Forge library.
     *
     * @function
     * @param {number}
     *            numBytes number of bytes to be generated..
     * @returns {string} secure random string.
     */
    getBytesSync: function(length) {
      return this.nextRandom(length);
    }
  };

  /**
   * The SCYTL Integer Random Generator
   *
   * @class
   * @memberof primitives/securerandom
   */
  function CryptoScytlRandomInteger() {}

  CryptoScytlRandomInteger.prototype = {

    /**
     * Generates a secure random big integer (Forge).
     *
     * @function
     * @params {number} length the bit length of the random integer.
     * @returns {object} secure random big integer.
     */
    nextRandom: function(length) {

      validate(length);

      var n = ((new box.forge.jsbn.BigInteger('10')).pow(length))
                  .subtract(box.forge.jsbn.BigInteger.ONE);

      var bitLength = n.bitLength();

      var numberOfBytes = bitLength / box.NUMBER_OF_BITS_PER_BYTE;
      numberOfBytes = Math.floor(numberOfBytes);

      var modBytes = bitLength % box.NUMBER_OF_BITS_PER_BYTE;

      if (modBytes !== 0) {
        numberOfBytes++;
      }

      var generatedInteger;

      do {
        var randomBytes = box.prng.generate(numberOfBytes);
        randomBytes = '\x00' + randomBytes;
        randomBytes = converters.bytesFromString(randomBytes);
        generatedInteger = new box.forge.jsbn.BigInteger(randomBytes);
      } while ((generatedInteger.compareTo(forge.jsbn.BigInteger.ZERO) <= 0) ||
               (generatedInteger.compareTo(n) > 0));

      return generatedInteger;
    },

    /**
     * Generates a secure random big integer according to the specified bit
     * length.
     *
     * @function
     * @params {number} bitsLength the bit length of the random integer.
     * @returns {object} Secure random big integer.
     */
    nextRandomByBits: function(bitsLength) {

      var byteLength = Math.ceil(bitsLength / box.NUMBER_OF_BITS_PER_BYTE);

      var modBytes = box.NUMBER_OF_BITS_PER_BYTE -
          (bitsLength % box.NUMBER_OF_BITS_PER_BYTE);

      var randomBytes = box.prng.generate(byteLength);

      randomBytes = '\x00' + randomBytes;
      randomBytes = converters.bytesFromString(randomBytes);
      var generatedInteger = new box.forge.jsbn.BigInteger(randomBytes);

      generatedInteger = generatedInteger.shiftRight(modBytes);

      return generatedInteger;
    }
  };
};
