package eu.dnetlib.springutils.stringtemplate;

import java.util.HashMap;
import java.util.Map;

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

import org.antlr.stringtemplate.AutoIndentWriter;
import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.support.BindStatus;
import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.view.AbstractUrlBasedView;

/**
 * View using the StringTemplate engine.
 * 
 * <p>
 * Exposes the following JavaBean properties:
 * </p>
 * <ul>
 * <li><b>stringTemplateName</b></li>: The name of the stringTemplate to be wrapped.
 * </ul>
 */
public class StringTemplateView extends AbstractUrlBasedView {
	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(StringTemplateView.class); // NOPMD

	/**
	 * template.
	 */
	private StringTemplate stringTemplate;

	public void setStringTemplate(final StringTemplate value) {
		stringTemplate = value;
	}

	public StringTemplate getStringTemplate() {
		return stringTemplate;
	}

	/**
	 * Overridable method to expose attributes to the model.
	 * 
	 * @param model
	 *            model
	 */
	// NOPMD
	protected void exposeToModel(@SuppressWarnings("rawtypes") final Map model) {
		if (isThemed()) {
			exposeTheme(model);
			exposeErrors(model);
		}
	}

	/**
	 * dummy.
	 * 
	 * @return false
	 */
	protected boolean isThemed() {
		return false;
	}

	/**
	 * get request context.
	 * 
	 * @param model
	 *            model
	 * @return request context
	 */
	private RequestContext getRequestContext(final Map<String, Object> model) {
		// XXX assumes name 
		return (RequestContext) model.get("rc");
	}

	/**
	 * expose theme.
	 * 
	 * @param model
	 *            model
	 */
	@SuppressWarnings("unchecked")
	private void exposeTheme(@SuppressWarnings("rawtypes") final Map model) {
		final RequestContext context = getRequestContext(model);

		// XXX assumes name
		model.put("cssFile", context.getThemeMessage("css"));
	}

	/**
	 * expose errors.
	 * 
	 * @param model
	 *            model
	 */
	@SuppressWarnings("unchecked")
	private void exposeErrors(@SuppressWarnings("rawtypes") final Map model) {
		final RequestContext context = getRequestContext(model);

		// XXX assumes name
		final Errors errors = context.getErrors("command");
		if (errors == null)
			return;

		final Map<String, String> errorMap = new HashMap<String, String>();
		
		for (ObjectError objError :  errors.getAllErrors()) {
			final FieldError error = (FieldError) objError;
			final String field = error.getField();
			final String objectName = error.getObjectName();
			final BindStatus bind = context.getBindStatus(objectName + "." + field);
			final String message = bind.getErrorMessage();
			errorMap.put(field, message);
		}
		// XXX check for collision
		model.put("errors", errorMap);
	}

	/**
	 * Process the model map by merging it with the StringTemplate template. Output is directed to the servlet response.
	 * <p>
	 * This method can be overridden if custom behavior is needed.
	 * 
	 * @param model
	 *            model
	 * @param request
	 *            request
	 * @param response
	 *            response
	 * @throws Exception
	 *             exception
	 */
	@Override
	protected void renderMergedOutputModel(@SuppressWarnings("rawtypes") final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { // NOPMD		
		if(response.getContentType() == null)
			response.setContentType(getContentType());

		exposeToModel(model);
		logModel(model);

		stringTemplate.setAttributes(model);
		stringTemplate.write(new AutoIndentWriter(response.getWriter()));
	}

	/**
	 * log the model.
	 * 
	 * @param model
	 *            model
	 */
	private void logModel(@SuppressWarnings("rawtypes") final Map model) {
		if (log.isDebugEnabled()) {
			log.debug("Environment contains:");
			for (Object key : model.keySet())
				log.debug("model[" + key + "] = " + model.get(key));
			log.debug("");
		}
	}
}
