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

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.commons.configuration.Provider;
import com.scytl.cryptolib.commons.utils.system.OperatingSystem;
import com.scytl.cryptolib.primitives.securerandom.configuration.SecureRandomPolicy;

/**
 * A factory class for creating random generators according to a
 * given {@link SecureRandomPolicy}. At the moment the following generators
 * are considered:
 * <ul>
 * <li> {@link CryptoRandomBytes}
 * <li> {@link CryptoRandomInteger}
 * <li> {@link CryptoRandomString}
 * </ul>
 */
public final class SecureRandomFactory {

    private final SecureRandomPolicy _secureRandomPolicy;

    /**
     * Constructs a SecureRandomFactory using the provided
     * {@link SecureRandomPolicy}.
     * 
     * @param secureRandomPolicy
     *            The SecureRandomPolicy to be used to configure this
     *            SecureRandomFactory.
     *            <P>
     *            NOTE: The received {@link SecureRandomPolicy} should be an
     *            immutable object. If this is the case, then the entire class
     *            is thread safe.
     */
    public SecureRandomFactory(final SecureRandomPolicy secureRandomPolicy) {
        _secureRandomPolicy = secureRandomPolicy;
    }

    /**
     * Instantiates a random byte generator.
     * 
     * @return a {@link CryptoRandomBytes} object.
     */
    public CryptoRandomBytes createByteRandom() {
        return new CryptoRandomBytes(createSecureRandom());
    }

    /**
     * Instantiates a random integer generator.
     * 
     * @return a {@link CryptoRandomInteger} object.
     */
    public CryptoRandomInteger createIntegerRandom() {
        return new CryptoRandomInteger(createSecureRandom());
    }

    /**
     * Instantiates a random string generator. Check the
     * {@link CryptoRandomString} class description for further information
     * about the accepted alphabets.
     * <p>
     * Two alphabets are provided in
     * {@link com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants}:
     * {@link com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants#ALPHABET_BASE32}
     * and
     * {@link com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants#ALPHABET_BASE64}
     * 
     * @param alphabet
     *            The alphabet to be used to generate the random strings.
     * @return a {@link CryptoRandomString} object.
     */
    public CryptoRandomString createStringRandom(final String alphabet) {

        return new CryptoRandomString(createSecureRandom(), alphabet);
    }

    /**
     * Create a {@link java.security.SecureRandom} according to the given
     * policy.
     * 
     * @return A {@link java.security.SecureRandom} object.
     */
    public SecureRandom createSecureRandom() {
        try {

            checkOS();

            if (Provider.DEFAULT == _secureRandomPolicy
                .getSecureRandomAlgorithmAndProvider().getProvider()) {
                return SecureRandom.getInstance(_secureRandomPolicy
                    .getSecureRandomAlgorithmAndProvider().getAlgorithm());
            } else {
                return SecureRandom.getInstance(_secureRandomPolicy
                    .getSecureRandomAlgorithmAndProvider().getAlgorithm(),
                    _secureRandomPolicy
                        .getSecureRandomAlgorithmAndProvider()
                        .getProvider().getProviderName());

            }

        } catch (GeneralSecurityException e) {
            throw new CryptoLibException(
                "Failed to create SecureRandom in this environment. Attempted to use the provider: "
                    + _secureRandomPolicy
                        .getSecureRandomAlgorithmAndProvider()
                        .getProvider()
                    + ", and the algorithm: "
                    + _secureRandomPolicy
                        .getSecureRandomAlgorithmAndProvider()
                        .getAlgorithm()
                    + ". Error message was "
                    + e.getMessage(), e);
        }
    }

    private void checkOS() {
        if (!_secureRandomPolicy.getSecureRandomAlgorithmAndProvider()
            .isOSCompliant(OperatingSystem.current())) {
            throw new CryptoLibException(
                "The given algorithm and provider are not compliant with the "
                    + OperatingSystem.current() + " OS.");
        }
    }

}
