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

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

import org.junit.BeforeClass;

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.Permutation;
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.PrivateCommitment;
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.ExponentTools;
import com.scytl.products.ov.mixnet.commons.tools.GroupTools;
import com.scytl.products.ov.mixnet.commons.tools.MatrixArranger;
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;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.ParallelPrivateAndPublicCommitmentsGenerator;
import com.scytl.products.ov.mixnet.proofs.bg.shuffle.PrivateAndPublicCommitmentsGenerator;

public class BasicProofsInitializer {

    public static final int BALLOT_BOX_LENGTH = 512;

    protected static ElGamalPublicKey pubKey;

    protected static ZpSubgroup zp;

    protected static int m;

    protected static int n;

    protected static int mu;

    protected static int numiterations;

    protected static CommitmentParams commitmentParams;

    protected static GjosteenElGamal elgamal;

    protected static int concurrencyLevel;

    protected static ProofsGeneratorConfigurationParams config;

    protected static BigInteger p;

    protected static BigInteger q;

    protected static BigInteger g;

    protected static MultiExponentiation limMultiExpo;

    protected static CiphertextTools ciphertextTools;

    protected static ComputeAllE computeAllE;

    protected static PrivateAndPublicCommitmentsGenerator privateAndPublicCommitmentsGenerator;

    @BeforeClass
    public static void setUp() throws IOException {

        try {
            p = new BigInteger("114727648638092901303037950596653216698134713713320757469185195847827007146277");

            q = new BigInteger("4176867913");

            g = new BigInteger("64234935082206209924651906168426787772383972472097293774232814781118017724241");

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

            m = 8;

            n = 64;

            mu = 2;

            numiterations = 0;

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

            // Create ElGamal public keys (we dont need the private key)
            final List<ZpGroupElement> publicKeyGroupElements = new ArrayList<>();

            publicKeyGroupElements.add( //
                zp.getGenerator().exponentiate( //
                    ExponentTools.getRandomExponent(zp.getQ())));

            pubKey = new ElGamalPublicKey(publicKeyGroupElements, zp);

            commitmentParams = new CommitmentParams(zp, n);

            elgamal = new GjosteenElGamal(zp, pubKey);

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

            limMultiExpo = MultiExponentiationImpl.getInstance();

            ciphertextTools = new CiphertextTools(limMultiExpo);

            computeAllE = new ParallelComputeAllE(ciphertextTools);

            privateAndPublicCommitmentsGenerator = new ParallelPrivateAndPublicCommitmentsGenerator();

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

    protected Permutation getMockedPermutation() {

        final int[] permutationInt = new int[BALLOT_BOX_LENGTH];
        for (int i = 0; i < BALLOT_BOX_LENGTH; i++) {
            permutationInt[i] = i;
        }

        return new Permutation(permutationInt);
    }

    protected Ciphertext[][] getMockedEncryptedCiphertexts() throws GeneralCryptoLibException {

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

        for (int i = 0; i < BALLOT_BOX_LENGTH; i++) {
            final CiphertextImpl encryptedBallot =
                new CiphertextImpl(GroupTools.getRandomElement(zp), GroupTools.getRandomElement(zp));
            ballots.add(encryptedBallot);
        }

        final ElGamalEncryptedBallots encryptedBallots = new ElGamalEncryptedBallots(ballots);

        return MatrixArranger.arrangeInCiphertextMatrix(encryptedBallots, m, n);
    }

    protected Ciphertext[][] getMockedReEncryptedCiphertexts() throws GeneralCryptoLibException {

        return getMockedEncryptedCiphertexts();
    }

    protected Randomness[] getMockedRandomExponents() {

        final Randomness[] exponents = new Randomness[BALLOT_BOX_LENGTH];

        for (int i = 0; i < BALLOT_BOX_LENGTH; i++) {
            exponents[i] = new GjosteenElGamalRandomness(ExponentTools.getRandomExponent(q));
        }

        return exponents;
    }

    protected PrivateCommitment[] getMockedPrivateCommitments(final Exponent[][] exponents) {

        final PrivateCommitment[] mockedPrivateCommitments = new PrivateCommitment[m];

        final Randomness[] randomExponents = getMockedRandomExponents();

        for (int i = 0; i < m; i++) {
            mockedPrivateCommitments[i] = new PrivateCommitment(exponents[i], randomExponents[i].getExponent(),
                commitmentParams, limMultiExpo);
        }
        return mockedPrivateCommitments;
    }

}
