/*
 * Copyright (C) 2014-2016 LinkedIn Corp. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the
 * License at  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.
 */
package gobblin.http;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;

import gobblin.annotation.Alias;
import gobblin.configuration.State;

/**
 * Default implementation that uses the following properties to configure an {@link HttpClient}.
 *
 * <ul>
 *  <li>{@link #PROXY_HOSTPORT_KEY}
 *  <li>{@link #PROXY_URL_KEY}
 *  <li>{@link #PROXY_PORT_KEY}
 * </ul>
 */
@Alias(value="default")
public class DefaultHttpClientConfigurator implements HttpClientConfigurator {
  // IMPORTANT: don't change the values for PROXY_URL_KEY and PROXY_PORT_KEY as they are meant to
  // be backwards compatible with SOURCE_CONN_USE_PROXY_URL and SOURCE_CONN_USE_PROXY_PORT when
  // the statePropertiesPrefix is "source.conn."
  /** The hostname of the HTTP proxy to use */
  public static final String PROXY_URL_KEY = "use.proxy.url";
  /** The port of the HTTP proxy to use */
  public static final String PROXY_PORT_KEY = "use.proxy.port";
  /** Similar to {@link #PROXY_URL_KEY} and {@link #PROXY_PORT_KEY} but allows you to set it on
   * one property as <host>:<port> . This property takes precedence over those properties.  */
  public static final String PROXY_HOSTPORT_KEY = "proxyHostport";
  /** Port to use if the HTTP Proxy is enabled but no port is specified */
  public static final int DEFAULT_HTTP_PROXY_PORT = 8080;

  private static final Pattern HOSTPORT_PATTERN = Pattern.compile("([^:]+)(:([0-9]+))?");

  protected final HttpClientBuilder _builder = HttpClientBuilder.create();
  protected String _statePropertiesPrefix = null;

  /** {@inheritDoc} */
  @Override
  public DefaultHttpClientConfigurator configure(Config httpClientConfig) {
    Optional<HttpHost> proxy = getProxyAddr(httpClientConfig);
    if (proxy.isPresent()) {
      getBuilder().setProxy(proxy.get());
    }
    return this;
  }

  /** {@inheritDoc} */
  @Override
  public DefaultHttpClientConfigurator configure(State state) {
    Config cfg = stateToConfig(state);
    return configure(cfg);
  }

  protected Config stateToConfig(State state) {
    String proxyUrlKey = getPrefixedPropertyName(PROXY_URL_KEY);
    String proxyPortKey = getPrefixedPropertyName(PROXY_PORT_KEY);
    String proxyHostportKey = getPrefixedPropertyName(PROXY_HOSTPORT_KEY);

    Config cfg = ConfigFactory.empty();
    if (state.contains(proxyUrlKey)) {
      cfg = cfg.withValue(PROXY_URL_KEY, ConfigValueFactory.fromAnyRef(state.getProp(proxyUrlKey)));
    }
    if (state.contains(proxyPortKey)) {
      cfg = cfg.withValue(PROXY_PORT_KEY, ConfigValueFactory.fromAnyRef(state.getPropAsInt(proxyPortKey)));
    }
    if (state.contains(proxyHostportKey)) {
      cfg = cfg.withValue(PROXY_HOSTPORT_KEY, ConfigValueFactory.fromAnyRef(state.getProp(proxyHostportKey)));
    }
    return cfg;
  }

  /** {@inheritDoc} */
  @Override
  public CloseableHttpClient createClient() {
    return _builder.build();
  }

  @VisibleForTesting
  public static Optional<HttpHost> getProxyAddr(Config httpClientConfig) {
    String proxyHost = null;
    int proxyPort = DEFAULT_HTTP_PROXY_PORT;
    if (httpClientConfig.hasPath(PROXY_URL_KEY) &&
        !httpClientConfig.getString(PROXY_URL_KEY).isEmpty()) {
      proxyHost = httpClientConfig.getString(PROXY_URL_KEY);
    }
    if (httpClientConfig.hasPath(PROXY_PORT_KEY)) {
      proxyPort = httpClientConfig.getInt(PROXY_PORT_KEY);
    }
    if (httpClientConfig.hasPath(PROXY_HOSTPORT_KEY)) {
      String hostport = httpClientConfig.getString(PROXY_HOSTPORT_KEY);
      Matcher hostportMatcher = HOSTPORT_PATTERN.matcher(hostport);
      if (!hostportMatcher.matches()) {
        throw new IllegalArgumentException("Invalid HTTP proxy hostport: " + hostport);
      }
      proxyHost = hostportMatcher.group(1);
      if (!Strings.isNullOrEmpty(hostportMatcher.group(3))) {
        proxyPort = Integer.parseInt(hostportMatcher.group(3));
      }
    }
    return null != proxyHost ? Optional.of(new HttpHost(proxyHost, proxyPort))
        : Optional.<HttpHost>absent();
  }

  @Override
  public DefaultHttpClientConfigurator setStatePropertiesPrefix(String propertiesPrefix) {
    _statePropertiesPrefix = propertiesPrefix;
    return this;
  }

  String getPrefixedPropertyName(String propertyName) {
    return null != _statePropertiesPrefix ? _statePropertiesPrefix + propertyName : propertyName;
  }

  @Override
  public HttpClientBuilder getBuilder() {
    return _builder;
  }

}
