/*
 * 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 static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import java.math.BigInteger;
import java.util.Arrays;

import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.ov.mixnet.commons.beans.Permutation;
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.tools.ExponentTools;
import com.scytl.products.ov.mixnet.commons.tools.MatrixArranger;
import com.scytl.products.ov.mixnet.commons.tools.MultiExponentiation;
import com.scytl.products.ov.mixnet.proofs.bg.BasicProofsInitializer;

public class ParallelPrivateAndPublicCommitmentsGeneratorTest extends BasicProofsInitializer {

    private static final int ONE_PROCESSOR = 1;

    private ParallelPrivateAndPublicCommitmentsGenerator _sut;

    private int concurrencyLevel = ONE_PROCESSOR;

    private Exponent[][] _permutationAsExponentsMatrix;

    private PrivateCommitment[] _privateCommitments;

    private PublicCommitment[] _publicCommitments;

    @Test
    public void generate_and_set_related_public_and_private_commitments() {

        concurrencyLevel = ONE_PROCESSOR;

        final Permutation permutation = getPermutation();

        _permutationAsExponentsMatrix = MatrixArranger.transformPermutationToExponentMatrix(permutation, q, m, n);

        _sut = new ParallelPrivateAndPublicCommitmentsGenerator();

        Exponent[] r = ExponentTools.getVectorRandomExponent(m * n, q);

        _privateCommitments = new PrivateCommitment[m];
        _publicCommitments = new PublicCommitment[m];

        _sut.generateAndSet(_permutationAsExponentsMatrix, r, m, commitmentParams, _privateCommitments,
            _publicCommitments, limMultiExpo, concurrencyLevel);

        checkContentsAndAssertEquals();
    }

    private Permutation getPermutation() {
        int[] permutationAsIntArray = new int[m * n];
        for (int i = 0; i < permutationAsIntArray.length; i++) {
            permutationAsIntArray[i] = i;
        }
        return new Permutation(permutationAsIntArray);
    }

    @Test
    public void generate_private_and_public_commitments() {

        final Exponent[][] exponents =
            MatrixArranger.transformPermutationToExponentMatrix(getMockedPermutation(), q, m, n);

        _sut = new ParallelPrivateAndPublicCommitmentsGenerator();

        final Exponent[] r = ExponentTools.getVectorRandomExponent(m, q);

        _privateCommitments = new PrivateCommitment[m];
        _publicCommitments = new PublicCommitment[m];

        _sut.generateAndSet(exponents, r, m, commitmentParams, _privateCommitments, _publicCommitments, limMultiExpo,
            concurrencyLevel);

        for (int i = 0; i < m; i++) {
            assertThat(_privateCommitments[i] != null, is(true));
            assertThat(_publicCommitments[i] != null, is(true));

            assertThat(_publicCommitments[i].isEqual(_privateCommitments[i].makePublicCommitment()), is(true));
        }
    }

    @Test
    public void generate_the_same_output_independently_from_the_level_of_concurrency() {

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

        final CommitmentParams comParams = getCommitmentParams(g, p, q);

        final Exponent[][] permutationAsExponentsMatrix =
            MatrixArranger.transformPermutationToExponentMatrix(getPermutation(), q, m, n);

        final Exponent[] r = ExponentTools.getVectorRandomExponent(m * n, q);

        final PrivateCommitment[] privateCommitmentsOne = new PrivateCommitment[m];
        final PublicCommitment[] publicCommitmentsOne = new PublicCommitment[m];

        generateAndSetWithN(privateCommitmentsOne, publicCommitmentsOne, permutationAsExponentsMatrix, comParams,
            limMultiExpo, m, r);

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

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

        concurrencyLevel = ONE_PROCESSOR;

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

            concurrencyLevel = cpus;

            final PrivateCommitment[] privateCommitmentsN = new PrivateCommitment[m];
            final PublicCommitment[] publicCommitmentsN = new PublicCommitment[m];

            generateAndSetWithN(privateCommitmentsN, publicCommitmentsN, permutationAsExponentsMatrix, comParams,
                limMultiExpo, m, r);

            for (int i = 0; i < m; i++) {
                assertThat(Arrays.equals(privateCommitmentsOne[i].getM(), privateCommitmentsN[i].getM()), is(true));
                assertThat(privateCommitmentsOne[i].getR(), is(privateCommitmentsN[i].getR()));
                assertThat(publicCommitmentsOne[i].getElement(), is(publicCommitmentsN[i].getElement()));
            }
        }
    }

    private CommitmentParams getCommitmentParams(final BigInteger g, final BigInteger p, final BigInteger q) {

        try {

            ZpSubgroup zp = new ZpSubgroup(g, p, q);
            return new CommitmentParams(zp, n);

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

    private void checkContentsAndAssertEquals() {

        for (int i = 0; i < _privateCommitments.length; i++) {

            final PublicCommitment publicCom = _privateCommitments[i].makePublicCommitment();

            assertThat(publicCom.getElement(), is(_publicCommitments[i].getElement()));
        }
    }

    private void generateAndSetWithN(final PrivateCommitment[] privateCommitmentsTwo,
            final PublicCommitment[] publicCommitmentsTwo, final Exponent[][] _permutationAsExponentsMatrix,
            final CommitmentParams commitmentParams, final MultiExponentiation limMultiExpo, final int m,
            final Exponent[] r) {

        _sut = new ParallelPrivateAndPublicCommitmentsGenerator();

        _sut.generateAndSet(_permutationAsExponentsMatrix, r, m, commitmentParams, privateCommitmentsTwo,
            publicCommitmentsTwo, limMultiExpo, concurrencyLevel);
    }
}
