package com.sei.web;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.faces.FactoryFinder;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseStream;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.Renderer;
import javax.faces.render.ResponseStateManager;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.async.Asynchronous;
import org.jboss.seam.annotations.async.Duration;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.log.Log;
import org.jboss.seam.mock.MockExternalContext;
import org.jboss.seam.ui.util.JSF;
import org.jboss.seam.util.Resources;

import com.icesoft.faces.context.BridgeExternalContext;
import com.icesoft.faces.context.BridgeFacesContext;
import com.icesoft.faces.webapp.http.servlet.ServletContextConfiguration;
import com.icesoft.faces.webapp.xmlhttp.PersistentFacesState;
import com.sun.facelets.Facelet;
import com.sun.facelets.compiler.SAXCompiler;
import com.sun.facelets.impl.DefaultFaceletFactory;
import com.sun.facelets.impl.DefaultResourceResolver;
import com.sun.faces.RIConstants;
import com.sun.faces.application.ViewHandlerImpl;
import com.sun.faces.renderkit.html_basic.HtmlResponseWriter;

/*
 * Asynchronous setup based on Seam mail example
 * IceFaces workaround based on FaceletsRenderer
 * 
 * The purpose of this class is to work-around the incompatibilty 
 * between IceFaces and Seam email.  The problems occur when the IceFaces
 * context object, ServletExternalContext, is used to create the proper
 * responseWriter.  Since we know that all we want is a simple HTMLResponseWriter
 * this class is used to replace the standard Seam FaceletsRenderer when we are
 * rendering emails.
 * 
 */
@Name("asynchronousMailProcessor")
@AutoCreate
public class AsynchronousIceFacesMailProcessor {
	@Logger
	private Log log;

	/**
	 * @param delay
	 *            when to send the email, ms from now
	 * @param template
	 *            email template location
	 * @param contextMap
	 *            map of key, value pairs to add to the temporary Seam context,
	 *            and then used in the rendering
	 */
	@Asynchronous
	public void scheduleEmailSend(@Duration
	long delay, String template, HashMap<String, Object> contextMap) {
		// emailSend(template, contextMap);
		// }
		//	
		// public void emailSend(String template, HashMap<String, Object>
		// contextMap) {
		log.debug("AsynchronousMailProcessor::emailSend start");

		Context context = new Context(template);
		try {
			context.wrap();

			if (contextMap != null) {
				for (String key : contextMap.keySet()) {
					Contexts.getEventContext().set(key, contextMap.get(key));
				}
			}

			FacesContext facesContext = FacesContext.getCurrentInstance();
			URL url = Resources.getResource(template, (ServletContext)facesContext
			        .getExternalContext().getContext());
			if (url == null) {
				throw new IllegalArgumentException("resource doesn't exist: " + template);
			}
			UIViewRoot root = facesContext.getViewRoot();
			Facelet facelet = new DefaultFaceletFactory(new SAXCompiler(),
			        new DefaultResourceResolver()).getFacelet(url);
			facelet.apply(facesContext, root);
			JSF.renderChildren(facesContext, root);

			log.debug("AsynchronousMailProcessor::emailSend end");
		}
		catch (IOException e) {
			log.error("AsynchronousMailProcessor::emailSend exception - " + e.getMessage());
			throw new RuntimeException(e);
		}
		finally {
			context.unwrap();
		}
	}

	private class Context {

		public Context(String viewId) {
			this.viewId = viewId;
		}

		private String viewId;

		private ResponseWriter originalResponseWriter;

		private UIViewRoot originalViewRoot;

		private StringWriter writer = new StringWriter();

		// save off key members of the context, and replace with our own
		public Context wrap() {
			// If a FacesContext isn't available, set one up
			if (FacesContext.getCurrentInstance() == null) {
				if (PersistentFacesState.getInstance() != null) {
					BridgeFacesContext bcontext = (BridgeFacesContext)PersistentFacesState
					        .getInstance().getFacesContext();
					bcontext.setCurrentInstance();
				}
				else {
					try {
						FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
					}
					catch (IllegalStateException ex) {
						FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY,
													"com.sun.faces.application.ApplicationFactoryImpl");
					}
					BridgeFacesContext bfc = new MockBridgeFacesContext(viewId);
					bfc.setCurrentInstance();
				}
			}

			FacesContext facesContext = FacesContext.getCurrentInstance();

			// Create a new UIViewRoot
			originalViewRoot = facesContext.getViewRoot();
			UIViewRoot viewRoot = new UIViewRoot();
			viewRoot.setRenderKitId(facesContext.getApplication().getViewHandler()
			        .calculateRenderKitId(facesContext));
			viewRoot.setViewId(viewId);
			viewRoot.setLocale(originalViewRoot.getLocale());
			facesContext.setViewRoot(viewRoot);

			// Wrap the ResponseWriter, needs to be done after the view root as
			// icefaces resets the writer when you set the viewRoot
			originalResponseWriter = facesContext.getResponseWriter();
			facesContext.setResponseWriter(new HtmlResponseWriter(writer, "text/html",
			        RIConstants.CHAR_ENCODING, false));

			return this;
		}

		// restore the context
		public void unwrap() {
			FacesContext facesContext = FacesContext.getCurrentInstance();
			if (originalResponseWriter != null) {
				facesContext.setResponseWriter(originalResponseWriter);
			}
			if (originalViewRoot != null) {
				facesContext.setViewRoot(originalViewRoot);
			}
		}

		public String getWrittenOutput() {
			return writer.getBuffer().toString();
		}

	}

	/*
	 * A mockup of the BridgeFacesContext class for fooling some of the IceFaces instanceof checks
	 */
	private class MockBridgeFacesContext extends BridgeFacesContext {
		public MockBridgeFacesContext(String view) {
			super(new MockBridgeExternalContext(view, new MockExternalContext()), view, "", null,
			        null, null);
			UIViewRoot newViewRoot = new UIViewRoot();
			newViewRoot.setViewId("index.xhtml");
			ViewHandler handler = getApplication().getViewHandler();
			if (handler == null) {
				// create a viewhandler
				handler = new ViewHandlerImpl();
				getApplication().setViewHandler(handler);
			}
			newViewRoot
				.setRenderKitId(handler.calculateRenderKitId(this));
			setViewRoot(newViewRoot);
		}

		@Override
		public void switchToNormalMode() {};
		
		@Override
	    public RenderKit getRenderKit() {
	        UIViewRoot viewRoot = getViewRoot();
	        if (null == viewRoot) {
	            return (null);
	        }
	        String renderKitId = viewRoot.getRenderKitId();
	        if (null == renderKitId) {
	            return (null);
	        }

	        RenderKitFactory renderKitFactory = (RenderKitFactory)
	                FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
	        RenderKit renderKit = renderKitFactory.getRenderKit(this, renderKitId);
	        return new MockRenderKit(renderKit);
	    }
	}
	
	/*
	 *  A mockup of the BridgExternalContext class for fooling some of the IceFaces instanceof checks
	 */
	private class MockBridgeExternalContext extends BridgeExternalContext {

		private ExternalContext delegate;

		public MockBridgeExternalContext(String viewIdentifier, ExternalContext externalContext) {
			super(viewIdentifier, null, new ServletContextConfiguration("com.icesoft.faces", (ServletContext)externalContext.getContext()));
			delegate = externalContext;
			recreateParameterAndCookieMaps();
			requestMap = new HashMap();
			applicationMap = new HashMap<String, Object>();
		}

		public boolean isSeamLifecycleShortcut() {
			return false;
		}

		public void switchToNormalMode() {};

		public void switchToPushMode() {};

		@Override
		public String getRequestURI() {
			return ((HttpServletRequest)delegate.getRequest()).getRequestURI();
		}

		@Override
		public Writer getWriter(String encoding) throws IOException {
			return ((HttpServletResponse)delegate.getResponse()).getWriter();
		}

		@Override
		public void update(HttpServletRequest arg0, HttpServletResponse arg1) {
			recreateParameterAndCookieMaps();
		}

		@Override
		public void updateOnPageLoad(Object request, Object response) {
		// TODO Auto-generated method stub
		}

		@Override
		public void dispatch(String arg0) throws IOException {
			delegate.dispatch(arg0);
		}

		@Override
		public String encodeActionURL(String arg0) {
			return delegate.encodeActionURL(arg0);
		}

		@Override
		public String encodeNamespace(String arg0) {
			return delegate.encodeNamespace(arg0);
		}

		@Override
		public String encodeResourceURL(String arg0) {
			return delegate.encodeResourceURL(arg0);
		}

		@Override
		public String getAuthType() {
			return delegate.getAuthType();
		}

		@Override
		public Object getContext() {
			return delegate.getContext();
		}

		@Override
		public String getRemoteUser() {
			return delegate.getRemoteUser();
		}

		@Override
		public Object getRequest() {
			return delegate.getRequest();
		}

		@Override
		public String getRequestContextPath() {
			return delegate.getRequestContextPath();
		}

		@Override
		public Map<String, String> getRequestHeaderMap() {
			return delegate.getRequestHeaderMap();
		}

		@Override
		public Map<String, String[]> getRequestHeaderValuesMap() {
			return delegate.getRequestHeaderValuesMap();
		}

		@Override
		public Locale getRequestLocale() {
			return delegate.getRequestLocale();
		}

		@Override
		public Iterator<Locale> getRequestLocales() {
			return delegate.getRequestLocales();
		}

		@Override
		public String getRequestPathInfo() {
			return delegate.getRequestPathInfo();
		}

		@Override
		public String getRequestServletPath() {
			return delegate.getRequestServletPath();
		}

		@Override
		public URL getResource(String arg0) throws MalformedURLException {
			return delegate.getResource(arg0);
		}

		@Override
		public InputStream getResourceAsStream(String arg0) {
			return delegate.getResourceAsStream(arg0);
		}

		@Override
		public Set<String> getResourcePaths(String arg0) {
			return delegate.getResourcePaths(arg0);
		}

		@Override
		public Object getResponse() {
			return delegate.getResponse();
		}

		@Override
		public Object getSession(boolean arg0) {
			return delegate.getSession(arg0);
		}

		@Override
		public Principal getUserPrincipal() {
			return delegate.getUserPrincipal();
		}

		@Override
		public boolean isUserInRole(String arg0) {
			return delegate.isUserInRole(arg0);
		}

		@Override
		public void log(String arg0, Throwable arg1) {
			delegate.log(arg0, arg1);
		}

		@Override
		public void log(String arg0) {
			delegate.log(arg0);
		}
	}

	/*
	 * A mockup of a RenderKit.
	 * Main purpose is to replace IceFaces renderers with base Faces renderers
	 */
	private class MockRenderKit extends RenderKit {
		private RenderKit delegate;
		
		public MockRenderKit(RenderKit delegate) {
			this.delegate = delegate;
		}
		
		@Override
		public Renderer getRenderer(String family, String rendererType) {
			Renderer foundRenderer = delegate.getRenderer(family, rendererType);
			String className = foundRenderer.getClass().getName();
			String icefacesPackageName = "com.icesoft.faces.renderkit.dom_html_basic";
			String facesPackageName = "com.sun.faces.renderkit.html_basic";
			if (className.startsWith(icefacesPackageName)) {
				// found an IceFaces renderer
				String newClassName = className.replaceFirst(icefacesPackageName, facesPackageName);
				try {
					Class newRendererClass = Class.forName(newClassName);
					foundRenderer = (Renderer)newRendererClass.newInstance();
				}
				catch (Exception e) {
					// can't replace the renderer - this will blow up if the tag is within
					// a mail tag
					foundRenderer = null;
				}
			}
			return foundRenderer;
		}

		@Override
        public void addRenderer(String family, String rendererType, Renderer renderer) {
	        delegate.addRenderer(family, rendererType, renderer);
        }

		@Override
        public ResponseStream createResponseStream(OutputStream out) {
	        delegate.createResponseStream(out);
	        return null;
        }

		@Override
        public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) {
	        delegate.createResponseWriter(writer, contentTypeList, characterEncoding);
	        return null;
        }

		@Override
        public ResponseStateManager getResponseStateManager() {
	        return delegate.getResponseStateManager();
        }
	}
}

