/*
 * 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.primitives = cryptolib.modules.primitives || {};

/**
 * @namespace primitives/messagedigest
 */
cryptolib.modules.primitives.messagedigest = function(box) {
  'use strict';

  box.primitives = box.primitives || {};
  box.primitives.messagedigest = box.primitives.messagedigest || {};

  /**
   * A module that holds message digest functionalities.
   * @exports primitives/messagedigest
   */
  box.primitives.messagedigest.factory =
      box.primitives.messagedigest.factory || {};

  var policies = {
    messagedigest: {
      algorithm: box.policies.primitives.messagedigest.algorithm,
      provider: box.policies.primitives.messagedigest.provider
    }
  };

  var converters, exceptions;

  cryptolib('commons', function(box) {
    converters = new box.commons.utils.Converters();
    exceptions = box.commons.exceptions;
  });

  /**
   * A factory class for creating a message digest generator and verifier.
   * @class
   */
  box.primitives.messagedigest.factory.MessageDigestFactory = function() {};

  box.primitives.messagedigest.factory.MessageDigestFactory.prototype = {
    /**
     * Gets a message dister object.
     * @function
     * @returns {primitives/messagedigest.CryptoForgeMessageDigest}
     */
    getCryptoMessageDigest: function() {

      try {
        if (policies.messagedigest.provider ===
            Config.primitives.messagedigest.provider.FORGE) {
          return this.getCryptoForgeMessageDigest();
        } else {
          throw new exceptions.CryptoLibException(
              'No suitable provider was provided.');
        }
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'A CryptoSymmetricCipher could not be obtained.', error);
      }
    },

    getCryptoForgeMessageDigest: function() {
      return new CryptoForgeMessageDigest();
    }
  };

  /**
   * Defines a message digest hash function.
   * @class
   * @memberof primitives/messagedigest
   */
  function CryptoForgeMessageDigest() {
    this.digester = null;
    try {
      if (policies.messagedigest.algorithm ===
          Config.primitives.messagedigest.algorithm.SHA256) {
        this.digester = forge.md.sha256.create();
      } else if (
          policies.messagedigest.algorithm ===
          Config.primitives.messagedigest.algorithm.SHA512_224) {
        this.digester = forge.md.sha512.sha224.create();
      } else {
        var errorMessage = 'Message digester type \'' +
            policies.messagedigest.algorithm +
            '\' not recognized by CryptoForgeMessageDigest.';
        throw new exceptions.CryptoLibException(errorMessage);
      }
    } catch (error) {
      throw new exceptions.CryptoLibException(
          'CryptoForgeMessageDigest could not be created.', error);
    }
  }

  CryptoForgeMessageDigest.prototype = {

    /**
     * Initializes the message digest generator.
     * @function
     */
    start: function() {
      try {
        this.digester.start();
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Message digest could not be initialized.', error);
      }
    },

    /**
     * Updates the message digest generator.
     * @function
     * @param {Array} arrayDataBase64
     *            data to compute the hash, as an array of string in Base64
     *            encoded format.
     */
    update: function(arrayDataBase64) {

      try {
        if (!arrayDataBase64 || arrayDataBase64.length < 1) {
          throw new exceptions.CryptoLibException(
              'The array of data in Base64 should contain at least one element.');
        }

        for (var i = 0; i < arrayDataBase64.length; i++) {
          this.digester.update(converters.base64Decode(arrayDataBase64[i]));
        }

      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Message digest could not be updated.', error);
      }
    },

    /**
     * Generates the message digest for the updated data.
     * @function
     * @returns {string} Message digest, as string in Base64 encoded format.
     */
    digest: function() {

      try {
        var messageDigest = this.digester.digest();

        var rawOutput = messageDigest.getBytes();

        var encodedOutput = converters.base64Encode(rawOutput);

        return encodedOutput;
      } catch (error) {
        throw new exceptions.CryptoLibException(
            'Message digest could not be generated.', error);
      }
    }
  };
};
