/*
 * 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.stores.service;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.stores.StoresServiceAPI;
import com.scytl.cryptolib.api.stores.bean.KeyStoreType;
import com.scytl.cryptolib.commons.configuration.Provider;
import com.scytl.cryptolib.commons.constants.Constants;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.stores.keystore.configuration.KeyStorePolicy;
import com.scytl.cryptolib.stores.keystore.configuration.KeyStorePolicyFromProperties;

/**
 * Class which implements {@link StoresServiceAPI}.
 * <p>
 * Instances of this class are immutable.
 */
public class StoresService implements StoresServiceAPI {

    private final Provider _provider;

    /**
     * Default constructor, which retrieves the stores cryptographic policy from
     * the default properties file
     * {@link Constants#CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH}.
     * 
     * @throws GeneralCryptoLibException
     *             if the default properties are invalid.
     */
    public StoresService() throws GeneralCryptoLibException {

        this(Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH);
    }

    /**
     * Constructor which retrieves the stores cryptographic policy from the
     * specified properties file.
     * 
     * @param path
     *            the path of the stores cryptographic policy properties file.
     * @throws GeneralCryptoLibException
     *             if the path is invalid or the properties are invalid.
     */
    public StoresService(final String path) throws GeneralCryptoLibException {

        Validate.notNullOrBlank(path, "Properties file path");

        KeyStorePolicy storePolicy = new KeyStorePolicyFromProperties(path);

        _provider = storePolicy.getKeyStoreSpec().getProvider();
    }

    @Override
    public KeyStore createKeyStore(final KeyStoreType type)
            throws GeneralCryptoLibException {

        Validate.notNull(type, "Key store type");

        try {
            KeyStore keyStore = getKeyStoreInstance(type);

            keyStore.load(null, null);

            return keyStore;
        } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
            throw new GeneralCryptoLibException("Could not create key store.",
                e);
        }
    }

    @Override
    public KeyStore loadKeyStore(final KeyStoreType type,
            final InputStream inStream, final char[] password)
            throws GeneralCryptoLibException {

        Validate.notNull(type, "Key store type");
        Validate.notNull(inStream, "Key store input stream");
        Validate.notNullOrBlank(password, "Key store password");

        try {
            KeyStore keyStore = getKeyStoreInstance(type);

            keyStore.load(inStream, password);

            return keyStore;
        } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
            throw new GeneralCryptoLibException("Could not load key store.", e);
        }
    }

    private KeyStore getKeyStoreInstance(final KeyStoreType type)
            throws GeneralCryptoLibException {

        try {
            if (_provider == Provider.DEFAULT) {
                return KeyStore.getInstance(type.getKeyStoreTypeName());
            } else {
                String providerName = getProviderName(type);

                return KeyStore.getInstance(type.getKeyStoreTypeName(),
                    providerName);
            }
        } catch (KeyStoreException | NoSuchProviderException e) {
            throw new GeneralCryptoLibException(
                "Could not get key store instance.", e);
        }
    }

    private String getProviderName(final KeyStoreType type) {

        // Note: Supported SUN cryptographic service provider will depend on
        // type of key store being used.
        String providerName = _provider.getProviderName();
        if (_provider == Provider.SUN) {
            if (type == KeyStoreType.PKCS12) {
                providerName = Provider.SUN_JSSE.getProviderName();
            } else {
                providerName = Provider.SUN_JCE.getProviderName();
            }
        }

        return providerName;
    }
}
