package org.openid4java.samples.serverservlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.association.AssociationException;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.DirectError;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.server.InMemoryServerAssociationStore;
import org.openid4java.server.IncrementalNonceGenerator;
import org.openid4java.server.ServerException;
import org.openid4java.server.ServerManager;

/**
 * Sample Server (OpenID Provider) implementation.
 */
public class OpenIdServlet extends javax.servlet.http.HttpServlet {
	private static final String HTTP_LOCALHOST_8080_OPENID_SERVER_SERVLET_PROVIDER = "http://localhost:8080/server-servlet/provider";

	private static final long serialVersionUID = -3170453746145256812L;

	private ServletContext context;

	private final Log log = LogFactory.getLog(this.getClass());

	private static ServerManager manager;

	// OpenID Spec 1.x or 2.0
	private static final String OPENID_VERSION = "2.0";

	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		context = config.getServletContext();
		log.debug("context: " + context);
		log.info("OpenID Provider: " + context);

		String endPointUrl = HTTP_LOCALHOST_8080_OPENID_SERVER_SERVLET_PROVIDER;

		if (manager == null) {
			manager = new ServerManager();
		}

		manager.setOPEndpointUrl(endPointUrl);
		manager.setExpireIn(900000);

		manager.setSharedAssociations(new InMemoryServerAssociationStore());
		manager.setPrivateAssociations(new InMemoryServerAssociationStore());
		manager.setNonceGenerator(new IncrementalNonceGenerator());

		manager.getRealmVerifier().setEnforceRpId(false);
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		ParameterList request = new ParameterList(req.getParameterMap());
		if (request.hasParameter("id")) {
			// For discovery...
			String id = request.getParameterValue("id");
			log.info("GET Endpoint with Id: " + id);
			if (id.equalsIgnoreCase("user")) {
				getEndpoint(resp);
			}
		} else {
			try {
				processRequest(req, resp);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private void processRequest(HttpServletRequest httpReq,
			HttpServletResponse httpResp) throws Exception {
		// Extract the parameters from the request
		log.info("PROCESS Request...");
		ParameterList request = new ParameterList(httpReq.getParameterMap());

		String mode = request.hasParameter("openid.mode") ? request
				.getParameterValue("openid.mode") : null;

		Message response;
		String responseText;

		if ("associate".equals(mode)) {
			// --- Process an association request ---
			response = manager.associationResponse(request);
			responseText = response.keyValueFormEncoding();
			directResponse(httpResp, responseText);
		} else if ("checkid_setup".equals(mode)
				|| "checkid_immediate".equals(mode)) {
			// --- Process checkid ---
			response = checkid(httpResp, request);
			responseText = response.getDestinationUrl(true);

			if (OPENID_VERSION.equalsIgnoreCase("1.x")) {
				// HTTP redirect
				log.info("Redirect GET HTTP: " + responseText);
				httpResp.sendRedirect(responseText);
			} else {
				// HTML FORM redirect
				log.info("Redirect HTML FORM: " + responseText);
				indirectHtmlFormResponse(httpReq, httpResp, response);
			}
		} else if ("check_authentication".equals(mode)) {
			// --- Processing a verification request ---
			response = manager.verify(request);
			responseText = response.keyValueFormEncoding();
			directResponse(httpResp, responseText);
		} else {
			// --- Error response ---
			response = DirectError.createDirectError("Unknown request");
			responseText = response.keyValueFormEncoding();
			directResponse(httpResp, responseText);
		}
	}

	private void getEndpoint(HttpServletResponse resp) throws IOException {
		String response;

		if (OPENID_VERSION.equalsIgnoreCase("1.x")) {
			// HTML discovery OpenID 1.x
			log.info("OpenID 1.x");
			response = "<html>" + "<head>"
					+ "<link rel=\"openid.server\" href=\""
					+ HTTP_LOCALHOST_8080_OPENID_SERVER_SERVLET_PROVIDER
					+ "\"/>" + "</head>" + "<body>"
					+ "This is the identity page for users of this server."
					+ "</body>" + "</html>";
		} else {
			// HTML discovery OpenID 2.0
			log.info("OpenID 2.0");
			response = "<html>" + "<head>"
					+ "<link rel=\"openid2.provider\" href=\""
					+ HTTP_LOCALHOST_8080_OPENID_SERVER_SERVLET_PROVIDER
					+ "\"/>" + "</head>" + "<body>"
					+ "This is the identity page for users of this server."
					+ "</body>" + "</html>";
		}

		log.info("GET Endpoint Params Id = user: " + response);
		directResponse(resp, response);
	}

	private Message checkid(HttpServletResponse httpResp, ParameterList request)
			throws MessageException, IOException, ServerException,
			AssociationException {
		Message response;
		// Interact with the user and obtain data needed to continue
		List<?> userData = userInteraction(request);

		String userSelectedClaimedId = (String) userData.get(0);
		Boolean authenticatedAndApproved = (Boolean) userData.get(1);
		String email = (String) userData.get(2);
		String nickname = (String) userData.get(3);
		String fullname = (String) userData.get(4);

		// --- Process an authentication request ---
		AuthRequest authReq = AuthRequest.createAuthRequest(request, manager
				.getRealmVerifier());

		String opLocalId = null;
		// If the user chose a different claimed_id than the one in request
		if (userSelectedClaimedId != null
				&& userSelectedClaimedId.equals(authReq.getClaimed())) {
			// opLocalId = lookupLocalId(userSelectedClaimedId);
		}

		response = manager.authResponse(request, opLocalId,
				userSelectedClaimedId, authenticatedAndApproved.booleanValue(),
				false); // Sign after we added extensions.

		if (response instanceof DirectError)
			return directResponse(httpResp, response.keyValueFormEncoding());
		else {
			if (authReq.hasExtension(AxMessage.OPENID_NS_AX)) {
				createAxMessage(response, email, nickname, fullname, authReq);
			}
			if (authReq.hasExtension(SRegMessage.OPENID_NS_SREG)) {
				createSRegMessage(response, email, nickname, fullname, authReq);
			}

			// Sign the auth success message.
			// This is required as AuthSuccess.buildSignedList has a `todo'
			// tag now.
			manager.sign((AuthSuccess) response);

			return response;
		}
	}

	private void createSRegMessage(Message response, String email,
			String nickname, String fullname, AuthRequest authReq)
			throws MessageException {
		log.info("OP using SReg...");
		MessageExtension ext = authReq.getExtension(SRegMessage.OPENID_NS_SREG);
		if (ext instanceof SRegRequest) {
			SRegRequest sregReq = (SRegRequest) ext;
			// False means that all the attributes are optional
			List<?> required = sregReq.getAttributes(false);
			if (required.contains("email")) {
				// Data released by the user
				Map<String, String> userDataSReg = new HashMap<String, String>();
				userDataSReg.put("email", email);
				userDataSReg.put("nickname", nickname);
				userDataSReg.put("fullname", fullname);

				SRegResponse sregResp = SRegResponse.createSRegResponse(
						sregReq, userDataSReg);

				// (Alternatively) manually add attribute values
				// sregResp.addAttribute("email", email);

				response.addExtension(sregResp);
			}
		} else {
			throw new UnsupportedOperationException("TODO");
		}
	}

	private void createAxMessage(Message response, String email,
			String nickname, String fullname, AuthRequest authReq)
			throws MessageException {
		log.info("OP using AX...");
		MessageExtension ext = authReq.getExtension(AxMessage.OPENID_NS_AX);
		if (ext instanceof FetchRequest) {
			FetchRequest fetchReq = (FetchRequest) ext;
			// False means that all the attributes are optional
			Map<?, ?> required = fetchReq.getAttributes(false);
			if (required.containsKey("email")) {
				Map<String, String> userDataExt = new HashMap<String, String>();
				userDataExt.put("email", email);
				userDataExt.put("nickname", nickname);
				userDataExt.put("fullname", fullname);

				FetchResponse fetchResp = FetchResponse.createFetchResponse(
						fetchReq, userDataExt);

				// (Alternatively) manually add attribute values
				// fetchResp.addAttribute("email",
				// "http://schema.openid.net/contact/email", email);

				response.addExtension(fetchResp);
			}
		} else {
			// If (ext instanceof StoreRequest)
			throw new UnsupportedOperationException("TODO");
		}
	}

	private List<?> userInteraction(ParameterList request) {
		log.info("User-interaction...");
		List<Object> userData = new ArrayList<Object>();
		userData.add(HTTP_LOCALHOST_8080_OPENID_SERVER_SERVLET_PROVIDER
				+ "?id=user");
		userData.add(new Boolean(true));
		userData.add("Lofi@lofi.de");
		userData.add("Dr. Java");
		userData.add("Lofi Dewanto");

		return userData;
	}

	private void indirectHtmlFormResponse(HttpServletRequest req,
			HttpServletResponse resp, Message authResponse)
			throws ServletException, IOException {
		RequestDispatcher dispatcher = getServletContext()
				.getRequestDispatcher("/formredirection.jsp");
		req.setAttribute("parameterMap", req.getParameterMap());
		req.setAttribute("message", authResponse);
		dispatcher.forward(req, resp);
	}

	private Message directResponse(HttpServletResponse httpResp, String response)
			throws IOException {
		// Method for doing direct response for direct communication
		ServletOutputStream os = httpResp.getOutputStream();
		os.write(response.getBytes());
		os.close();
		return null;
	}
}