/*
 * 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.basic;

import java.math.BigInteger;

import org.junit.Assert;
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.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.MultiExponentiationBasicProofAnswer;
import com.scytl.products.ov.mixnet.commons.beans.proofs.MultiExponentiationBasicProofInitialMessage;
import com.scytl.products.ov.mixnet.commons.homomorphic.Randomness;
import com.scytl.products.ov.mixnet.commons.homomorphic.impl.GjosteenElGamal;
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.MultiExponentiation;
import com.scytl.products.ov.mixnet.commons.tools.MultiExponentiationImpl;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.ComputeAllE;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.MultiExponentiationProofGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.multiexp.ParallelComputeAllE;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.SecondCommitmentGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.state.CommonStateLabels;
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 BasicProofGeneratorITest{

    private static final int NUM_OPTIONS = 1;

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

    private static MultiExponentiation limMultiExpo;

    private static CiphertextTools ciphertextTools;

    private static ComputeAllE computeAllE;

    @Test
    public void multiExpoBasicArgTest() 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);

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

        limMultiExpo = MultiExponentiationImpl.getInstance();

        ciphertextTools = new CiphertextTools(limMultiExpo);

        computeAllE = new ParallelComputeAllE(ciphertextTools);

        final int n = 2;
        final int m = 3;

        final Exponent[] a1 =
            new Exponent[] {new Exponent(q, new BigInteger("2")), new Exponent(q, new BigInteger("3")) };
        final Exponent[] a2 =
            new Exponent[] {new Exponent(q, new BigInteger("2")), new Exponent(q, new BigInteger("5")) };
        final Exponent[] a3 =
            new Exponent[] {new Exponent(q, new BigInteger("2")), new Exponent(q, new BigInteger("1")) };

        final Exponent[][] a = {a1, a2, a3 };

        final Exponent[] r = new Exponent[] {new Exponent(q, new BigInteger("1")), new Exponent(q, new BigInteger("2")),
                new Exponent(q, new BigInteger("3")) };

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

        final PrivateCommitment cA1 = new PrivateCommitment(a1, r[0], pars, limMultiExpo);
        final PrivateCommitment cA2 = new PrivateCommitment(a2, r[1], pars, limMultiExpo);
        final PrivateCommitment cA3 = new PrivateCommitment(a3, r[2], pars, limMultiExpo);

        final PrivateCommitment[] cA = new PrivateCommitment[] {cA1, cA2, cA3 };

        final PublicCommitment[] verifCA = new PublicCommitment[] {cA1.makePublicCommitment(),
                cA2.makePublicCommitment(), cA3.makePublicCommitment() };

        ZpGroupElement[] elements_1 = getAsGroupElementArray(1, zp);
        ZpGroupElement[] elements_3 = getAsGroupElementArray(3, zp);
        ZpGroupElement[] elements_9 = getAsGroupElementArray(9, zp);
        ZpGroupElement[] elements_2 = getAsGroupElementArray(2, zp);
        ZpGroupElement[] elements_4 = getAsGroupElementArray(4, zp);
        ZpGroupElement[] elements_6 = getAsGroupElementArray(6, zp);

        final Ciphertext C11 = elgamal.encrypt(elements_1);
        final Ciphertext C12 = elgamal.encrypt(elements_3);
        final Ciphertext C21 = elgamal.encrypt(elements_9);
        final Ciphertext C22 = elgamal.encrypt(elements_2);
        final Ciphertext C31 = elgamal.encrypt(elements_4);
        final Ciphertext C32 = elgamal.encrypt(elements_6);

        final Ciphertext[][] vecC = {{C11, C12 }, {C21, C22 }, {C31, C32 } };

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

        final ProofsGeneratorConfigurationParams config =
            new ProofsGeneratorConfigurationParams(0, 2, elgamal, 0, 0, zp);

        final StateHarvester harvester = new MapBasedHarvester();
        harvester.collect(MultiExponentiationProofGenerator.Produced.CALCULATED_RANDOM_EXPONENT, rho);
        harvester.collect(CommonStateLabels.CIPHERTEXTS, vecC);
        harvester.collect(SecondCommitmentGenerator.Produced.PRIVATE_COMMITS, cA);
        PublicCommitment[] statementCA = CommitmentTools.makePublic(cA);
        harvester.collect(SecondCommitmentGenerator.Produced.PUBLIC_COMMITS, statementCA);

        final BasicProofGenerator prover =
            new ParallelBasicProofGenerator(config, pars, limMultiExpo, computeAllE, concurrencyLevel);

        prover.generate(harvester);

        final MultiExponentiationBasicProofInitialMessage ini =
            harvester.offer(BasicInitialMessageGenerator.Produced.BASIC_MULTIEXP_INIT_MSG,
                MultiExponentiationBasicProofInitialMessage.class);

        final MultiExponentiationBasicProofAnswer ans = harvester.offer(
            ParallelBasicAnswerGenerator.Produced.BASIC_MULTIEXP_ANSWER, MultiExponentiationBasicProofAnswer.class);

        final MultiExponentiationBasicProofVerifier verifier = new MultiExponentiationBasicProofVerifier(m, n, elgamal,
            pars, vecC, C, verifCA, zp.getQ(), ciphertextTools, limMultiExpo);

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

        Assert.assertTrue(testResult);
    }

    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);
        }
    }
}
