/*
 * 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.ov.mixing.core.elgamal;

import static org.junit.Assert.assertEquals;

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

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.CiphertextImpl;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalKeyPairGenerator;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.ov.mixing.commons.configuration.MatrixDimensions;
import com.scytl.ov.mixing.core.AlgorithmNotFoundException;
import com.scytl.ov.mixing.core.MixAndProveJob;
import com.scytl.ov.mixing.core.MixJob;
import com.scytl.ov.mixing.core.Mixable;
import com.scytl.ov.mixing.core.MixerContext;
import com.scytl.ov.mixing.core.MixingException;
import com.scytl.ov.mixing.core.ProofGenerationConfigParams;

public class ElGamalMixerSPITest {

    // private static ElGamalMixerSPI target = new ElGamalMixerSPI();

    private static ElGamalMixerSPI target = ElGamalMixerSPI.getInstance();

    public static BigInteger p = TestData.P_MEDIUM_SIZE;

    public static BigInteger q = TestData.Q_MEDIUM_SIZE;

    public static BigInteger g = TestData.G_MEDIUM_SIZE;

    public static ElGamalEncryptionParameters encryptionParameters;

    public static ElGamalServiceAPI elGamalService;

    public static ElGamalPublicKey publicKey;

    public static ElGamalPrivateKey privateKey;

    public static ZpSubgroup group;

    public static ProofGenerationConfigParams proofGenerationConfigParams;

    public static int NUM_MIXABLES = 1;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        elGamalService = new ElGamalService();

        encryptionParameters = new ElGamalEncryptionParameters(p, q, g);

        CryptoAPIElGamalKeyPairGenerator keyPairGenerator = elGamalService.getElGamalKeyPairGenerator();

        ElGamalKeyPair keyPair = keyPairGenerator.generateKeys(new ElGamalEncryptionParameters(p, q, g), 2);

        publicKey = keyPair.getPublicKeys();
        privateKey = keyPair.getPrivateKeys();

        group = getGroup();

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

        int m = 1;
        int n = NUM_MIXABLES;
        MatrixDimensions matrixDimensions = new MatrixDimensions(m, n);
        int numIterations = 0;
        int mu = 2;

        proofGenerationConfigParams = new ProofGenerationConfigParams(matrixDimensions, numIterations, mu, group);
    }

    @Test
    public void whenGetInstanceThenOk() {

        ElGamalMixerSPI instance = ElGamalMixerSPI.getInstance();
        assertEquals(target, instance);
    }

    @Test
    public void whenGetAlgorithmThenOk() {

        String expectedAlgorithm = "ElGamal";
        assertEquals(expectedAlgorithm, target.algorithm());
    }

    @Test
    public void whenCreateMixJobThenOk() throws AlgorithmNotFoundException, MixingException, GeneralCryptoLibException {

        List<Mixable> mixables = createListMixables(group);

        MixerContext mixerContext = null;
        MixJob mixJob = target.newMixJob(mixerContext);

        mixJob.setParameters(new ElGamalJobParameters(encryptionParameters, publicKey, proofGenerationConfigParams));

        mixJob.start(mixables);

        List<ElGamalMixableImpl> mixedMixables = mixJob.getMix(ElGamalMixableImpl.class);

        assertEquals(1, mixedMixables.size());
    }

    @Test
    public void whenCreateMixAndProveJobThenOk()
            throws AlgorithmNotFoundException, MixingException, GeneralCryptoLibException {

        List<Mixable> mixables = createListMixables(group);

        MixerContext mixerContext = null;
        MixAndProveJob mixAndProveJob = target.newMixAndProveJob(mixerContext);

        mixAndProveJob
            .setParameters(new ElGamalJobParameters(encryptionParameters, publicKey, proofGenerationConfigParams));

        mixAndProveJob.start(mixables);

        List<ElGamalMixableImpl> mixedMixables = mixAndProveJob.getMix(ElGamalMixableImpl.class);

        assertEquals(1, mixedMixables.size());
    }

    private List<Mixable> createListMixables(ZpSubgroup group) throws GeneralCryptoLibException {

        List<Mixable> listOfMixables = new ArrayList<>();

        for (int i = 0; i < NUM_MIXABLES; i++) {
            ElGamalMixable elGamalMixable = new ElGamalMixableImpl(createCiphertext(group));
            listOfMixables.add(elGamalMixable);
        }

        return listOfMixables;
    }

    private Ciphertext createCiphertext(ZpSubgroup group) throws GeneralCryptoLibException {

        ZpGroupElement gamma = new ZpGroupElement(new BigInteger("9"), group);
        ZpGroupElement phi1 = new ZpGroupElement(new BigInteger("4"), group);
        ZpGroupElement phi2 = new ZpGroupElement(new BigInteger("2"), group);
        List<ZpGroupElement> phis = new ArrayList<>();
        phis.add(phi1);
        phis.add(phi2);
        return new CiphertextImpl(gamma, phis);
    }

    private static ZpSubgroup getGroup() throws GeneralCryptoLibException {

        return new ZpSubgroup(g, p, q);
    }
}
