/*
 * 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 ZpGroupElement = require('./zp-group-element');
var Exponent = require('./exponent');
var MathematicalArrayCompressor = require('./array-compressor');
var MathematicalRandomGenerator = require('./random-generator');
var MathematicalGroupHandler = require('./group-handler');
var secureRandom = require('scytl-securerandom');
var validator = require('./input-validator');
var codec = require('scytl-codec');
var forge = require('node-forge');

var BigInteger = forge.jsbn.BigInteger;

module.exports = MathematicalService;

/**
 * @class MathematicalService
 * @classdesc The mathematical service API. To instantiate this object, use the
 *            method {@link newService}.
 * @hideconstructor
 * @param {Object}
 *            [options] An object containing optional arguments.
 * @param {SecureRandomService}
 *            [options.secureRandomService=Created internally] The secure random
 *            service to use.
 */
function MathematicalService(options) {
  options = options || {};

  var secureRandomService;
  if (options.secureRandomService) {
    secureRandomService = options.secureRandomService;
  } else {
    secureRandomService = secureRandom.newService();
  }

  /**
   * Creates a new MathematicalRandomGenerator object for generating random
   * mathematical objects.
   *
   * @function newRandomGenerator
   * @memberof MathematicalService
   * @returns {MathematicalRandomGenerator} The new
   *          MathematicalRandomGenerator object.
   */
  this.newRandomGenerator = function() {
    return new MathematicalRandomGenerator(secureRandomService);
  };
}

MathematicalService.prototype = {
  /**
   * Creates a new ZpSubgroup object, which encapsulates a Zp subgroup.
   *
   * @function newZpSubgroup
   * @memberof MathematicalService
   * @param {forge.jsbn.BigInteger|string}
   *            pOrJson The modulus of the Zp subgroup <b>OR</b> a JSON
   *            string representation of a ZpSubgroup object, compatible with
   *            its <code>toJson</code> method. For the latter case, any
   *            additional input arguments will be ignored.
   * @param {forge.jsbn.BigInteger}
   *            q The order of the Zp subgroup. It must be within the range
   *            <code>[1, p-1]</code>.
   * @param {forge.jsbn.BigInteger}
   *            g The generator of the Zp subgroup. It must be within the
   *            range <code>[2, p-1]</code>.
   * @returns {ZpSubgroup} The new ZpSubgroup object.
   * @throws {Error}
   *             If the input data validation fails.
   */
  newZpSubgroup: function(pOrJson, q, g) {
    if (typeof pOrJson !== 'string') {
      validator.checkZpSubgroupModulus(
          pOrJson, 'Modulus p to create new ZpSubgroup object');
      validator.checkZpSubgroupOrder(
          q, 'Order q to create new ZpSubgroup object', pOrJson);
      validator.checkZpSubgroupGenerator(
          g, 'Generator to create new ZpSubgroup object', pOrJson);

      return new ZpSubgroup(pOrJson, q, g);
    } else {
      return jsonToZpSubgroup(pOrJson);
    }
  },

  /**
   * Creates a new ZpGroupElement object, which encapsulates a Zp group
   * element.
   *
   * @function newZpGroupElement
   * @memberof MathematicalService
   * @param {forge.jsbn.BigInteger|string}
   *            pOrJson The modulus of the Zp subgroup to which the Zp group
   *            element belongs <b>OR</b> a JSON string representation of a
   *            ZpGroupElement object, compatible with its <code>toJson</code>
   *            method. For the latter case, any additional input arguments
   *            will be ignored.
   * @param {forge.jsbn.BigInteger}
   *            q The order of the Zp subgroup to which the Zp group element
   *            belongs. It must be within the range <code>[1, p-1]</code>.
   * @param {forge.jsbn.BigInteger}
   *            value The value of the Zp group element. It must be within the
   *            range <code>[1, p-1]</code>.
   * @returns {ZpGroupElement} The new ZpGroupElement object.
   * @throws {Error}
   *             If the input data validation fails.
   */
  newZpGroupElement: function(pOrJson, q, value) {
    if (typeof pOrJson !== 'string') {
      validator.checkZpSubgroupModulus(
          pOrJson, 'Modulus p to create new ZpGroupElement object');
      validator.checkZpSubgroupOrder(
          q, 'Order q to create new ZpGroupElement object', pOrJson);
      validator.checkZpGroupElementValue(
          value, 'Value to create new ZpGroupElement object', pOrJson);

      return new ZpGroupElement(pOrJson, q, value);
    } else {
      return jsonToZpGroupElement(pOrJson);
    }
  },

  /**
   * Creates a new Exponent object, which encapsulates an exponent.
   *
   * @function newExponent
   * @memberof MathematicalService
   * @param {forge.jsbn.BigInteger|string}
   *            qOrJson The order of the Zp subgroup to which the exponent is
   *            associated <b>OR</b> a JSON string representation of an
   *            Exponent object, compatible with its <code>toJson</code>
   *            method. For the latter case, any additional input arguments
   *            will be ignored.
   * @param {forge.jsbn.BigInteger}
   *            value The value of the exponent. If this value is greater than
   *            or equal to <code>q</code> it will be adjusted to be less
   *            then <code>q</code>, via the <code>mod(q)</code>
   *            operation.
   * @returns {Exponent} The new Exponent object.
   * @throws {Error}
   *             If the input data validation fails.
   */
  newExponent: function(qOrJson, value) {
    if (typeof qOrJson !== 'string') {
      validator.checkZpSubgroupOrder(
          qOrJson, 'Order q to create new Exponent object');
      validator.checkExponentValue(
          value, 'Value to create new Exponent object');

      return new Exponent(qOrJson, value);
    } else {
      return jsonToExponent(qOrJson);
    }
  },

  /**
   * Creates a new quadratic residue Zp subgroup from the modulus
   * <code>p</code> and <code>generator</code> provided as input.
   * <p>
   * <b>NOTE</b>: This method creates a particular type of Zp subgroup, with
   * the following property:
   * <p>
   * <code>p = 2q + 1</code>
   *
   * @function newQuadraticResidueGroup
   * @memberof MathematicalService
   * @param {forge.jsbn.BigInteger}
   *            p The modulus of the quadratic residue group.
   * @param {forge.jsbn.BigInteger}
   *            g The generator of the quadratic residue group.
   * @returns {ZpSubgroup} The quadratic residue group.
   * @throws {Error}
   *             If the input validation fails.
   */
  newQuadraticResidueGroup: function(p, g) {
    validator.checkZpSubgroupModulus(
        p, 'Modulus p for quadratic residue group generation');
    validator.checkZpSubgroupGenerator(
        g, 'Generator for quadratic residue group generation', p);

    var q = p.subtract(BigInteger.ONE).divide(new BigInteger('2'));

    return new ZpSubgroup(p, q, g);
  },

  /**
   * Creates a new MathematicalArrayCompressor object for reducing the size of
   * arrays containing mathematical objects.
   *
   * @function newArrayCompressor
   * @memberof MathematicalService
   * @returns {MathematicalArrayCompressor} The new
   *          MathematicalArrayCompressor object.
   */
  newArrayCompressor: function() {
    return new MathematicalArrayCompressor();
  },

  /**
   * Creates a new MathematicalGroupHandler object for performing operations
   * involving mathematical groups.
   *
   * @function newGroupHandler
   * @memberof MathematicalService
   * @returns {MathematicalGroupHandler} The new MathematicalGroupHandler
   *          object.
   */
  newGroupHandler: function() {
    return new MathematicalGroupHandler();
  }
};

function jsonToZpSubgroup(json) {
  validator.checkIsJsonString(
      json, 'JSON string representation of ZpSubgroup object');

  var parsed = JSON.parse(json);

  var p = codec.bytesToBigInteger(codec.base64Decode(parsed.zpSubgroup.p));
  var q = codec.bytesToBigInteger(codec.base64Decode(parsed.zpSubgroup.q));
  var g = codec.bytesToBigInteger(codec.base64Decode(parsed.zpSubgroup.g));

  return new ZpSubgroup(p, q, g);
}

function jsonToZpGroupElement(json) {
  validator.checkIsJsonString(
      json, 'JSON string representation of ZpGroupElement object');

  var parsed = JSON.parse(json);

  var p = codec.bytesToBigInteger(codec.base64Decode(parsed.zpGroupElement.p));
  var q = codec.bytesToBigInteger(codec.base64Decode(parsed.zpGroupElement.q));
  var value =
      codec.bytesToBigInteger(codec.base64Decode(parsed.zpGroupElement.value));

  return new ZpGroupElement(p, q, value);
}

function jsonToExponent(json) {
  validator.checkIsJsonString(
      json, 'JSON string representation of Exponent object');

  var parsed = JSON.parse(json);

  var q = codec.bytesToBigInteger(codec.base64Decode(parsed.exponent.q));
  var value =
      codec.bytesToBigInteger(codec.base64Decode(parsed.exponent.value));

  return new Exponent(q, value);
}
