package org.esupportail.smsuapi.services.servlet;


import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

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

import org.esupportail.commons.services.logging.Logger;
import org.esupportail.commons.services.logging.LoggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.codehaus.jackson.map.ObjectMapper;

/**
 * Servlet used to access webservices through simple REST
 *
 */
public class RestServlet implements org.springframework.web.HttpRequestHandler {

	private final Logger logger = new LoggerImpl(getClass());
	
	@Autowired private RestServletActions restServletActions;

	public void setRestServletActions(RestServletActions restServletActions) {
		this.restServletActions = restServletActions;
	}
	
	public void handleRequest(HttpServletRequest req, HttpServletResponse resp) {
		try {
			Method m = getAction(RestServletActions.getString(req, "action"));
			executeMethodRaw(m, req, resp);
		} catch (Throwable e) {
			answerError(resp, e);
		}

	}

	private void executeMethodRaw(Method m, HttpServletRequest req, HttpServletResponse resp) throws Throwable {
		if (!restServletActions.isAuthValid()) {
			answerBasicAuthNeeded(resp);
			return;
		}
		logger.info("invoking " + m.getName());
		Object val;
		try { 
			val = m.invoke(restServletActions, req);
		} catch (java.lang.reflect.InvocationTargetException e) {
			throw e.getCause();
		}
		writeJson(resp, val);
	}

	private void answerBasicAuthNeeded(HttpServletResponse resp) {
		String err = "HTTP authentication needed";
		logger.error(err);
		String s = "Basic realm=\"Smsuapi\"";
		resp.setHeader("WWW-Authenticate", s);
		resp.setStatus(401);
		writeJson(resp, err);
	}
	private void answerError(HttpServletResponse resp, Throwable e) {
		logger.error(e);
		resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		writeJson(resp, "" + e);
	}

	private void writeJson(HttpServletResponse resp, Object val) {
		try {
			String jsonVal= new ObjectMapper().writeValueAsString(val);
			logger.debug("jsonVal: " + jsonVal);
			getJsonWriter(resp).write(jsonVal);
		} catch (IOException e) {
			logger.error(e);
		}
	}

	private PrintWriter getJsonWriter(HttpServletResponse resp) throws IOException {
		resp.setContentType("application/json");         
		resp.setCharacterEncoding("UTF-8");        
		return resp.getWriter();
	}

	private Method getAction(String actionName) {
		Map<String, Method> actions = getActionsThroughReflection();

		Method m = actions.get(actionName);
		if (m == null) {
			String err = "Action " + actionName + " is unknown. Known actions: " + join(actions.keySet(), ", ");
			logger.error(err);
			throw new RuntimeException(err);
		}
		return m;
	}

	private Map<String, Method> getActionsThroughReflection() {
		Map<String, Method> actions = new HashMap<String, Method>();
		for (Method m : RestServletActions.class.getDeclaredMethods()) {
			String action = removePrefixOrNull(m.getName(), "wsAction");
			if (action != null)
				actions.put(action, m);
		}
		return actions;
	}

	private String removePrefixOrNull(String s, String prefix) {
		return s.startsWith(prefix) ? s.substring(prefix.length()) : null;
	}

	public static String join(Iterable<?> elements, CharSequence separator) {
		if (elements == null) return "";

		StringBuilder sb = null;

		for (Object s : elements) {
			if (sb == null)
				sb = new StringBuilder();
			else
				sb.append(separator);
			sb.append(s);			
		}
		return sb == null ? "" : sb.toString();
	}
}

@@ -46,16 +46,59 @@
 let;%0D%0A%0D%0A
+import org.apache.commons.lang.StringUtils;
 %0D%0Aimport
@@ -2087,24 +2087,216 @@
 _REQUEST);%0D%0A
+%09%09Map%3CString, String%3E map = new HashMap%3CString, String%3E();%0D%0A%09%09map.put(%22error%22, e.getClass().getSimpleName());%0D%0A%09%09if (!StringUtils.isEmpty(e.getMessage())) map.put(%22message%22, e.getMessage());%0D%0A
 %09%09writeJson(
@@ -2305,14 +2305,11 @@
 sp, 
-%22%22 + e
+map
 );%0D%0A

instead of returning a plain error string, return a json object { error: "xxx", message: "xxx" }