/*
 * 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.elgamal.bean;

import static junitparams.JUnitParamsRunner.$;

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

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.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.test.tools.configuration.QrSubgroupLoader;
import com.scytl.cryptolib.test.tools.configuration.ZpSubgroupLoader;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of the ElGamal bean API input validation.
 */
@RunWith(JUnitParamsRunner.class)
public class ElGamalBeanValidationTest {

    private static ZpSubgroup _zpSubgroup;

    private static List<Exponent> _exponentList;

    private static List<ZpGroupElement> _elementList;

    private static List<Exponent> _emptyExponentList;

    private static List<ZpGroupElement> _emptyElementList;

    private static List<ZpGroupElement> _elementListWithNullValue;

    private static ZpGroupElement _gamma;

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

    public static void setUp() throws GeneralCryptoLibException {

        ZpSubgroupLoader zpSubgroupLoader = new ZpSubgroupLoader();
        _zpSubgroup = new ZpSubgroup(zpSubgroupLoader.getG(),
            zpSubgroupLoader.getP(), zpSubgroupLoader.getQ());

        _exponentList = new ArrayList<>();
        _exponentList
            .add(new Exponent(_zpSubgroup.getQ(), BigInteger.TEN));

        _elementList = new ArrayList<>();
        _elementList.add(new ZpGroupElement(BigInteger.ONE, _zpSubgroup));

        _emptyExponentList = new ArrayList<>();

        _emptyElementList = new ArrayList<>();

        _elementListWithNullValue = new ArrayList<>(_elementList);
        _elementListWithNullValue.add(null);

        _gamma = _elementList.get(0);
    }

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

    @Test
    @Parameters(method = "createElGamalEncryptionParameters")
    public void testElGamalEncryptionParametersCreationValidation(
            BigInteger p, BigInteger q, BigInteger g, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalEncryptionParameters(p, q, g);
    }

    public static Object[] createElGamalEncryptionParameters() {

        BigInteger p = _zpSubgroup.getP();
        BigInteger q = _zpSubgroup.getQ();
        BigInteger g = _zpSubgroup.getG();

        return $($(null, q, g, "Zp subgroup p parameter"),
            $(p, null, g, "Zp subgroup q parameter"),
            $(p, q, null, "Zp subgroup generator"),
            $(p, BigInteger.ZERO, g,
                "Zp subgroup q parameter must be greater than or equal to : 1; Found 0"),
            $(p, p, g,
                "Zp subgroup q parameter must be less than or equal to Zp subgroup p parameter minus 1: "
                    + p.subtract(BigInteger.ONE).toString() + "; Found "
                    + p),
            $(p, q, BigInteger.valueOf(1),
                "Zp subgroup generator must be greater than or equal to : 2; Found 1"),
            $(p, q, p,
                "Zp subgroup generator must be less than or equal to Zp subgroup p parameter minus 1: "
                    + p.subtract(BigInteger.ONE).toString() + "; Found "
                    + p));
    }

    @Test
    @Parameters(method = "createElGamalKeyPair")
    public void testElGamalKeyPairCreationValidation(
            ElGamalPrivateKey privateKey, ElGamalPublicKey publicKey,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalKeyPair(privateKey, publicKey);
    }

    public static Object[] createElGamalKeyPair()
            throws GeneralCryptoLibException {

        ElGamalPrivateKey privateKey =
            new ElGamalPrivateKey(_exponentList, _zpSubgroup);
        ElGamalPublicKey publicKey =
            new ElGamalPublicKey(_elementList, _zpSubgroup);

        List<Exponent> exponentsLarger = new ArrayList<>(_exponentList);
        exponentsLarger
            .add(new Exponent(_zpSubgroup.getQ(), BigInteger.TEN));
        ElGamalPrivateKey privateKeyWithMoreExponents =
            new ElGamalPrivateKey(exponentsLarger, _zpSubgroup);

        QrSubgroupLoader qrSubgroupLoader = new QrSubgroupLoader();
        ZpSubgroup qrSubgroup = new ZpSubgroup(qrSubgroupLoader.getG(),
            qrSubgroupLoader.getP(), qrSubgroupLoader.getQ());
        ElGamalPrivateKey privateKeyForOtherGroup =
            new ElGamalPrivateKey(_exponentList, qrSubgroup);

        return $($(null, publicKey, "ElGamal private key is null."),
            $(privateKey, null, "ElGamal public key is null."),
            $(privateKeyWithMoreExponents, publicKey,
                "ElGamal private key length must be equal to ElGamal public key length: "
                    + publicKey.getKeys().size() + "; Found "
                    + privateKeyWithMoreExponents.getKeys().size()),
            $(privateKeyForOtherGroup, publicKey,
                "ElGamal public and private keys must belong to same mathematical group."));
    }

    @Test
    @Parameters(method = "createElGamalPrivateKey")
    public void testElGamalPrivateKeyCreationValidation(
            List<Exponent> exponents, ZpSubgroup zpSubgroup,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalPrivateKey(exponents, zpSubgroup);
    }

    public static Object[] createElGamalPrivateKey() {

        List<Exponent> exponentsWithNullValue =
            new ArrayList<>(_exponentList);
        exponentsWithNullValue.add(null);

        return $(
            $(null, _zpSubgroup,
                "List of ElGamal private key exponents is null."),
            $(_emptyExponentList, _zpSubgroup,
                "List of ElGamal private key exponents is empty."),
            $(exponentsWithNullValue, _zpSubgroup,
                "List of ElGamal private key exponents contains one or more null elements."),
            $(_exponentList, null, "Zp subgroup is null."));
    }

    @Test
    @Parameters(method = "createElGamalPublicKey")
    public void testElGamalPublicKeyCreationValidation(
            List<ZpGroupElement> elements, ZpSubgroup zpSubgroup,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalPublicKey(elements, zpSubgroup);
    }

    public static Object[] createElGamalPublicKey() {

        return $(
            $(null, _zpSubgroup,
                "List of ElGamal public key elements is null."),
            $(_emptyExponentList, _zpSubgroup,
                "List of ElGamal public key elements is empty."),
            $(_elementListWithNullValue, _zpSubgroup,
                "List of ElGamal public key elements contains one or more null elements."),
            $(_elementList, null, "Zp subgroup is null."));
    }

    @Test
    @Parameters(method = "createElGamalComputationsValuesFromElements")
    public void testElGamalComputationsValuesCreationFromElementsValidation(
            ZpGroupElement gammaElement, List<ZpGroupElement> phiElements,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalComputationsValues(gammaElement, phiElements);
    }

    public static Object[] createElGamalComputationsValuesFromElements() {

        return $($(null, _elementList, "ElGamal gamma element is null."),
            $(_gamma, null, "List of ElGamal phi elements is null."),
            $(_gamma, _emptyElementList,
                "List of ElGamal phi elements is empty."),
            $(_gamma, _elementListWithNullValue,
                "List of ElGamal phi elements contains one or more null elements."));
    }

    @Test
    @Parameters(method = "createElGamalComputationsValuesFromCiphertext")
    public void testElGamalComputationsValuesCreationFromCiphertextValidation(
            List<ZpGroupElement> ciphertext, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalComputationsValues(ciphertext);
    }

    public static Object[] createElGamalComputationsValuesFromCiphertext() {

        return $($(null, "ElGamal ciphertext is null."),
            $(_emptyElementList, "ElGamal ciphertext is empty."),
            $(_elementListWithNullValue,
                "ElGamal ciphertext contains one or more null elements."),
            $(_elementList,
                "ElGamal ciphertext length must be greater than or equal to : 2; Found 1"));
    }

    @Test
    @Parameters(method = "createElGamalEncrypterValues")
    public void testElGamalEncrypterValuesValidation(Exponent exponent,
            ElGamalComputationsValues values, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalEncrypterValues(exponent, values);
    }

    public static Object[] createElGamalEncrypterValues()
            throws GeneralCryptoLibException {

        ElGamalComputationsValues values =
            new ElGamalComputationsValues(_gamma, _elementList);

        return $($(null, values, "Random exponent is null."),
            $(_exponentList.get(0), null, "ElGamal ciphertext is null."));
    }

    @Test
    @Parameters(method = "createWitnessImpl")
    public void testWitnessImplCreationValidation(Exponent exponent,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new WitnessImpl(exponent);
    }

    public static Object[] createWitnessImpl() {

        return $($(null, "Exponent is null."));
    }
}
