/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.util;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.net.proxy.IProxyService;
import org.jboss.tools.common.core.CommonCorePlugin;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

/**
 * HTTP Utilities
 * @author Alexey Kazakov
 */
public class HttpUtil {

	private static IProxyService proxyService;

	/**
	 * @param url
	 * @return InputStream of the response body of the http GET request. Proxy settings from preferences is used.
	 * @throws Exception
	 * @deprecated Use getInputStreamReader(HttpURLConnection connection)
	 */
	@Deprecated
	public static InputStream getInputStreamFromUrlByGetMethod(String url) throws IOException {
		return getInputStreamFromUrlByGetMethod(url, false);
	}

	/**
	 * Returns the response body of the Get HTTP method. If checkStatusCode==true then return null if the status code is not OK (200)
	 * Uses proxy settings from preferences.
	 * @param url
	 * @return 
	 * @throws IOException
	 * @deprecated Use getInputStreamReader(HttpURLConnection connection)
	 */
	@Deprecated
	public static InputStream getInputStreamFromUrlByGetMethod(String url, boolean checkStatusCode) throws IOException {
		InputStream is = null;
		GetMethod method = executeGetMethod(url);
		if(!checkStatusCode || method.getStatusCode()==HttpStatus.SC_OK) {
			is = method.getResponseBodyAsStream();
		}
		return is;
	}

	/**
	 * Create and open an HTTP connection (GET). If the response code is 200 (OK) then returns an input stream reader that reads from this open connection.
	 * Otherwise returns null.
	 * @param httpUrl
	 * @param timeout
	 * @return
	 * @throws IOException
	 */
	public static InputStreamReader getInputStreamReader(String httpUrl, int timeout) throws IOException {
		HttpURLConnection connection = createHttpURLConnection(httpUrl, timeout);
		if(connection!=null) {
			return getInputStreamReader(connection);
		}
		return null;
	}

	/**
	 * Open the HTTP connection. If the response code is 200 (OK) then returns an input stream reader that reads from this connection.
	 * Otherwise returns null.
	 * @param httpUrl
	 * @param timeout
	 * @return
	 * @throws IOException
	 */
	public static InputStreamReader getInputStreamReader(HttpURLConnection connection) throws IOException {
		return getInputStreamReader(connection, 0);
	}

	/**
	 * Create an HTTP connection (GET). Returns null if the URL does not use HTTP(S) protocol.
	 * @param urlString
	 * @param timeout
	 * @return
	 * @throws IOException
	 */
	public static HttpURLConnection createHttpURLConnection(String urlString, int timeout) throws IOException {
		URL url = new URL(urlString);
		URLConnection connetion = url.openConnection();
		HttpURLConnection httpConnection = null;
		if(connetion instanceof HttpURLConnection) {
			httpConnection = (HttpURLConnection) url.openConnection();
			httpConnection.setInstanceFollowRedirects(true);
			httpConnection.setRequestMethod("GET");
			httpConnection.setConnectTimeout(timeout);
		}
		return httpConnection;
	}

	private static InputStreamReader getInputStreamReader(HttpURLConnection connection, int redirectAttemptCount) throws IOException {
		InputStreamReader responseReader = null;
		connection.connect();
		int responseCode = connection.getResponseCode();
		if (responseCode == HttpURLConnection.HTTP_OK) { // OK
			String contentTypeCharset = null;
			String contentType = connection.getContentType();
			if(contentType!=null) {
				Matcher matcher = Pattern.compile("charset=(.+)").matcher(contentType);
				if (matcher.find()) {
					contentTypeCharset = matcher.group(1);
				}
			}
			InputStream inputStream = connection.getInputStream();
			if (contentTypeCharset != null && contentTypeCharset.length() > 0) {
				responseReader = new InputStreamReader(new BufferedInputStream(inputStream), contentTypeCharset);
			} else {
				responseReader = new InputStreamReader(new BufferedInputStream(inputStream));
			}
		} else if(responseCode >= 300 && responseCode < 400) { // Redirect
			// URLConnection does not redirect automatically if the protocol is different. HTTP -> HTTPS for example.
			// So we have to do it manually.
			String redirectLocation = connection.getHeaderField("location");
			if(redirectLocation!=null && !connection.getURL().toString().equalsIgnoreCase(redirectLocation.trim())) { // Ignore responses with empty redirect locations or with the same redirect URL
				redirectAttemptCount++;
				if(redirectAttemptCount>1) {
					// Only one redirect is allowed.
					HttpURLConnection redirectConnection = createHttpURLConnection(redirectLocation, connection.getConnectTimeout());
					redirectConnection.setIfModifiedSince(connection.getIfModifiedSince());
					return getInputStreamReader(redirectConnection, redirectAttemptCount);
				}
			}
		}
		return responseReader;
	}

	/**
	 * @param url
	 * @return InputStream of response to http GET request. Use proxy settings from preferences.
	 * @throws Exception
	 */
	public static InputStream getInputStreamFromUrlByGetMethod(String url, IProxyService proxyService) throws IOException {
		InputStream is = executeGetMethod(url, proxyService).getResponseBodyAsStream();
		return is;
	}

	/**
	 * @param url
	 * @param proxyService
	 * @return Status code of response to http GET request. Use given proxy settings.
	 * @throws Exception
	 */
	public static int getStatusCodeFromUrlByGetMethod(String url, IProxyService proxyService) throws IOException {
		int code = executeGetMethod(url, proxyService).getStatusCode();
		return code;
	}

	/**
	 * @param url
	 * @return Status code of response to http GET request. Use proxy settings from preferences.
	 * @throws Exception
	 */
	public static int getStatusCodeFromUrlByGetMethod(String url) throws IOException {
		int code = executeGetMethod(url).getStatusCode();
		return code;
	}

	/**
	 * @param url
	 * @return InputStream of response to http POST request. Use proxy settings from preferences.
	 * @throws Exception
	 */
	public static InputStream getInputStreamFromUrlByPostMethod(String url) throws IOException {
		InputStream is = executePostMethod(url).getResponseBodyAsStream();
		return is;
	}

	/**
	 * @param url
	 * @param proxyService
	 * @return Status code of response to http POST request. Use given proxy settings.
	 * @throws Exception
	 */
	public static int getStatusCodeFromUrlByPostMethod(String url, IProxyService proxyService) throws IOException {
		int code = executePostMethod(url, proxyService).getStatusCode();
		return code;
	}

	/**
	 * @param url
	 * @return Status code of response to http POST request. Use proxy settings from preferences.
	 * @throws Exception
	 */
	public static int getStatusCodeFromUrlByPostMethod(String url) throws IOException {
		int code = executePostMethod(url).getStatusCode();
		return code;
	}

	private static GetMethod executeGetMethod(String url) throws IOException {
		IProxyService proxyService = getProxyService();
		return executeGetMethod(url, proxyService);
	}

	private static PostMethod executePostMethod(String url) throws IOException {
		IProxyService proxyService = getProxyService();
		return executePostMethod(url, proxyService);
	}

	/**
	 * @param url
	 * @param proxyService
	 * @return
	 * @throws Exception
	 */
	public static GetMethod executeGetMethod(String url, IProxyService proxyService) throws IOException {
		GetMethod httpGet = new GetMethod(url);
		HttpClient httpClient = createHttpClient(url, proxyService);
		httpClient.executeMethod(httpGet);
		return httpGet;
	}

	private static PostMethod executePostMethod(String url, IProxyService proxyService) throws IOException {
		PostMethod httpPost = new PostMethod(url);
		HttpClient httpClient = createHttpClient(url, proxyService);
	    httpClient.executeMethod(httpPost);
		return httpPost;
	}

	/**
	 * @param url
	 * @return HttpClient with internet proxy settings;
	 * @throws Exception
	 */
	public static HttpClient createHttpClient(String url) throws IOException {
		return createHttpClient(url, getProxyService());
	}

	public static HttpClient createHttpClient(String url, int connectionTimeout) throws IOException {
		return createHttpClient(url, getProxyService(), connectionTimeout);
	}

	private static HttpClient createHttpClient(String url, IProxyService proxyService) throws IOException {
	    return createHttpClient(url, proxyService, 30000);
	}

	private static HttpClient createHttpClient(String url, IProxyService proxyService, int connectionTimeout) throws IOException {
		HttpClient httpClient = new HttpClient();

		if(proxyService.isProxiesEnabled()) {
			IProxyData[] proxyData = proxyService.getProxyData();
			URL netUrl = new URL(url);
			String hostName = netUrl.getHost();
			String[] nonProxiedHosts = proxyService.getNonProxiedHosts();
			boolean nonProxiedHost = false;
			for (int i = 0; i < nonProxiedHosts.length; i++) {
				String nonProxiedHostName = nonProxiedHosts[i];
				if(nonProxiedHostName.equalsIgnoreCase(hostName)) {
					nonProxiedHost = true;
					break;
				}
			}
			if(!nonProxiedHost) {
				for (int i = 0; i < proxyData.length; i++) {
					IProxyData proxy = proxyData[i];
					if(IProxyData.HTTP_PROXY_TYPE.equals(proxy.getType())) {
						String proxyHostName = proxy.getHost();
						if(proxyHostName==null) {
							break;
						}
						int portNumber = proxy.getPort();
						if(portNumber==-1) {
							portNumber = 80;
						}
						httpClient.getHostConfiguration().setProxy(proxyHostName, portNumber);
						if(proxy.isRequiresAuthentication()) {
						    String userName = proxy.getUserId();
						    if(userName!=null) {
							    String password = proxy.getPassword();
							    httpClient.getState().setProxyCredentials(new AuthScope(null, AuthScope.ANY_PORT, null, AuthScope.ANY_SCHEME), new UsernamePasswordCredentials(userName, password));
						    }
						}
						break; // Use HTTP proxy only.
					}
				}
			}
		}

	    httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout);

	    return httpClient;
	}

	private static IProxyService getProxyService() {
		if(proxyService==null) {
			BundleContext bundleContext = CommonCorePlugin.getInstance().getBundle().getBundleContext();
			ServiceReference serviceReference = bundleContext.getServiceReference(IProxyService.class.getName());
			proxyService = (IProxyService)bundleContext.getService(serviceReference);
		}
		return proxyService;
	}
}