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

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

import javax.crypto.SecretKeyFactory;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.configuration.Provider;
import com.scytl.cryptolib.primitives.derivation.configuration.DerivationPolicy;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomBytes;
import com.scytl.cryptolib.primitives.securerandom.factory.SecureRandomFactory;

/**
 * A factory class for generating
 * {@link com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIKDFDeriver} objects.
 */
public class CryptoKeyDeriverFactory {

    private final DerivationPolicy _derivationPolicy;

    private static final String KEY_DERIVATION_ERROR = "Error while deriving the key";

    /**
     * Constructor which receives a {@link DerivationPolicy}.
     * 
     * @param derivationPolicy
     *            The policy containing the properties to configure the objects
     *            that this class creates.
     */
    public CryptoKeyDeriverFactory(final DerivationPolicy derivationPolicy) {
        _derivationPolicy = derivationPolicy;
    }

    /**
     * Creates a {@link CryptoKDFDeriver}.
     * 
     * @return a {@link CryptoKDFDeriver}.
     */
    public CryptoKDFDeriver createKDFDeriver() {

        try {
            return new CryptoKDFDeriver(
                _derivationPolicy.getKDFDerivationParameters());
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }

    /**
     * Creates a {@link CryptoPBKDFDeriver}.
     * 
     * @return a {@link CryptoPBKDFDeriver}.
     */
    public CryptoPBKDFDeriver createPBKDFDeriver() {

        int saltBitLength =
            _derivationPolicy.getPBKDFDerivationParameters()
                .getSaltBitLength();

        int iterations =
            _derivationPolicy.getPBKDFDerivationParameters()
                .getIterations();

        int keyLength =
            _derivationPolicy.getPBKDFDerivationParameters()
                .getKeyBitLength();

        SecureRandomFactory secureRandomFactory =
            new SecureRandomFactory(_derivationPolicy);

        CryptoRandomBytes cryptoRandomBytes =
            secureRandomFactory.createByteRandom();

        try {
            return new CryptoPBKDFDeriver(createSecretKeyFactory(),
                cryptoRandomBytes, saltBitLength, iterations, keyLength);
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }
    }

    /**
     * Returns the length of salt from
     * {@link com.scytl.cryptolib.primitives.derivation.configuration.ConfigPBKDFDerivationParameters}
     * in bits.
     * 
     * @return the length of salt in bits
     */
    public int getSaltBitLength() {
        return _derivationPolicy.getPBKDFDerivationParameters()
            .getSaltBitLength();
    }

    private SecretKeyFactory createSecretKeyFactory() {
        try {
            if (Provider.DEFAULT == _derivationPolicy
                .getSecureRandomAlgorithmAndProvider().getProvider()) {
                return SecretKeyFactory.getInstance(_derivationPolicy
                    .getPBKDFDerivationParameters().getAlgorithm());
            } else {
                return SecretKeyFactory.getInstance(_derivationPolicy
                    .getPBKDFDerivationParameters().getAlgorithm(),
                    _derivationPolicy.getPBKDFDerivationParameters()
                        .getProvider().getProviderName());
            }
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            throw new CryptoLibException(KEY_DERIVATION_ERROR, e);
        }
    }
}
