package org.seasar.jsf.jsp;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyContent;

import org.seasar.framework.log.Logger;
import org.seasar.framework.util.EnumerationAdapter;
import org.seasar.jsf.JsfConstants;
import org.seasar.jsf.exception.NoHttpSessionRuntimeException;
import org.seasar.jsf.exception.UndefinedScopeRuntimeException;

public class PageContextImpl extends PageContext {

	private static Logger logger = Logger.getLogger(PageContextImpl.class);

	private Servlet servlet;

	private ServletConfig config;

	private ServletContext context;

	private boolean needsSession;

	private String errorPageURL;

	private boolean autoFlush;

	private int bufferSize;

	private Map attributes = new HashMap();

	private ServletRequest request;

	private ServletResponse response;

	private HttpSession session;

	private boolean included;

	private JspWriter out;

	private Stack bodyContentStack = new Stack();

	public PageContextImpl() {
	}

	public void initialize(Servlet servlet, ServletRequest request,
			ServletResponse response, String errorPageURL)
			throws IllegalStateException, IllegalArgumentException, IOException {

		initialize(servlet, request, response, errorPageURL, true,
				JsfConstants.DEFAULT_BUFFER_SIZE, true);
	}

	public void initialize(Servlet servlet, ServletRequest request,
			ServletResponse response, String errorPageURL,
			boolean needsSession, int bufferSize, boolean autoFlush)
			throws IOException, IllegalStateException, IllegalArgumentException {

		this.servlet = servlet;
		this.config = servlet.getServletConfig();
		this.context = config.getServletContext();
		this.needsSession = needsSession;
		this.errorPageURL = errorPageURL;
		this.bufferSize = bufferSize;
		this.autoFlush = autoFlush;
		this.request = request;
		this.response = response;

		if (request instanceof HttpServletRequest && needsSession) {
			this.session = ((HttpServletRequest) request).getSession();
		}
		out = new JspWriterImpl(response, bufferSize, autoFlush);
		setAttribute(OUT, out);
		setAttribute(REQUEST, request);
		setAttribute(RESPONSE, response);
		if (session != null) {
			setAttribute(SESSION, session);
		}
		setAttribute(PAGE, servlet);
		setAttribute(CONFIG, config);
		setAttribute(PAGECONTEXT, this);
		setAttribute(APPLICATION, context);
		included = request.getAttribute(JsfConstants.INCLUDE_SERVLET_PATH) != null;
	}

	public void release() {
		try {
			if (included) {
				((JspWriterImpl) out).flushBuffer();
			} else {
				out.flush();
			}
		} catch (IOException ex) {
			logger.log(ex);
		}
		servlet = null;
		config = null;
		context = null;
		needsSession = false;
		errorPageURL = null;
		bufferSize = JspWriter.DEFAULT_BUFFER;
		autoFlush = true;
		request = null;
		response = null;
		session = null;
		out = null;
		attributes.clear();
	}

	public Object getAttribute(String name) {
		return attributes.get(name);
	}

	public Object getAttribute(String name, int scope) {
		switch (scope) {
		case PAGE_SCOPE:
			return attributes.get(name);
		case REQUEST_SCOPE:
			return request.getAttribute(name);
		case SESSION_SCOPE:
			if (session == null) {
				throw new NoHttpSessionRuntimeException();
			} else {
				return session.getAttribute(name);
			}
		case APPLICATION_SCOPE:
			return context.getAttribute(name);
		default:
			throw new UndefinedScopeRuntimeException(scope);
		}
	}

	public void setAttribute(String name, Object attribute) {
		attributes.put(name, attribute);
	}

	public void setAttribute(String name, Object o, int scope) {
		switch (scope) {
		case PAGE_SCOPE:
			attributes.put(name, o);
			break;
		case REQUEST_SCOPE:
			request.setAttribute(name, o);
			break;
		case SESSION_SCOPE:
			if (session == null) {
				throw new NoHttpSessionRuntimeException();
			} else {
				session.setAttribute(name, o);
			}
			break;
		case APPLICATION_SCOPE:
			context.setAttribute(name, o);
			break;
		default:
			throw new UndefinedScopeRuntimeException(scope);
		}
	}

	public void removeAttribute(String name, int scope) {
		switch (scope) {
		case PAGE_SCOPE:
			attributes.remove(name);
			break;
		case REQUEST_SCOPE:
			request.removeAttribute(name);
			break;
		case SESSION_SCOPE:
			if (session == null) {
				throw new NoHttpSessionRuntimeException();
			} else {
				session.removeAttribute(name);
			}
			break;
		case APPLICATION_SCOPE:
			context.removeAttribute(name);
			break;
		default:
			throw new UndefinedScopeRuntimeException(scope);
		}
	}

	public int getAttributesScope(String name) {
		if (getAttribute(name) != null) {
			return PAGE_SCOPE;
		} else if (request.getAttribute(name) != null) {
			return REQUEST_SCOPE;
		} else if (session != null && session.getAttribute(name) != null) {
			return SESSION_SCOPE;
		} else if (context.getAttribute(name) != null) {
			return APPLICATION_SCOPE;
		}
		return 0;
	}

	public Object findAttribute(String name) {
		Object o = getAttribute(name);
		if (o != null) {
			return o;
		}
		o = request.getAttribute(name);
		if (o != null) {
			return o;
		}
		if (session != null) {
			o = session.getAttribute(name);
			if (o != null) {
				return o;
			}
		}
		return context.getAttribute(name);
	}

	public Enumeration getAttributeNamesInScope(int scope) {
		switch (scope) {
		case PAGE_SCOPE:
			return new EnumerationAdapter(attributes.keySet().iterator());
		case REQUEST_SCOPE:
			return request.getAttributeNames();
		case SESSION_SCOPE:
			if (session != null) {
				return session.getAttributeNames();
			} else {
				throw new NoHttpSessionRuntimeException();
			}
		case APPLICATION_SCOPE:
			return context.getAttributeNames();
		default:
			throw new UndefinedScopeRuntimeException(scope);
		}
	}

	public void removeAttribute(String name) {
		try {
			removeAttribute(name, PAGE_SCOPE);
			removeAttribute(name, REQUEST_SCOPE);
			if (session != null) {
				removeAttribute(name, SESSION_SCOPE);
			}
			removeAttribute(name, APPLICATION_SCOPE);
		} catch (Exception ignore) {
		}
	}

	public JspWriter getOut() {
		return out;
	}

	public HttpSession getSession() {
		return session;
	}

	public Servlet getServlet() {
		return servlet;
	}

	public ServletConfig getServletConfig() {
		return config;
	}

	public ServletContext getServletContext() {
		return context;
	}

	public ServletRequest getRequest() {
		return request;
	}

	public ServletResponse getResponse() {
		return response;
	}

	public Exception getException() {
		return (Exception) request.getAttribute(EXCEPTION);
	}

	public Object getPage() {
		return servlet;
	}

	public void include(String relativePath) throws ServletException,
			IOException {

		include(relativePath, true);
	}

	public void include(String relativePath, boolean flush)
			throws ServletException, IOException {

		String resourcePath = getContextRelativePath(relativePath);
		RequestDispatcher rd = request.getRequestDispatcher(resourcePath);
		rd.include(request, new ServletResponseWrapperInclude(response, out));
	}

	protected String getContextRelativePath(String relativePath) {
		if (relativePath.startsWith("/")) {
			return relativePath;
		}
		if (!(request instanceof HttpServletRequest)) {
			return (relativePath);
		}
		HttpServletRequest req = (HttpServletRequest) request;
		String uri = (String) request
				.getAttribute(JsfConstants.INCLUDE_SERVLET_PATH);
		if (uri == null) {
			uri = req.getServletPath();
		}
		return uri.substring(0, uri.lastIndexOf('/')) + '/' + relativePath;

	}

	public void forward(String relativePath) throws ServletException,
			IOException {

		try {
			out.clear();
		} catch (IOException ex) {
			IllegalStateException ise = new IllegalStateException(ex.toString());
			ise.initCause(ex);
			throw ise;
		}
		while (response instanceof ServletResponseWrapperInclude) {
			response = ((ServletResponseWrapperInclude) response).getResponse();
		}

		String path = getContextRelativePath(relativePath);
		String includeUri = (String) request
				.getAttribute(JsfConstants.INCLUDE_SERVLET_PATH);
		if (includeUri != null) {
			request.removeAttribute(JsfConstants.INCLUDE_SERVLET_PATH);
		}
		try {
			context.getRequestDispatcher(path).forward(request, response);
		} finally {
			if (includeUri != null) {
				request.setAttribute(JsfConstants.INCLUDE_SERVLET_PATH,
						includeUri);
			}
			request.setAttribute(JsfConstants.FORWARD_SEEN, "true");
		}
	}

	public BodyContent pushBody() {
		BodyContent bodyContent = new BodyContentImpl(out);
		bodyContentStack.push(bodyContent);
		out = bodyContent;
		return bodyContent;
	}

	public JspWriter popBody() {
		out = ((BodyContent) bodyContentStack.pop()).getEnclosingWriter();
		return out;
	}

	public void handlePageException(Exception ex) throws IOException,
			ServletException {

		handlePageException((Throwable) ex);
	}

	public void handlePageException(Throwable t) throws IOException,
			ServletException {

		request.setAttribute(JsfConstants.JSP_EXCEPTION, t);
		if (errorPageURL != null && !errorPageURL.equals("")) {
			try {
				forward(errorPageURL);
			} catch (IllegalStateException ise) {
				include(errorPageURL);
			}
		} else {
			if (t instanceof IOException) {
				throw (IOException) t;
			}
			if (t instanceof ServletException) {
				throw (ServletException) t;
			}
			if (t instanceof RuntimeException) {
				throw (RuntimeException) t;
			}
			if (t instanceof JspException) {
				Throwable rootCause = ((JspException) t).getRootCause();
				if (rootCause != null) {
					throw new ServletException(t.getMessage(), rootCause);
				} else {
					throw new ServletException(t);
				}
			}
			throw new ServletException(t);
		}
	}
	
	public boolean isNeedsSession() {
		return needsSession;
	}
	
	public boolean isAutoFlush() {
		return autoFlush;
	}
	
	public int getBufferSize() {
		return bufferSize;
	}
}