/*
 * 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 DigitalEnvelope = require('./envelope');
var DigitalEnvelopeGenerator = require('./generator');
var DigitalEnvelopeOpener = require('./opener');
var EncryptedSecretKeyPair = require('./encrypted-secret-keypair');
var validator = require('./input-validator');
var codec = require('scytl-codec');

module.exports = DigitalEnvelopeService;

/**
 * @class DigitalEnvelopeService
 * @classdesc The digital envelope 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 {SymmetricCryptographyService}
 *            [options.symmetricCryptographyService=Created internally] The
 *            symmetric cryptography service to use.
 * @param {AsymmetricCryptographyService}
 *            [options.asymmetricCryptographyService=Created internally] The
 *            asymmetric cryptography service to use.
 */
function DigitalEnvelopeService(options) {
  /**
   * Creates a new DigitalEnvelopeGenerator object for generating digital
   * envelopes. It must be initialized with one or more public keys.
   *
   * @function newGenerator
   * @memberof DigitalEnvelopeService
   * @returns {DigitalEnvelopeGenerator} The new DigitalEnvelopeGenerator
   *          object.
   */
  this.newGenerator = function() {
    return new DigitalEnvelopeGenerator(options);
  };

  /**
   * Creates a new DigitalEnvelopeOpener object for opening digital envelopes.
   * It must be initialized with a private key.
   *
   * @function newOpener
   * @memberof DigitalEnvelopeService
   * @returns {DigitalEnvelopeOpener} The new DigitalEnvelopeOpener object.
   */
  this.newOpener = function() {
    return new DigitalEnvelopeOpener(options);
  };
}

DigitalEnvelopeService.prototype = {
  /**
   * Creates a new DigitalEnvelope object from a provided digital envelope or
   * its components.
   *
   * @function newEnvelope
   * @memberof DigitalEnvelopeService
   * @param {Uint8Array}
   *            encryptedDataOrJson The symmetrically encrypted data <b>OR</b>
   *            a JSON string representation of a DigitalEnvelope object,
   *            compatible with its <code>toJson</code> method. For the
   *            latter case, any additional input arguments will be ignored.
   * @param {Uint8Array}
   *            mac The MAC of the symmetrically encrypted data.
   * @param {EncryptedSecretKeyPair[]}
   *            encryptedSecretKeyPairs The array of asymmetric encryptions of
   *            the concatenation of the encryption secret key and MAC secret
   *            key used to generate and open the digital envelope, each
   *            asymmetric encryption having been generated using a different
   *            one of the public keys used to initialize digital envelope
   *            generator.
   * @returns {DigitalEnvelope} The new DigitalEnvelope object.
   * @throws {Error}
   *             If the input data validation fails.
   */
  newEnvelope: function(encryptedDataOrJson, mac, encryptedSecretKeyPairs) {
    if (typeof encryptedDataOrJson !== 'string') {
      validator.checkIsInstanceOf(
          encryptedDataOrJson, Uint8Array, 'Uint8Array',
          'Symmetrically encrypted data for new DigitalEnvelope object');
      validator.checkIsInstanceOf(
          mac, Uint8Array, 'Uint8Array', 'MAC for new DigitalEnvelope object');
      checkEncryptedSecretKeyPairs(
          encryptedSecretKeyPairs,
          'Encrypted secret key pairs for new DigitalEnvelope object');

      return new DigitalEnvelope(
          encryptedDataOrJson, mac, encryptedSecretKeyPairs);
    } else {
      return jsonToEnvelope(encryptedDataOrJson);
    }   
  }
};

function checkEncryptedSecretKeyPairs(keyPairs, label) {
  validator.checkIsNonEmptyArray(keyPairs, label);

  for (var i = 0; i < keyPairs.length; i++) {
    validator.checkIsObjectWithProperties(
        keyPairs[i], 'element ' + i + ' of ' + label);
  }
}

function jsonToEnvelope(json) {
  validator.checkIsJsonString(
      json, 'JSON string to deserialize to DigitalEnvelope object');

  var parsed = JSON.parse(json).digitalEnvelope;

  var encryptedData = codec.base64Decode(parsed.encryptedDataBase64);
  var mac = codec.base64Decode(parsed.macBase64);
  var encryptedSecretKeyPairsB64 = parsed.encryptedSecretKeyConcatsBase64;
  var publicKeysPem = parsed.publicKeysPem;

  var encryptedSecretKeyPairs = [];
  for (var i = 0; i < encryptedSecretKeyPairsB64.length; i++) {
    encryptedSecretKeyPairs.push(new EncryptedSecretKeyPair(
        codec.base64Decode(encryptedSecretKeyPairsB64[i]), publicKeysPem[i]));
  }

  return new DigitalEnvelope(encryptedData, mac, encryptedSecretKeyPairs);
}
