package org.spbu.benchmark.web;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.spbu.benchmark.Action;
import org.spbu.benchmark.BenchmarkException;
import org.spbu.benchmark.LoggingSupport;

import com.sun.org.apache.xerces.internal.util.HTTPInputSource;

import HTTPClient.HttpURLConnection;

public class HttpAction extends LoggingSupport implements Action, Serializable {
	public enum Method {
		GET, POST
	};

	private String address;
	private Map<String, Object> parameters;
	private Method method;
	private List<HttpCookie> cookies;

	public HttpAction(String address, Method method) {
		this.setAddress(address);
		this.setMethod(method);
		this.cookies = new ArrayList<HttpCookie>();
		this.parameters = new HashMap<String, Object>();
	}

	public HttpActionResult invoke() throws BenchmarkException {

		HttpActionResult result = new HttpActionResult();

		try {
			Date start = new Date();
			String address = this.address;
			String data = new String();
			if (method.equals(Method.GET)) {
				address = appendQueryString(address);
			} else {
				data = buildQueryString();
			}
			URL url = new URL(address);
			URLConnection conn = url.openConnection();
			String cookieHeader = "";
			for(HttpCookie cookie : cookies){
				if(cookieHeader.length()>0) cookieHeader += ", ";
				cookieHeader += cookie.toString();
				log.debug("Sending cookie: "+cookie.toString());
			}
			conn.setRequestProperty("Cookie", cookieHeader);
			if (method.equals(Method.POST)) {
				conn.setDoOutput(true);
				OutputStreamWriter out = new OutputStreamWriter(conn
						.getOutputStream());
				out.write(data);
				out.flush();
				out.close();
			}
			Object content = conn.getContent();
			if(content instanceof InputStream){
				InputStream is = (InputStream)content;
				BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		        StringBuilder sb = new StringBuilder();				
		        String line = null;
		        try {
		            while ((line = reader.readLine()) != null) {
		                sb.append(line + "\n");
		            }
		        }catch (IOException e) {
		        	log.error("Error in action: "+this.address, e);
		        } finally {
		            try {
		                is.close();
		            } catch (IOException e) {
		                log.error("Error in action: "+this.address, e);
		            }
		        }
		        log.trace(sb.toString());
			}
			mergeCookies(conn);
			log.debug(content);
			long execTime = (new Date()).getTime() - start.getTime();
			result.setSuccessful(true);
			result.setTime(execTime);
			result.setContent(content);
		} catch (Exception e) {
			throw new BenchmarkException(
					"Error invoking http request for address: " + address, e);
		}
		return result;
	}

	private void mergeCookies(URLConnection conn){
		List<HttpCookie> cookies = new ArrayList<HttpCookie>();
		if(conn.getHeaderFields().containsKey("Set-Cookie")){
			for(String cookieStr : conn.getHeaderFields().get("Set-Cookie")){
				log.debug("Recieved cookie: "+cookieStr);
				try{
					cookies.addAll(HttpCookie.parse(cookieStr));
				}catch(Exception e){
					
				}
			}
		}
		for(HttpCookie cookie : cookies){
			boolean flag = false;
			for(HttpCookie existingCookie : this.cookies){
				if(cookie.equals(existingCookie)){
					existingCookie.setValue(cookie.getValue());
					existingCookie.setMaxAge(cookie.getMaxAge());
					flag = true;
				}
			}
			if(!flag) this.cookies.add(cookie);
		}
	}
	private String appendQueryString(String address){
		if (!address.contains("?"))
			address += "?";
		address += buildQueryString();
		return address;
	}
	private String buildQueryString() {
		String query = "";
		try {
			if (parameters.size()>0) {
				for (Entry<String, Object> entry : parameters.entrySet()) {
					query += URLEncoder.encode(entry.getKey(), "UTF-8")
							+ "="
							+ URLEncoder.encode(entry.getValue().toString(),"UTF-8") 
							+ "&";
				}
				query = query.substring(0, query.length() - 1);
			}
		} catch (UnsupportedEncodingException e) {
			log.error(e);
		}
		return query;
	}

	/*
	 * private Cookie parseResultCookie(String headerValue) { // Parse cookie
	 * String[] fields = headerValue.split(";\\s*"); String cookieName =
	 * fields[0].substring(0, fields[0].indexOf("=")) .trim(); String
	 * cookieValue = fields[0].substring(fields[0].indexOf("=") + 1) .trim();
	 * Integer expires = null; String path = null; String domain = null; boolean
	 * secure = false; Cookie result = new Cookie(cookieName, cookieValue);
	 * result.setPath("/"); result.setMaxAge(-1); // Parse each field for (int j =
	 * 1; j < fields.length; j++) if ("secure".equalsIgnoreCase(fields[j])) {
	 * result.setSecure(true); } else if (fields[j].indexOf('=') > 0) { String[]
	 * field = fields[j].split("="); if ("expires".equalsIgnoreCase(field[0])) {
	 * try { result.setMaxAge(Integer.valueOf(field[1])); } catch
	 * (NumberFormatException ne) { result.setMaxAge(-1); } } else if
	 * ("domain".equalsIgnoreCase(field[0])) { result.setDomain(field[1]); }
	 * else if ("path".equalsIgnoreCase(field[0])) { result.setPath(field[1]); } }
	 * return result; }
	 */

	public Map<String, Object> getParameters() {
		return parameters;
	}
	public HttpAction addParameter(String name, Object value){
		this.getParameters().put(name, value);
		return this;
	}
	public void setParameters(Map<String, Object> parameters) {
		this.parameters = parameters;
	}

	public Method getMethod() {
		return method;
	}

	public void setMethod(Method method) {
		this.method = method;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public List<HttpCookie> getCookies() {
		return cookies;
	}

	public void setCookies(List<HttpCookie> cookies) {
		this.cookies = cookies;
	}

}
