/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 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.servlet;

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

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import nuts.core.servlet.HttpServletUtils;
import nuts.exts.struts2.NutsStrutsConstants;
import nuts.exts.struts2.operations.NutsPrepareOperations;
import nuts.exts.struts2.util.StrutsContextUtils;
import nuts.exts.struts2.views.freemarker.FreemarkerDecorateResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.ng.InitOperations;
import org.apache.struts2.dispatcher.ng.servlet.ServletHostConfig;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;

import freemarker.template.Configuration;

/**
 * FreemarkerServlet
 */
@SuppressWarnings("serial")
public class FreemarkerServlet extends HttpServlet {

	private static Log log = LogFactory.getLog(FreemarkerServlet.class);

	private NutsPrepareOperations prepare;

	private String mockAction;

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

	/**
	 * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
	 */
	@Override
	public void init(ServletConfig servletConfig) throws ServletException {
		InitOperations init = new InitOperations();
		try {
			ServletHostConfig config = new ServletHostConfig(servletConfig);
			init.initLogging(config);
			Dispatcher dispatcher = init.initDispatcher(config);
			init.initStaticContentLoader(config, dispatcher);

			prepare = new NutsPrepareOperations(servletConfig.getServletContext(), dispatcher);
		}
		finally {
			init.cleanup();
		}
	}

	/**
	 *
	 */
	@Override
	public void service(HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {

		if (log.isDebugEnabled()) {
			log.debug(request.getRequestURI());
		}
		
		String location = requestUrlToTemplatePath(request);

		try {
			ActionContext.setContext(null);
			prepare.createActionContext(request, response);
			prepare.assignDispatcherToThread();
			prepare.setEncodingAndLocale(request, response);
			request = prepare.wrapRequest(request);

			ActionContext ctx = ActionContext.getContext();

			FreemarkerDecorateResult result = new FreemarkerDecorateResult(location);

			Container container = StrutsContextUtils.getContainer();
			container.inject(this);
			container.inject(result);

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

			result.doExecute(location, ctx.getActionInvocation());
		}
		catch (FileNotFoundException e) {
			log.warn(e.getMessage());
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
		}
		catch (Throwable e) {
			String msg = "Failed to process template: " + location;
			HttpServletUtils.logException(e, request, msg);
			throw new ServletException(msg, e);
		}
		finally {
			prepare.cleanupRequest(request);
		}

		if (log.isDebugEnabled()) {
			log.debug(request.getRequestURI() + " - END");
		}
	}

	/**
	 * Maps the request URL to a template path that is passed to
	 * {@link Configuration#getTemplate(String, Locale)}. You can override it
	 * (i.e. to provide advanced rewriting capabilities), but you are strongly
	 * encouraged to call the overridden method first, then only modify its
	 * return value.
	 * @param request the currently processed request
	 * @return a String representing the template path
	 */
	protected String requestUrlToTemplatePath(HttpServletRequest request) {
		// First, see if it is an included request
		String includeServletPath = (String)request.getAttribute("javax.servlet.include.servlet_path");
		if (includeServletPath != null) {
			// Try path info; only if that's null (servlet is mapped to an
			// URL extension instead of to prefix) use servlet path.
			String includePathInfo = (String)request.getAttribute("javax.servlet.include.path_info");
			return includePathInfo == null ? includeServletPath
					: includePathInfo;
		}
		
		// Seems that the servlet was not called as the result of a
		// RequestDispatcher.include(...). Try pathInfo then servletPath again,
		// only now directly on the request object:
		String path = request.getPathInfo();
		if (path != null) {
			return path;
		}
		
		path = request.getServletPath();
		if (path != null) {
			return path;
		}
		
		// Seems that it is a servlet mapped with prefix, and there was no extra
		// path info.
		return "";
	}

	/**
	 * @see javax.servlet.GenericServlet#destroy()
	 */
	@Override
	public void destroy() {
		prepare = null;
	}

}
