/*
 * 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 org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
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;

@Ignore
public abstract class BaseShuffleProofITest {

    private static final int NUM_OPTIONS = 1;

    private static int concurrencyLevel;

    private static ZpSubgroup zp;

    private static BigInteger q;

    private static MultiExponentiation limMultiExpo;

    private static CiphertextTools ciphertextTools;

    private static ComputeAllE computeAllE;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

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

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

        concurrencyLevel = Runtime.getRuntime().availableProcessors();

        limMultiExpo = MultiExponentiationImpl.getInstance();

        ciphertextTools = new CiphertextTools(limMultiExpo);

        computeAllE = new ParallelComputeAllE(ciphertextTools);
    }

    @Test
    public void shuffleArgTest() throws Exception {
        final int n = 2;
        final int m = 3;

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

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

        ZpGroupElement[] elements1 = getAsGroupElementArray(1, zp);
        ZpGroupElement[] elements3 = getAsGroupElementArray(3, zp);
        ZpGroupElement[] elements9 = getAsGroupElementArray(9, zp);
        ZpGroupElement[] elements2 = getAsGroupElementArray(2, zp);
        ZpGroupElement[] elements4 = getAsGroupElementArray(4, zp);
        ZpGroupElement[] elements6 = getAsGroupElementArray(6, zp);

        final Ciphertext C11 = elgamal.encrypt(elements1);
        final Ciphertext C12 = elgamal.encrypt(elements3);
        final Ciphertext C21 = elgamal.encrypt(elements9);
        final Ciphertext C22 = elgamal.encrypt(elements2);
        final Ciphertext C31 = elgamal.encrypt(elements4);
        final Ciphertext C32 = elgamal.encrypt(elements6);

        final GjosteenElGamalRandomness rho11 = new GjosteenElGamalRandomness(3, q);
        final GjosteenElGamalRandomness rho12 = new GjosteenElGamalRandomness(3, q);
        final GjosteenElGamalRandomness rho21 = new GjosteenElGamalRandomness(3, q);
        final GjosteenElGamalRandomness rho22 = new GjosteenElGamalRandomness(3, q);
        final GjosteenElGamalRandomness rho31 = new GjosteenElGamalRandomness(3, q);
        final GjosteenElGamalRandomness rho32 = new GjosteenElGamalRandomness(3, q);

        final GjosteenElGamalRandomness[] rho = {rho11, rho12, rho21, rho22, rho31, rho32 };

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

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

        final Ciphertext[][] ciphertexts = {
                {C12.multiply(elgamal.encrypt(identityElement, rho11)),
                        C11.multiply(elgamal.encrypt(identityElement, rho12)) },
                {C21.multiply(elgamal.encrypt(identityElement, rho21)),
                        C22.multiply(elgamal.encrypt(identityElement, rho22)) },
                {C31.multiply(elgamal.encrypt(identityElement, rho31)),
                        C32.multiply(elgamal.encrypt(identityElement, rho32)) } };

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

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

        final PrivateAndPublicCommitmentsGenerator commitmentsInformer = commitmentsInformer();

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

        final ShuffleProof shuffleProof = prover.generate();

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

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

        final ShuffleProofSecondAnswer ans = shuffleProof.getSecondAnswer();

        final ShuffleProofVerifier verifier = new ShuffleProofVerifier(zp, elgamal, pars, vecC, ciphertexts, m, n,
            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,
            final ComputeAllE computeAllE, final MultiExponentiation limMultiExpo, final int concurrencyLevel);

}
