/*
 * 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 RsaPublicKey = require('./rsa-public-key');
var RsaPrivateKey = require('./rsa-private-key');
var KeyPair = require('./keypair');
var KeyPairGenerator = require('./keypair-generator');
var Signer = require('./signer');
var SignatureVerifier = require('./signature-verifier');
var AsymmetricEncrypter = require('./encrypter');
var AsymmetricDecrypter = require('./decrypter');
var validator = require('./input-validator');
var cryptoPolicy = require('scytl-cryptopolicy');
var secureRandom = require('scytl-securerandom');
var forge = require('node-forge');

module.exports = AsymmetricCryptographyService;

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

  var policy_;
  if (options.policy) {
    policy_ = options.policy;
  } else {
    policy_ = cryptoPolicy.newInstance();
  }

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

  /**
   * Creates a new RsaPublicKey object from a provided RSA public key or its
   * components.
   *
   * @function newRsaPublicKey
   * @memberof AsymmetricCryptographyService
   * @param {Object}
   *            params An object containing the input parameters for the
   *            RsaPublicKey object creation.
   * @param {string}
   *            params.pem a PEM string representation of the key. Any
   *            additional parameters will be ignored.
   * @param {number}
   *            params.n The modulus of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.e The public exponent of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @returns {RsaPublicKey} The new RsaPublicKey object.
   */
  this.newRsaPublicKey = function(params) {
    validator.checkIsObjectWithProperties(
        params, 'Parameters object for creation of RsaPublicKey object');

    if (!params.pem) {
      checkPublicKeyData(params);

      return new RsaPublicKey(params);
    } else {
      validator.checkIsNonEmptyString(
          params.pem, 'RSA public key, PEM encoded');

      var forgePublicKey = forge.pki.publicKeyFromPem(params.pem);
      if (typeof forgePublicKey.encrypt === 'undefined') {
        throw new Error(
            'PEM encoding of public key for creation of RsaPublicKey object is corrupt');
      }

      return new RsaPublicKey({n: forgePublicKey.n, e: forgePublicKey.e});
    }
  };

  /**
   * Creates a new RsaPrivateKey object from a provided RSA private key or its
   * components.
   *
   * @function newRsaPrivateKey
   * @memberof AsymmetricCryptographyService
   * @param {Object}
   *            params An object containing the input parameters for the
   *            RsaPrivateKey object creation.
   * @param {Object}
   *            params An object containing the required input parameters.
   * @param {string}
   *            params.pem a PEM string representation of the key. Any
   *            additional parameters will be ignored.
   * @param {number}
   *            params.n The modulus of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.e The public exponent of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.d The private exponent of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.p The first prime of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.q The second prime of the key Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.dP The first exponent of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.dQ The second exponent of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @param {number}
   *            params.qInv The coefficient of the key. Required if
   *            <code>params.pem</code> is undefined.
   * @returns {RsaPrivateKey} The new RsaPrivateKey object.
   */
  this.newRsaPrivateKey = function(params) {
    validator.checkIsObjectWithProperties(
        params, 'Parameters object for creation of RsaPrivateKey object');

    if (!params.pem) {
      checkPrivateKeyData(params);

      return new RsaPrivateKey(params);
    } else {
      validator.checkIsNonEmptyString(
          params.pem, 'RSA public key, PEM encoded');

      var forgePrivateKey = forge.pki.privateKeyFromPem(params.pem);
      if (typeof forgePrivateKey.decrypt === 'undefined') {
        throw new Error(
            'PEM encoding of private key for creation of RsaPrivateKey object is corrupt');
      }

      return new RsaPrivateKey({
        n: forgePrivateKey.n,
        e: forgePrivateKey.e,
        d: forgePrivateKey.d,
        p: forgePrivateKey.p,
        q: forgePrivateKey.q,
        dP: forgePrivateKey.dP,
        dQ: forgePrivateKey.dQ,
        qInv: forgePrivateKey.qInv
      });
    }
  };

  /**
   * Creates a new KeyPair object from a provided key pair.
   *
   * @function newKeyPair
   * @memberof AsymmetricCryptographyService
   * @param {string}
   *            publicKey The public key comprising the key pair, in PEM
   *            format.
   * @param {string}
   *            privateKey The private key comprising the key pair, in PEM
   *            format.
   * @returns {KeyPair} The new KeyPair object.
   */
  this.newKeyPair = function(publicKey, privateKey) {
    validator.checkIsNonEmptyString(publicKey, 'Public key, PEM encoded');
    validator.checkIsNonEmptyString(privateKey, 'Private key, PEM encoded');

    return new KeyPair(publicKey, privateKey);
  };

  /**
   * Creates a new KeyPairGenerator object for generating key pairs.
   *
   * @function newKeyPairGenerator
   * @memberof AsymmetricCryptographyService
   * @returns {RsaKeyPairGenerator} The new RsaKeyPairGenerator object.
   */
  this.newKeyPairGenerator = function() {
    return new KeyPairGenerator(policy_, secureRandomService_);
  };

  /**
   * Creates a new Signer object for digitally signing data. It must be
   * initialized with a private key.
   *
   * @function newSigner
   * @memberof AsymmetricCryptographyService
   * @returns {Signer} The new Signer object.
   */
  this.newSigner = function() {
    return new Signer(policy_, secureRandomService_);
  };

  /**
   * Creates a new SignatureVerifier object for verifying a digital
   * signatures. It must be initialized with a public key.
   *
   * @function newSignatureVerifier
   * @memberof AsymmetricCryptographyService
   * @returns {SignatureVerifier} The new SignatureVerifier object.
   */
  this.newSignatureVerifier = function() {
    return new SignatureVerifier(policy_, secureRandomService_);
  };

  /**
   * Creates a new AsymmetricEncrypter object for encrypting data. It must be
   * initialized with a public key.
   *
   * @function newEncrypter
   * @memberof AsymmetricCryptographyService
   * @returns {AsymmetricEncrypter} The new AsymmetricEncrypter object.
   */
  this.newEncrypter = function() {
    return new AsymmetricEncrypter(policy_, secureRandomService_);
  };

  /**
   * Creates a new AsymmetricDecrypter object for decrypting data. It must be
   * initialized with a private key.
   *
   * @function newDecrypter
   * @memberof AsymmetricCryptographyService
   * @returns {AsymmetricDecrypter} The new AsymmetricDecrypter object.
   */
  this.newDecrypter = function() {
    return new AsymmetricDecrypter(policy_);
  };

  function checkPublicKeyData(params) {
    validator.checkIsPositiveBigInteger(params.n, 'RSA public key modulus');
    validator.checkIsPositiveBigInteger(
        params.e, 'RSA public key public exponent');
  }

  function checkPrivateKeyData(params) {
    validator.checkIsPositiveBigInteger(params.n, 'RSA private key modulus');
    validator.checkIsPositiveBigInteger(
        params.e, 'RSA private key public exponent');
    validator.checkIsPositiveBigInteger(
        params.d, 'RSA private key private exponent');
    validator.checkIsPositiveBigInteger(
        params.p, 'RSA private key first prime');
    validator.checkIsPositiveBigInteger(
        params.q, 'RSA private key second prime');
    validator.checkIsPositiveBigInteger(
        params.dP, 'RSA private key first exponent');
    validator.checkIsPositiveBigInteger(
        params.dQ, 'RSA private key second exponent');
    validator.checkIsPositiveBigInteger(
        params.qInv, 'RSA private key coefficient');
  }
}
