/*
 * 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 org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;

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.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.mixnet.commons.beans.proofs.MultiExponentiationReductionAnswer;
import com.scytl.products.ov.mixnet.commons.beans.proofs.MultiExponentiationReductionInitialMessage;
import com.scytl.products.ov.mixnet.commons.homomorphic.impl.GjosteenElGamal;
import com.scytl.products.ov.mixnet.commons.homomorphic.impl.GjosteenElGamalRandomness;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.CommitmentParams;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.PrivateCommitment;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.PublicCommitment;
import com.scytl.products.ov.mixnet.commons.proofs.bg.config.ProofsGeneratorConfigurationParams;
import com.scytl.products.ov.mixnet.commons.tests.categories.SlowTest;
import com.scytl.products.ov.mixnet.commons.tools.CiphertextTools;
import com.scytl.products.ov.mixnet.commons.tools.CommitmentTools;
import com.scytl.products.ov.mixnet.commons.tools.ExponentTools;
import com.scytl.products.ov.mixnet.commons.tools.MultiExponentiation;
import com.scytl.products.ov.mixnet.commons.tools.MultiExponentiationImpl;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.basic.BasicProofGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.basic.ParallelBasicProofGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.reduction.MultiExponentiationReductionProofVerifier;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.reduction.ParallelReductionProofGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.reduction.ReductionProofGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.SecondCommitmentGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.state.MapBasedHarvester;
import com.scytl.products.ov.mixnet.proofs.bg.state.StateHarvester;

@Category(SlowTest.class)
public class MultiExponentiationProofGeneratorITest {

    private static final int mu = 4;

    private static final int numiterations = 2;

    private static final int concurrencyLevel = Runtime.getRuntime().availableProcessors();

    private static final int numOptions = 1;

    // Reduction currently is not supported.
    // {@see ParallelShuffleReductionProofIT}
    @Ignore
    @Test
    public void multiExpoReduction() throws Exception {

        final BigInteger p = new BigInteger("15294034768093677312256663166625633354362303");
        final BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2));
        final BigInteger g = new BigInteger("2");

        final ZpSubgroup zp = new ZpSubgroup(g, p, q);

        MultiExponentiation limMultiExpo = MultiExponentiationImpl.getInstance();

        CiphertextTools ciphertextTools = new CiphertextTools(limMultiExpo);

        ComputeAllE computeAllE = new ParallelComputeAllE(ciphertextTools);

        final int n = 10;
        final int m = 16;

        final CommitmentParams pars = new CommitmentParams(zp, n);

        final GjosteenElGamal elgamal = new GjosteenElGamal(zp, numOptions);

        final Ciphertext[][] vecC = new CiphertextImpl[m][n];
        for (int i = 0; i < vecC.length; i++) {
            for (int j = 0; j < vecC[i].length; j++) {
                // ok, all are encryptions of 4, but it is enough

                ZpGroupElement[] elements = getAsGroupElementArray(4, zp);
                vecC[i][j] = elgamal.encrypt(elements);
            }
        }

        final Exponent[][] a = new Exponent[m][n];
        for (int i = 0; i < a.length; i++) {
            a[i] = ExponentTools.getVectorRandomExponent(n, zp.getQ());
        }
        final Exponent[] r = ExponentTools.getVectorRandomExponent(m, zp.getQ());

        final PrivateCommitment[] cA = new PrivateCommitment[m];
        for (int i = 0; i < cA.length; i++) {
            cA[i] = new PrivateCommitment(a[i], r[i], pars, limMultiExpo);
        }

        final PublicCommitment[] caVerifier = new PublicCommitment[m];
        for (int i = 0; i < caVerifier.length; i++) {
            caVerifier[i] = cA[i].makePublicCommitment();
        }

        final GjosteenElGamalRandomness rho = new GjosteenElGamalRandomness(3, zp.getQ());
        Ciphertext C = elgamal.getEncryptionOf1(rho);
        for (int i = 0; i < vecC.length; i++) {
            C = C.multiply(ciphertextTools.compVecCiphVecExp(vecC[i], a[i]));
        }

        final ProofsGeneratorConfigurationParams config =
            new ProofsGeneratorConfigurationParams(m, n, elgamal, numiterations, mu, zp);

        final BasicProofGenerator basicProofGenerator =
            new ParallelBasicProofGenerator(config, pars, limMultiExpo, computeAllE, 1);

        final MultiExponentiationProofGenerator prover = new MultiExponentiationProofGenerator(config, vecC, null, //
            basicProofGenerator, //
            new ParallelReductionProofGenerator(config, pars.getCommitmentLength(), basicProofGenerator, pars,
                computeAllE, limMultiExpo, concurrencyLevel));

        final StateHarvester harvester = new MapBasedHarvester();
        harvester.collect(MultiExponentiationProofGenerator.Produced.CALCULATED_RANDOM_EXPONENT, rho);
        harvester.collect(SecondCommitmentGenerator.Produced.PRIVATE_COMMITS, cA);
        harvester.collect(SecondCommitmentGenerator.Produced.PUBLIC_COMMITS, CommitmentTools.makePublic(cA));
        prover.generate(harvester);

        final MultiExponentiationReductionInitialMessage ini =
            harvester.offer(ReductionProofGenerator.Produced.REDUCT_MULTIEXP_INIT_MSG,
                MultiExponentiationReductionInitialMessage.class);

        final MultiExponentiationReductionAnswer ans = harvester
            .offer(ReductionProofGenerator.Produced.REDUCT_MULTIEXP_ANSWER, MultiExponentiationReductionAnswer.class);

        final MultiExponentiationReductionProofVerifier verifier = new MultiExponentiationReductionProofVerifier(m, n,
            elgamal, zp, pars, vecC, C, caVerifier, mu, numiterations, ciphertextTools, limMultiExpo);

        final boolean verify = verifier.verify(ini, ans);

        Assert.assertTrue(verify);
    }

    private ZpGroupElement[] getAsGroupElementArray(final int i, final ZpSubgroup groupParams) {

        try {
            ZpGroupElement[] elements = new ZpGroupElement[1];
            elements[0] = new ZpGroupElement(BigInteger.valueOf( + i), groupParams.getP(), groupParams.getQ());
            return elements;
        } catch (GeneralCryptoLibException ex) {
            throw new CryptoLibException(ex);
        }
    }
}
