package it.mice.voila.runtime.springmvc.util;

import it.mice.voila.runtime.springmvc.bean.PathStack;
import it.mice.voila.runtime.springmvc.bean.PathStackElement;
import it.mice.voila.runtime.springmvc.csrf.CSRFHandlerInterceptor;
import it.mice.voila.runtime.springmvc.flashmap.FlashMap;
import it.mice.voila.runtime.springmvc.form.JfFormBean;

import java.io.IOException;
import java.util.Enumeration;

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

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;

/**
 * Spring MVC Utility class.
 *
 */
public class SpringMvcUtil {
	/**
	 * default logger.
	 */
	private static final Log log = LogFactory.getLog(SpringMvcUtil.class);
	/**
	 * The path stack identifier.
	 */
	public static final String PATH_STACK = SpringMvcUtil.class.getName() + ".pathStackForm";
	/**
	 * The last view name identifier.
	 */
	public static final String LAST_VIEW_NAME = SpringMvcUtil.class.getName() + ".lastViewNameForm";

	/**
	 * Return the current http request.
	 * @return the current http request.
	 */
	public static HttpServletRequest getRequest() {
		RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
		return ((ServletRequestAttributes)requestAttributes).getRequest();
	}

	/**
	 * Return the current http session.
	 * @return the current http session.
	 */
	public static HttpSession getSession() {
		return getRequest().getSession();
	}

	/**
	 * Return the current servlet context.
	 * @return the current servlet context.
	 */
	public static ServletContext getServletContext() {
		return getSession().getServletContext();
	}

	/**
	 * Return the path stack.
	 * @return the path stack.
	 */
	public static PathStack getStack() {
		HttpSession session = getRequest().getSession();
		PathStack stack = (PathStack)session.getAttribute(PATH_STACK);
		if (stack == null) {
			stack = new PathStack(); 
			session.setAttribute(PATH_STACK, stack);
		}
		return stack;
	}

	/**
	 * Return the url of the last path stack.
	 * @return url of the last path stack.
	 */
	public static String popLastStackPath() {
		PathStack stack = SpringMvcUtil.getStack();
		if (stack.empty()) {
			log.warn("Stack is empty but a redirect:goBack was requested. Continue anyway...");
			return getRequest().getContextPath() + "/home";
		}
		return stack.pop().getPath();
	}

	/**
	 * Perform a response riderection to the last path.
	 * @param request the http request.
	 * @param response the http response
	 * @param requestURI the http request URI
	 * @param popCounter the number of steps to undo.
	 */
	public static void doLastPathRedirect(HttpServletRequest request,
			HttpServletResponse response, String requestURI, int popCounter) {
		doLastPathRedirect(request, response, requestURI, popCounter, null);
	}

	/**
	 * Perform a response riderection to the last path.
	 * @param request the http request.
	 * @param response the http response
	 * @param requestURI the http request URI
	 * @param popCounter the number of steps to undo.
	 * @param currentUri the currentUri.
	 */
	public static void doLastPathRedirect(HttpServletRequest request,
			HttpServletResponse response, String requestURI, int popCounter, String currentUri) {
		PathStack stack = SpringMvcUtil.getStack();
		String lastStackPath = null;
		try {
			lastStackPath = stack.lastElement().getPath();
		} catch (Exception e) {
			//No elements
		}
		if (stack.size() < popCounter) {
			log.warn("Stack contains " + stack.size() + " elements, so the go back cannot work properly. Redirecting to the home page...");
			lastStackPath = request.getContextPath() + "/";
			stack.clear();
		} else {
			for (int i=0; i < popCounter; i++) {
				lastStackPath = stack.pop().getPath();
			}
			if (currentUri != null && request.getMethod().equals("GET")) {
				try {
					while (StringUtils.contains(lastStackPath, currentUri)) {
						lastStackPath = stack.pop().getPath();
					}
				} catch (Exception e) {
					lastStackPath = request.getContextPath() + "/";
				}
			}
		}
		if (log.isDebugEnabled()) {
			log.debug("GoBack is about to redirect navigation to " + lastStackPath);
		}
		
		doRedirection(response, requestURI, ((lastStackPath!=null)? lastStackPath : request.getContextPath()+"/"), request);
	}

	/**
	 * Perform a response riderection to the path just before the new activated flow.
	 * @param request the http request.
	 * @param response the http response
	 * @param requestURI the http request URI
	 */
	public static void doPrevFlowRedirect(HttpServletRequest request,
			HttpServletResponse response, String requestURI) {
		String lastStackPath = null;
		PathStack stack = SpringMvcUtil.getStack();
		
		PathStackElement element = null;
		if (!stack.isEmpty()) {
			element = stack.pop();
			while (!element.isNewFlow() && !stack.isEmpty()) {
				element = stack.pop();
			}
		}
		
		if (element == null || !element.isNewFlow()) {
			log.warn("No start flow found into path stack but a special.endFlow was requested, so the go back cannot work properly. Redirecting to the home page...");
			lastStackPath = request.getContextPath() + "/home";
			stack.clear();
		} else {
			//element = stack.pop();
			lastStackPath = element.getPath();
			if (log.isDebugEnabled()) {
				log.debug("GoBack is about to redirect navigation to " + lastStackPath);
			}
		}
		
		doRedirection(response, requestURI, lastStackPath, request);
	}

	/**
	 * Return the request attribute or the request parameter of the given name.
	 * @param name request attribute or the request parameter name.
	 * @return the value of the request attribute or the request parameter of the given name.
	 */
	public static Object getRequestParameterOrAttribute(String name) {
		Object result = getRequest().getAttribute(name);
		if (result == null) {
			result = getRequest().getParameter(name);
		}
		return result;
	}
	
	/**
	 * Perform a response riderection to the given lastStackPath.
	 * @param response the http response
	 * @param requestURI the http request URI
	 * @param lastStackPath the last stack path to redirect to.
	 */
	private static void doRedirection(HttpServletResponse response,
			String requestURI, String lastStackPath, HttpServletRequest request) {
		int pos = requestURI.indexOf("?");
		if (pos > -1) {
			lastStackPath = appendQueryString(lastStackPath, requestURI.substring(pos+1));
		} else {
			if (!StringUtils.isEmpty(request.getQueryString())) {
				lastStackPath = appendQueryString(lastStackPath, request.getQueryString()); //FIXME - asteriscato per evitare redirection in caso di parameter con attacco XSS - verificare!!!!
			}
		}
		try {
			response.sendRedirect(response.encodeRedirectURL(lastStackPath));
			FlashMap.recycleIntoSession();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static String appendQueryString(String target, String chunk) {
		String result = target;
		if (result.indexOf("?") > -1) {
			result += "&amp;";
		} else {
			result += "?";
		}
		String[] parts = chunk.split("&");
		for (int i = 0; i < parts.length; i++) {
			String[] subParts = parts[i].split("=");
			if (CSRFHandlerInterceptor.EXCLUDED_PARAMS.contains(subParts[0])) {
				result += parts[i] + "&amp;";
			}
		}
		return result;
	}
	/**
	 * NOT UNIT TESTED Returns the URL (including query parameters) minus the
	 * scheme, host, and context path. This method probably be moved to a more
	 * general purpose class.
	 * @return the result.
	 */
	public static String getRelativeUrl() {
		HttpServletRequest request = getRequest();
		String baseUrl = null;

		baseUrl = getBaseUrl();
		StringBuffer buf = request.getRequestURL();

		if (request.getQueryString() != null) {
			buf.append("?");
			buf.append(request.getQueryString());
		}

		return buf.substring(baseUrl.length());
	}

	/**
	 * NOT UNIT TESTED Returns the base url (e.g,
	 * <tt>http://myhost:8080/myapp</tt>) suitable for using in a base tag or
	 * building reliable urls.
	 * @return the result.
	 */
	public static String getBaseUrl() {
		return getServerBaseUrl() + getRequest().getContextPath();
	}

	/**
	 * @return the result.
	 */
	public static String getServerBaseUrl() {
		HttpServletRequest request = getRequest();
		String baseUrl;
		if ((request.getServerPort() == 80) || (request.getServerPort() == 443)) {
			baseUrl = request.getScheme() + "://" + request.getServerName();
		} else {
			baseUrl = request.getScheme() + "://" + request.getServerName()
					+ ":" + request.getServerPort();
		}
		return baseUrl;
	}

	public static Object getApplicationAttribute(String name) {
		return getServletContext().getAttribute(name);
	}	  

	public static void cleanupSessionAttrsStartingWith(String prefix) {
		Enumeration<String> e = getSession().getAttributeNames();
		while (e.hasMoreElements()) {
			String sessionElementName = (String) e.nextElement();
			if (sessionElementName.startsWith(prefix)) {
				getSession().removeAttribute(sessionElementName);
			}
		}
	}

	public static Object getHandlerBean(Object handler) {
		if (handler instanceof HandlerMethod) {
			return ((HandlerMethod)handler).getBean();
		}
		return handler;
	}
		
	public static JfFormBean getForm(HandlerMethod handler) {
		Enumeration<String> e = getSession().getAttributeNames();
		while (e.hasMoreElements()) {
			String name = (String) e.nextElement();
			Object value = getSession().getAttribute(name);
			if (value instanceof JfFormBean) {
				for (MethodParameter method : handler.getMethodParameters()) {
					if (method.getParameterType().isAssignableFrom(value.getClass())) {
						return (JfFormBean)value;
					}
				}
			}
		}
		return getFormFromSession();
	}
	
	public static JfFormBean getFormFromSession() {
		String formName = (String)getSession().getAttribute("formId");
		if (formName != null) {
			JfFormBean form = (JfFormBean)getSession().getAttribute(formName);
			if (form != null) {
				return form;
			}
		}
		return null;
	}

}
