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

import java.math.BigInteger;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomInteger;
import com.scytl.math.BigIntegers;

public class QuadResParamsGenerator implements SubgroupParamsGenerator {

    public static final BigInteger BIG_INTEGER_TWO = new BigInteger("2");

    private final int _L;

    private final int _certainty;

    private final CryptoAPIRandomInteger _cryptoRandomInteger;

    /**
     * Creates an ElGamal Encryption Parameters generator
     *
     * @param L
     *            the length of p in bytes.
     * @param certainty
     *            the certainty level for prime generating.
     * @param cryptoRandomInteger
     *            the course of random integers that should be used when
     *            generating encryption parameters.
     */
    public QuadResParamsGenerator(final int L, final int certainty,
            final CryptoAPIRandomInteger cryptoRandomInteger) {
        _L = L;
        _certainty = certainty;
        _cryptoRandomInteger = cryptoRandomInteger;
    }

    @Override
    public ElGamalEncryptionParameters generate() {
        BigInteger p = null;
        BigInteger q = null;

        boolean parametersFound = false;

        try {

            while (!parametersFound) {

                p = _cryptoRandomInteger
                    .getRandomProbablePrimeBigInteger(_L);

                q = p.subtract(BigInteger.ONE).divide(BIG_INTEGER_TWO);

                if (q.isProbablePrime(_certainty)) {
                    parametersFound = true;
                }
            }

            return new ElGamalEncryptionParameters(p, q,
                findSmallestGenerator(p, q));
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(
                "Exception while creating QuadResParameters using generator",
                e);
        }
    }

    /**
     * Attempts to find the smallest generator for the Zp subgroup defined by
     * the received p (modulus) and q (order) parameters. The p and q parameters
     * should both be prime numbers.
     *
     * @param p
     *            the p (modulus) parameter of the group.
     * @param q
     *            the q (order) parameter of the group.
     * @return the smallest generator of the group defined by the {@code p}
     *         parameter.
     * @throws CryptoLibException
     *             if it fails to find a generator.
     */
    private BigInteger findSmallestGenerator(final BigInteger p,
            final BigInteger q) {

        BigInteger candidateGenerator = BIG_INTEGER_TWO;
        boolean generatorFound = false;

        while ((!generatorFound) && (candidateGenerator.compareTo(p) < 0)) {
            if (BigIntegers.modPow(candidateGenerator, q, p).equals(BigInteger.ONE)) {
                generatorFound = true;
            } else {
                candidateGenerator = candidateGenerator.add(BigInteger.ONE);
            }
        }

        if (!generatorFound) {
            throw new CryptoLibException(
                "Failed to find a generator, p was: " + p + ", q was: "
                    + q);
        }

        return candidateGenerator;
    }

}
