package nuts.core.net.http;

import nuts.core.io.Streams;
import nuts.core.lang.Strings;
import nuts.core.lang.time.StopWatch;
import nuts.core.log.Log;
import nuts.core.log.Logs;

import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 */
public class HttpClient {
	private static Log log = Logs.getLog(HttpClient.class);

	/**
	 * DEFAULT_CONN_TIMEOUT = 30 seconds
	 */
	public static int DEFAULT_CONN_TIMEOUT = 30 * 1000;

	/**
	 * DEFAULT_READ_TIMEOUT = 10 minutes
	 */
	public static int DEFAULT_READ_TIMEOUT = 10 * 60 * 1000;

	public static HttpResponse get(String url) throws IOException {
		return send(url, HttpMethod.GET, null);
	}
	
	public static HttpResponse get(String url, int timeout) throws IOException {
		return new HttpClient(HttpRequest.get(url)).setTimeout(timeout).send();
	}

	public static HttpResponse get(String url, Map<String, Object> params) throws IOException {
		return send(url, HttpMethod.GET, params);
	}
	
	public static HttpResponse post(String url) throws IOException {
		return send(url, HttpMethod.POST, null);
	}
	
	public static HttpResponse post(String url, Map<String, Object> forms) throws IOException {
		return send(url, HttpMethod.POST, forms);
	}
	
	public static HttpResponse send(String url, HttpMethod method, Map<String, Object> forms) throws IOException {
		return new HttpClient(HttpRequest.create(url, method, forms)).send();
	}
	
	//---------------------------------------------------------------------
	protected HttpRequest request;
	protected HttpResponse response;

	protected int timeout;

	protected Proxy proxy;
	
	protected HttpURLConnection conn;

	public HttpClient() {
	}

	public HttpClient(HttpRequest request) {
		this.request = request;
	}

	/**
	 * @return the request
	 */
	public HttpRequest getRequest() {
		return request;
	}

	/**
	 * @param request the request to set
	 */
	public void setRequest(HttpRequest request) {
		this.request = request;
	}

	/**
	 * @return the response
	 */
	public HttpResponse getResponse() {
		return response;
	}

	/**
	 * @return the timeout
	 */
	public int getTimeout() {
		return timeout;
	}

	/**
	 * @param timeout the timeout to set
	 */
	public HttpClient setTimeout(int timeout) {
		this.timeout = timeout;
		return this;
	}

	/**
	 * @return the proxy
	 */
	public Proxy getProxy() {
		return proxy;
	}

	/**
	 * @param proxy the proxy to set
	 */
	public void setProxy(Proxy proxy) {
		this.proxy = proxy;
	}

	public HttpResponse doGet() throws IOException {
		try {
			openConnection();
			setupRequestHeader();
			return createResponse();
		}
		catch (Exception e) {
			throw new IOException(request.getUrl().toString(), e);
		}
	}

	public HttpResponse doPost() throws IOException {
		try {
			openConnection();
			setupRequestHeader();
			setupDoInputOutputFlag();
			InputStream ins = request.getInputStream();
			if (null != ins) {
				OutputStream ops = null;
				try {
					ops = conn.getOutputStream();
					Streams.copy(ins, ops);
				}
				finally {
					Streams.safeClose(ins);
					Streams.safeFlush(ops);
					Streams.safeClose(ops);
				}
			}
			return createResponse();
		}
		catch (Exception e) {
			throw new IOException(request.getUrl().toString(), e);
		}
	}

	public HttpResponse doPostFiles() throws IOException {
		try {
			String boundary = "---------------------------[Nutz]7d91571440efc";
			openConnection();
			setupRequestHeader();
			conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
			setupDoInputOutputFlag();
			Map<String, Object> params = request.getParams();
			if (null != params && params.size() > 0) {
				DataOutputStream outs = new DataOutputStream(conn.getOutputStream());
				for (Entry<String, ?> entry : params.entrySet()) {
					outs.writeBytes("--" + boundary + Strings.CRLF);
					String key = entry.getKey();
					File f = null;
					if (entry.getValue() instanceof File)
						f = (File)entry.getValue();
					else if (entry.getValue() instanceof String)
						f = new File(entry.getValue().toString());
					if (f != null && f.exists()) {
						outs.writeBytes("Content-Disposition:    form-data;    name=\"" + key + "\";    filename=\""
								+ entry.getValue() + "\"\r\n");
						outs.writeBytes("Content-Type:   application/octet-stream\r\n\r\n");
						if (f.length() == 0)
							continue;

						Streams.copy(f, outs);
						outs.writeBytes("\r\n");
					}
					else {
						outs.writeBytes("Content-Disposition:    form-data;    name=\"" + key + "\"\r\n\r\n");
						outs.writeBytes(entry.getValue() + "\r\n");
					}
				}
				outs.writeBytes("--" + boundary + "--" + Strings.CRLF);
				Streams.safeFlush(outs);
				Streams.safeClose(outs);
			}

			return createResponse();

		}
		catch (IOException e) {
			throw new IOException(request.getUrl().toString(), e);
		}
	}

	public HttpResponse send() throws IOException {
		final String CLRF = Streams.LINE_SEPARATOR;

		if (log.isDebugEnabled()) {
			log.debug(request.toString());
		}
		else if (log.isInfoEnabled()) {
			log.info(request.getMethod() + " " + request.getUrl());
		}
		
		HttpResponse response;

		StopWatch sw = new StopWatch(true);
		if (request.isPost()) {
			response = doPost();
		}
		else {
			response = doGet();
		}

		if (log.isInfoEnabled()) {
			StringBuilder msg = new StringBuilder();
			msg.append(request.getUrl()).append(" - ").append(response.getStatusLine());
			msg.append(" (");
			if (response.getContentLength() != null) {
				msg.append(response.getContentLength()).append("B ");
			}
			msg.append(sw.getTime()).append("ms)");

			if (log.isDebugEnabled()) {
				if (response.getHeader() != null) {
					msg.append(CLRF); 
					response.getHeader().toString(msg);
				}

				if (log.isTraceEnabled()) {
					String text = response.getContentText();
					if (Strings.isNotEmpty(text)) {
						msg.append(Strings.CRLF).append(text);
					}
					log.trace(msg);
				}
				else {
					log.debug(msg);
				}
			}
			else {
				log.info(msg);
			}
		}
		return response;
	}

	protected HttpResponse createResponse() throws IOException {
		response = new HttpResponse(conn);
		return response;
	}

	protected void setupDoInputOutputFlag() {
		conn.setDoInput(true);
		conn.setDoOutput(true);
	}

	protected void openConnection() throws IOException {
		if (proxy != null) {
			conn = (HttpURLConnection)request.getUrl().openConnection(proxy);
		}
		else {
			conn = (HttpURLConnection)request.getUrl().openConnection();
		}

		conn.setConnectTimeout(DEFAULT_CONN_TIMEOUT);
		conn.setReadTimeout(timeout > 0 ? timeout : DEFAULT_READ_TIMEOUT);
	}

	protected void setupRequestHeader() {
		URL url = request.getUrl();
		String host = url.getHost();
		if (url.getPort() > 0 && url.getPort() != 80) {
			host += ":" + url.getPort();
		}
		conn.setRequestProperty(HttpHeader.HOST, host);
		
		HttpHeader header = request.getHeader();
		if (null != header) {
			for (Entry<String, Object> en : header.entrySet()) {
				String key = en.getKey();
				Object val = en.getValue();
				if (val instanceof List) {
					for (Object v : (List)val) {
						conn.addRequestProperty(key, v.toString());
					}
				}
				else {
					conn.addRequestProperty(key, val.toString());
				}
			}
		}
	}
}
