/*
 * 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.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.HashSet;
import java.util.Set;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
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 CryptoRandomStringTest {

    private CryptoRandomString _cryptoStringRandom32;

    private CryptoRandomString _cryptoStringRandom64;

    private CryptoRandomString _cryptoStringRandom4;

    @Before
    public void setup() throws GeneralCryptoLibException {
        _cryptoStringRandom32 =
            new SecureRandomFactory(new SecureRandomPolicyFromProperties(
                Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
                .createStringRandom(SecureRandomConstants.ALPHABET_BASE32);

        _cryptoStringRandom64 =
            new SecureRandomFactory(new SecureRandomPolicyFromProperties(
                Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
                .createStringRandom(SecureRandomConstants.ALPHABET_BASE64);

        _cryptoStringRandom4 =
            new SecureRandomFactory(new SecureRandomPolicyFromProperties(
                Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
                .createStringRandom("ABCD");

    }

    @Test
    public void testThatGeneratesUpToTheSpecifiedLengthOfCharacters()
            throws GeneralCryptoLibException {

        String randomString32;
        String randomString64;

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

            randomString32 = _cryptoStringRandom32.nextRandom(i);

            randomString64 = _cryptoStringRandom64.nextRandom(i);

            assertTrue(i >= randomString32.length());
            assertTrue(i >= randomString64.length());
        }

    }

    @Test
    public void testThatGeneratesWithOtherAlphabetsPowerOfTwo()
            throws GeneralCryptoLibException {

        _cryptoStringRandom4.nextRandom(1);
    }

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

    @Test
    public void whenGiveAnIncorrectLength()
            throws GeneralCryptoLibException {

        int aboveMaxStringLength =
            SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH + 1;

        exception.expect(GeneralCryptoLibException.class);
        exception
            .expectMessage("Length in characters must be less than or equal to maximum allowed value for secure random Strings: "
                + SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH
                + "; Found " + aboveMaxStringLength);
        _cryptoStringRandom32.nextRandom(aboveMaxStringLength);
    }

    @Test
    public void whenGiveAnAlphabetWithRepeatedCharacters()
            throws GeneralCryptoLibException {

        exception.expect(CryptoLibException.class);
        exception
            .expectMessage("The given alphabet has repeated characters");

        new SecureRandomFactory(new SecureRandomPolicyFromProperties(
            Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
            .createStringRandom("AAAA");
    }

    @Test
    public void whenGiveAnEmptyAlphabet() throws GeneralCryptoLibException {
        exception.expect(CryptoLibException.class);
        exception
            .expectMessage("The given alphabet cannot be an empty string");
        new SecureRandomFactory(new SecureRandomPolicyFromProperties(
            Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
            .createStringRandom("");
    }

    @Test
    public void whenGiveAnAlphabetLengthNotPowerOfTwo()
            throws GeneralCryptoLibException {
        exception.expect(CryptoLibException.class);
        exception
            .expectMessage("The alphabet length should be a power of two.");
        new SecureRandomFactory(new SecureRandomPolicyFromProperties(
            Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
            .createStringRandom("ABC");
    }

    @Test
    public void whenGiveANullAlphabet() throws GeneralCryptoLibException {
        exception.expect(CryptoLibException.class);
        exception.expectMessage("The given alphabet is null");
        new SecureRandomFactory(new SecureRandomPolicyFromProperties(
            Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
            .createStringRandom(null);
    }

    @Test
    public void testThatGeneratesRandomStringGivenABlackList()
            throws GeneralCryptoLibException {

        Set<String> blackList = new HashSet<String>();
        String randomString;

        long combinations = (long) Math.floor(Math.pow(32, 1));

        for (int i = 0; i < combinations; i++) {
            randomString = _cryptoStringRandom32.nextRandom(1, blackList);
            assertFalse(blackList.contains(randomString));
            blackList.add(randomString);
        }
    }

    @Test
    public void testThatGeneratesRandomStringGivenABlackListWithOtherAlphabetsPowerOfTwo()
            throws GeneralCryptoLibException {

        Set<String> blackList = new HashSet<String>();
        String randomString;

        long combinations = (long) Math.floor(Math.pow(4, 1));

        for (int i = 0; i < combinations; i++) {
            randomString = _cryptoStringRandom4.nextRandom(1, blackList);
            assertFalse(blackList.contains(randomString));
            blackList.add(randomString);
        }
    }

    @Test
    public void testThatNotGeneratesRandomStringWhenBlackListContainsCharactersNotInAlphabet()
            throws GeneralCryptoLibException {
        exception.expect(GeneralCryptoLibException.class);
        exception
            .expectMessage("Blacklist contains characters that are not in alphabet");
        Set<String> blackList = new HashSet<String>();
        blackList.add("Z");
        _cryptoStringRandom4.nextRandom(1, blackList);
    }

    @Test
    public void testThatNotGeneratesRandomStringWhenBlackListContainsMoreCharactersThanTheAlphabet()
            throws GeneralCryptoLibException {
        exception.expect(GeneralCryptoLibException.class);
        exception
            .expectMessage("Blacklist contains characters that are not in alphabet");
        Set<String> blackList = new HashSet<String>();
        blackList.add("ABCD");
        blackList.add("ABCZ");
        _cryptoStringRandom4.nextRandom(4, blackList);
    }

    @Test
    public void testThatNotGeneratesRandomStringWhenBlackListContainsStringsLongerThanTheRequestedLength()
            throws GeneralCryptoLibException {
        exception.expect(GeneralCryptoLibException.class);
        exception
            .expectMessage("Blacklist strings length should be equal to 2");
        Set<String> blackList = new HashSet<String>();
        blackList.add("ABC");
        _cryptoStringRandom4.nextRandom(2, blackList);
    }

    @Test
    public void testThatNotGeneratesRandomStringGivenABlackListThatIsFull()
            throws GeneralCryptoLibException {

        exception.expect(GeneralCryptoLibException.class);
        exception
            .expectMessage("The black list contains all possible generated values.");

        long combinations = (long) Math.pow(4, 1);
        String randomString;
        Set<String> blackList = new HashSet<String>();

        for (int i = 0; i <= combinations; i++) {
            randomString = _cryptoStringRandom4.nextRandom(1, blackList);
            blackList.add(randomString);
        }
    }

    @Test
    public void testThatNotGeneratesGivenANullSet()
            throws GeneralCryptoLibException {
        exception.expect(GeneralCryptoLibException.class);
        exception.expectMessage("Blacklist is null.");
        _cryptoStringRandom32.nextRandom(1, null);
    }

    @Test
    public void testThatNotGeneratesBytesWhenLengthIsZero()
            throws GeneralCryptoLibException {
        int lengthInBytes = 0;
        exception.expect(GeneralCryptoLibException.class);
        exception
            .expectMessage("Length in characters must be greater than or equal to : 1; Found "
                + lengthInBytes);
        _cryptoStringRandom32.nextRandom(lengthInBytes);
    }
}
