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

import static java.text.MessageFormat.format;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;

/**
 * Contains utility methods to work with prime numbers.
 */
public class PrimesUtils {

    /**
     * A measure of the uncertainty that we are willing to tolerate. It ensures
     * that the uncertainty level is below 1 - 1/2<sup> {@code certainty}</sup>
     */
    public static final int CERTAINTY = 100;

    private PrimesUtils() {
    }

    /**
     * Checks whether a given {@code integer} is prime.
     * <P>
     * The method returns {@code true} with a certainty of: 1/(2^
     * {@link #CERTAINTY})
     * 
     * @see java.math.BigInteger#isProbablePrime(int)
     * @param integer
     *            The integer to be checked.
     * @return True if {@code integer} is prime. False otherwise.
     */
    public static final boolean isProbablePrime(final int integer) {
        return isProbablePrime(BigInteger.valueOf(integer));
    }

    /**
     * Checks whether a given {@link BigInteger} is prime.
     * <P>
     * The method returns {@code true} with a certainty of: 1/(2^
     * {@link #CERTAINTY})
     * 
     * @see java.math.BigInteger#isProbablePrime(int)
     * @param bigInteger
     *            The {@link BigInteger} to be checked.
     * @return True if {@code bigInteger} is prime. False otherwise.
     */
    public static final boolean isProbablePrime(
            final BigInteger bigInteger) {
        return bigInteger.isProbablePrime(CERTAINTY);
    }

    /**
     * Obtains {@code numberOfPrimes} prime values from a source, and returns
     * these values as a list of BigIntegers.
     * 
     * @param numberOfPrimes
     *            The number of primes to be read.
     * @return The list of primes.
     */
    public static final List<BigInteger> getPrimesList(
            final int numberOfPrimes) {
        List<BigInteger> list;
        try (Stream<BigInteger> primes = getPrimes()) {
            list = primes.limit(numberOfPrimes)
                .collect(Collectors.toList());
        }
        if (list.size() < numberOfPrimes) {
            throw new CryptoLibException(format(
                "Too many primes ''{0}'' are requested.", numberOfPrimes));
        }
        return list;
    }

    /**
     * Obtains all of the prime values that are available from a source, and
     * returns these values as a list of BigIntegers.
     * <P>
     * NOTE: this method attempts to read all of the primes in a file, and to
     * return those primes in a list of type {@link BigInteger}. It is the
     * responsibility of the caller of this method to ensure that the file to be
     * read does not contain so much data that it affects performance.
     * 
     * @return The list of primes.
     */
    public static final List<BigInteger> getPrimesList() {
        try (Stream<BigInteger> primes = getPrimes()) {
            return primes.collect(Collectors.toList());
        }
    }

    /**
     * Returns the finite stream of the pre-computed primes.
     * 
     * @return the primes.
     */
    public static Stream<BigInteger> getPrimes() {
        InputStream stream =
            PrimesUtils.class.getResourceAsStream("/primes/primes.txt");
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(stream, StandardCharsets.UTF_8));
        return reader.lines().map(BigInteger::new);
    }
}
