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

import static org.junit.Assert.assertTrue;

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

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.bean.ElGamalPublicKey;
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.ballots.ElGamalEncryptedBallots;
import com.scytl.products.ov.mixnet.commons.beans.ShuffleOutput;
import com.scytl.products.ov.mixnet.commons.tools.ExponentTools;
import com.scytl.products.ov.mixnet.commons.tools.GroupTools;
import com.scytl.products.ov.mixnet.shuffler.permutation.PermutationGenerator;
import com.scytl.products.ov.mixnet.shuffler.permutation.Permutator;

public class ShuffleITest {

    private static final int ONE_PROCESSOR = 1;

    private static ElGamalShuffler target;

    private static ZpSubgroup group;

    private static ElGamalPublicKey publicKey;

    @BeforeClass
    public static void setUp() {

        try {

            final BigInteger p = new BigInteger("15294034768093677312256663166625633354362303");
            final BigInteger q = new BigInteger("7647017384046838656128331583312816677181151");

            group = GroupTools.createGroupWithRandomGenerator(p, q);

            final Exponent randomExponent = ExponentTools.getRandomExponent(q);

            final List<ZpGroupElement> keys = new ArrayList<>();
            keys.add(group.getGenerator().exponentiate(randomExponent));

            publicKey = new ElGamalPublicKey(keys, group);

            target = new ElGamalShuffler(new Permutator(), new PermutationGenerator());

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

    @Test
    public void whenShufflingThenPermutateAndReEncryptEncryptedBallots() {

        try {
            final int numElements = 5;

            final List<Ciphertext> ballots = new ArrayList<>();

            // The input preparation should be handled by the PreProcessor
            final String[] gammaString = new String[] {"7606155287466709247547562508296526348227245",
                    "4012556967239296284125919604329103854893595", "11583975953794885768215634543368899452667420",
                    "7155423998998370092051586125176128718489417", "7830708621782553206289139938032543522538535" };

            final ZpGroupElement[] gammas = new ZpGroupElement[numElements];
            for (int i = 0; i < gammaString.length; i++) {

                BigInteger value = new BigInteger(gammaString[i]);
                gammas[i] = new ZpGroupElement(value, group.getP(), group.getQ());
            }

            final String[] phisString = new String[] {"9894101363857510707841445959199640374054748",
                    "6054959594806155309765068576952771674747761", "2114609311581369961287633946894396492461861",
                    "4070746468346708777122357082197449848916890", "795602734299768285623543899693683842706894" };

            final ZpGroupElement[] phis = new ZpGroupElement[numElements];
            for (int i = 0; i < phisString.length; i++) {

                BigInteger value = new BigInteger(phisString[i]);
                phis[i] = new ZpGroupElement(value, group.getP(), group.getQ());
            }

            for (int i = 0; i < numElements; i++) {

                final List<ZpGroupElement> ballotPhis = new ArrayList<>();
                ballotPhis.add(phis[i]);
                ballots.add(new CiphertextImpl(gammas[i], ballotPhis));
            }

            final ElGamalEncryptedBallots elGamalEncryptedBallots = new ElGamalEncryptedBallots(ballots);

            ShuffleOutput<ElGamalEncryptedBallots> shuffleOutput =
                target.sequentialShuffle(group, publicKey, ONE_PROCESSOR, elGamalEncryptedBallots, null, null);

            assertTrue(elGamalEncryptedBallots.getBallots().size() == shuffleOutput.getReEncryptedBallots().getBallots()
                .size());

            final int availableProcessors = Runtime.getRuntime().availableProcessors();

            System.out.println("Testing concurrency up to " + availableProcessors + " CPUs");

            for (int cpus = 2; cpus < availableProcessors; cpus++) {

                target = new ElGamalShuffler(new Permutator(), new PermutationGenerator());

                shuffleOutput = target.sequentialShuffle(group, publicKey, cpus, elGamalEncryptedBallots, null, null);

                assertTrue(elGamalEncryptedBallots.getBallots().size() == shuffleOutput.getReEncryptedBallots()
                    .getBallots().size());

            }

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