/*
 * 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
 */
package com.scytl.products.ov.mixnet.proofs.bg.multiexp;

import java.math.BigInteger;
import java.util.List;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.CiphertextImpl;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.products.ov.mixnet.commons.concurrent.LoopParallelizerAction;
import com.scytl.products.ov.mixnet.commons.concurrent.processor.A0Processor;
import com.scytl.products.ov.mixnet.commons.concurrent.processor.EncryptRaisingToRandomProcessor;
import com.scytl.products.ov.mixnet.commons.concurrent.processor.EvaluatorProcessor;
import com.scytl.products.ov.mixnet.commons.concurrent.processor.InterpolatorProcessor;
import com.scytl.products.ov.mixnet.commons.homomorphic.Cryptosystem;
import com.scytl.products.ov.mixnet.commons.homomorphic.Randomness;
import com.scytl.products.ov.mixnet.commons.mathematical.tools.LUDecomposition;
import com.scytl.products.ov.mixnet.commons.tools.CiphertextTools;
import com.scytl.products.ov.mixnet.commons.tools.ExponentTools;
import com.scytl.products.ov.mixnet.commons.tools.LoopTools;
import com.scytl.products.ov.mixnet.commons.tools.LoopTools.Range;

public class ParallelComputeAllE implements ComputeAllE {

    private final CiphertextTools ciphertextTools;

    public ParallelComputeAllE(final CiphertextTools ciphertextTools) {
        this.ciphertextTools = ciphertextTools;
    }

    @Override
    public Ciphertext[] computeAllENoRandomizing(final Exponent[][] a, final Ciphertext[][] vecC,
            final BigInteger order) {

        try {

            final int n = vecC[0].length;
            final int m = vecC.length;

            final int numberOfDiagonals = 2 * m - 1;
            final CiphertextImpl[] cE = new CiphertextImpl[numberOfDiagonals];

            // Let's first generate E0...E2m-2 (total of 2m-1 values)
            // without randomizing values

            // Choose omega values
            final Exponent[] omega = new Exponent[numberOfDiagonals];
            omega[0] = new Exponent(order, BigInteger.ZERO);
            for (int i = 1; i < omega.length; i++) {
                omega[i] = new Exponent(order, BigInteger.valueOf(i));
            }

            // first compute the vandermonde matrix
            final Exponent[][] vandermondeOmega = new Exponent[numberOfDiagonals][numberOfDiagonals];
            final Exponent[] omegaAcumulator = ExponentTools.get1Vector(numberOfDiagonals, order);
            for (int i = 0; i < numberOfDiagonals; i++) {
                for (int j = 0; j < numberOfDiagonals; j++) {
                    vandermondeOmega[j][i] = omegaAcumulator[j];
                    omegaAcumulator[j] = omegaAcumulator[j].multiply(omega[j]);
                }
            }

            // Compute each E_{k} before interpolation

            final CiphertextImpl[] evalC = new CiphertextImpl[numberOfDiagonals];

            final List<Range> ranges = LoopTools.asRangesList(numberOfDiagonals, numberOfDiagonals);

            // first compute the exponent
            final EvaluatorProcessor evaluatorProcessor =
                new EvaluatorProcessor(evalC, m, n, a, vecC, order, ciphertextTools, vandermondeOmega);
            new LoopParallelizerAction(evaluatorProcessor, ranges).invoke();

            // now compute the inverse
            // this is done using the generic algorithm
            // maybe the special form of the matrix could be used
            // not critical
            final LUDecomposition lu = new LUDecomposition(vandermondeOmega, order);
            final Exponent[][] inverse = lu.getInverseMatrix();

            final InterpolatorProcessor interpolatorProcessor =
                new InterpolatorProcessor(cE, evalC, inverse, ciphertextTools);
            new LoopParallelizerAction(interpolatorProcessor, ranges).invoke();

            return cE;

        } catch (GeneralCryptoLibException ex) {
            throw new CryptoLibException(ex);
        }
    }

    @Override
    public Ciphertext[] addRandomizingEncryption(final Ciphertext[] ePrime, final Exponent[] b, final Randomness[] tau,
            final Cryptosystem cryptosystem, final int concurrencyLevel) {

        final int length = ePrime.length;
        // Ideally, the number of tasks should be independent from the number of
        // processors. In this case we use it this way to calculate the ranges.
        final List<Range> ranges = LoopTools.asRangesList(length, concurrencyLevel);

        final Ciphertext[] cE = ePrime.clone();

        final EncryptRaisingToRandomProcessor encryptRaisingToRandomProcessor =
            new EncryptRaisingToRandomProcessor(cE, b, tau, cryptosystem);

        new LoopParallelizerAction(encryptRaisingToRandomProcessor, ranges).invoke();

        return cE;
    }

    @Override
    public Ciphertext[] addA0FactorsIncludingE0(final Ciphertext[] ePrime, final Ciphertext[][] vecC,
            final Exponent[] a0) {
        final Ciphertext[] cE = new CiphertextImpl[ePrime.length + 1];
        System.arraycopy(ePrime, 0, cE, 1, ePrime.length);
        final int m = vecC.length;

        final List<Range> ranges = LoopTools.asRangesList(m, m);

        new LoopParallelizerAction(new A0Processor(cE, vecC, a0, ciphertextTools, m), ranges).invoke();

        return cE;
    }

}
