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

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

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

import junit.framework.TestCase;
import nuts.core.io.IOUtils;
import nuts.core.servlet.HttpServletUtils;

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.springframework.core.io.FileSystemResourceLoader;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockFilterConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.mock.web.MockServletContext;

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;

/**
 * StrutsTestCase
 */
public abstract class StrutsTestCase extends TestCase {
	protected static final Log log = LogFactory.getLog(StrutsTestCase.class);

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

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

	/**
	 * Constructor
	 * @param name the name to set
	 */
	public StrutsTestCase(String name) {
		super(name);
	}

	@Override
	protected void setUp() throws Exception {
		super.setUp();
	}

	@Override
	protected void tearDown() throws Exception {
		super.tearDown();
		cleanUp();
	}

	protected static void initDispatcher(String contextRoot) {
		if (filterDispatcher == null) {
			try {
				mockServletContext = new MockServletContext(contextRoot, new FileSystemResourceLoader());
				
				MockFilterConfig filterConfig = new MockFilterConfig(mockServletContext, "struts");

				filterDispatcher = new StrutsTestFilter();
				filterDispatcher.init(filterConfig);
			}
			catch (Exception e) {
				log.error("init", e);
				throw new RuntimeException(e);
			}
		}
	}
	
	protected Map<String, String> loadRequestParameters(String requestFile) throws Exception {
		InputStream is = getClass().getResourceAsStream(requestFile);
		Properties p = new Properties();
		p.load(is);
		IOUtils.closeQuietly(is);

		Map<String, String> rps = new HashMap<String, String>();
		for (Entry<Object, Object> a : p.entrySet()) {
			rps.put(a.getKey().toString(), a.getValue().toString());
		}
		return rps;
	}

	protected ActionProxy createActionProxy(String namespace, String actionName, String methodName) throws Exception {
		return createActionProxy(namespace, actionName, methodName, (Map<String, String>)null);
	}

	protected ActionProxy createActionProxy(String namespace, String actionName,
			String methodName, String paramFile) throws Exception {
		Map<String, String> params = null;
		if (paramFile != null) {
			params = loadRequestParameters(paramFile);
		}
		return createActionProxy(namespace, actionName, methodName, params);
	}

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

		initMockRequest(uri, requestParams);

        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
				);
	}

	protected void initMockRequest(String uri, Map<String, String> params) {
		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());
			}
		}

		mockResponse = new MockHttpServletResponse();
	}

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

	protected void doRequest(String uri, String paramFile) throws Exception {
		Map<String, String> requestParams = null;
		if (paramFile != null) {
			requestParams = loadRequestParameters(paramFile);
		}
		doRequest(uri, requestParams);
	}

	protected void doRequest(String uri, Map<String, String> params) throws Exception {
		initMockRequest(uri, params);
		doRequest(mockRequest, mockResponse);
	}

	protected 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. Response: \n")
				.append(getMockResponse().getContentAsString());
			log.debug(sb.toString());
		}
	}

    protected void cleanUp() {
    	if (mockRequest != null) {
    		filterDispatcher.cleanUp(mockRequest);
    	}
	}

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

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

	protected String getTestMethodName() {
		String mn = "";

		StackTraceElement stack[] = (new Throwable()).getStackTrace();
		for (int i = 0; i < stack.length; i++) {
			StackTraceElement ste = stack[i];
			if (this.getClass().getName().equals(ste.getClassName())) {
				mn = ste.getMethodName();
				for (i++; i < stack.length; i++) {
					ste = stack[i];
					if (this.getClass().getName().equals(ste.getClassName())) {
						mn = ste.getMethodName();
					}
					else {
						break;
					}
				}
			}
		}
		return mn;
	}

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

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

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

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

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

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

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

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

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

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

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

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