/**
 * 
 */
package org.jboss.test.faces.staging;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.jboss.test.faces.TestException;

/**
 * This class represent single connection ( request ) to the virtual server. These instance should not be created directly, but by the {@link StagingServer#getConnection(URL)}.
 * method only.
 * Since instance have been created, additional request parameters and headers can be set.
 * @author asmirnov
 * 
 */
public class StagingConnection extends HttpConnection {

	private static final Logger log = ServerLogger.SERVER.getLogger();

	private final StagingServer server;

	private final URL url;

	private final ConnectionRequest request;

	private final ConnectionResponse response;

	private final RequestChain servlet;

	private static final Cookie[] COOKIE = new Cookie[] {};

	private List<Cookie> cookies = new ArrayList<Cookie>();

	private final String pathInfo;

	private final String servletPath;

	private boolean finished = false;

	private boolean started = false;

	private final HttpServletRequest requestProxy;

	private final HttpServletResponse responseProxy;

	/**
	 * Create connection instance.
	 * @param localServer virtual server instance.
	 * @param url request URL.
	 */
	StagingConnection(StagingServer localServer, URL url) {
		this.server = localServer;
		this.url = url;
		// TODO - context path support.
		String path = url.getPath();
		servlet = localServer.getServlet(path);
		if (null == servlet) {
			throw new IllegalArgumentException();
		}
		this.pathInfo = servlet.getPathInfo(path);
		this.servletPath = servlet.getServletPath(path);
		this.request = new ConnectionRequest();
		this.response = new ConnectionResponse();
		this.request.setAttribute("javax.servlet.include.path_info",
				this.pathInfo);
		this.request.setAttribute("javax.servlet.include.servlet_path",
				this.servletPath);
		String queryString = url.getQuery();
		if (null != queryString) {
			parseFormParameters(queryString);
		}
		
		// Create proxy objects.
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		if(null == loader){
			loader = this.getClass().getClassLoader();
		}
		requestProxy = (HttpServletRequest) Proxy.newProxyInstance(loader, new Class[]{HttpServletRequest.class}, server.getInvocationHandler(request));
		responseProxy = (HttpServletResponse) Proxy.newProxyInstance(loader, new Class[]{HttpServletResponse.class}, server.getInvocationHandler(response));
	}


	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#isFinished()
     */
	@Override
    public boolean isFinished() {
		return finished;
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#isStarted()
     */
	@Override
    public boolean isStarted() {
		return started;
	}

//	private void checkStarted() {
//		if (!isFinished()) {
//			throw new TestException("request have not been started");
//		}
//	}

//	private void checkNotStarted() {
//		if (isStarted()) {
//			throw new TestException("request was started, no parameters changes allowed");
//		}
//	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#execute()
     */
	@Override
    public void execute() {
		if (isStarted() || isFinished()) {
			throw new TestException(
					"request have already been executed");
		}
		start();
		try {
			this.servlet.execute(request, response);
		} catch (ServletException e) {
			throw new TestException("Error execute request ",e);
		} catch (IOException e) {
			throw new TestException("IO Error during request execution",e);
		} finally {
			finish();
		}
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#finish()
     */
	@Override
    public void finish() {
		server.requestFinished(request);
		finished = true;
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#start()
     */
	@Override
    public void start() {
		log.fine("start " + getRequestMethod() + " request processing for file "
				+ url.getFile());
		log.fine("request parameters: " + getRequestParameters());
		server.requestStarted(request);
		started = true;
	}

	/**
	 * Get request url.
	 * @return the url
	 */
	public URL getUrl() {
		return url;
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getContentAsString()
     */
	@Override
    public String getContentAsString() {
//		checkStarted();
		String content = response.getWriterContent();
		if (null == content) {
			byte[] streamContent = response.getStreamContent();
			if (null != streamContent) {
				String encoding = response.getCharacterEncoding();
				if (null != encoding) {
					try {
						content = new String(streamContent, encoding);
					} catch (UnsupportedEncodingException e) {
						throw new TestException(e);
					}
				} else {
					content = new String(streamContent);
				}
			}
		}
		return content;
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getResponseBody()
     */
	@Override
    public byte[] getResponseBody() {
//		checkStarted();
		byte[] content = response.getStreamContent();
		if (null == content) {
			String writerContent = response.getWriterContent();
			if (null != writerContent) {
				try {
					content = writerContent.getBytes(response
							.getCharacterEncoding());
				} catch (UnsupportedEncodingException e) {
					content = writerContent.getBytes();
				}
			} else {
				content = new byte[0];
			}
		}
		return content;
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getCookies()
     */
	private List<Cookie> getCookies() {
		return cookies;
	}

	/**
	 * request object for the this connection.
	 * @return the request
	 */
    @Override
    public HttpServletRequest getRequest() {
		return requestProxy;
	}

	/**
	 * response object for the this connection.
	 * @return the response
	 */
    @Override
    public HttpServletResponse getResponse() {
		return responseProxy;
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getResponseCharacterEncoding()
     */
	@Override
    public String getResponseCharacterEncoding() {
//		checkStarted();
		return response.getCharacterEncoding();
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getResponseContentType()
     */
	@Override
    public String getResponseContentType() {
//		checkStarted();
		return response.getContentType();
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getResponseStatus()
     */
	@Override
    public int getResponseStatus() {
//		checkStarted();
		return response.getStatus();
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getErrorMessage()
     */
	@Override
    public String getErrorMessage() {
//		checkStarted();
		return response.getErrorMessage();
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getResponseHeaders()
     */
	@Override
    public Map<String, String[]> getResponseHeaders() {
//		checkStarted();
		return response.getHeaders();
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#setRequestCharacterEncoding(java.lang.String)
     */
	@Override
    public void setRequestCharacterEncoding(String charset) throws UnsupportedEncodingException {
//		checkNotStarted();
		request.setCharacterEncoding(charset);		
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#setRequestBody(java.lang.String)
     */
	@Override
    public void setRequestBody(String body) {
//		checkNotStarted();
		request.setRequestBody(body);		
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#setRequestContentType(java.lang.String)
     */
	@Override
    public void setRequestContentType(String contentType) {
//		checkNotStarted();
		request.setContentType(contentType);
		
	}

	/**
	 * Append additional HTTP request headers.
	 * @param headers
	 */
    @Override
    public void addRequestHeaders(Map<String, String> headers) {
//		checkNotStarted();
		request.addHeaders(headers);		
	}

	private class ConnectionRequest extends StagingHttpRequest {
	
		public Cookie[] getCookies() {
			return cookies.toArray(COOKIE);
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.http.HttpServletRequest#getMethod()
		 */
		public String getMethod() {
			return getRequestMethod().toString();
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.http.HttpServletRequest#getServletPath()
		 */
		public String getServletPath() {
			return servletPath;
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.http.HttpServletRequest#getPathInfo()
		 */
		public String getPathInfo() {
			return pathInfo;
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.http.HttpServletRequest#getQueryString()
		 */
		public String getQueryString() {
		    return getRequestQueryString();
		}

        /*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.http.HttpServletRequest#getRequestURI()
		 */
		public String getRequestURI() {
			return url.getPath();
		}
		
		/* (non-Javadoc)
		 * @see org.jboss.test.faces.staging.StagingHttpRequest#getServerName()
		 */
		@Override
		public String getServerName() {
			return url.getHost();
		}
		
	
		@Override
		public int getLocalPort() {
			int port = url.getPort();
			if(port < 0){
				port = super.getLocalPort();
			}
			return port;
		}
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
		 */
		public String getParameter(String name) {
			String[] values = getRequestParameters().get(name);
			if (null != values && values.length > 0) {
				return values[0];
			}
			return null;
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.ServletRequest#getParameterMap()
		 */
		@SuppressWarnings("unchecked")
		public Map getParameterMap() {
			return Collections.unmodifiableMap(getRequestParameters());
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.ServletRequest#getParameterNames()
		 */
		@SuppressWarnings("unchecked")
		public Enumeration getParameterNames() {
			return Collections.enumeration(getRequestParameters().keySet());
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * javax.servlet.ServletRequest#getParameterValues(java.lang.String)
		 */
		public String[] getParameterValues(String name) {
			return getRequestParameters().get(name);
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.http.HttpServletRequest#getSession()
		 */
		public HttpSession getSession() {
			return server.getSession();
		}
	
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
		 */
		public HttpSession getSession(boolean create) {
			return server.getSession(create);
		}
	
		@Override
		public RequestDispatcher getRequestDispatcher(String path) {
			RequestDispatcher dispatcher = null;
			if (!path.startsWith("/")) {
				try {
					URL absoluteUrl = new URL(url, path);
					path = absoluteUrl.getFile();
				} catch (MalformedURLException e) {
					return null;
				}
			}
			final RequestChain dispatchedServlet = server.getServlet(path);
			if (null != dispatchedServlet) {
				dispatcher = new RequestDispatcher() {
	
					public void forward(ServletRequest request,
							ServletResponse response) throws ServletException,
							IOException {
						response.reset();
						dispatchedServlet.execute(request, response);
					}
	
					public void include(ServletRequest request,
							ServletResponse response) throws ServletException,
							IOException {
						dispatchedServlet.execute(request, response);
					}
	
				};
			}
			return dispatcher;
		}
	
		@Override
		protected void attributeAdded(String name, Object o) {
			server.requestAttributeAdded(this, name, o);
	
		}
	
		@Override
		protected void attributeRemoved(String name, Object removed) {
			server.requestAttributeRemoved(this, name, removed);
	
		}
	
		@Override
		protected void attributeReplaced(String name, Object o) {
			server.requestAttributeReplaced(this, name, o);
	
		}
	
	}

	private class ConnectionResponse extends StagingHttpResponse {
		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http
		 * .Cookie )
		 */
		@Override
        public void addCookie(Cookie cookie) {
		    super.addCookie(cookie);
			cookies.add(cookie);
	
		}
	
	}

	/* (non-Javadoc)
     * @see org.jboss.test.faces.staging.HttpConnection#getResponseContentLength()
     */
	@Override
    public long getResponseContentLength() {
		return response.getContentLength();		
	}


    @Override
    protected String getRequestCharacterEncoding() {
        return request.getCharacterEncoding();
    }

}
