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

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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

import nuts.core.lang.ExceptionUtils;
import nuts.core.lang.StringUtils;
import nuts.core.servlet.HttpServletSupport;
import nuts.exts.struts2.util.StrutsContextUtils;
import nuts.exts.xwork2.ActionValidationAware;
import nuts.exts.xwork2.ApplicationValidationAwareSupport;
import nuts.exts.xwork2.SessionValidationAwareSupport;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.dispatcher.StrutsResultSupport;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ValidationAware;


/**
 * <!-- START SNIPPET: description -->
 *
 * A abstract result for Object-? mapping.
 *
 * <!-- END SNIPPET: description -->
 *
 *
 * <!-- START SNIPPET: params -->
 *
 * <ul>
 *  <li>noCache (optional) = no cache to set. (default: true)</li>
 *  <li>contentType (optional) = content type to be used. </li>
 *  <li>encoding (optional) = character encoding to be used. This character encoding will be used to set. (default: UTF-8)</li>
 *  <li>properties (default) = extra properties to be output. { "user": ... }</li>
 *  <li>circleDetect (optional) = circleDetect to be used. [strict, lenient, noprop] (default: lenient)</li>
 * </ul>
 *
 * <!-- END SNIPPET: params -->
 */
@SuppressWarnings("serial")
public abstract class AbstractOMResult extends StrutsResultSupport {
	protected static final Log log = LogFactory.getLog(AbstractOMResult.class);

	protected static final String DEFAULT_CONTENT_TYPE = "text/html";

	protected static final String DEFAULT_ENCODING = "UTF-8";

	protected static final String SEPERATOR = ",";

	protected static final String DEFAULT_FILTER_TYPES = "[B, [Ljava.lang.Byte;, java.io.*, java.awt.*, org.apache.commons.vfs.*";

	protected static final String DATE_FORMAT_LONG = "long";

	protected static final String CYCLE_DETECT_STRICT = "strict";
	
	protected static final String CYCLE_DETECT_LENIENT = "lenient";
	
	protected static final String CYCLE_DETECT_NOPROP = "noprop";
	
	protected String filterTypes = DEFAULT_FILTER_TYPES;

	protected String dateFormat = DATE_FORMAT_LONG;

	protected Boolean noCache = true;

	protected String contentType = DEFAULT_CONTENT_TYPE;

	protected String encoding = DEFAULT_ENCODING;

	protected String cycleDetect = CYCLE_DETECT_NOPROP;
	
	protected Boolean ignoreTransient = true;
	
	protected String properties;

	/**
	 * Constructor.
	 */
	public AbstractOMResult() {
		super();
	}

	/**
	 * @return the cycleDetect
	 */
	public String getCycleDetect() {
		return cycleDetect;
	}

	/**
	 * @param cycleDetect the cycleDetect to set
	 */
	public void setCycleDetect(String cycleDetect) {
		this.cycleDetect = cycleDetect;
	}

	/**
	 * @return the ignoreTransient
	 */
	public Boolean getIgnoreTransient() {
		return ignoreTransient;
	}

	/**
	 * @param ignoreTransient the ignoreTransient to set
	 */
	public void setIgnoreTransient(Boolean ignoreTransient) {
		this.ignoreTransient = ignoreTransient;
	}

	/**
	 * @return the dateFormat
	 */
	public String getDateFormat() {
		return dateFormat;
	}

	/**
	 * @param dateFormat the dateFormat to set
	 */
	public void setDateFormat(String dateFormat) {
		this.dateFormat = StringUtils.lowerCase(dateFormat);
	}

	/**
	 * @return the filterTypes
	 */
	public String getFilterTypes() {
		return filterTypes;
	}

	/**
	 * @param filterTypes the filterTypes to set
	 */
	public void setFilterTypes(String filterTypes) {
		this.filterTypes = filterTypes;
	}

	/**
	 * @return the contentType
	 */
	public String getContentType() {
		return contentType;
	}

	/**
	 * @param contentType the contentType to set
	 */
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	/**
	 * @return the noCache
	 */
	public Boolean getNoCache() {
		return noCache;
	}

	/**
	 * @param noCache the noCache to set
	 */
	public void setNoCache(Boolean noCache) {
		this.noCache = noCache;
	}

	/**
	 * @return the encoding
	 */
	public String getEncoding() {
		return encoding;
	}

	/**
	 * @param encoding the encoding to set
	 */
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	/**
	 * @return the properties
	 */
	public String getProperties() {
		return properties;
	}

	/**
	 * @param properties the properties to set
	 */
	public void setProperties(String properties) {
		this.properties = properties;
	}

	/**
	 * write result
	 * @param writer response writer
	 * @param result result object
	 * @throws Exception
	 */
	protected void writeResult(PrintWriter writer, Object result) throws Exception {
		if (result != null) {
			writer.write(result.toString());
		}
	}
	
	/**
	 * write result
	 * @param invocation actionInvocation
	 * @param result result object
	 * @throws Exception
	 */
	protected void writeResult(ActionInvocation invocation, Object result) throws Exception {
		HttpServletRequest request = StrutsContextUtils.getServletRequest(invocation);
		HttpServletResponse response = StrutsContextUtils.getServletResponse(invocation);

		HttpServletSupport hsrs = new HttpServletSupport(request, response); 
		hsrs.setNoCache(noCache);
		hsrs.setCharset(encoding);
		hsrs.writeResponseHeader();

		PrintWriter writer = response.getWriter();
		if ("UTF-8".equalsIgnoreCase(encoding)) {
			writer.write("\uFEFF");
		}

		writeResult(writer, result);

		response.flushBuffer();
	}

	/**
	 * @see org.apache.struts2.dispatcher.StrutsResultSupport#doExecute(java.lang.String, com.opensymphony.xwork2.ActionInvocation)
	 */
	protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
		Object o = invocation.getStack().findValue("exception");
		if (o == null || !(o instanceof Throwable)) {
			o = StrutsContextUtils.getServletRequest().getAttribute("exception");
		}

		if (o != null && o instanceof Throwable) {
			Throwable e = (Throwable)o;

			log.error("exception", e);

			Map<String, Object> result = new LinkedHashMap<String, Object>();
			result.put("success", false);

			Map<String, Object> em = new HashMap<String, Object>();
			em.put("message", e.getMessage());
			em.put("stackTrace", ExceptionUtils.getStackTrace(e));

			result.put("exception", em);

			writeResult(invocation, result);
			return;
		}

		if (StringUtils.isBlank(properties)) {
			properties = finalLocation;
		}

		Map<String, Object> result = new LinkedHashMap<String, Object>();

		Boolean success = true;
		result.put("success", success);
		
		ApplicationValidationAwareSupport avas = new ApplicationValidationAwareSupport();
		if (avas.hasApplicationErrors()) {
			result.put("applicationErrors", avas.getApplicationErrors());
			success = false;
		}
		if (avas.hasApplicationWarnings()) {
			result.put("applicationWarnings", avas.getApplicationWarnings());
		}
		if (avas.hasApplicationConfirms()) {
			result.put("applicationConfirms", avas.getApplicationConfirms());
		}
		if (avas.hasApplicationMessages()) {
			result.put("applicationMessages", avas.getApplicationMessages());
		}
		
		SessionValidationAwareSupport svas = new SessionValidationAwareSupport();
		if (svas.hasSessionErrors()) {
			result.put("sessionErrors", svas.getSessionErrors());
			success = false;
		}
		if (svas.hasSessionWarnings()) {
			result.put("sessionWarnings", svas.getSessionWarnings());
		}
		if (svas.hasSessionConfirms()) {
			result.put("sessionConfirms", svas.getSessionConfirms());
		}
		if (svas.hasSessionMessages()) {
			result.put("sessionMessages", svas.getSessionMessages());
		}
		
		Object action = invocation.getAction();
		if (action instanceof ValidationAware) {
			ValidationAware va = (ValidationAware)action;

			if (va.hasErrors()) {
				success = false;
			}

			if (va.hasFieldErrors()) {
				result.put("fieldErrors", va.getFieldErrors());
			}

			if (va.hasActionErrors()) {
				result.put("actionErrors", va.getActionErrors());
			}

			if (action instanceof ActionValidationAware) {
				ActionValidationAware ava = (ActionValidationAware)action;
				if (ava.hasActionWarnings()) {
					result.put("actionWarnings", ava.getActionWarnings());
				}
				if (ava.hasActionConfirms()) {
					result.put("actionConfirms", ava.getActionConfirms());
				}
			}

			if (va.hasActionMessages()) {
				result.put("actionMessages", va.getActionMessages());
			}
		}

		if (properties != null) {
			List<String> pnl = toList(properties);
			for (String pn : pnl) {
				Object value = invocation.getStack().findValue(pn);
				if (value != null) {
					result.put(pn, value);
				}
			}
		}

		result.put("success", success);
		
		writeResult(invocation, result);
	}

	protected List<String> toList(String str) {
		List<String> list = new ArrayList<String>();
		if (!StringUtils.isBlank(str)) {
			String[] ss = StringUtils.split(str, SEPERATOR);
			for (String s : ss) {
				s = StringUtils.stripToNull(s);
				if (s != null) {
					list.add(s);
				}
			}
		}
		return list;
	}
}
