/*
 * 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.asymmetric = cryptolib.modules.asymmetric || {};
/** @namespace asymmetric/keypair */
cryptolib.modules.asymmetric.keypair = function(box) {
  'use strict';

  box.asymmetric = box.asymmetric || {};
  box.asymmetric.keypair = {};

  /**
   * A module that holds certificates functionalities.
   *
   * @exports asymmetric/keypair/factory
   */
  box.asymmetric.keypair.factory = {};

  var policies = {
    encryption: {
      algorithm: box.policies.asymmetric.keypair.encryption.algorithm,
      provider: box.policies.asymmetric.keypair.encryption.provider,
      keyLength: box.policies.asymmetric.keypair.encryption.keyLength,
      publicExponent: box.policies.asymmetric.keypair.encryption.publicExponent
    }
  };

  var exceptions, randomFactory;

  var f = function(box) {
    exceptions = box.commons.exceptions;
    randomFactory =
        new box.primitives.securerandom.factory.SecureRandomFactory();
  };

  f.policies = {
    primitives: {
      secureRandom:
          {provider: box.policies.asymmetric.keypair.secureRandom.provider}
    }
  };

  cryptolib('commons.exceptions', 'primitives.securerandom', f);

  /**
   * A factory class for generating
   * {@link asymmetric.keypair.CryptoForgeKeyPairGenerator} objects.
   *
   * @class
   */
  box.asymmetric.keypair.factory.KeyPairGeneratorFactory = function() {};

  box.asymmetric.keypair.factory.KeyPairGeneratorFactory.prototype = {
    /**
     * @function
     * @returns CryptoForgeKeyPairGenerator
     */
    getEncryptionCryptoKeyPairGenerator: function() {
      var secureRandom;

      try {
        if (policies.encryption.provider ===
            Config.asymmetric.keypair.encryption.provider.FORGE) {
          secureRandom = randomFactory.getCryptoRandomBytes();

          return this.getCryptoForgeKeyPairGenerator(secureRandom);
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'An encryption CryptoKeyPairGenerator could not be obtained.',
            error);
      }
    },
    getCryptoForgeKeyPairGenerator: function(secureRandom) {
      return new CryptoForgeKeyPairGenerator(secureRandom);
    }
  };

  /**
   * Defines an encryption key pair generator for the FORGE provider.
   *
   * @class
   * @memberof asymmetric/keypair
   */
  function CryptoForgeKeyPairGenerator(secureRandom) {
    this.secureRandom = secureRandom;
  }

  CryptoForgeKeyPairGenerator.prototype = {

    /**
     * Generates an encryption key pair.
     *
     * @returns {asymmetric/keypair.KeyPair} an encryption key pair.
     */
    generate: function() {
      try {
        if (policies.encryption.algorithm ===
            Config.asymmetric.keypair.encryption.algorithm.RSA) {
          var options = {prng: this.secureRandom};

          var keys = forge.pki.rsa.generateKeyPair(
              policies.encryption.keyLength, policies.encryption.publicExponent,
              options);

          var forgePublicKey = keys.publicKey;
          var forgePrivateKey = keys.privateKey;

          var publicKeyPem = forge.pki.publicKeyToPem(forgePublicKey);
          var privateKeyPem = forge.pki.privateKeyToPem(forgePrivateKey);

          var publicKey = new PublicKey(
              policies.encryption.algorithm, forgePublicKey.n, forgePublicKey.e,
              publicKeyPem);

          var privateKey = new PrivateKey(
              policies.encryption.algorithm, forgePrivateKey.n,
              forgePrivateKey.e, forgePrivateKey.d, forgePrivateKey.p,
              forgePrivateKey.q, forgePrivateKey.dP, forgePrivateKey.dQ,
              forgePrivateKey.qInv, privateKeyPem);

          return new KeyPair(publicKey, privateKey);
        } else {
          throw new exceptions.CryptoLibException(
              'The given algorithm is not supported.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Encryption key pair could not be generated.', error);
      }
    }
  };

  /**
   * Container class for an encryption key pair.
   *
   * @class
   * @param publicKey
   *            public key of the encryption key pair.
   * @param privateKey
   *            private key of the encryption key pair.
   * @memberof asymmetric/keypair
   */
  function KeyPair(publicKey, privateKey) {
    /**
     * Retrieves the public key of the encryption key pair.
     *
     * @function
     * @returns PrivateKey Public key of encryption key pair.
     */
    var getPublicKey = function() {
      return publicKey;
    };

    /**
     * Retrieves the private key of the encryption key pair.
     *
     * @function
     * @returns PrivateKey Private key of encryption key pair.
     */
    var getPrivateKey = function() {
      return privateKey;
    };

    return {getPublicKey: getPublicKey, getPrivateKey: getPrivateKey};
  }

  /**
   * @class PrivateKey
   * @memberof asymmetric/keypair
   */
  function PrivateKey(algorithm, n, e, d, p, q, dP, dQ, qInv, pem) {
    /**
     * @function
     * @returns {string}
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getAlgorithm = function() {
      return algorithm;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getModulus = function() {
      return n;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPublicExponent = function() {
      return e;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPrivateExponent = function() {
      return d;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPrime1 = function() {
      return p;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPrime2 = function() {
      return q;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getExponent1 = function() {
      return dP;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getExponent2 = function() {
      return dQ;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getCoefficient = function() {
      return qInv;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PrivateKey
     */
    var getPem = function() {
      return pem;
    };

    return {
      getAlgorithm: getAlgorithm,
      getModulus: getModulus,
      getPublicExponent: getPublicExponent,
      getPrivateExponent: getPrivateExponent,
      getPrime1: getPrime1,
      getPrime2: getPrime2,
      getExponent1: getExponent1,
      getExponent2: getExponent2,
      getCoefficient: getCoefficient,
      getPem: getPem
    };
  }

  /** @class PublicKey */
  function PublicKey(algorithm, n, e, pem) {
    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getAlgorithm = function() {
      return algorithm;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getModulus = function() {
      return n;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getPublicExponent = function() {
      return e;
    };

    /**
     * @function
     * @memberof asymmetric/keypair.PublicKey
     */
    var getPem = function() {
      return pem;
    };

    return {
      getAlgorithm: getAlgorithm,
      getModulus: getModulus,
      getPublicExponent: getPublicExponent,
      getPem: getPem
    };
  }
};
