/*
 * 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 static com.scytl.cryptolib.primitives.derivation.constants.DerivationConstants.MGF1;

import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.MGF1BytesGenerator;
import org.bouncycastle.crypto.params.MGFParameters;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.configuration.Provider;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIDerivedKey;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIKDFDeriver;
import com.scytl.cryptolib.primitives.derivation.configuration.ConfigKDFDerivationParameters;
import com.scytl.cryptolib.primitives.messagedigest.configuration.HashAlgorithm;

/**
 * Class that provides the functionality for key derivation.
 */
public class CryptoKDFDeriver implements CryptoAPIKDFDeriver {

    private final MGF1BytesGenerator _byteGenerator;

    /**
     * Creates an instance of key deriver configured by provided {@code params}.
     *
     * @param params
     *            The key derivation configuration parameters.
     */
    CryptoKDFDeriver(final ConfigKDFDerivationParameters params)
            throws GeneralCryptoLibException {

        _byteGenerator = configureByteGenerator(params);
    }

    private MGF1BytesGenerator configureByteGenerator(
            final ConfigKDFDerivationParameters params)
            throws GeneralCryptoLibException {
        validateProvider(params.getProvider());
        validateAlgorithm(params.getAlgorithm());

        Digest digest = configureDigest(params.getHashAlgorithm());
        return new MGF1BytesGenerator(digest);
    }

    private void validateProvider(final Provider _provider)
            throws GeneralCryptoLibException {
        if (_provider != Provider.BOUNCY_CASTLE
            && _provider != Provider.DEFAULT) {
            throw new GeneralCryptoLibException(
                "Key derivation is not presently defined for provider "
                    + _provider.getProviderName());
        }
    }

    private void validateAlgorithm(final String _algorithm)
            throws GeneralCryptoLibException {
        if (!MGF1.equals(_algorithm)) {
            throw new GeneralCryptoLibException(
                "Key derivation is not presently defined for algorithm "
                    + _algorithm);
        }
    }

    private Digest configureDigest(final String hashAlgorithm)
            throws GeneralCryptoLibException {
        if (HashAlgorithm.SHA256.getAlgorithm().equals(hashAlgorithm)) {
            return new SHA256Digest();
        }

        throw new GeneralCryptoLibException(
            "Key derivation is not presently defined for hash algorithm "
                + hashAlgorithm);
    }

    @Override
    public CryptoAPIDerivedKey deriveKey(final byte[] seed,
            final int lengthInBytes) throws GeneralCryptoLibException {

        Validate.notNullOrEmpty(seed, "Seed");
        Validate.isPositive(lengthInBytes, "Length in bytes");

        byte[] keyBytes = new byte[lengthInBytes];
        _byteGenerator.init(new MGFParameters(seed));
        _byteGenerator.generateBytes(keyBytes, 0, lengthInBytes);
        return new CryptoDerivedKey(keyBytes);
    }
}
