/*
 * 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 ZpGroupElement = require('./zp-group-element');
var Exponent = require('./exponent');
var validator = require('./input-validator');

module.exports = MathematicalArrayCompressor;

/**
 * @class MathematicalArrayCompressor
 * @classdesc The mathematical array compressor API. To instantiate this object,
 *            use the method {@link MathematicalService.newArrayCompressor}.
 * @hideconstructor
 */
function MathematicalArrayCompressor() {}

MathematicalArrayCompressor.prototype = {
  /**
   * Compresses an array of Zp group elements into a single element.
   *
   * @function compressZpGroupElements
   * @memberof MathematicalArrayCompressor
   * @param {ZpGroupElement[]}
   *            elements The array of Zp group elements to compress.
   * @returns {ZpGroupElement} The Zp group element into which all of the Zp
   *          group elements have been compressed.
   * @throws {Error}
   *             If the input validation fails.
   */
  compressZpGroupElements: function(elements) {
    checkZpGroupElements(elements, 'Zp group elements to compress');

    var element = elements[0];
    for (var i = 1; i < elements.length; i++) {
      element = element.multiply(elements[i]);
    }

    return element;
  },

  /**
   * Compresses a specified number of trailing elements of an array of Zp
   * group elements into a single Zp group element.
   *
   * @function compressTrailingZpGroupElements
   * @memberof MathematicalArrayCompressor
   * @param {ZpGroupElement[]}
   *            elements The array of Zp group elements to compress.
   * @param {number}
   *            numOutputElements The number of output elements required.
   * @returns {ZpGroupElement[]} The array of Zp group elements resulting from
   *          the compression.
   * @throws {Error}
   *             If the input validation fails.
   */
  compressTrailingZpGroupElements: function(elements, numOutputElements) {
    checkZpGroupElements(
        elements,
        'Zp group element array for which to compress trailing elements');
    validator.checkIsPositiveNumber(
        numOutputElements,
        'Number of elements to remain after compressing trailing elements');

    var offset = numOutputElements - 1;
    var outputElements = elements.slice(0, offset);

    var lastElement =
        this.compressZpGroupElements(elements.slice(offset, elements.length));

    outputElements.push(lastElement);

    return outputElements;
  },

  /**
   * Compresses an array of exponents into a single exponent.
   *
   * @function compressExponents
   * @memberof MathematicalArrayCompressor
   * @param {Exponent[]}
   *            exponents The array of exponents to compress.
   * @returns {Exponent} The exponent into which all of the exponents have
   *          been compressed.
   * @throws {Error}
   *             If the input validation fails.
   */
  compressExponents: function(exponents) {
    checkExponents(exponents, 'Exponents to compress');

    var exponent = exponents[0];
    for (var i = 1; i < exponents.length; i++) {
      exponent = exponent.add(exponents[i]);
    }

    return exponent;
  },

  /**
   * Compresses a specified number of trailing exponents of an array of
   * exponents into a single exponent.
   *
   * @function compressTrailingExponents
   * @memberof MathematicalArrayCompressor
   * @param {Exponent[]}
   *            exponents The array of exponents to compress.
   * @param {number}
   *            numOutputExponents The number of output exponents required.
   * @returns {Exponent[]} The array of exponents resulting from the
   *          compression.
   * @throws {Error}
   *             If the input validation fails.
   */
  compressTrailingExponents: function(exponents, numOutputExponents) {
    checkExponents(
        exponents, 'Exponent array for which to compress trailing exponents');
    validator.checkIsPositiveNumber(
        numOutputExponents,
        'Number of exponents to remain after compressing trailing exponents');

    var offset = numOutputExponents - 1;
    var outputExponents = exponents.slice(0, offset);

    var lastExponent =
        this.compressExponents(exponents.slice(offset, exponents.length));

    outputExponents.push(lastExponent);

    return outputExponents;
  }
};

function checkZpGroupElements(elements, label) {
  validator.checkZpGroupElements(elements, label);

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

function checkExponents(exponents, label, q) {
  validator.checkExponents(exponents, label, q);

  for (var i = 0; i < exponents.length; i++) {
    validator.checkIsInstanceOf(
        exponents[i], Exponent, 'Exponent', 'exponent ' + i + ' of ' + label);
  }
}
