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

import java.math.BigInteger;
import java.util.concurrent.TimeUnit;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

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.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.mixnet.commons.beans.Permutation;
import com.scytl.products.ov.mixnet.commons.beans.proofs.ShuffleProof;
import com.scytl.products.ov.mixnet.commons.beans.proofs.ShuffleProofSecondAnswer;
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.homomorphic.impl.GjosteenElGamalRandomness;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.CommitmentParams;
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.tools.CiphertextTools;
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.ParallelComputeAllE;

public abstract class BaseShuffleReductionITest {

    private static final int numOptions = 1;

    private static int concurrencyLevel;

    private static CiphertextTools ciphertextTools;

    private static MultiExponentiation limMultiExpo;

    private static ComputeAllE computeAllE;

    @BeforeClass
    public static void setUp() {
        concurrencyLevel = Runtime.getRuntime().availableProcessors();

        limMultiExpo = MultiExponentiationImpl.getInstance();

        ciphertextTools = new CiphertextTools(limMultiExpo);

        computeAllE = new ParallelComputeAllE(ciphertextTools);
    }

    @Test
    public void bigShuffleArgTest() throws Exception {
        final BigInteger p = new BigInteger("15294034768093677312256663166625633354362303");
        final BigInteger q = new BigInteger("7647017384046838656128331583312816677181151");
        final BigInteger g = new BigInteger("2");

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

        final int n = 10;
        final int m = 64;

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

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

        final Ciphertext[][] vecC = new CiphertextImpl[m][n];
        final Ciphertext[][] ciphertexts = new CiphertextImpl[m][n];
        final GjosteenElGamalRandomness[] rho = new GjosteenElGamalRandomness[m * n];

        final ZpGroupElement[] identityElementAsGroupElementArray = new ZpGroupElement[1];
        identityElementAsGroupElementArray[0] = zp.getIdentity();

        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

                final ZpGroupElement[] elements = getAsGroupElementArray(4, zp);

                vecC[i][j] = elgamal.encrypt(elements);
                rho[i * n + j] = new GjosteenElGamalRandomness(3, zp.getQ());

                ciphertexts[i][j] =
                    vecC[i][j].multiply(elgamal.encrypt(identityElementAsGroupElementArray, rho[i * n + j]));
            }
        }

        int[] permutationAsInts = new int[m * n];
        for (int i = 0; i < permutationAsInts.length; i++) {
            permutationAsInts[i] = i;
        }
        Permutation permutation = new Permutation(permutationAsInts);

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

        final PrivateAndPublicCommitmentsGenerator commitmentsInformer = commitmentsInformer();

        final ShuffleProofGenerator prover = new ShuffleProofGenerator(config, pars, vecC, permutation,
            commitmentsInformer, limMultiExpo, concurrencyLevel, ciphertexts, rho, computeAllE);

        final long ini = System.currentTimeMillis();
        System.out.println("Starting Shuffle Proof generation");
        final ShuffleProof shuffleProof = prover.generate();

        final PublicCommitment[] cA = shuffleProof.getInitialMessage();

        final PublicCommitment[] cB = shuffleProof.getFirstAnswer();

        final ShuffleProofSecondAnswer ans = shuffleProof.getSecondAnswer();

        final long end = System.currentTimeMillis();

        final long seconds = TimeUnit.SECONDS.convert((end - ini), TimeUnit.MILLISECONDS);
        System.out.println("Shuffle Proof generated in " + seconds + "s");
        System.out.println("Starting verification");
        final ShuffleProofVerifier verifier = new ShuffleProofVerifier(zp, elgamal, pars, vecC, ciphertexts, m, n, 4, 2,
            ciphertextTools, limMultiExpo);
        final boolean testResult = verifier.verifyProof(cA, cB, 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);
        }
    }

    protected abstract PrivateAndPublicCommitmentsGenerator commitmentsInformer();

    protected abstract SecondAnswerGenerator secondAnswerGenerator(final ProofsGeneratorConfigurationParams config,
            final CommitmentParams pars, final CiphertextImpl[][] ciphertexts, final Randomness[] rho,
            MultiExponentiation limMultiExpo, ComputeAllE computeAllE, final int concurrencyLevel);
}
