/*
 * 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 java.security.SecureRandom;
import java.util.Set;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomBytes;

/**
 * Class which implements {@link CryptoAPIRandomBytes}.
 * <p>
 * Note that the {@link SecureRandom} that is used to generate the random bytes
 * is stored in this class and cannot be modified.
 * <P>
 * Instances of this class are immutable.
 */
public final class CryptoRandomBytes implements CryptoAPIRandomBytes {

    private final SecureRandom _secureRandom;

    /**
     * Instantiates a random generator as a wrapper of a specified parameter.
     * 
     * @param secureRandom
     *            The instance of SecureRandom that should be wrapped within
     *            this class.
     */
    CryptoRandomBytes(final SecureRandom secureRandom) {
        _secureRandom = secureRandom;
    }

    @Override
    public byte[] nextRandom(final int lengthInBytes)
            throws GeneralCryptoLibException {

        Validate.inRange(lengthInBytes, 1,
            SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH,
            "Length in bytes", "",
            "maximum allowed value for secure random byte arrays");

        byte[] bytes = new byte[lengthInBytes];
        _secureRandom.nextBytes(bytes);
        return bytes;

    }

    @Override
    public byte[] nextRandom(final int lengthInBytes,
            final Set<Byte[]> blackList) throws GeneralCryptoLibException {

        Validate.inRange(lengthInBytes, 1,
            SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH,
            "Length in bytes", "",
            "maximum allowed value for secure random byte arrays");

        if (!validateBlackList(lengthInBytes, blackList)) {
            throw new GeneralCryptoLibException(
                "The black list should be strictly less than total amount of elements.");
        }

        byte[] bytes = nextRandom(lengthInBytes);

        while (blackListContains(blackList, bytes)) {
            bytes = nextRandom(lengthInBytes);
        }

        return bytes;
    }

    private boolean blackListContains(final Set<Byte[]> blackList,
            final byte[] bytes) {
        loop:
        for (Byte[] byteObjects : blackList) {
            if (bytes.length != byteObjects.length) {
                continue;
            }
            for (int i = 0; i < bytes.length; i++) {
                if (bytes[i] != byteObjects[i].byteValue()) {
                    continue loop;
                }
            }
            return true;
        }
        return false;
    }

    private boolean validateBlackList(final int length,
            final Set<Byte[]> blackList) {

        boolean setIsCorrect = true;

        if (blackList == null) {
            setIsCorrect = false;
        } else {
            if (!blackList.isEmpty()) {

				double logSetSize = Math.log10(blackList.size()) / Math.log10(2);
                double maximumSetLength = (double) Byte.SIZE * length;

                if (logSetSize >= maximumSetLength
                    || !checkThatSetContainsArraysOfCorrectLength(
                        blackList, length)) {
                    setIsCorrect = false;
                }
            }
        }

        return setIsCorrect;
    }

    private boolean checkThatSetContainsArraysOfCorrectLength(
            final Set<Byte[]> blackList, final int length) {
        for (Byte[] bytes : blackList) {
            if (bytes.length != length) {
                return false;
            }
        }
        return true;
    }
}
