/*
 * 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 ZpSubgroup = require('./zp-subgroup');
var Exponent = require('./exponent');
var MathematicalGroupHandler = require('./group-handler');
var constants = require('./constants');
var validator = require('./input-validator');
var forge = require('node-forge');
var BigInteger = forge.jsbn.BigInteger;
module.exports = MathematicalRandomGenerator;
/**
 * @class MathematicalRandomGenerator
 * @classdesc The mathematical random generator API. To instantiate this object,
 *            use the method {@link MathematicalService.newRandomGenerator}.
 * @hideconstructor
 * @param {SeureRandomService}
 *            secureRandomService The secure random service to use.
 */
function MathematicalRandomGenerator(secureRandomService) {
  var randomGenerator_ = secureRandomService.newRandomGenerator();
  var mathGroupHandler_ = new MathematicalGroupHandler();
  /**
   * Generates a random Zp group element belonging to the Zp subgroup provided
   * as input.
   *
   * @function nextZpGroupElement
   * @memberof MathematicalRandomGenerator
   * @param {ZpSubgroup}
   *            group The Zp subgroup to which the Zp group element belongs.
   * @param {Object}
   *            [options] An object containing optional arguments.
   * @param {SecureRandomGenerator}
   *            [options.secureRandomGenerator=Internal generator used] The
   *            secure random generator to use.
   * @param {boolean}
   *            [options.useShortExponent=false] If true, then
   *            a short exponent is to be generated for the exponentiation
   *            used to generate the Zp group element.
   * @returns {ZpGroupElement} The generated Zp group element.
   * @throws {Error}
   *             If the input validation fails.
   */
  this.nextZpGroupElement = function(group, options) {
    validator.checkIsInstanceOf(
        group, ZpSubgroup, 'ZpSubgroup',
        'Zp subgroup to which randomly generated element is to belong');
    var exponent = this.nextExponent(group, options);
    return group.generator.exponentiate(exponent);
  };
  /**
   * Generates a random exponent associated with the Zp subgroup provided as
   * input. The value of the exponent will be within the range [0,
   * q-1].
   *
   * @function nextExponent
   * @memberof MathematicalRandomGenerator
   * @param {ZpSubgroup}
   *            group The Zp subgroup to which the exponent is to be
   *            associated.
   * @param {Object}
   *            [options] An object containing optional arguments.
   * @param {SecureRandomGenerator}
   *            [options.secureRandomGenerator=Internal generator is used] The
   *            secure random generator to use.
   * @param {boolean}
   *            [options.useShortExponent=false] If true, then
   *            a short exponent is to be generated.
   * @returns {Exponent} The generated exponent.
   * @throws {Error}
   *             If the input validation fails.
   */
  this.nextExponent = function(group, options) {
    validator.checkIsInstanceOf(
        group, ZpSubgroup, 'ZpSubgroup',
        'Zp subgroup to which randomly generated exponent is to be associated');
    options = options || {};
    var randomGenerator = options.secureRandomGenerator || randomGenerator_;
    validator.checkIsObjectWithProperties(
        randomGenerator,
        'Random BigInteger generator for random exponent generation');
    var useShortExponent = options.useShortExponent || false;
    var q = group.q;
    var qBitLength = q.bitLength();
    var randomExponentBitLength;
    if (useShortExponent) {
      if (qBitLength < constants.SHORT_EXPONENT_BIT_LENGTH) {
        throw new Error(
            'Zp subgroup order bit length must be greater than or equal to short exponent bit length : ' +
            constants.SHORT_EXPONENT_BIT_LENGTH + '; Found ' + qBitLength);
      }
      randomExponentBitLength = constants.SHORT_EXPONENT_BIT_LENGTH;
    } else {
      randomExponentBitLength = qBitLength;
    }
    var randomExponentValue;
    var randomExponentFound = false;
    while (!randomExponentFound) {
      randomExponentValue =
          randomGenerator.nextBigInteger(randomExponentBitLength);
      if (randomExponentValue.compareTo(q) < 0) {
        randomExponentFound = true;
      }
    }
    return new Exponent(q, randomExponentValue);
  };
  /**
   * Generates a random quadratic residue Zp subgroup, that has the specified
   * bit length for its modulus p and the specified certainty
   * of having its modulus and the order q being prime.
   * 
* This method generates a Zp subgroup that satisfies the condition: *
   * p =
   * (q * 2) + 1
   * 
   * Note: The minimum bit length of the modulus p that
   * is permitted by this method is 2. If a bit length less than 2 is
   * requested, then an Error will be thrown.
   *
   * @function nextQuadraticResidueGroup
   * @memberof MathematicalRandomGenerator
   * @param {number}
   *            pBitLength The modulus bit length of the Zp subgroup to
   *            generate.
   * @param {number}
   *            certainty The level of certainty that the order q
   *            of the generated group is prime.
   * @param {Object}
   *            [options] An object containing optional arguments.
   * @param {SecureRandomGenerator}
   *            [options.secureRandomGenerator=Internal generator used] The
   *            secure random generator to use.
   * @returns {ZpSubgroup} The generated quadratic residue group.
   * @throws {Error}
   *             If the input validation fails.
   */
  this.nextQuadraticResidueGroup = function(pBitLength, certainty, options) {
    checkQrGroupGenerationData(pBitLength, certainty);
    options = options || {};
    var randomGenerator = options.secureRandomGenerator || randomGenerator_;
    validator.checkIsObjectWithProperties(
        randomGenerator,
        'Random bytes generator for random quadratic residue Zp subgroup generation');
    var p, q;
    var randomGroupFound = false;
    var primeOptions = {prng: randomGenerator};
    var callback = function(err, num) {
      if (err) {
        throw new Error('Error while generating prime number; ' + err);
      }
      p = num;
      q = num.subtract(BigInteger.ONE).divide(new BigInteger('2'));
    };
    while (!randomGroupFound) {
      forge.prime.generateProbablePrime(pBitLength, primeOptions, callback);
      if (q.isProbablePrime(certainty)) {
        randomGroupFound = true;
      }
    }
    var g = mathGroupHandler_.findMinGenerator(p, q);
    return new ZpSubgroup(p, q, g);
  };
  function checkQrGroupGenerationData(pBitLength, certainty) {
    validator.checkIsPositiveNumber(pBitLength);
    validator.checkIsPositiveNumber(certainty);
    if (pBitLength < 2) {
      throw new Error(
          'Bit length: ' + pBitLength +
          ' of modulus p provided as input for generating quadratic residue group is less than minimum required value of 2.');
    }
    if (certainty < constants.MINIMUM_PRIME_CERTAINTY_LEVEL) {
      throw new Error(
          'Certainty level: ' + certainty +
          ' provided as input for generating quadratic residue group is less than minimum required value of ' +
          constants.MINIMUM_PRIME_CERTAINTY_LEVEL);
    }
  }
}