/*
 * 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.cryptolib.proofs.service;

import static junitparams.JUnitParamsRunner.$;

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

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncrypterValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.elgamal.cryptoapi.Witness;
import com.scytl.cryptolib.elgamal.utils.ElGamalTestDataGenerator;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.mathematical.groups.utils.MathematicalTestDataGenerator;
import com.scytl.cryptolib.proofs.bean.ProofPreComputedValues;
import com.scytl.cryptolib.proofs.proof.Proof;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of Proofs service API input validation for plaintext zero knowledge
 * proofs of knowledge.
 */
@RunWith(JUnitParamsRunner.class)
public class PlaintextProofValidationTest {

    private static final int NUM_PLAINTEXT_ELEMENTS = 6;

    private static ProofsService _proofsServiceForDefaultPolicy;

    private static ZpSubgroup _zpSubgroup;

    private static List<ZpGroupElement> _plaintext;

    private static ElGamalPublicKey _publicKey;

    private static Ciphertext _ciphertext;

    private static Witness _witness;

    private static List<ZpGroupElement> _emptyPlaintext;

    private static List<ZpGroupElement> _plaintextWithNullValue;

    private static List<ZpGroupElement> _shorterPlaintext;

    private static Ciphertext _shorterCiphertext;

    private static int _publicKeyLength;

    private static int _ciphertextLength;

    private static int _shorterPlaintextLength;

    private static int _shorterCiphertextLength;

    static {
        try {
            setUp();
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException("Setup failed for class "
                + PlaintextProofValidationTest.class.getSimpleName());
        }
    }

    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _proofsServiceForDefaultPolicy = new ProofsService();

        _zpSubgroup = MathematicalTestDataGenerator.getZpSubgroup();

        _publicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS)
            .getPublicKeys();

        _plaintext = MathematicalTestDataGenerator
            .getZpGroupElements(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS);

        ElGamalEncrypterValues encrypterValues =
            (ElGamalEncrypterValues) ElGamalTestDataGenerator
                .encryptGroupElements(_publicKey, _plaintext);
        _ciphertext = encrypterValues;
        _witness = encrypterValues;

        _emptyPlaintext = new ArrayList<>();

        _plaintextWithNullValue = new ArrayList<>(_plaintext);
        _plaintextWithNullValue.set(0, null);

        _shorterPlaintext = MathematicalTestDataGenerator
            .getZpGroupElements(_zpSubgroup, (NUM_PLAINTEXT_ELEMENTS - 1));

        ElGamalPublicKey shorterPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, (NUM_PLAINTEXT_ELEMENTS - 1))
            .getPublicKeys();
        encrypterValues = (ElGamalEncrypterValues) ElGamalTestDataGenerator
            .encryptGroupElements(shorterPublicKey, _shorterPlaintext);
        _shorterCiphertext = encrypterValues;

        _publicKeyLength = _publicKey.getKeys().size();
        _ciphertextLength = _ciphertext.size();
        _shorterPlaintextLength = _shorterPlaintext.size();
        _shorterCiphertextLength = _shorterCiphertext.size();
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    @Parameters(method = "createPlaintextProof")
    public void testPlaintextProofCreationValidation(ZpSubgroup group,
            ElGamalPublicKey publicKey, Ciphertext ciphertext,
            List<ZpGroupElement> plaintext, Witness witness,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createPlaintextProof(publicKey, ciphertext, plaintext,
                witness);
    }

    public static Object[] createPlaintextProof() {

        return $(
            $(null, _publicKey, _ciphertext, _plaintext, _witness,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _ciphertext, _plaintext, _witness,
                "ElGamal public key is null."),
            $(_zpSubgroup, _publicKey, null, _plaintext, _witness,
                "Ciphertext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, null, _witness,
                "Plaintext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, _emptyPlaintext,
                _witness, "Plaintext is empty."),
            $(_zpSubgroup, _publicKey, _ciphertext,
                _plaintextWithNullValue, _witness,
                "Plaintext contains one or more null elements."),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext, null,
                "Witness is null."),
            $(_zpSubgroup, _publicKey, _shorterCiphertext, _plaintext,
                _witness,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _publicKey, _ciphertext, _shorterPlaintext,
                _witness,
                "Plaintext length must be equal to ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPlaintextLength));
    }

    @Test
    @Parameters(method = "preComputePlaintextProofProof")
    public void testPlaintextProofPreComputationValidation(
            ZpSubgroup group, ElGamalPublicKey publicKey, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofPreComputerAPI(group)
            .preComputePlaintextProof(publicKey);
    }

    public static Object[] preComputePlaintextProofProof() {

        return $($(null, _publicKey, "Zp subgroup is null."),
            $(_zpSubgroup, null, "ElGamal public key is null."));
    }

    @Test
    @Parameters(method = "createPlaintextProofUsingPreComputedValues")
    public void testPlaintextProofCreationUsingPreComputedValuesValidation(
            ZpSubgroup group, ElGamalPublicKey publicKey,
            Ciphertext ciphertext, List<ZpGroupElement> plaintext,
            Witness witness, ProofPreComputedValues preComputedValues,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createPlaintextProof(publicKey, ciphertext, plaintext,
                witness, preComputedValues);
    }

    public static Object[] createPlaintextProofUsingPreComputedValues()
            throws GeneralCryptoLibException {

        ProofPreComputedValues preComputedValues =
            _proofsServiceForDefaultPolicy
                .createProofPreComputerAPI(_zpSubgroup)
                .preComputePlaintextProof(_publicKey);

        return $(
            $(null, _publicKey, _ciphertext, _plaintext, _witness,
                preComputedValues, "Zp subgroup is null."),
            $(_zpSubgroup, null, _ciphertext, _plaintext, _witness,
                preComputedValues, "ElGamal public key is null."),
            $(_zpSubgroup, _publicKey, null, _plaintext, _witness,
                preComputedValues, "Ciphertext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, null, _witness,
                preComputedValues, "Plaintext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, _emptyPlaintext,
                _witness, preComputedValues, "Plaintext is empty."),
            $(_zpSubgroup, _publicKey, _ciphertext,
                _plaintextWithNullValue, _witness, preComputedValues,
                "Plaintext contains one or more null elements."),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext, null,
                preComputedValues, "Witness is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext, _witness,
                null, "Pre-computed values object is null."),
            $(_zpSubgroup, _publicKey, _shorterCiphertext, _plaintext,
                _witness, preComputedValues,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _publicKey, _ciphertext, _shorterPlaintext,
                _witness, preComputedValues,
                "Plaintext length must be equal to ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPlaintextLength));
    }

    @Test
    @Parameters(method = "verifyPlaintextProof")
    public void testPlaintextProofVerificationValidation(ZpSubgroup group,
            ElGamalPublicKey publicKey, Ciphertext ciphertext,
            List<ZpGroupElement> plaintext, Proof proof, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofVerifierAPI(group)
            .verifyPlaintextProof(publicKey, ciphertext, plaintext, proof);
    }

    public static Object[] verifyPlaintextProof()
            throws GeneralCryptoLibException {

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup).createPlaintextProof(
                _publicKey, _ciphertext, _plaintext, _witness);

        return $(
            $(null, _publicKey, _ciphertext, _plaintext, proof,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _ciphertext, _plaintext, proof,
                "ElGamal public key is null."),
            $(_zpSubgroup, _publicKey, null, _plaintext, proof,
                "Ciphertext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, null, proof,
                "Plaintext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, _emptyPlaintext, proof,
                "Plaintext is empty."),
            $(_zpSubgroup, _publicKey, _ciphertext,
                _plaintextWithNullValue, proof,
                "Plaintext contains one or more null elements."),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext, null,
                "Plaintext proof is null."),
            $(_zpSubgroup, _publicKey, _shorterCiphertext, _plaintext,
                proof,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _publicKey, _ciphertext, _shorterPlaintext,
                proof,
                "Plaintext length must be equal to ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPlaintextLength));
    }
}
