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

import java.security.GeneralSecurityException;
import java.security.SecureRandom;

import javax.crypto.KeyGenerator;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.commons.utils.system.OperatingSystem;
import com.scytl.cryptolib.primitives.securerandom.configuration.ConfigSecureRandomAlgorithmAndProvider;
import com.scytl.cryptolib.symmetric.key.configuration.ConfigSecretKeyAlgorithmAndSpec;
import com.scytl.cryptolib.symmetric.key.configuration.SymmetricKeyPolicy;

/**
 * A factory class that can be used for creating instances of
 * {@link CryptoSecretKeyGeneratorForEncryption}.
 * <P>
 * Instances of this class are immutable.
 */
public final class SecretKeyGeneratorFactory {

    private final SymmetricKeyPolicy _symmetricKeyPolicy;

    /**
     * Creates a SecretKeyGeneratorFactory, configured according to the received
     * {@link com.scytl.cryptolib.symmetric.key.configuration.SymmetricKeyPolicy}
     * .
     * 
     * @param symmetricKeyPolicy
     *            the secret key policy that should be used for creating
     *            instances of {@link CryptoSecretKeyGeneratorForEncryption}.
     */
    public SecretKeyGeneratorFactory(
            final SymmetricKeyPolicy symmetricKeyPolicy) {

        _symmetricKeyPolicy = symmetricKeyPolicy;
    }

    /**
     * Creates a new {@link CryptoSecretKeyGeneratorForEncryption}, configured
     * according the policy that was received when this
     * {@code SecretKeyGeneratorFactory} was created.
     * 
     * @return a newly created {@link CryptoSecretKeyGeneratorForEncryption}.
     */
    public CryptoSecretKeyGeneratorForEncryption createGeneratorForEncryption() {

        return new CryptoSecretKeyGeneratorForEncryption(
            createAndInitializeKeyGenerator());
    }

    /**
     * Creates a new {@link CryptoSecretKeyGeneratorForHmac}, configured
     * according the policy that was received when this
     * {@code SecretKeyGeneratorFactory} was created.
     * 
     * @return a newly created {@link CryptoSecretKeyGeneratorForHmac}.
     */
    public CryptoSecretKeyGeneratorForHmac createGeneratorForHmac() {

        return new CryptoSecretKeyGeneratorForHmac(_symmetricKeyPolicy);
    }

    private KeyGenerator createAndInitializeKeyGenerator() {

        KeyGenerator keyGen;

        ConfigSecretKeyAlgorithmAndSpec spec =
            _symmetricKeyPolicy.getSecretKeyAlgorithmAndSpec();

        try {

            keyGen =
                KeyGenerator.getInstance(spec.getAlgorithm(),
                    spec.getProvider());

        } catch (GeneralSecurityException e) {
            throw new CryptoLibException(
                "Error while trying to create a key generator. Algorithm was: "
                    + spec.getAlgorithm() + ", provider was: "
                    + spec.getProvider(), e);
        }

        initialize(keyGen);

        return keyGen;
    }

    private void initialize(final KeyGenerator keyGen) {

        ConfigSecureRandomAlgorithmAndProvider secureRandomConfig =
            _symmetricKeyPolicy.getSecureRandomAlgorithmAndProvider();

        try {

            checkOS();

            SecureRandom sr =
                SecureRandom.getInstance(
                    secureRandomConfig.getAlgorithm(), secureRandomConfig
                        .getProvider().getProviderName());

            keyGen.init(_symmetricKeyPolicy.getSecretKeyAlgorithmAndSpec()
                .getKeyLength(), sr);

        } catch (GeneralSecurityException e) {
            throw new CryptoLibException(
                "GeneralSecurityException while initializing the keypair generator",
                e);
        } catch (IllegalArgumentException e) {
            throw new CryptoLibException(
                "IllegalArgumentException while initializing the keypair generator",
                e);
        }
    }

    private void checkOS() {
        if (!_symmetricKeyPolicy.getSecureRandomAlgorithmAndProvider()
            .isOSCompliant(OperatingSystem.current())) {

            throw new CryptoLibException(
                "The given algorithm and provider are not compliant with the "
                    + OperatingSystem.current() + " OS.");
        }
    }
}
