/**
 * ******************************************************************************
 *  Copyright Fujitsu.
 *
 *  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.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * *****************************************************************************
 *
 *  NIFCLOUD SDK for Java
 *
 */
package com.nifty.cloud.sdk.tools;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;

import com.nifty.cloud.sdk.ClientConfiguration;
import com.nifty.cloud.sdk.Const;

/**
 *　HTTPクライアントクラス。<br />
 *　HTTPクライアント情報を格納します。
 */
public class HttpClient {

	/** Commons HTTP　Client  	 */
	private static HttpClient instance = new HttpClient();
	
	/**	初期化用フラグ  	 */
	private static String initialize = "";

	private CloseableHttpClient httpclient = null;


	private HttpClient() {
	}
	

	/**
	 *　HTTPクライアントクラスを取得します。
	 * 常に同一オブジェクトを取得します。
	 * 
	 * @param config - 設定情報
	 * @return  HTTPクライアント
	 */
	public static HttpClient getInstance(ClientConfiguration config) {
		
		if (initialize.isEmpty()) {
			synchronized (initialize) {
				HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

				if(config.getProxyHost() != null && !config.getProxyHost().isEmpty()) {
					HttpHost proxy = (config.getProxyPort()!= 0) ?
											new HttpHost(config.getProxyHost(), config.getProxyPort()) :
											new HttpHost(config.getProxyHost());
					httpClientBuilder.setProxy(proxy);

					if (config.getProxyUsername() != null && !config.getProxyUsername().isEmpty() &&
						config.getProxyPassword() != null && !config.getProxyPassword().isEmpty()) {
						CredentialsProvider credsProvider = new BasicCredentialsProvider();
						credsProvider.setCredentials(
								new AuthScope(config.getProxyHost(), config.getProxyPort()),
								new UsernamePasswordCredentials(config.getProxyUsername(), config.getProxyPassword()));
						httpClientBuilder.setDefaultCredentialsProvider(credsProvider);
					}
				}
				
				httpClientBuilder.setRetryHandler(
					new DefaultHttpRequestRetryHandler(config.getMaxErrorRetry(), false) {
						@Override
						public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
							boolean retry = super.retryRequest(exception, executionCount, context);
							if (retry) {
								try {
									Thread.sleep(300);
								} catch (InterruptedException e) {}
							}
							return retry;
						}
					});
				
				String userAgent = config.getUserAgent();
				if(userAgent != null && !userAgent.isEmpty()){
					httpClientBuilder.setUserAgent(
							userAgent.equals(Const.DEFAULT_USER_AGENT) ? userAgent : userAgent);
				}

				httpClientBuilder.setMaxConnTotal(config.getMaxConnections());
				RequestConfig requestConfig = RequestConfig.custom()
						.setConnectTimeout(config.getConnectionTimeout() * 1000)
						.setSocketTimeout(config.getSocketTimeout() * 1000)
						.build();
				httpClientBuilder.setDefaultRequestConfig(requestConfig);

				
				int [] bufferSizes = config.getSocketBufferSizeHints();
				SocketConfig.Builder socketConfigBuilder = SocketConfig.custom();
				if (bufferSizes[0] != 0) {
					socketConfigBuilder.setSndBufSize(bufferSizes[0]);
				}
				if (bufferSizes[1] != 0) {
					socketConfigBuilder.setRcvBufSize(bufferSizes[1]);
				}
				httpClientBuilder.setDefaultSocketConfig(socketConfigBuilder.build());
				
				// Trust self-signed certificate
				if (config.getSslTrustSelfSigned()) {
					try {
						SSLContext sslContext = SSLContexts.custom()
								.loadTrustMaterial(new TrustSelfSignedStrategy())
								.build();
						httpClientBuilder.setSSLContext(sslContext);
					} catch (NoSuchAlgorithmException e) {
						throw new RuntimeException(e);
					} catch (KeyStoreException e) {
						throw new RuntimeException(e);
					} catch (KeyManagementException e) {
						throw new RuntimeException(e);
					}
				}

				// Disable hostname verification
				if (!config.getSslVerifyHostname()) {
					httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
				}

				instance.httpclient = httpClientBuilder.build();
				initialize = "initialized";
			}
		}
		
		return instance;
	}

	/**
	 * HTTPリクエストを実行します。
	 *
	 * @param httpreq HTTPリクエスト
	 * @return HTTPレスポンス
	 */
	public CloseableHttpResponse execute(HttpUriRequest httpreq) throws IOException {
		return httpclient.execute(httpreq);
	}
}
