package org.bodington.sakai;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.sakaiproject.authz.cover.AuthzGroupService;
import org.sakaiproject.authz.cover.SecurityService;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.event.api.UsageSession;
import org.sakaiproject.event.cover.UsageSessionService;
import org.sakaiproject.id.cover.IdManager;
import org.sakaiproject.tool.api.ActiveTool;
import org.sakaiproject.tool.api.Placement;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.tool.api.ToolException;
import org.sakaiproject.tool.api.ToolSession;
import org.sakaiproject.tool.api.ToolURL;
import org.sakaiproject.tool.cover.ActiveToolManager;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.user.api.Authentication;
import org.sakaiproject.user.api.AuthenticationException;
import org.sakaiproject.user.api.Evidence;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.user.cover.AuthenticationManager;
import org.sakaiproject.user.cover.UserDirectoryService;
import org.sakaiproject.util.Web;

/**
 * NullPortal is derived from MercuryPortal (written by Glenn Golden) to serve
 * as a way to startup Sakai tools and let them run in a Sakai session
 * environment. This is necessary, as most Sakai tools have direct dependencies
 * on run time services like The tool manager, etc.
 * <p>
 * NullPortal is a kind of null portal. Used in a course management environment
 * like Stellar, it allows Sakai tools to be invoked and run. It has a very
 * limited user interface (left over from Mercury) that's mostly used for
 * development purposes. Hard coded links to currently supported tools and
 * current session info is provided.
 * <p>
 * Warning! This application doesn't support login. It is assumed that the user
 * has logged in elsewhere. Authorization and user id are passed in via request
 * attributes. These attributes include:
 * <ul>
 * <li>userid - the user's login id.</li>
 * <li>siteHandle - the site id or site context.</li>
 * </ul>
 * 
 * @author Mark J. Norton
 */
public class NullPortal extends HttpServlet {

	private static final long serialVersionUID = -5120231547837165060L;

	/** Our log (commons). */
	private static Logger M_log = Logger.getLogger(NullPortal.class);

	/** Map of context+toolId -> Placement for keeping tool placements. */
	protected Map<String, Placement> m_placements = new HashMap<String, Placement>();

	/**
	 * Access the Servlet's information display.
	 * 
	 * @return servlet information.
	 */
	public String getServletInfo() {
		return "Stellar Launch Tool";
	}

	/**
	 * Initialize the servlet.
	 * 
	 * @param config
	 *            The servlet config.
	 * @throws ServletException
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		M_log.info("init()");
	}

	/**
	 * Shutdown the servlet.
	 */
	public void destroy() {
		M_log.info("destroy()");

		super.destroy();
	}

	/**
	 * Respond to initial tool launches or a UI display request.
	 * 
	 * @param req
	 *            The servlet request.
	 * @param res
	 *            The servlet response.
	 * @throws ServletException.
	 * @throws IOException.
	 */
	protected void doGet(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
		doWork(req, res);
	}

	/**
	 * Respond to data posting requests. Posts usually come from previously
	 * launched tools.
	 * 
	 * @param req
	 *            The servlet request.
	 * @param res
	 *            The servlet response.
	 * @throws ServletException.
	 * @throws IOException.
	 */
	protected void doPost(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
		doWork(req, res);
	}

	protected void doWork(HttpServletRequest req, HttpServletResponse res)
		throws ServletException, IOException {
		// get the Sakai session which should have been setup by the request filter
		Session session = SessionManager.getCurrentSession();
	

		String urlToolId = (String)req.getAttribute("tool.id");
		String urlContext = (String)req.getAttribute("context");
		String toolPath = (String) req.getAttribute("tool.path");
	
		// Initialize the user session.
		initSession(req, res, session, urlContext);
	
		// recognize and dispatch a tool request option: parts[2] is the
		// context,
		// parts[1] is a known tool id, parts[3..n] are for the tool
		if (urlToolId != null && urlContext != null) {
			String toolContextPath = (String) req
			.getAttribute("tool.context.path"); // The new way.

			if (toolContextPath == null) {
				M_log.warn("Stellar tool path not found in post.");
				doError(req, res, session);
			} else {

				SecurityService.pushAdvisor(new BodingtonAdvisor());
				String toolPathInfo = "";//Web.makePath(parts, 3, parts.length);
				doTool(req, res, session, urlToolId, urlContext, toolContextPath,
						toolPathInfo);
			}
		}
	
		// handle an unrecognized request
		else {
			M_log.warn("Unrecognized request.");
			doError(req, res, session);
		}
	}

	/**
	 * Process a tool request. This is an initial tool request. Environment
	 * intialization may be done here.
	 * 
	 */
	protected void doTool(HttpServletRequest req, HttpServletResponse res,
			Session session, String toolId, String context,
			String toolContextPath, String toolPathInfo) throws IOException {

		M_log.debug("doTool. Tool Id: "+ toolId+ " Context: " + context+ " Tool Id: " + toolId + " Tool Path: "+ toolPathInfo+ " Tool Context: "+ toolContextPath);
		// Get the tool associated with the tool id.
		ActiveTool tool = ActiveToolManager.getActiveTool(toolId);

		if (tool == null) {
			M_log.warn("Failed to find tool with ID: "+ toolId);
			doError(req, res, session);
		}

		else {
			// find or make the placement id for this tool - placements are
			// cached locally keyed by context+toolId
			String key = context + "-" + toolId;
			Placement p = m_placements.get(key);
			if (p == null) {
				p = new org.sakaiproject.util.Placement(IdManager.createUuid(),
						toolId, tool, null, context, null);
				m_placements.put(key, p);
			}

			forwardTool(tool, req, res, p, toolContextPath, toolPathInfo);
		}
	}

	/**
	 * Display an error message page.
	 */
	protected void doError(HttpServletRequest req, HttpServletResponse res,
			Session session) throws IOException {
		// start the response
		res.setContentType("text/html; charset=UTF-8");
		res.addDateHeader("Expires", System.currentTimeMillis()
				- (1000L * 60L * 60L * 24L * 365L));
		res.addDateHeader("Last-Modified", System.currentTimeMillis());
		res
				.addHeader("Cache-Control",
						"no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
		res.addHeader("Pragma", "no-cache");

		PrintWriter out = res.getWriter();
		out
				.println("<html><head><title>Sakai Tool Launch Error</title></head><body>");

		out.println("<h1>Sakai Tool Launch Error</h1>");
		out
				.println("An error was detected while trying to launch a Sakai tool. ");
		out
				.println("Please report this problem to your system administrator.<br>");

		// Show session information
		out.println("<H2>Session Information</H2>");
		showSession(out, true);

		out.println("<H2>Unknown Request</H2>");
		out.println("Path Information: " + req.getPathInfo() + "<br>");
		out.println("Context Path Information: " + req.getContextPath()
				+ "<br>");
		out.println("Query String: " + req.getQueryString() + "<br>");
		out.println("Request URL: " + req.getRequestURL() + "<br>");

		// close the response
		out.println("</body></html>");
	}

	/**
	 * Forward to the tool - setup JavaScript/CSS etc that the tool will render
	 */
	protected void forwardTool(ActiveTool tool, HttpServletRequest req,
			HttpServletResponse res, Placement p, String toolContextPath,
			String toolPathInfo) {
		String skin = ServerConfigurationService.getString("skin.default");
		String skinRepo = ServerConfigurationService.getString("skin.repo");

		// setup html information that the tool might need (skin, body on load,
		// js includes, etc).
		String headCssToolBase = "<link href=\""
				+ skinRepo
				+ "/tool_base.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n";
		String headCssToolSkin = "<link href=\""
				+ skinRepo
				+ "/"
				+ skin
				+ "/tool.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n";
		String headCss = headCssToolBase + headCssToolSkin;
		String headJs = "<script type=\"text/javascript\" language=\"JavaScript\" src=\"/library/js/headscripts.js\"></script>\n";
		String head = headCss + headJs;
		StringBuffer bodyonload = new StringBuffer();
		if (p != null) {
			String element = Web.escapeJavascript("Main" + p.getId());
			bodyonload.append("setMainFrameHeight('" + element + "');");
		}
		bodyonload.append("setFocus(focus_path);");

		req.setAttribute("sakai.html.head", head);
		req.setAttribute("sakai.html.head.css", headCss);
		req.setAttribute("sakai.html.head.css.base", headCssToolBase);
		req.setAttribute("sakai.html.head.css.skin", headCssToolSkin);
		req.setAttribute("sakai.html.head.js", headJs);
		req.setAttribute("sakai.html.body.onload", bodyonload.toString());
		req.setAttribute(ToolURL.MANAGER, new ToolURLManagerImpl(res));

		System.out.println("****** Launch:  Forward Tool");
		System.out.println("******    Tool Placement Id: " + p.getId());
		System.out.println("******    Tool Placement Context: "
				+ p.getContext());
		System.out.println("******    Tool Context Path: " + toolContextPath);
		System.out.println("******    Tool Path Info: " + toolPathInfo);
		System.out.println("******    javax.servlet.forward.request_uri: "
				+ req.getAttribute("javax.servlet.forward.request_uri"));
		System.out.println("******    stellar.toolPath:  "
				+ req.getAttribute("stellar.toolPath"));

		// Forward the request.
		try {
			tool.forward(req, res, p, toolContextPath, toolPathInfo);
		} catch (ToolException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Output some session information
	 * 
	 * @param out
	 *            The print writer
	 * @param html
	 *            If true, output in HTML, else in text.
	 */
	protected void showSession(PrintWriter out, boolean html) {
		// get the current user session information
		Session s = SessionManager.getCurrentSession();
		if (s == null) {
			out.println("no session established");
			if (html)
				out.println("<br />");
		} else {
			out.println("session: "
					+ s.getId()
					+ " <br>user id: "
					+ s.getUserId()
					+ " <br>enterprise id: "
					+ s.getUserEid()
					+ " <br>started: "
					+ DateFormat.getDateInstance().format(
							new Date(s.getCreationTime()))
					+ " <br>accessed: "
					+ DateFormat.getDateInstance().format(
							new Date(s.getLastAccessedTime()))
					+ " <br>inactive after: " + s.getMaxInactiveInterval()
					+ "<br>");
			if (html)
				out.println("<br />");
		}

		ToolSession ts = SessionManager.getCurrentToolSession();

		if (ts == null) {
			out.println("No tool session established");
			if (html)
				out.println("<br />");
		} else {
			out.println("Tool session: "
					+ ts.getId()
					+ " started: "
					+ DateFormat.getDateInstance().format(
							new Date(ts.getCreationTime()))
					+ " accessed: "
					+ DateFormat.getDateInstance().format(
							new Date(ts.getLastAccessedTime())));
			if (html)
				out.println("<br />");
		}
	}

	/**
	 * Initialize the Sakai session information using data passed via request
	 * attributes (or potentially other sources). Stellar passes the user id on
	 * "userid". The Stellar sideHandle is used for the AuthZGroup id, and could
	 * be used as the site context also.
	 * 
	 * If there is no current session, one is started using the information
	 * defined above. The AuthZGroup service is refreshed in case information
	 * has changed (likely).
	 * 
	 * @author Mark J. Norton
	 */
	private void initSession(HttpServletRequest req, HttpServletResponse res,
			Session session, String context) {
		String eid = null;
		String id = null;

		// System.out.println ("****** Launcher: initSession: Session Id:
		// "+session.getId()+"Context: "+context);
		// showAttributes(req, res, session);

		// Use the user name attribute for user id.
		eid = (String) req.getAttribute("username");
		if (eid == null) {
			eid = "Stubbed-User-Id";
			M_log.warn("No user found in request.");
		}

			
		// Use the site handle for authz group id.
		// context = (String)req.getAttribute("siteHandle");
		// The siteHandle attribute contains the Stellar site id. Later, this
		// will be used as the context.

		//if (session.getUserId() == null || !session.getUserId().equals(user)) {


			M_log.debug("Starting a session. User: "+ eid + " Context:" + context);
			try {
				id = UserDirectoryService.getUserId(eid);
				M_log.debug("Id: "+ id) ;
				M_log.debug(UserDirectoryService.getCurrentUser().getDisplayName());
			} catch (UserNotDefinedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			Evidence ev = new org.sakaiproject.util.ExternalTrustedEvidence(eid);
			try {
				Authentication a = AuthenticationManager.authenticate(ev);
                if (UsageSessionService.login(a, req))
                {
                        // get the return URL
                        String url = (String) session.getAttribute(Tool.
HELPER_DONE_URL);

                        // cleanup session
                        session.removeAttribute(Tool.HELPER_MESSAGE);
                        session.removeAttribute(Tool.HELPER_DONE_URL);

                        // redirect to the done URL
                        //res.sendRedirect(res.encodeRedirectURL(url));

                        return;
                }

			} catch (AuthenticationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			UsageSession usession = UsageSessionService.getSession();
			M_log.debug("User ID: "+ usession.getUserId()+ " EID: "+ usession.getUserEid());
			// update the user's externally provided realm definitions
			AuthzGroupService.refreshUser(eid);

			// post the login event
			// EventTrackingService.post(EventTrackingService.newEvent(UsageSessionService.EVENT_LOGIN,
			// null, true));
		//}
	}

}
