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

import nuts.core.io.Streams;
import nuts.core.io.resource.FileSystemResourceLoader;
import nuts.core.lang.Strings;
import nuts.core.log.Log;
import nuts.core.log.Logs;
import nuts.core.mock.web.ConsoleServletOutputStream;
import nuts.core.mock.web.MockFilterChain;
import nuts.core.mock.web.MockFilterConfig;
import nuts.core.mock.web.MockHttpServletRequest;
import nuts.core.mock.web.MockHttpServletResponse;
import nuts.core.mock.web.MockHttpSession;
import nuts.core.mock.web.MockServletContext;
import nuts.core.net.http.HttpResponse;
import nuts.core.servlet.HttpServletUtils;

import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Set;

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

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;

/**
 */
public class StrutsMockSupport {
	protected static final Log log = Logs.getLog(StrutsMockSupport.class);

	private static MockServletContext mockServletContext;
	private static StrutsMockFilter filterDispatcher;
	
	private MockHttpSession mockSession;
	private MockHttpServletRequest mockRequest;
	private MockHttpServletResponse mockResponse;

	/**
	 * Constructor
	 */
	public StrutsMockSupport() {
		super();
	}

	public synchronized static void initContext(String contextRoot) {
		if (mockServletContext == null) {
			mockServletContext = new MockServletContext(contextRoot, new FileSystemResourceLoader());
		}
	}

	public synchronized static void initDispatcher(String contextRoot) {
		if (filterDispatcher == null) {
			try {
				initContext(contextRoot);
				
				MockFilterConfig filterConfig = new MockFilterConfig(mockServletContext, "nuts");

				filterDispatcher = new StrutsMockFilter();
				filterDispatcher.init(filterConfig);
			}
			catch (Exception e) {
				log.error("init", e);
				throw new RuntimeException(e);
			}
		}
	}
	
	public ActionProxy createActionProxy(String namespace, String actionName, String methodName) throws Exception {
		return createActionProxy(namespace, actionName, methodName, (Map<String, String>)null);
	}

	public ActionProxy createActionProxy(String namespace, String actionName,
			String methodName, Map<String, String> requestParams) throws Exception {
		String uri = ("/".equals(namespace) ? "" : namespace) + "/" + actionName;

		initMockRequest(uri, requestParams);
		initMockResponse();

		ValueStack stack = getDispatcher().getContainer().getInstance(ValueStackFactory.class).createValueStack();
		ActionContext ctx = new ActionContext(stack.getContext());
		ActionContext.setContext(ctx);

		ServletActionContext.setRequest(mockRequest);
		ServletActionContext.setResponse(mockResponse);
		ServletActionContext.setServletContext(mockServletContext);

		Map<String, Object> map = getDispatcher().createContextMap(mockRequest, mockResponse, null, mockServletContext);

		return getDispatcher().getContainer().getInstance(ActionProxyFactory.class)
				.createActionProxy(
						namespace,
						actionName,
						methodName,
						map,
						true, // execute result
						false // cleanup context
				);
	}

	public void initMockRequest(String uri, Map<String, String> params) {
		if (Strings.isNotEmpty(getActionExtension())) {
			uri += "." + getActionExtension();
		}

		mockRequest = new MockHttpServletRequest();
		mockRequest.setRequestURI(uri);
		mockRequest.setServletPath(uri);
		if (params != null) {
			for (Map.Entry<String, String> param : params.entrySet()) {
				mockRequest.addParameter(param.getKey(), param.getValue());
			}
		}
		if (mockSession != null) {
			mockRequest.setSession(mockSession);
		}
	}

	public void initMockResponse() {
		mockResponse = new MockHttpServletResponse();
	}
	
	public void initConsoleResponse() {
		mockResponse = new MockHttpServletResponse();
		mockResponse.setOutputStream(new ConsoleServletOutputStream());
	}
	
	public void doRequest() throws Exception {
		doRequest(mockRequest, mockResponse);
	}

	public void doRequest(String uri) throws Exception {
		doRequest(uri, null);
	}

	public void doRequest(String uri, Map<String, String> params) throws Exception {
		initMockRequest(uri, params);
		initMockResponse();
		doRequest();
	}

	public void doRequest(ServletRequest req, ServletResponse res) throws Exception {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (log.isDebugEnabled()) {
			StringBuilder sb = new StringBuilder();
			sb.append(request.getRequestURI()).append(" - start ... ");

			if (!request.getParameterMap().isEmpty()) {
				sb.append(HttpServletUtils.dumpRequestParameters(request));
			}
			
			log.debug(sb.toString());
		}
		
		MockFilterChain chain = new MockFilterChain();
		try {
			filterDispatcher.doFilter(request, response, chain);
		}
		catch (Exception ex) {
			log.error("doRequest", ex);
			throw ex;
		}
		
		Throwable ex = (Throwable)request.getAttribute("javax.servlet.error.exception");
		if (ex != null) {
			log.error("doRequest", ex);
		}

		if (log.isDebugEnabled()) {
			StringBuilder sb = new StringBuilder();
			sb.append(request.getRequestURI()).append(" - end.");
			sb.append(Streams.LINE_SEPARATOR);
			sb.append("  Response Status: ");
			sb.append(getMockResponse().getStatus());
			sb.append(" - ");
			sb.append(HttpResponse.getStatusReason(getMockResponse().getStatus()));
			String rh = dumpMockResponseHeader("\t");
			if (Strings.isNotEmpty(rh)) {
				sb.append(Streams.LINE_SEPARATOR);
				sb.append("  Response Head: ");
				sb.append(Streams.LINE_SEPARATOR);
				sb.append(rh);
			}
			String rb = getMockResponse().getContentAsString();
			if (Strings.isNotEmpty(rb)) {
				sb.append(Streams.LINE_SEPARATOR);
				sb.append("  Response Body: ");
				sb.append(rb);
			}
			log.debug(sb.toString());
		}
	}

	public String dumpMockResponseHeader(String prefix) {
		StringBuilder sb = new StringBuilder();
		Set<String> hns = getMockResponse().getHeaderNames();
		for (String hn : hns) {
			if (sb.length() > 0) {
				sb.append(Streams.LINE_SEPARATOR);
			}
			sb.append(prefix);
			sb.append(hn);
			sb.append(": ");
			sb.append(Strings.join(getMockResponse().getHeaders(hn)));
		}
		return sb.toString();
	}
	
	public void cleanUp() {
		if (mockRequest != null) {
			filterDispatcher.cleanUp(mockRequest);
		}
	}

	public ActionContext getActionContext() {
		return ActionContext.getContext();
	}

	public Object getAction() {
    	return getActionContext().getActionInvocation().getProxy().getAction();
	}

	public String getResponseContentAsString() throws UnsupportedEncodingException {
		return getMockResponse().getContentAsString();
	}

	public byte[] getResponseContentAsByteArray() {
		return getMockResponse().getContentAsByteArray();
	}

	// setter & getter
	/**
	 * @return the mockServletContext
	 */
	public static MockServletContext getMockServletContext() {
		return mockServletContext;
	}

	/**
	 * @param mockServletContext the mockServletContext to set
	 */
	public static void setMockServletContext(MockServletContext mockServletContext) {
		StrutsMockSupport.mockServletContext = mockServletContext;
	}

	/**
	 * @return the dispatcher
	 */
	public static Dispatcher getDispatcher() {
		Dispatcher dispatcher = Dispatcher.getInstance();
		if (dispatcher == null) {
			dispatcher = filterDispatcher.getDispatcher();
			Dispatcher.setInstance(dispatcher);
		}
		return dispatcher;
	}

	/**
	 * @return the mockSession
	 */
	public MockHttpSession getMockSession() {
		return mockSession;
	}

	/**
	 * @param mockSession the mockSession to set
	 */
	public void setMockSession(MockHttpSession mockSession) {
		this.mockSession = mockSession;
	}

	/**
	 * @return the mockRequest
	 */
	public MockHttpServletRequest getMockRequest() {
		return mockRequest;
	}

	/**
	 * @param mockRequest the mockRequest to set
	 */
	public void setMockRequest(MockHttpServletRequest mockRequest) {
		this.mockRequest = mockRequest;
	}

	/**
	 * @return the mockResponse
	 */
	public MockHttpServletResponse getMockResponse() {
		return mockResponse;
	}

	/**
	 * @param mockResponse the mockResponse to set
	 */
	public void setMockResponse(MockHttpServletResponse mockResponse) {
		this.mockResponse = mockResponse;
	}

	/**
	 * @return the actionExtension
	 */
	public String getActionExtension() {
		return filterDispatcher.getActionExtension();
	}
}