/*
 * 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 validator = require('./input-validator');
var cryptoPolicy = require('scytl-cryptopolicy');
var codec = require('scytl-codec');
var forge = require('node-forge');

module.exports = MessageDigester;

/**
 * @class MessageDigester
 * @classdesc The message digester API. To instantiate this object, use the
 *            method {@link MessageDigestService.newDigester}.
 * @hideconstructor
 * @param {Policy}
 *            policy The cryptographic policy to use.
 */
function MessageDigester(policy) {
  var digester_ = getDigester(policy.primitives.messageDigest.algorithm);
  digester_.start();
  var updated_ = false;

  /**
   * Generates a message digest from the provided data. If there were any
   * prior calls to the method <code>update</code>, then the provided data
   * will be bitwise appended to the data provided to those calls before
   * digesting. If no data is provided here the digest will only by generated
   * for the data provided to prior calls to the method <code>update</code>.
   * The message digester will be automatically reinitialized after this
   * method completes.
   *
   * @function digest
   * @memberof MessageDigester
   * @param {Uint8Array|string}
   *            [data] The data from which to generate the message digest.
   *            <b>NOTE:</b> Data of type <code>string</code> will be UTF-8
   *            encoded.
   * @returns {Uint8Array} The generated message digest.
   * @throws {Error}
   *             If no data was provided as input and the update method was
   *             not previously called.
   */
  this.digest = function(data) {
    if (typeof data !== 'undefined') {
      if (typeof data === 'string') {
        data = codec.utf8Encode(data);
      }
      validator.checkIsInstanceOf(
          data, Uint8Array, 'Uint8Array', 'Data to digest');

      this.update(data);
    } else if (!updated_) {
      throw new Error(
          'Attempt to generate message digest without either providing data as input or having made previous call to method \'update\'');
    }

    var bytesBinaryEncoded = digester_.digest().getBytes();

    // Reinitialize digester.
    digester_.start();
    updated_ = false;

    return codec.binaryDecode(bytesBinaryEncoded);
  };

  /**
   * Updates the message digester with the provided data. The data will be
   * internally bitwise appended to any data provided to previous calls to
   * this method, after the last call to the method <code>digest</code>.
   *
   * @function update
   * @memberof MessageDigester
   * @param {Uint8Array|string}
   *            data The data with which to update the message digester.
   *            <b>NOTE:</b> Data of type <code>string</code> will be UTF-8
   *            encoded.
   * @returns {MessageDigester} A reference to this object, to facilitate
   *          method chaining.
   * @throws {Error}
   *             If the input data validation fails.
   */
  this.update = function(data) {
    if (typeof data === 'string') {
      data = codec.utf8Encode(data);
    }
    validator.checkIsInstanceOf(
        data, Uint8Array, 'Uint8Array', 'Data to update message digester');

    digester_.update(codec.binaryEncode(data));
    updated_ = true;

    return this;
  };

  function getDigester(algorithm) {
    if (algorithm ===
        cryptoPolicy.options.primitives.messageDigest.algorithm.SHA256) {
      return forge.md.sha256.create();
    } else if (
        algorithm ===
        cryptoPolicy.options.primitives.messageDigest.algorithm.SHA512_224) {
      return forge.md.sha512.sha224.create();
    } else {
      throw new Error(
          'Could not create new message digester for unrecognized hash algorithm \'' +
          algorithm + '\'.');
    }
  }
}
