/*
 * This file is part of Nuts Framework.
 * Copyright (C) 2009 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 *
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.exts.sitemesh;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.opensymphony.module.sitemesh.Page;
import com.opensymphony.module.sitemesh.PageParserSelector;
import com.opensymphony.module.sitemesh.filter.Buffer;
import com.opensymphony.module.sitemesh.filter.HttpContentType;
import com.opensymphony.module.sitemesh.filter.RoutablePrintWriter;
import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;

/**
 * Implementation of HttpServletResponseWrapper that captures page data instead
 * of sending to the writer.
 * <p/>
 * <p>
 * Should be used in filter-chains or when forwarding/including pages using a
 * RequestDispatcher.
 * </p>
 */
public class PageResponseWrapper extends HttpServletResponseWrapper {
	private static final Log log = LogFactory.getLog(PageResponseWrapper.class);

	private final RoutablePrintWriter routablePrintWriter;
	private final RoutableServletOutputStream routableServletOutputStream;
	private final PageParserSelector parserSelector;

	private Buffer buffer;
	private boolean aborted = false;
	private boolean parseablePage = false;

	public PageResponseWrapper(final HttpServletResponse response,
			PageParserSelector parserSelector) {
		super(response);
		this.parserSelector = parserSelector;

		routablePrintWriter = new RoutablePrintWriter(
				new RoutablePrintWriter.DestinationFactory() {
					public PrintWriter activateDestination() throws IOException {
						return response.getWriter();
					}
				});
		routableServletOutputStream = new RoutableServletOutputStream(
				new RoutableServletOutputStream.DestinationFactory() {
					public ServletOutputStream create() throws IOException {
						return response.getOutputStream();
					}
				});

	}

	/**
	 * Set the content-type of the request and store it so it can be passed to
	 * the {@link com.opensymphony.module.sitemesh.PageParser}.
	 */
	public void setContentType(String type) {
		super.setContentType(type);

		if (type != null) {
			HttpContentType httpContentType = new HttpContentType(type);

			if (parserSelector.shouldParsePage(httpContentType.getType())) {
				activateSiteMesh(httpContentType.getType(),
						httpContentType.getEncoding());
			}
			else {
				deactivateSiteMesh();
			}
		}

	}

	public void activateSiteMesh(String contentType, String encoding) {
		if (parseablePage) {
			return; // already activated
		}
		buffer = new Buffer(parserSelector.getPageParser(contentType), encoding);
		routablePrintWriter.updateDestination(new RoutablePrintWriter.DestinationFactory() {
			public PrintWriter activateDestination() {
				return buffer.getWriter();
			}
		});
		routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
			public ServletOutputStream create() {
				return buffer.getOutputStream();
			}
		});
		parseablePage = true;
	}

	private void deactivateSiteMesh() {
		parseablePage = false;
		buffer = null;
		routablePrintWriter.updateDestination(new RoutablePrintWriter.DestinationFactory() {
			public PrintWriter activateDestination() throws IOException {
				return getResponse().getWriter();
			}
		});
		routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
			public ServletOutputStream create() throws IOException {
				return getResponse().getOutputStream();
			}
		});
	}

	/**
	 * Prevent content-length being set if page is parseable.
	 */
	public void setContentLength(int contentLength) {
		if (!parseablePage)
			super.setContentLength(contentLength);
	}

	/**
	 * Prevent buffer from being flushed if this is a page being parsed.
	 */
	public void flushBuffer() throws IOException {
		if (!parseablePage)
			super.flushBuffer();
	}

	/**
	 * Prevent content-length being set if page is parseable.
	 */
	public void setHeader(String name, String value) {
		if (name.toLowerCase().equals("content-type")) { // ensure ContentType
															// is always set
															// through
															// setContentType()
			setContentType(value);
		}
		else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
			super.setHeader(name, value);
		}
	}

	/**
	 * Prevent content-length being set if page is parseable.
	 */
	public void addHeader(String name, String value) {
		if (name.toLowerCase().equals("content-type")) { // ensure ContentType
															// is always set
															// through
															// setContentType()
			setContentType(value);
		}
		else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
			super.addHeader(name, value);
		}
	}

	/**
	 * If 'not modified' (304) HTTP status is being sent - then abort parsing,
	 * as there shouldn't be any body
	 */
	public void setStatus(int sc) {
		if (sc == HttpServletResponse.SC_NOT_MODIFIED) {
			aborted = true;
			// route any content back to the original writer. There shouldn't be
			// any content, but just to be safe
			deactivateSiteMesh();
		}
		super.setStatus(sc);
	}

	public ServletOutputStream getOutputStream() {
		return routableServletOutputStream;
	}

	public PrintWriter getWriter() {
		return routablePrintWriter;
	}

	public Page getPage() throws IOException {
		if (aborted || !parseablePage) {
			return null;
		}
		else {
			return buffer.parse();
		}
	}

	public void sendError(int sc) throws IOException {
		if (log.isDebugEnabled()) {
			log.debug("SendError - " + sc);
		}
		aborted = true;
		super.sendError(sc);
	}

	public void sendError(int sc, String msg) throws IOException {
		if (log.isDebugEnabled()) {
			log.debug("SendError - " + sc + ":" + msg);
		}
		aborted = true;
		super.sendError(sc, msg);
	}

	public void sendRedirect(String location) throws IOException {
        if (log.isDebugEnabled()) {
        	log.debug("SendRedirect - " + location);
        }
		aborted = true;
		super.sendRedirect(location);
	}


	public boolean isUsingStream() {
		return buffer != null && buffer.isUsingStream();
	}

	public char[] getContents() throws IOException {
		if (aborted || !parseablePage) {
			return null;
		}
		else {
			return buffer.getContents();
		}
	}
}
