/*
 * @(#)ContextFactory.java
 *
 * Copyright (c) 2004 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.inet.ftps;

import com.jscape.inet.ftp.FtpException;
import com.jscape.util.Assert;
import com.jscape.util.StringTools;
import com.jscape.util.net.ssl.SslContextFactory;

import javax.net.ssl.*;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.X509Certificate;

/**
 * The SSL context factory.
 */
public class ContextFactory {

    protected static final String DEFAULT_PROTOCOL = "TLS";
    protected static final String DEFAULT_ALGORITHM = "SunX509";
    protected static final String DEFAULT_PROVIDER = "SunJSSE";
    protected static final String DEFAULT_TYPE = "JKS";

    protected String protocol;
    protected String algorithm;
    protected String keystoreProvider;
    protected String protocolProvider;
    protected String algorithmProvider;
    protected String storeType;
    protected KeyManager[] keyManagers;
    protected TrustManager[] trustManagers;

    /**
     * Returns the "trust all" managers.
     *
     * @return the "trust all" managers
     */
    protected static TrustManager[] getTrustAllManagers() {
        return new TrustManager[]{
                new X509TrustManager() {

                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }

                    public boolean isClientTrusted(X509Certificate[] certs) {
                        return true;
                    }

                    public boolean isServerTrusted(X509Certificate[] certs) {
                        return true;
                    }

                    public void checkClientTrusted(X509Certificate[] certs, String val) {

                    }

                    public void checkServerTrusted(X509Certificate[] certs, String val) {

                    }
                }
        };
    }

    public ContextFactory(String protocol,
                          String algorithm,
                          String keystoreProvider,
                          String protocolProvider,
                          String algorithmProvider,
                          String storeType,
                          KeyManager[] keyManagers,
                          TrustManager[] trustManagers) {
        this.protocol = protocol;
        this.algorithm = algorithm;
        this.keystoreProvider = keystoreProvider;
        this.protocolProvider = protocolProvider;
        this.algorithmProvider = algorithmProvider;
        this.storeType = storeType;
        this.keyManagers = keyManagers;
        this.trustManagers = trustManagers;
    }

    /**
     * Creates a new factory instance.
     *
     * @param protocol  the target protocol
     * @param algorithm the target algorithm
     * @param provider  the target security provider
     */
    public ContextFactory(String protocol, String algorithm, String provider) {
        this(protocol, algorithm, null, provider, null, DEFAULT_TYPE, null, getTrustAllManagers());
    }

    /**
     * Creates a new factory instance.
     */
    public ContextFactory() {
        this(DEFAULT_PROTOCOL, DEFAULT_ALGORITHM, null);
    }

    /**
     * Returns the current protocol.
     * Default is TLS.
     *
     * @return the current protocol
     */
    public String getProtocol() {
        return this.protocol;
    }

    /**
     * Sets current protocol.
     * Default is TLS.
     *
     * @param protocol
     */
    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    /**
     * Returns the current algorithm for use in SSL communications.
     * Default is SunX509.
     *
     * @return the current algorithm
     */
    public String getAlgorithm() {
        return this.algorithm;
    }

    /**
     * Sets algorithm for use in SSL communications
     * Default is SunX509.
     *
     * @param algorithm the algorithm to use for SSL communications
     */
    public void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    /**
     * Returns the current keystore type.
     *
     * @return the current keystore type
     */
    public String getStoreType() {
        return this.storeType;
    }

    /**
     * Setups the current keystore type.
     *
     * @param type the current keystore type
     */
    public void setStoreType(String type) {
        Assert.notNull(type);

        try {
            if (StringTools.isValidString(this.keystoreProvider)) {
                KeyStore.getInstance(type, this.keystoreProvider);
            } else {
                KeyStore.getInstance(type);
            }
            this.storeType = type;
        } catch (Exception e) {
            throw new IllegalArgumentException("bad store type: " + type);
        }

    }

    /**
     * Setups the client SSL keys.
     *
     * @param filename the keystore filename
     * @param password the file password
     *
     * @throws FtpException if an error occurs
     */
    public void setKeys(String filename, String password)
            throws FtpException {
        Assert.notNull(filename);
        Assert.isTrue(filename.length() > 0, "bad filename");
        Assert.notNull(password);

        try (InputStream in = Files.newInputStream(Paths.get(filename))) {
            KeyStore keyStore = StringTools.isValidString(this.keystoreProvider) ? KeyStore.getInstance(this.storeType, this.keystoreProvider) : KeyStore.getInstance(this.storeType);
            KeyManagerFactory factory = StringTools.isValidString(this.algorithmProvider) ? KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm(), this.algorithmProvider) : KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyStore.load(in, password.toCharArray());
            factory.init(keyStore, password.toCharArray());
            this.keyManagers = factory.getKeyManagers();
        } catch (Exception e) {
            throw new FtpException(e.getMessage(), e);
        }
    }

    public void setKeys(KeyStore keyStore, String password)
            throws FtpException {
        try {
            KeyManagerFactory factory;
            if (StringTools.isValidString(this.algorithmProvider)) {
                factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm(), this.algorithmProvider);
            } else {
                factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            }
            factory.init(keyStore, password.toCharArray());
            this.keyManagers = factory.getKeyManagers();
        } catch (Exception e) {
            throw new FtpException(e.getMessage(), e);
        }
    }

    /**
     * Setups the trusted certificates.
     *
     * @param filename the trust store filename
     * @param password the store password
     *
     * @throws FtpException if an error occurs
     */
    public void setCertificates(String filename, String password)
            throws FtpException {
        Assert.notNull(filename);
        Assert.isTrue(filename.length() > 0, "bad filename");
        Assert.notNull(password);

        try (InputStream in = Files.newInputStream(Paths.get(filename))) {
            KeyStore trustStore = StringTools.isValidString(this.keystoreProvider) ? KeyStore.getInstance(this.storeType, this.keystoreProvider) : KeyStore.getInstance(this.storeType);
            TrustManagerFactory factory = StringTools.isValidString(this.algorithmProvider) ? TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(), this.algorithmProvider) : TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustStore.load(in, password.toCharArray());
            factory.init(trustStore);
            this.trustManagers = factory.getTrustManagers();
        } catch (Exception e) {
            throw new FtpException(e);
        }
    }

    public void setCertificates(KeyStore keyStore)
            throws FtpException {
        try {
            TrustManagerFactory factory = StringTools.isValidString(this.algorithmProvider) ? TrustManagerFactory.getInstance(this.algorithm, this.algorithmProvider) : TrustManagerFactory.getInstance(this.algorithm);
            factory.init(keyStore);
            this.trustManagers = factory.getTrustManagers();
        } catch (Exception e) {
            throw new FtpException(e.getMessage(), e);
        }
    }


    /**
     * Returns the SSL context instance.
     *
     * @return the SSL context instance
     *
     * @throws FtpException if an instantiation error occurs
     */
    public SSLContext getContext()
            throws FtpException {
        try {
            return SslContextFactory.contextFor(this.keyManagers, this.trustManagers, false);
//
//            SSLContext context;
//
//            if (StringTools.isValidString(this.protocolProvider)) {
//                context = SSLContext.getInstance(this.protocol, this.protocolProvider);
//            } else {
//                context = SSLContext.getInstance(this.protocol);
//            }
//
//            context.init(this.keyManagers, this.trustManagers, new SecureRandom());
//
//            return context;
        } catch (Exception e) {
            throw new FtpException(e.getMessage(), e);
        }
    }

    /**
     * Gets algorithm provider.
     *
     * @return the algorithm provider
     */
    public String getAlgorithmProvider() {
        return algorithmProvider;
    }

    /**
     * Set algorithm provider.
     *
     * @param algorithmProvider The provider.
     */
    public void setAlgorithmProvider(String algorithmProvider) {
        this.algorithmProvider = algorithmProvider;
    }

    /**
     * Gets keystore provider.
     *
     * @return keystore provider
     */
    public String getKeystoreProvider() {
        return keystoreProvider;
    }

    /**
     * Sets keystore provider.
     *
     * @param keystoreProvider The provider.
     */
    public void setKeystoreProvider(String keystoreProvider) {
        this.keystoreProvider = keystoreProvider;
    }

    /**
     * Gets protocol provider.
     *
     * @return The protocol provider
     */
    public String getProtocolProvider() {
        return protocolProvider;
    }

    /**
     * Sets the protocol provider.
     *
     * @param protocolProvider protocol provider.
     */
    public void setProtocolProvider(String protocolProvider) {
        this.protocolProvider = protocolProvider;
    }

}
