/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.runtime.core.api.security.tls;

import java.io.IOException;
import java.util.Properties;

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

/**
 * Move a {@link TlsConfiguration} to and from Properties (typically System Properties). This can be used to store TLS/SSL
 * configuration for a library (eg. Javamail, which reads java.mail properties) or for later retrieval by
 * {@link TlsPropertiesSocketFactory}.
 */
public class TlsPropertiesMapper {

  private static final String TRUST_NAME_SUFFIX = ".ssl.trustStore";
  private static final String TRUST_TYPE_SUFFIX = ".ssl.trustStoreType";
  private static final String TRUST_PASSWORD_SUFFIX = ".ssl.trustStorePassword";
  private static final String TRUST_ALGORITHM_SUFFIX = ".ssl.trustManagerAlgorithm";

  private static final String KEY_NAME_SUFFIX = ".ssl.keyStore";
  private static final String KEY_TYPE_SUFFIX = ".ssl.keyStoreType";
  private static final String KEY_PASSWORD_SUFFIX = ".ssl.keyStorePassword";

  private Logger logger = LoggerFactory.getLogger(getClass());
  private String namespace;

  public TlsPropertiesMapper(String namespace) {
    this.namespace = namespace;
  }

  public void writeToProperties(Properties properties, TlsConfiguration configuration) {
    writeTrustStoreToProperties(properties, configuration);
    writeKeyStoreToProperties(properties, configuration);
  }

  public void readFromProperties(TlsConfiguration configuration, Properties properties) throws IOException {
    readTrustStoreFromProperties(configuration, properties);
    readKeyStoreFromProperties(configuration, properties);
  }

  private void writeTrustStoreToProperties(Properties properties, TlsConfiguration configuration) {
    String trustStoreName = configuration.getTrustStore();
    String trustStorePassword = configuration.getTrustStorePassword();

    if (null == trustStoreName && !configuration.isExplicitTrustStoreOnly()) {
      logger.info("Defaulting " + namespace + " trust store to client Key Store");
      trustStoreName = configuration.getClientKeyStore();
      trustStorePassword = configuration.getClientKeyStorePassword();
    }
    if (null != trustStoreName) {
      synchronized (properties) {
        setProperty(properties, TRUST_NAME_SUFFIX, trustStoreName);
        setProperty(properties, TRUST_TYPE_SUFFIX, configuration.getTrustStoreType());
        setProperty(properties, TRUST_PASSWORD_SUFFIX, trustStorePassword);
        setProperty(properties, TRUST_ALGORITHM_SUFFIX, configuration.getTrustManagerAlgorithm());
      }
      logger.debug("Set Trust Store: " + namespace + TRUST_NAME_SUFFIX + " = " + trustStoreName);
    }
  }

  private void readTrustStoreFromProperties(TlsConfiguration configuration, Properties properties) throws IOException {
    configuration.setTrustStore(getProperty(properties, TRUST_NAME_SUFFIX, configuration.getTrustStore()));
    configuration.setTrustStoreType(getProperty(properties, TRUST_TYPE_SUFFIX, configuration.getTrustStoreType()));
    configuration.setTrustStorePassword(getProperty(properties, TRUST_PASSWORD_SUFFIX, configuration.getTrustStorePassword()));
    configuration
        .setTrustManagerAlgorithm(getProperty(properties, TRUST_ALGORITHM_SUFFIX, configuration.getTrustManagerAlgorithm()));
  }

  private void writeKeyStoreToProperties(Properties properties, TlsConfiguration configuration) {
    if (null != configuration.getClientKeyStore()) {
      synchronized (properties) {
        setProperty(properties, KEY_NAME_SUFFIX, configuration.getClientKeyStore());
        setProperty(properties, KEY_TYPE_SUFFIX, configuration.getClientKeyStoreType());
        setProperty(properties, KEY_PASSWORD_SUFFIX, configuration.getClientKeyStorePassword());
      }
      logger.info("Set Key Store: " + namespace + KEY_NAME_SUFFIX + " = " + configuration.getClientKeyStore());
    }
  }

  // note the asymmetry here. this preserves the semantics of the original implementation.

  // originally, the "client" keystore data were written to system properties (only) and
  // used implicitly to construct sockets, while "non-client" keystore information was
  // used explicitly.

  // now we construct some of those implicit sockets explicitly (as part of avoiding global
  // configuration for tls across all transports). in these cases we read the data needed
  // from (namespaced) proeprties. if we read that information back into "non-client" keystore
  // data, even though it was written from "client" data, then we can use the same code in
  // TlsConfiguration to generate the sockets in both cases.

  private void readKeyStoreFromProperties(TlsConfiguration configuration, Properties properties) throws IOException {
    configuration.setKeyStore(getProperty(properties, KEY_NAME_SUFFIX, configuration.getKeyStore()));
    configuration.setKeyStoreType(getProperty(properties, KEY_TYPE_SUFFIX, configuration.getKeyStoreType()));
    configuration.setKeyStorePassword(getProperty(properties, KEY_PASSWORD_SUFFIX, configuration.getKeyStorePassword()));
  }


  private void setProperty(Properties properties, String suffix, String value) {
    if (null != value) {
      properties.setProperty(namespace + suffix, value);
      if (logger.isDebugEnabled()) {
        logger.debug(namespace + suffix + " <- " + value);
      }
    }
  }

  private String getProperty(Properties properties, String suffix, String deflt) {
    String value = properties.getProperty(namespace + suffix);
    if (null == value) {
      value = deflt;
    }
    if (logger.isDebugEnabled()) {
      logger.debug(namespace + suffix + " -> " + value);
    }
    return value;
  }

}


