/*
 * 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.products.ov.keystore;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.stores.bean.KeyStoreType;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.cryptolib.stores.service.StoresService;
import com.scytl.products.ov.commons.beans.exceptions.AbstractKeystoreReaderException;
import com.scytl.products.ov.commons.infrastructure.remote.client.NodeIdentifier;

/**
 * @author jbarrio
 * @date 13/12/16 Copyright (C) 2016 Scytl Secure Electronic Voting SA All rights reserved.
 */
abstract class AbstractKeystoreReader implements KeystoreReader {

    protected static final Logger LOG = LoggerFactory.getLogger("std");

    private final KeystoreResources resources;

    private final KeystoreProperties properties;

    private final StoresService storesService;

    protected AbstractKeystoreReader(final KeystoreResources resources) {

        this.resources = resources;
        this.properties = resources.getKeystoreProperties();

        try {
            storesService = new StoresService();
        } catch (GeneralCryptoLibException gcE) {
            throw new AbstractKeystoreReaderException("Error trying to create StoresService", gcE);
        }
    }

    @Override
    public PrivateKey readSigningPrivateKey(final NodeIdentifier nodeIdentifier, final String keystorePassword) {
        return this.readSigningPrivateKey(nodeIdentifier, keystorePassword, keystorePassword);
    }

    @Override
    public PrivateKey readSigningPrivateKey(final NodeIdentifier nodeIdentifier, final String keystorePassword,
            final String signingPrivateKeyPassword) {
        final KeystoreProperties.NodeProperties nodeProperties = this.properties.getNodeProperties(nodeIdentifier);
        return this.readPrivateKey(nodeProperties.getPrivateKeystoreFilename(), nodeProperties.getPrivateKeystoreType(),
            keystorePassword, nodeProperties.getSigningPrivateKeyAlias(), signingPrivateKeyPassword);
    }

    @Override
    public PrivateKey readSigningPrivateKey(final NodeIdentifier nodeIdentifier,
            final KeystorePasswords keystorePasswords) {
        final AbstractKeystorePasswords readableKeystorePasswords = (AbstractKeystorePasswords) keystorePasswords;
        return this.readSigningPrivateKey(nodeIdentifier, readableKeystorePasswords.getPrivateKeystorePassword(),
            readableKeystorePasswords.getSigningPrivateKeyPassword());

    }

    @Override
    public PrivateKey readEncryptionPrivateKey(final NodeIdentifier nodeIdentifier, final String keystorePassword) {
        return this.readEncryptionPrivateKey(nodeIdentifier, keystorePassword, keystorePassword);
    }

    @Override
    public PrivateKey readEncryptionPrivateKey(final NodeIdentifier nodeIdentifier, final String keystorePassword,
            final String encryptionPrivateKeyPassword) {
        final KeystoreProperties.NodeProperties nodeProperties = this.properties.getNodeProperties(nodeIdentifier);
        return this.readPrivateKey(nodeProperties.getPrivateKeystoreFilename(), nodeProperties.getPrivateKeystoreType(),
            keystorePassword, nodeProperties.getEncryptionPrivateKeyAlias(), encryptionPrivateKeyPassword);
    }

    @Override
    public PrivateKey readEncryptionPrivateKey(final NodeIdentifier nodeIdentifier,
            final KeystorePasswords keystorePasswords) {
        final AbstractKeystorePasswords readableKeystorePasswords = (AbstractKeystorePasswords) keystorePasswords;
        return this.readEncryptionPrivateKey(nodeIdentifier, readableKeystorePasswords.getPrivateKeystorePassword(),
            readableKeystorePasswords.getEncryptionPrivateKeyPassword());
    }

    private PrivateKey readPrivateKey(final String privateKeystoreFilename, final String privateKeystoreType,
            final String privateKeystorePassword, final String privateKeyAlias, final String privateKeyPassword) {
        final String resourcePath = this.resources.getResourcePath(privateKeystoreFilename);
        try (final InputStream inputStream = this.resources.getResourceAsStream(resourcePath)) {
            if (inputStream == null) {
                return null;
            } else {
                try {
                    final KeyStore privateKeystore = this.storesService.loadKeyStore(
                        KeyStoreType.valueOf(privateKeystoreType), inputStream, privateKeystorePassword.toCharArray());
                    return (PrivateKey) privateKeystore.getKey(privateKeyAlias, privateKeyPassword.toCharArray());
                    
                } catch (final GeneralCryptoLibException | UnrecoverableKeyException | KeyStoreException
                        | NoSuchAlgorithmException e) {
					LOG.error("Error while trying to read private key {} in keystore {}: {}", privateKeyAlias,
							resourcePath, e.getMessage(), e);
					return null;
                }
            }
        } catch (final IOException ioE) {
            LOG.error("Could not find private keystore {}.", resourcePath, ioE);
            return null;
        }
    }

    @Override
    public Certificate readRootCertificate() {
        return this.readCertificate(this.properties.getRootCertificateFilename());
    }

    @Override
    public Certificate readSigningCertificate(final NodeIdentifier nodeIdentifier) {
        final KeystoreProperties.NodeProperties nodeProperties = this.properties.getNodeProperties(nodeIdentifier);
        return this.readCertificate(nodeProperties.getSigningCertificateFilename());
    }

    @Override
    public Certificate readEncryptionCertificate(final NodeIdentifier nodeIdentifier) {
        final KeystoreProperties.NodeProperties nodeProperties = this.properties.getNodeProperties(nodeIdentifier);
        return this.readCertificate(nodeProperties.getEncryptionCertificateFilename());
    }

    private Certificate readCertificate(final String certificateFilename) {
        final String resourcePath = this.resources.getResourcePath(certificateFilename);
        try (final InputStream inputStream = this.resources.getResourceAsStream(resourcePath)) {
            if (inputStream == null) {
                return null;
            } else {
                final String certificateString;
                try (final BufferedReader buffer = new BufferedReader(new InputStreamReader(inputStream))) {
                    certificateString = buffer.lines().collect(Collectors.joining("\n"));
                } catch (final IOException ioE) {
					LOG.error("Error while trying to read certificate {}: {}", resourcePath, ioE.getMessage(), ioE);
                    return null;
                }
                try {
                    return PemUtils.certificateFromPem(certificateString);
                } catch (final GeneralCryptoLibException gclE) {                  
					LOG.error("Error while trying to read certificate {}: {}", resourcePath, gclE.getMessage(), gclE);
                    return null;
                }
            }
        } catch (final IOException ioE) {
            LOG.error("Error while trying to read certificate {}: {}", resourcePath, ioE.getMessage(), ioE);
            return null;
        }
    }

}
