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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;

import org.junit.Before;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.constants.Constants;
import com.scytl.cryptolib.primitives.securerandom.configuration.SecureRandomPolicyFromProperties;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;

public class CryptoIntegerRandomTest {

    private CryptoRandomInteger _cryptoIntegerRandom;

    private static final int PROBABLE_PRIME_CERTAINTY = 150;

    @Before
    public void setUp() throws GeneralCryptoLibException {
        _cryptoIntegerRandom =
            new SecureRandomFactory(new SecureRandomPolicyFromProperties(
                Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
                .createIntegerRandom();
    }

    @Test
    public void testThatGeneratesUpToTheSpecifiedLengthOfDigits()
            throws GeneralCryptoLibException {

        BigInteger n;
        BigInteger bigInteger;

        for (int i = 1; i < 100; i++) {

            bigInteger = _cryptoIntegerRandom.nextRandom(i);

            n = BigInteger.TEN.pow(i).subtract(BigInteger.ONE);

            assertTrue(n.compareTo(bigInteger) == 1
                || n.compareTo(bigInteger) == 0);

            assertTrue(i >= bigInteger.toString().length());
        }

    }

    @Test(expected = GeneralCryptoLibException.class)
    public void whenGiveAHighLength() throws GeneralCryptoLibException {

        _cryptoIntegerRandom
            .nextRandom(SecureRandomConstants.MAXIMUM_GENERATED_BIG_INTEGER_DIGIT_LENGTH + 1);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void whenGiveAnIncorrectLength()
            throws GeneralCryptoLibException {

        _cryptoIntegerRandom.nextRandom(0);
    }

    @Test
    public void testThatGeneratesGivenBitLength()
            throws GeneralCryptoLibException {

        for (int numBits = 1; numBits < 100; numBits++) {
            BigInteger bigInteger =
                _cryptoIntegerRandom.nextRandomByBits(numBits);
            assertTrue(numBits >= bigInteger.bitLength());
        }
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void whenGiveAnIncorrectNumberOfBits()
            throws GeneralCryptoLibException {
        _cryptoIntegerRandom.nextRandomByBits(0);
    }

    @Test
    public void testThatGeneratesIntegerEqualToZero()
            throws GeneralCryptoLibException {

        BigInteger bi = _cryptoIntegerRandom.nextRandom(1);

        while (!bi.equals(BigInteger.ZERO)) {

            bi = _cryptoIntegerRandom.nextRandom(1);
        }

        assertEquals(BigInteger.ZERO, bi);
    }

    @Test
    public void testThatGeneratesIntegerNotInBlackList()
            throws GeneralCryptoLibException {
        Set<BigInteger> blackList = new HashSet<BigInteger>();

        for (int i = 0; i < 99; i++) {
            blackList.add(new BigInteger(Integer.toString(i)));
        }

        BigInteger bi = _cryptoIntegerRandom.nextRandom(2, blackList);

        assertFalse(blackList.contains(bi));
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void testThatNotGeneratesIntegerWhenBlackListIsFull()
            throws GeneralCryptoLibException {
        Set<BigInteger> blackList = new HashSet<BigInteger>();

        for (int i = 0; i < 100; i++) {
            blackList.add(new BigInteger(Integer.toString(i)));
        }

        _cryptoIntegerRandom.nextRandom(2, blackList);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void testThatNotGeneratesIntegerWhenBlackListIsNull()
            throws GeneralCryptoLibException {

        _cryptoIntegerRandom.nextRandom(1, null);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void testThatNotGeneratesIntegerWhenLengthIsNull()
            throws GeneralCryptoLibException {

        _cryptoIntegerRandom.nextRandom(0);
    }

    @Test
    public void whenGetProbablePrimeBigInteger()
            throws GeneralCryptoLibException {

        BigInteger bigInteger;

        for (int numberOfbits = 2; numberOfbits < 100; numberOfbits++) {
            bigInteger =
                _cryptoIntegerRandom
                    .getRandomProbablePrimeBigInteger(numberOfbits);

            assertEquals(
                -1,
                bigInteger.compareTo(BigInteger.valueOf(2).pow(
                    numberOfbits)));
        }
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void whenGetProbablePrimeBigIntegerOfZeroBits()
            throws GeneralCryptoLibException {
        _cryptoIntegerRandom.getRandomProbablePrimeBigInteger(0);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void whenGetProbablePrimeBigIntegerOfOneBit()
            throws GeneralCryptoLibException {
        _cryptoIntegerRandom.getRandomProbablePrimeBigInteger(1);
    }

    @Test
    public void whenGetProbablePrimeBigIntegerWithSetCertainty()
            throws GeneralCryptoLibException {

        BigInteger bigInteger;

        for (int numberOfbits = 2; numberOfbits < 100; numberOfbits++) {
            bigInteger =
                _cryptoIntegerRandom
                    .getRandomProbablePrimeBigIntegerWithSetCertainty(
                        numberOfbits, PROBABLE_PRIME_CERTAINTY);

            assertEquals(
                -1,
                bigInteger.compareTo(BigInteger.valueOf(2).pow(
                    numberOfbits)));

            assertTrue(bigInteger
                .isProbablePrime(PROBABLE_PRIME_CERTAINTY));
        }
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void whenGetProbablePrimeBigIntegerOfZeroBitsWithSetCertainty()
            throws GeneralCryptoLibException {
        _cryptoIntegerRandom
            .getRandomProbablePrimeBigIntegerWithSetCertainty(0,
                PROBABLE_PRIME_CERTAINTY);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void whenGetProbablePrimeBigIntegerOfOneBitWithSetCertainty()
            throws GeneralCryptoLibException {
        _cryptoIntegerRandom
            .getRandomProbablePrimeBigIntegerWithSetCertainty(1,
                PROBABLE_PRIME_CERTAINTY);
    }
}
