/*
 * 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 junitparams.JUnitParamsRunner;
import junitparams.Parameters;

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.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;

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

    private static final int NUM_BASE_ELEMENTS = 5;

    private static ProofsService _proofsServiceForDefaultPolicy;

    private static ZpSubgroup _zpSubgroup;

    private static Witness _witness;

    private static List<ZpGroupElement> _baseElementList;

    private static List<ZpGroupElement> _exponentiatedElementList;

    private static List<ZpGroupElement> _shorterBaseElementList;

    private static List<ZpGroupElement> _emptyElementList;

    private static List<ZpGroupElement> _elementListWithNullValue;

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

    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _proofsServiceForDefaultPolicy = new ProofsService();

        _zpSubgroup = MathematicalTestDataGenerator.getZpSubgroup();

        _witness = ElGamalTestDataGenerator.getWitness(_zpSubgroup);
        _baseElementList =
            MathematicalTestDataGenerator.getZpGroupElements(_zpSubgroup,
                NUM_BASE_ELEMENTS);
        _exponentiatedElementList =
            MathematicalTestDataGenerator.exponentiateZpGroupElements(
                _baseElementList, _witness.getExponent());

        _shorterBaseElementList =
            MathematicalTestDataGenerator.getZpGroupElements(_zpSubgroup,
                (NUM_BASE_ELEMENTS - 1));

        _emptyElementList = new ArrayList<>();

        _elementListWithNullValue = new ArrayList<>(_baseElementList);
        _elementListWithNullValue.set(0, null);
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

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

    @Test
    @Parameters(method = "createExponentiationProof")
    public void testExponentiationProofCreationValidation(
            ZpSubgroup group, List<ZpGroupElement> exponentiatedElements,
            List<ZpGroupElement> baseElements, Witness witness,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createExponentiationProof(exponentiatedElements,
                baseElements, witness);
    }

    public static Object[] createExponentiationProof() {

        return $(
            $(null, _exponentiatedElementList, _baseElementList, _witness,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _baseElementList, _witness,
                "List of exponentiated elements is null."),
            $(_zpSubgroup, _emptyElementList, _baseElementList, _witness,
                "List of exponentiated elements is empty."),
            $(_zpSubgroup, _elementListWithNullValue, _baseElementList,
                _witness,
                "List of exponentiated elements contains one or more null elements."),
            $(_zpSubgroup, _exponentiatedElementList, null, _witness,
                "List of base elements is null."),
            $(_zpSubgroup, _exponentiatedElementList, _emptyElementList,
                _witness, "List of base elements is empty."),
            $(_zpSubgroup, _exponentiatedElementList,
                _elementListWithNullValue, _witness,
                "List of base elements contains one or more null elements."),
            $(_zpSubgroup, _exponentiatedElementList, _baseElementList,
                null, "Witness is null."),
            $(_zpSubgroup,
                _exponentiatedElementList,
                _shorterBaseElementList,
                _witness,
                "Number of exponentiated elements must be equal to number of base elements: "
                    + _shorterBaseElementList.size()
                    + "; Found "
                    + _exponentiatedElementList.size()));
    }

    @Test
    @Parameters(method = "preComputeExponentiationProof")
    public void testExponentiationProofPreComputationValidation(
            ZpSubgroup group, List<ZpGroupElement> baseElements,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofPreComputerAPI(group)
            .preComputeExponentiationProof(baseElements);
    }

    public static Object[] preComputeExponentiationProof() {

        return $(
            $(null, _baseElementList, "Zp subgroup is null."),
            $(_zpSubgroup, null, "List of base elements is null."),
            $(_zpSubgroup, _emptyElementList,
                "List of base elements is empty."),
            $(_zpSubgroup, _elementListWithNullValue,
                "List of base elements contains one or more null elements."));
    }

    @Test
    @Parameters(method = "createExponentiationProofUsingPreComputedValues")
    public void testExponentiationProofCreationUsingPreComputedValuesValidation(
            ZpSubgroup group, List<ZpGroupElement> exponentiatedElements,
            List<ZpGroupElement> baseElements, Witness witness,
            ProofPreComputedValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createExponentiationProof(exponentiatedElements,
                baseElements, witness, preComputedValues);
    }

    public static Object[] createExponentiationProofUsingPreComputedValues()
            throws GeneralCryptoLibException {

        ProofPreComputedValues preComputedValues =
            _proofsServiceForDefaultPolicy.createProofPreComputerAPI(
                _zpSubgroup).preComputeExponentiationProof(
                _baseElementList);

        return $(
            $(null, _exponentiatedElementList, _baseElementList, _witness,
                preComputedValues, "Zp subgroup is null."),
            $(_zpSubgroup, null, _baseElementList, _witness,
                preComputedValues,
                "List of exponentiated elements is null."),
            $(_zpSubgroup, _emptyElementList, _baseElementList, _witness,
                preComputedValues,
                "List of exponentiated elements is empty."),
            $(_zpSubgroup, _elementListWithNullValue, _baseElementList,
                _witness, preComputedValues,
                "List of exponentiated elements contains one or more null elements."),
            $(_zpSubgroup, _exponentiatedElementList, null, _witness,
                preComputedValues, "List of base elements is null."),
            $(_zpSubgroup, _exponentiatedElementList, _emptyElementList,
                _witness, preComputedValues,
                "List of base elements is empty."),
            $(_zpSubgroup, _exponentiatedElementList,
                _elementListWithNullValue, _witness, preComputedValues,
                "List of base elements contains one or more null elements."),
            $(_zpSubgroup, _exponentiatedElementList, _baseElementList,
                null, preComputedValues, "Witness is null."),
            $(_zpSubgroup,
                _exponentiatedElementList,
                _shorterBaseElementList,
                _witness,
                preComputedValues,
                "Number of exponentiated elements must be equal to number of base elements: "
                    + _shorterBaseElementList.size()
                    + "; Found "
                    + _exponentiatedElementList.size()),
            $(_zpSubgroup, _exponentiatedElementList, _baseElementList,
                _witness, null, "Pre-computed values object is null."));
    }

    @Test
    @Parameters(method = "verifyExponentiationProof")
    public void testExponentiationProofVerificationValidation(
            ZpSubgroup group, List<ZpGroupElement> exponentiatedElements,
            List<ZpGroupElement> baseElements, Proof proof, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofVerifierAPI(group)
            .verifyExponentiationProof(exponentiatedElements,
                baseElements, proof);
    }

    public static Object[] verifyExponentiationProof()
            throws GeneralCryptoLibException {

        Proof proof =
            _proofsServiceForDefaultPolicy.createProofProverAPI(
                _zpSubgroup).createExponentiationProof(
                _exponentiatedElementList, _baseElementList, _witness);

        return $(
            $(null, _exponentiatedElementList, _baseElementList, proof,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _baseElementList, proof,
                "List of exponentiated elements is null."),
            $(_zpSubgroup, _emptyElementList, _baseElementList, proof,
                "List of exponentiated elements is empty."),
            $(_zpSubgroup, _elementListWithNullValue, _baseElementList,
                proof,
                "List of exponentiated elements contains one or more null elements."),
            $(_zpSubgroup, _exponentiatedElementList, null, proof,
                "List of base elements is null."),
            $(_zpSubgroup, _exponentiatedElementList, _emptyElementList,
                proof, "List of base elements is empty."),
            $(_zpSubgroup, _exponentiatedElementList,
                _elementListWithNullValue, proof,
                "List of base elements contains one or more null elements."),
            $(_zpSubgroup, _exponentiatedElementList, _baseElementList,
                null, "Exponentiation proof is null."),
            $(_zpSubgroup,
                _exponentiatedElementList,
                _shorterBaseElementList,
                proof,
                "Number of exponentiated elements must be equal to number of base elements: "
                    + _shorterBaseElementList.size()
                    + "; Found "
                    + _exponentiatedElementList.size()));
    }
}
