package com.rafali.flickruploader;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;

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

import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.common.base.Joiner;
import com.rafali.common.Base64UrlSafe;

public class HttpClientGAE {
	public static final String POSTPROXY_PHP = "http://log.pictarine.com/postproxy.php";
	private static final Logger logger = LoggerFactory.getLogger(HttpClientGAE.class.getPackage().getName());
	private static final Charset UTF8 = Charset.forName("UTF-8");

	private HttpClientGAE() {

	}

	public static String getResponseDELETE(String url, Map<String, String> params, Map<String, String> headers) {
		int retry = 0;
		while (retry < 3) {
			long start = System.currentTimeMillis();
			try {
				URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
				String urlStr = ToolString.toUrl(url.trim(), params);
				logger.debug("DELETE : " + urlStr);
				HTTPRequest httpRequest = new HTTPRequest(new URL(urlStr), HTTPMethod.DELETE, FetchOptions.Builder.withDeadline(deadline));
				HTTPResponse response = fetcher.fetch(httpRequest);
				return processResponse(response);
			} catch (Throwable e) {
				retry++;
				if (e instanceof RuntimeException) {
					throw (RuntimeException) e;
				} else if (retry < 3) {
					logger.warn("retrying after " + (System.currentTimeMillis() - start) + " and " + retry + " retries\n" + e.getClass() + e.getMessage());
				} else {
					logger.error(e.getClass() + "\n" + ToolString.stack2string(e));
				}
			}
		}
		return null;
	}

	public static String getResponsePUT(String url, Map<String, String> params, String json, Map<String, String> headers) {
		int retry = 0;
		while (retry < 3) {
			long start = System.currentTimeMillis();
			try {
				URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
				String urlStr = ToolString.toUrl(url.trim(), params);
				logger.debug("PUT : " + urlStr);
				HTTPRequest httpRequest = new HTTPRequest(new URL(urlStr), HTTPMethod.PUT, FetchOptions.Builder.withDeadline(deadline));
				if (headers != null) {
					for (String header : headers.keySet()) {
						httpRequest.addHeader(new HTTPHeader(header, headers.get(header)));
					}
				}
				httpRequest.setPayload(json.getBytes());
				HTTPResponse response = fetcher.fetch(httpRequest);
				return processResponse(response);
			} catch (Throwable e) {
				retry++;
				if (e instanceof RuntimeException) {
					throw (RuntimeException) e;
				} else if (retry < 3) {
					logger.warn("retrying after " + (System.currentTimeMillis() - start) + " and " + retry + " retries\n" + e.getClass() + e.getMessage());
				} else {
					logger.error(e.getClass() + "\n" + ToolString.stack2string(e));
				}
			}
		}
		return null;
	}

	public static String getResponseGET(String url) {
		return getResponseGET(url, null, null);
	}

	public static String getResponseGET(String url, Map<String, String> params) {
		return getResponseGET(url, params, null);
	}

	public static String getResponseGET(String url, Map<String, String> params, Map<String, String> headers) {
		int retry = 0;
		while (retry < 3) {
			long start = System.currentTimeMillis();
			try {
				URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
				String urlStr = ToolString.toUrl(url.trim(), params);
				HTTPRequest httpRequest = new HTTPRequest(new URL(urlStr), HTTPMethod.GET, FetchOptions.Builder.withDeadline(deadline));
				if (headers != null) {
					for (String name : headers.keySet()) {
						httpRequest.addHeader(new HTTPHeader(name, headers.get(name)));
					}
				}
				return processResponse(fetcher.fetch(httpRequest));
			} catch (Throwable e) {
				retry++;
				if (e instanceof RuntimeException) {
					throw (RuntimeException) e;
				} else if (retry < 3) {
					logger.warn("retrying after " + (System.currentTimeMillis() - start) + " and " + retry + " retries\n" + e.getClass() + e.getMessage());
				} else {
					logger.error(e.getClass() + "\n" + ToolString.stack2string(e));
				}
			}
		}
		return null;
	}

	public static String processResponse(HTTPResponse response) {
		String content = new String(response.getContent(), UTF8);
		if (response.getResponseCode() >= 400) {
			throw new RuntimeException("HttpError:" + response.getResponseCode() + "\n" + content);
		}
		return content;
	}

	public static String getResponseProxyPOST(URL url) throws MalformedURLException, UnsupportedEncodingException, IOException {
		URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
		String base64payload = "base64url=" + Base64UrlSafe.encodeServer(url.toString());
		String urlStr = url.toString();
		if (urlStr.contains("?")) {
			base64payload = "base64url=" + Base64UrlSafe.encodeServer(urlStr.substring(0, urlStr.indexOf("?")));
			base64payload += "&base64content=" + Base64UrlSafe.encodeServer(urlStr.substring(urlStr.indexOf("?") + 1));
		} else {
			base64payload = "base64url=" + Base64UrlSafe.encodeServer(urlStr);
		}
		HTTPRequest httpRequest = new HTTPRequest(new URL(HttpClientGAE.POSTPROXY_PHP), HTTPMethod.POST, FetchOptions.Builder.withDeadline(30d).doNotValidateCertificate());
		httpRequest.setPayload(base64payload.getBytes(UTF8));
		HTTPResponse response = fetcher.fetch(httpRequest);
		String processResponse = HttpClientGAE.processResponse(response);
		logger.info("proxying " + url + "\nprocessResponse:" + processResponse);
		return processResponse;
	}

	public static String getResponsePOST(String url, Map<String, String> params) {
		return getResponsePOST(url, null, params, null);
	}

	public static double deadline = 50d;

	public static String getResponsePOST(String url, Map<String, String> headers, Map<String, String> params, String content) {
		return getResponsePOST(url, headers, params, content, deadline);
	}

	public static String getResponsePOST(String url, Map<String, String> headers, Map<String, String> params, String content, double timeoutSeconds) {
		int retry = 0;
		Throwable previousException = null;
		while (retry < 3) {
			long start = System.currentTimeMillis();
			try {
				URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
				HTTPRequest httpRequest;
				String urlStr;
				if (content == null) {
					urlStr = url;
				} else {
					urlStr = ToolString.toUrl(url.trim(), params);
				}
				String base64payload = null;
				if (previousException instanceof SocketTimeoutException) {
					base64payload = "base64url=" + Base64UrlSafe.encodeServer(urlStr);
					urlStr = POSTPROXY_PHP;
					if (content != null) {
						base64payload += "&base64content=" + Base64UrlSafe.encodeServer(content);
					}
					logger.info("proxy call : " + urlStr + "\n" + base64payload);
				}
				httpRequest = new HTTPRequest(new URL(urlStr), HTTPMethod.POST, FetchOptions.Builder.withDeadline(timeoutSeconds).doNotValidateCertificate());
				if (base64payload != null) {
					httpRequest.setPayload(base64payload.getBytes(UTF8));
				} else if (content == null) {
					if (params != null) {
						String paramStr = Joiner.on("&").withKeyValueSeparator("=").useForNull("null").join(params);
						httpRequest.setPayload(paramStr.getBytes(UTF8));
						logger.debug(paramStr);
					}
				} else {
					httpRequest.setPayload(content.getBytes(UTF8));
				}
				if (headers != null) {
					for (String header : headers.keySet()) {
						httpRequest.addHeader(new HTTPHeader(header, headers.get(header)));
					}
				}
				HTTPResponse response = fetcher.fetch(httpRequest);
				return processResponse(response);
			} catch (Throwable e) {
				retry++;
				previousException = e;
				if (e instanceof RuntimeException) {
					throw (RuntimeException) e;
				} else if (retry < 3) {
					logger.warn("retrying after " + (System.currentTimeMillis() - start) + " and " + retry + " retries\n" + e.getClass() + " : " + e.getMessage());
				} else {
					logger.error(e.getClass() + "\n" + ToolString.stack2string(e));
				}
			}
		}
		return null;
	}

	// public String getResponsePOST(String url, Map<String, String> params, String json, Map<String, String> headers) {
	// int retry = 0;
	// while (retry < 3) {
	// long start = System.currentTimeMillis();
	// try {
	// URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
	// String urlStr = ToolString.toUrl(url.trim(), params);
	// logger.debug("POST : " + urlStr);
	// HTTPRequest httpRequest = new HTTPRequest(new URL(urlStr), HTTPMethod.POST, FetchOptions.Builder.withDeadline(50d));
	// if (headers != null) {
	// for (String header : headers.keySet()) {
	// httpRequest.addHeader(new HTTPHeader(header, headers.get(header)));
	// }
	// }
	// httpRequest.setPayload(json.getBytes());
	// HTTPResponse response = fetcher.fetch(httpRequest);
	// return processResponse(response);
	// } catch (Throwable e) {
	// retry++;
	// if (e instanceof RuntimeException) {
	// throw (RuntimeException) e;
	// } else if (retry < 3) {
	// logger.warn("retrying after " + (System.currentTimeMillis() - start) + " and " + retry + " retries\n" + e.getClass() + e.getMessage());
	// } else {
	// logger.error(e.getClass() + "\n" + ToolException.stack2string(e));
	// }
	// }
	// }
	// return null;
	// }

	public static Map<String, String> getResponses(Collection<String> urls) {
		Map<String, String> responses = new HashMap<String, String>();
		URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
		Map<String, Future<HTTPResponse>> futures = new HashMap<String, Future<HTTPResponse>>();
		try {
			for (String url : urls) {
				HTTPRequest httpRequest = new HTTPRequest(new URL(url), HTTPMethod.GET, FetchOptions.Builder.withDeadline(deadline));
				Future<HTTPResponse> future = fetcher.fetchAsync(httpRequest);
				futures.put(url, future);
			}
		} catch (MalformedURLException e) {
			logger.error(e.getClass() + "\n" + ToolString.stack2string(e));
		}

		for (String url : urls) {
			Future<HTTPResponse> future = futures.get(url);
			try {
				HTTPResponse httpResponse = future.get();
				responses.put(url, new String(httpResponse.getContent(), UTF8));
			} catch (Exception e) {
				logger.error(e.getClass() + "\n" + ToolString.stack2string(e));
			}
		}

		return responses;
	}

	public static String getResponseProxyGET(URL url) {
		String urlproxy;
		String urlStr = url.toString();
		if (urlStr.contains("?")) {
			urlproxy = "http://log.pictarine.com/getproxy.php?base64url=" + Base64UrlSafe.encodeServer(urlStr.substring(0, urlStr.indexOf("?"))) + "&base64query="
					+ Base64UrlSafe.encodeServer(urlStr.substring(urlStr.indexOf("?") + 1));
		} else {
			urlproxy = "http://log.pictarine.com/getproxy.php?base64url=" + Base64UrlSafe.encodeServer(urlStr);
		}
		String responseGET = getResponseGET(urlproxy);
		logger.info("urlproxy : " + urlproxy + "\nresponseGET : " + responseGET);
		return responseGET;
	}

}