/*
 * 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';

module.exports = PhiFunction;

/**
 * @class PhiFunction
 * @classdesc Encapsulates the PHI function defined by Maurer's unified
 *            framework for zero-knowledge proofs of knowledge. This object is
 *            instantiated internally by the <code>prove</code> method of any
 *            zero-knowledge proof of knowledge prover object or the
 *            <code>verify</code> method of any zero-knowledge proof of
 *            knowledge verifier object.
 * @private
 * @param {number}
 *            numInputs The number of inputs of the PHI function.
 * @param {number}
 *            numOutputs The number of outputs of the PHI function.
 * @param {number[][][]}
 *            computationRules The computation rules of the PHI function.
 * @param {ZpGroupElement[]}
 *            baseElements The base elements of the PHI function.
 */
function PhiFunction(numInputs, numOutputs, computationRules, baseElements) {
  validateComputationRules(
      numInputs, numOutputs, computationRules, baseElements);

  this.numInputs = numInputs;
  this.numOutputs = numOutputs;
  Object.freeze(this.computationRules = computationRules);
  Object.freeze(this.baseElements = baseElements);

  function validateComputationRules(
      numSecrets, numOutputs, computationRules, baseElements) {
    // Validate that both of the values in each of the
    // pairs of rules have values in the correct ranges.
    var numBaseElements = baseElements.length;
    for (var i = 0; i < numOutputs; i++) {
      for (var j = 0; j < computationRules[i].length; j++) {
        // Validate the first value of the pair
        var pairValue1 = computationRules[i][j][0];
        if ((pairValue1 < 1) || (pairValue1 > numBaseElements)) {
          throw new Error(
              'First value in index pair is invalid; It should be within the range [1, ' +
              numBaseElements + '], but it was ' + pairValue1);
        }

        // Validate the second value of the pair.
        var pairValue2 = computationRules[i][j][1];
        if ((pairValue2 < 1) || (pairValue2 > numSecrets)) {
          throw new Error(
              'Second value in index pair is valid; It should be within the range [1, ' +
              numSecrets + '], but it was ' + pairValue2);
        }
      }
    }
  }
}

PhiFunction.prototype = {
  /**
   * Performs the PHI function calculation.
   *
   * @function calculate
   * @memberof PhiFunction
   * @param {Exponent[]}
   *            exponents The exponents with which to exponentiate the base
   *            elements.
   * @param {function}
   *            [progressCallback] An optional callback function if a progress
   *            meter is being used.
   * @returns {ZpGroupElement[]} The array of Zp group elements that comprise
   *          the output of the PHI function calculation.
   */
  calculate: function(exponents, progressMeter) {
    var measureProgress = false;
    if (typeof progressMeter !== 'undefined') {
      progressMeter.reset();
      measureProgress = true;
    }

    var partialResult;
    var resultForThisListOfPairs;
    var result = [];
    var progress = 0;
    for (var i = 0; i < this.computationRules.length; i++) {
      var numPairsInList = this.computationRules[i].length;

      resultForThisListOfPairs =
          this.baseElements[this.computationRules[i][0][0] - 1].exponentiate(
              exponents[this.computationRules[i][0][1] - 1]);

      if (measureProgress) {
        progressMeter.update(++progress);
      }

      for (var j = 1; j < numPairsInList; j++) {
        var index1 = this.computationRules[i][j][0];
        index1 = index1 - 1;
        var index2 = this.computationRules[i][j][1];
        index2 = index2 - 1;

        partialResult =
            this.baseElements[index1].exponentiate(exponents[index2]);

        resultForThisListOfPairs =
            resultForThisListOfPairs.multiply(partialResult);

        if (measureProgress) {
          progressMeter.update(++progress);
        }
      }
      result.push(resultForThisListOfPairs);
    }

    return result;
  }
};
