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

import java.security.PrivateKey;
import java.util.HashSet;
import java.util.Set;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.bean.CertificatePublicData;
import com.scytl.cryptolib.certificates.bean.extensions.AbstractCertificateExtension;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.factory.builders.BouncyCastleX509CertificateBuilder;
import com.scytl.cryptolib.certificates.factory.builders.X509CertificateBuilder;
import com.scytl.cryptolib.commons.configuration.Provider;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomInteger;

/**
 * Class to generate an X509 certificate.
 */
public class CryptoX509CertificateGenerator {

    private final String _signatureAlgorithm;

    private final Provider _provider;

    private final CryptoRandomInteger _cryptoIntegerRandom;

    private X509CertificateBuilder _x509CertBuilder;

    /**
     * Creates a certificate generator with the provided arguments.
     * 
     * @param signatureAlgorithm
     *            algorithm used to sign certificates.
     * @param provider
     *            name of cryptographic service {@link Provider} of
     *            certificates.
     * @param cryptoIntegerRandom
     *            generator of random integers.
     */
    CryptoX509CertificateGenerator(final String signatureAlgorithm,
            final Provider provider,
            final CryptoRandomInteger cryptoIntegerRandom) {

        _signatureAlgorithm = signatureAlgorithm;
        _provider = provider;
        _cryptoIntegerRandom = cryptoIntegerRandom;
    }

    /**
     * Builds the certificate.
     * 
     * @param certificateGenerationParameters
     *            the parameters required to create the certificate.
     * @param extensions
     *            the certificate extension.
     * @param issuerPrivateKey
     *            private key of issuer of certificate.
     * @return a {@link CryptoX509Certificate} certificate.
     * @throws GeneralCryptoLibException
     *             if {@code certificateGenerationParameters} is invalid or
     *             {@code issuerPrivateKey} cannot be parsed.
     */
    public CryptoAPIX509Certificate generate(
            final CertificatePublicData certificateGenerationParameters,
            final AbstractCertificateExtension[] extensions,
            final PrivateKey issuerPrivateKey)
            throws GeneralCryptoLibException {

        addPublicData(certificateGenerationParameters);
        addExtensions(extensions);

        return buildX509Certificate(issuerPrivateKey);
    }

    /**
     * Initializes the certificate.
     * 
     * @param certificateGenerationParameters
     *            the parameters required to create the certificate.
     * @throws GeneralCryptoLibException
     *             if generation parameters are invalid.
     */
    private void addPublicData(
            final CertificatePublicData certificateGenerationParameters)
            throws GeneralCryptoLibException {

        createIX509CertificateBuilder(certificateGenerationParameters);
    }

    private void addExtensions(
            final AbstractCertificateExtension[] extensions) {
        // duplicates.
        Set<AbstractCertificateExtension> extensionSet = new HashSet<>();

        for (AbstractCertificateExtension extension : extensions) {
            extensionSet.add(extension);
        }

        // Add certificate extensions to generator.
        for (AbstractCertificateExtension extension : extensionSet) {
            _x509CertBuilder.addExtension(extension);
        }
    }

    private void createIX509CertificateBuilder(
            final CertificatePublicData certificateGenerationParameters)
            throws GeneralCryptoLibException {

        _x509CertBuilder =
            new BouncyCastleX509CertificateBuilder(_signatureAlgorithm,
                _provider,
                certificateGenerationParameters.getSubjectPublicKey(),
                certificateGenerationParameters.getSubjectDn(),
                certificateGenerationParameters.getIssuerDn(),
                certificateGenerationParameters.getValidityDates(),
                _cryptoIntegerRandom);

    }

    private CryptoAPIX509Certificate buildX509Certificate(
            final PrivateKey issuerPrivateKey)
            throws GeneralCryptoLibException {
        return new CryptoX509Certificate(
            _x509CertBuilder.build(issuerPrivateKey));
    }
}
