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

import java.util.List;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.system.OperatingSystem;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncrypterValues;
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.bean.WitnessImpl;
import com.scytl.cryptolib.elgamal.configuration.ConfigGroupType;
import com.scytl.cryptolib.elgamal.configuration.ElGamalPolicy;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.elgamal.cryptoapi.Witness;
import com.scytl.cryptolib.elgamal.factory.CryptoElGamalDecrypter;
import com.scytl.cryptolib.elgamal.factory.CryptoElGamalEncrypter;
import com.scytl.cryptolib.elgamal.factory.ElGamalFactory;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
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.mathematical.groups.utils.MathematicalTestDataGenerator;
import com.scytl.cryptolib.primitives.securerandom.configuration.ConfigSecureRandomAlgorithmAndProvider;

/**
 * Utility to generate various types of ElGamal related data needed by tests.
 */
public class ElGamalTestDataGenerator {

    /**
     * Retrieves the ElGamal encryption parameters for a pre-generated Zp
     * subgroup.
     * 
     * @param zpSubgroup
     *            the pre-generated Zp subgroup.
     * @return the ElGamal encryption parameters.
     * @throws GeneralCryptoLibException
     *             if the ElGamal encryption parameters cannot be retrieved.
     */
    public static ElGamalEncryptionParameters getElGamalEncryptionParameters(
            final ZpSubgroup zpSubgroup) throws GeneralCryptoLibException {

        return new ElGamalEncryptionParameters(zpSubgroup.getP(),
            zpSubgroup.getQ(), zpSubgroup.getG());
    }

    /**
     * Randomly generates an ElGamal key pair for a specified Zp subgroup and of
     * a specified component length.
     * 
     * @param zpSubgroup
     *            the Zp subgroup to which the components of each key to be
     *            generated belong.
     * @param length
     *            the component length of each key to be generated.
     * @return the generated ElGamal key pair.
     * @throws GeneralCryptoLibException
     *             if the key pair generation process fails.
     */
    public static ElGamalKeyPair getKeyPair(final ZpSubgroup zpSubgroup,
            final int length) throws GeneralCryptoLibException {

        ElGamalEncryptionParameters encryptionParams =
            new ElGamalEncryptionParameters(zpSubgroup.getP(),
                zpSubgroup.getQ(), zpSubgroup.getG());

        return new ElGamalService().getElGamalKeyPairGenerator()
            .generateKeys(encryptionParams, length);
    }

    /**
     * Randomly generates a witness object for a specified Zp subgroup.
     * 
     * @param zpSubgroup
     *            the Zp subgroup to which the exponent of the generated witness
     *            is to belong.
     * @return the generated witness.
     * @throws GeneralCryptoLibException
     *             if the witness generation process fails.
     */
    public static Witness getWitness(final ZpSubgroup zpSubgroup)
            throws GeneralCryptoLibException {

        Exponent randomExponent =
            MathematicalTestDataGenerator.getExponent(zpSubgroup);

        return new WitnessImpl(randomExponent);
    }

    /**
     * ElGamal encrypts a list of Zp group elements using a specified ElGamal
     * public key.
     * 
     * @param publicKey
     *            the ElGamal public key used for the encryption.
     * @param zpGroupElements
     *            the list of Zp group elements to encrypt.
     * @return the encrypted Zp group elements.
     * @throws GeneralCryptoLibException
     *             if the ElGamal encryption process fails.
     */
    public static Ciphertext encryptGroupElements(
            final ElGamalPublicKey publicKey,
            final List<ZpGroupElement> zpGroupElements)
            throws GeneralCryptoLibException {

        return new ElGamalService().createEncrypter(publicKey)
            .encryptGroupElements(zpGroupElements);
    }

    /**
     * Pre-computes the ElGamal encrypter values for a specified ElGamal public
     * key.
     * 
     * @param publicKey
     *            the ElGamal public key used for the encryption.
     * @return the pre-computed encrypter values.
     * @throws GeneralCryptoLibException
     *             if the pre-computation process fails.
     */
    public static ElGamalEncrypterValues preComputeEncrypterValues(
            final ElGamalPublicKey publicKey)
            throws GeneralCryptoLibException {

        return new ElGamalService().createEncrypter(publicKey)
            .preCompute();
    }

    /**
     * ElGamal encrypts a list of Zp group elements, using a specified ElGamal
     * public key and using pre-computed values.
     * 
     * @param publicKey
     *            the ElGamal public key used for the encryption.
     * @param zpGroupElements
     *            the list of Zp group elements to encrypt.
     * @param preComputedValues
     *            the pre-computed values.
     * @return the encrypted Zp group elements.
     * @throws GeneralCryptoLibException
     *             if the ElGamal encryption process fails.
     */
    public static Ciphertext encryptGroupElements(
            final ElGamalPublicKey publicKey,
            final List<ZpGroupElement> zpGroupElements,
            final ElGamalEncrypterValues preComputedValues)
            throws GeneralCryptoLibException {

        return new ElGamalService().createEncrypter(publicKey)
            .encryptGroupElements(zpGroupElements, preComputedValues);
    }

    /**
     * Creates an ElGamal encrypter for use with short exponents, for a
     * specified ElGamal public key, which also must be for short exponents.
     * 
     * @param publicKey
     *            the ElGamal public key for use with short exponents.
     * @return the ElGamal encrypter for use with short exponents.
     * @throws GeneralCryptoLibException
     *             if the encrypter cannot be created.
     */
    public static CryptoElGamalEncrypter getEncrypterForShortExponents(
            final ElGamalPublicKey publicKey)
            throws GeneralCryptoLibException {

        ElGamalFactory elGamalFactory =
            new ElGamalFactory(getElGamalPolicyForQr2048Group());

        return elGamalFactory.createEncrypter(publicKey);
    }

    /**
     * Creates an ElGamal decrypter for use with short exponents, for a
     * specified ElGamal private key, which also must be for short exponents.
     * 
     * @param privateKey
     *            the ElGamal private key for use with short exponents.
     * @return the ElGamal decrypter for use with short exponents.
     * @throws GeneralCryptoLibException
     *             if the decrypter cannot be created.
     */
    public static CryptoElGamalDecrypter getDecrypterForShortExponents(
            final ElGamalPrivateKey privateKey)
            throws GeneralCryptoLibException {

        ElGamalFactory elGamalFactory =
            new ElGamalFactory(getElGamalPolicyForQr2048Group());

        return elGamalFactory.createDecrypter(privateKey);
    }

    private static ElGamalPolicy getElGamalPolicyForQr2048Group() {

        return new ElGamalPolicy() {
            @Override
            public ConfigGroupType getGroupType() {
                return ConfigGroupType.QR_2048;
            }

            @Override
            public ConfigSecureRandomAlgorithmAndProvider getSecureRandomAlgorithmAndProvider() {

                switch (OperatingSystem.current()) {

                case WINDOWS:
                    return ConfigSecureRandomAlgorithmAndProvider.PRNG_SUN_MSCAPI;
                case LINUX:
                    return ConfigSecureRandomAlgorithmAndProvider.NATIVE_PRNG_SUN;
                default:
                    throw new CryptoLibException("OS not supported");
                }
            }
        };
    }
}
