/*
 * 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.struts2.sitemesh;

import java.io.IOException;
import java.util.Locale;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import nuts.core.servlet.HttpServletUtils;
import nuts.exts.struts2.NutsStrutsConstants;
import nuts.exts.struts2.util.StrutsContextUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.views.freemarker.FreemarkerManager;

import com.opensymphony.module.sitemesh.Decorator;
import com.opensymphony.module.sitemesh.HTMLPage;
import com.opensymphony.module.sitemesh.Page;
import com.opensymphony.module.sitemesh.RequestConstants;
import com.opensymphony.sitemesh.Content;
import com.opensymphony.sitemesh.SiteMeshContext;
import com.opensymphony.sitemesh.compatability.Content2HTMLPage;
import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.profiling.UtilTimerStack;

import freemarker.template.Configuration;
import freemarker.template.SimpleHash;
import freemarker.template.Template;

/**
 */
public class FreemarkerDecorator implements
		com.opensymphony.sitemesh.Decorator, RequestConstants {
	private static final Log log = LogFactory.getLog(FreemarkerDecorator.class);

	private final ServletContext servletContext;
	private final com.opensymphony.module.sitemesh.Decorator oldDecorator;
	private final FreemarkerManager freemarkerManager;
	
	private String mockAction;

	/**
	 * @param mockAction the mockAction to set
	 */
	@Inject(value=NutsStrutsConstants.NUTS_MOCKACTION, required=false)
	public void setMockAction(String mockAction) {
		this.mockAction = mockAction;
	}

	/**
	 * @param freemarkerManager freemarker manager
	 * @param servletContext servletContext
	 * @param oldDecorator oldDecorator
	 */
	public FreemarkerDecorator(FreemarkerManager freemarkerManager, 
			ServletContext servletContext,
			Decorator oldDecorator) {
		this.freemarkerManager = freemarkerManager;
		this.servletContext = servletContext;
		this.oldDecorator = oldDecorator;
	}

	/**
	 * @see com.opensymphony.sitemesh.Decorator#render(com.opensymphony.sitemesh.Content,
	 *      com.opensymphony.sitemesh.SiteMeshContext)
	 */
	public void render(Content content, SiteMeshContext context) {
		SiteMeshWebAppContext webAppContext = (SiteMeshWebAppContext)context;
		try {
			Page page = new Content2HTMLPage(content,
					webAppContext.getRequest());
			applyDecorator(page, oldDecorator, webAppContext.getRequest(),
					webAppContext.getResponse());
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Applies the decorator, creating the relevent contexts and delegating to
	 * the extended applyDecorator().
	 * 
	 * @param page The page
	 * @param decorator The decorator
	 * @param req The servlet request
	 * @param res The servlet response
	 */
	protected void applyDecorator(Page page, Decorator decorator,
			HttpServletRequest req, HttpServletResponse res)
			throws Exception {

		ActionContext ctx = ServletActionContext.getActionContext(req);
		if (ctx == null) {
			// ok, one isn't associated with the request, so let's create one
			// using the current Dispatcher
			ValueStack vs = Dispatcher.getInstance().getContainer().getInstance(
					ValueStackFactory.class).createValueStack();
			vs.getContext().putAll(
					Dispatcher.getInstance().createContextMap(req, res, null,
							servletContext));
			ctx = new ActionContext(vs.getContext());
			ActionContext.setContext(ctx);
			if (ctx.getActionInvocation() == null) {
				Container container = StrutsContextUtils.getContainer();
				container.inject(this);

				StrutsContextUtils.createMockAction(HttpServletUtils.getRelativeURI(req), 
					container, ctx, mockAction);
			}
		}

		// delegate to the actual page decorator
		applyDecorator(page, decorator, req, res, servletContext, ctx);
	}

	/**
	 * Applies the decorator, using the relevent contexts
	 * 
	 * @param page The page
	 * @param decorator The decorator
	 * @param req The servlet request
	 * @param res The servlet response
	 * @param servletContext The servlet context
	 * @param ctx The action context for this request, populated with the server
	 *            state
	 */
	protected void applyDecorator(Page page, Decorator decorator,
			HttpServletRequest req, HttpServletResponse res,
			ServletContext servletContext, ActionContext ctx)
			throws ServletException, IOException {

		String timerKey = "FreemarkerPageFilter_applyDecorator: ";
		if (freemarkerManager == null) {
			throw new ServletException("Missing freemarker dependency");
		}

		try {
			UtilTimerStack.push(timerKey);

			// get the configuration and template
			Configuration config = freemarkerManager.getConfiguration(servletContext);
			Template template = config.getTemplate(decorator.getPage(), getLocale(ctx, config));

			// get the main hash
			SimpleHash model = freemarkerManager.buildTemplateModel(
					ctx.getValueStack(), null, servletContext, req, res,
					config.getObjectWrapper());

			// populate the hash with the page
			model.put("base", req.getContextPath());
			model.put("page", page);
			if (page instanceof HTMLPage) {
				HTMLPage htmlPage = ((HTMLPage)page);
				model.put("head", htmlPage.getHead());
			}
			model.put("title", page.getTitle());
			model.put("body", page.getBody());
			model.put("page.properties", new SimpleHash(page.getProperties()));

			// finally, render it
			template.process(model, res.getWriter());
		}
		catch (Exception e) {
			String msg = "Error applying decorator: " + e.getMessage();
			log.error(msg, e);
			throw new ServletException(msg, e);
		}
		finally {
			UtilTimerStack.pop(timerKey);
		}
	}

	/**
	 * Returns the locale used for the
	 * {@link Configuration#getTemplate(String, Locale)} call. The base
	 * implementation simply returns the locale setting of the action (assuming
	 * the action implements {@link LocaleProvider}) or, if the action does not
	 * the configuration's locale is returned. Override this method to provide
	 * different behaviour,
	 */
	private Locale getLocale(ActionContext ctx, Configuration configuration) {
		Locale locale = ctx.getLocale();

		if (locale == null) {
			locale = configuration.getLocale();
		}

		if (locale == null) {
			locale = Locale.getDefault();
		}

		return locale;
	}
}
