/**
 * 
 */
package org.ajax4jsf.portlet.application;

import java.io.IOException;
import java.io.Writer;

import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.portlet.RenderResponse;
import javax.portlet.faces.Bridge;
import javax.servlet.ServletContext;

import org.ajax4jsf.application.AjaxViewHandler;
import org.ajax4jsf.component.AjaxViewRoot;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.portlet.component.UIPortletViewRoot;
import org.ajax4jsf.portlet.context.AbstractExternalContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;




/**
 * @author asmirnov
 * 
 */
public class PortletViewHandler extends AjaxViewHandler {
	public static final String INTERWEAVING_RESPONSE_INTERFACE = "com.sun.faces.application.InterweavingResponse";
	
	private static final Log _log = LogFactory.getLog(PortletViewHandler.class);
	/**
	 * @param parent
	 */
	public PortletViewHandler(ViewHandler parent) {
		super(parent);
	}

	public void initView(FacesContext context) throws FacesException {
		super.initView(context);
	}

	public UIViewRoot createView(FacesContext facesContext, String viewId) {
		AjaxContext ajaxContext = AjaxContext.getCurrentInstance(facesContext);
		boolean ajaxRequest = ajaxContext.isAjaxRequest(facesContext);
		UIViewRoot root = super.createView(facesContext, viewId);
		Class<? extends UIViewRoot> rootClass = root.getClass();
		if (rootClass == AjaxViewRoot.class || rootClass == UIViewRoot.class) {
			root = new UIPortletViewRoot(root);
		}
		if (ajaxRequest && null != ajaxContext.getViewIdHolder()) {
			// Store new viewId for a portlet.
		}
		return root;
	}


	public String getActionURL(FacesContext context, String url) {
		String actionURL = super.getActionURL(context, url);
		if( null != context.getExternalContext().getRequestMap().get(Bridge.PORTLET_LIFECYCLE_PHASE)){
			actionURL = actionURL + ((actionURL.lastIndexOf('?')>0)?"&":"?")+AbstractExternalContext.VIEW_ID_PARAMETER+"="+url;
		}
		return actionURL;
	}

	protected synchronized void fillChain(FacesContext context) {
		// Do nothing - chain must be filled in AjaxViewHandler
	}
	
	// TODO - create own implementation. Now, this code are copied from MyFaces implementation.
	  @Override
	  public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException,
	                                                                       FacesException
	  {
	    // Get the renderPolicy from the requestScope
	    Bridge.BridgeRenderPolicy renderPolicy = (Bridge.BridgeRenderPolicy) context
	                                                                                .getExternalContext()
	                                                                                .getRequestMap()
	                                                                                .get(
	                                                                                     AbstractExternalContext.RENDER_POLICY_ATTRIBUTE);

	    if (renderPolicy == null)
	    {
	      renderPolicy = Bridge.BridgeRenderPolicy.valueOf("DEFAULT");
	    }

	    if (context.getExternalContext().getContext() instanceof ServletContext
	        || renderPolicy == Bridge.BridgeRenderPolicy.ALWAYS_DELEGATE)
	    {
	      super.renderView(context, viewToRender);
	      return;
	    }
	    else if (renderPolicy == Bridge.BridgeRenderPolicy.DEFAULT)
	    {
	      try
	      {
	        super.renderView(context, viewToRender);
	        return;
	      }
	      catch (Throwable t)
	      {
	    	if(_log.isInfoEnabled()){
	    		_log.info("Error rendering view by parent ViewHandler, try to render as portlet JSP page",t);
	    	}
	        // catch all throws and swallow -- falling through to our own
	        // render
	      }
	    }

	    // suppress rendering if "rendered" property on the component is
	    // false
	    if (!viewToRender.isRendered())
	    {
	      return;
	    }

	    ExternalContext extContext = context.getExternalContext();
	    RenderResponse renderResponse = (RenderResponse) extContext.getResponse();

	    try
	    {

	      // set request attribute indicating we can deal with content
	      // that is supposed to be delayed until after JSF tree is ouput.
	      extContext.getRequestMap().put(Bridge.RENDER_CONTENT_AFTER_VIEW, Boolean.TRUE);
	      // TODO JSF 1.2 - executePageToBuildView() creates
	      // ViewHandlerResponseWrapper
	      // to handle error page and text that exists after the <f:view> tag
	      // among other things which have lots of servlet dependencies -
	      // we're skipping this for now for portlet
	      //	      extContext.dispatch(viewToRender.getViewId());
	      
	       if (executePageToBuildView(context, viewToRender)) { renderResponse.flushBuffer(); return; }
	       
	    }
	    catch (IOException e)
	    {
	      throw new FacesException(e);
	    }

	    // set up the ResponseWriter
	    RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder
	                                                                     .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
	    RenderKit renderKit = renderFactory.getRenderKit(context, viewToRender.getRenderKitId());

	    ResponseWriter oldWriter = context.getResponseWriter();
	    StringBuilderWriter strWriter = new StringBuilderWriter(context, 4096);
	    ResponseWriter newWriter;
	    if (null != oldWriter)
	    {
	      newWriter = oldWriter.cloneWithWriter(strWriter);
	    }
	    else
	    {
	      newWriter = renderKit.createResponseWriter(strWriter, null,
	                                                 renderResponse.getCharacterEncoding());
	    }
	    context.setResponseWriter(newWriter);

	    newWriter.startDocument();

	    doRenderView(context, viewToRender);

	    newWriter.endDocument();

	    // replace markers in the body content and write it to response.

	    ResponseWriter responseWriter;
	    if (null != oldWriter)
	    {
	      responseWriter = oldWriter.cloneWithWriter(renderResponse.getWriter());
	    }
	    else
	    {
	      responseWriter = newWriter.cloneWithWriter(renderResponse.getWriter());
	    }
	    context.setResponseWriter(responseWriter);

	    strWriter.write(responseWriter);

	    if (null != oldWriter)
	    {
	      context.setResponseWriter(oldWriter);
	    }

        // write any AFTER_VIEW_CONTENT to the response
        // side effect: AFTER_VIEW_CONTENT removed
        if(hasInterweavingResponse()) {
            ViewHandlerPortletRenderResponseWrapper wrapper = (ViewHandlerPortletRenderResponseWrapper)
            extContext.getRequestMap().remove(Bridge.AFTER_VIEW_CONTENT);
            if (null != wrapper) {
                wrapper.flushToWriter(renderResponse.getWriter(),
                        renderResponse.getCharacterEncoding());
            }
        }
	    renderResponse.flushBuffer();
	  }

	  /**
	   * <p>
	   * This is a separate method to account for handling the content after the view tag.
	   * </p>
	   * 
	   * <p>
	   * Create a new ResponseWriter around this response's Writer. Set it into the FacesContext, saving
	   * the old one aside.
	   * </p>
	   * 
	   * <p>
	   * call encodeBegin(), encodeChildren(), encodeEnd() on the argument <code>UIViewRoot</code>.
	   * </p>
	   * 
	   * <p>
	   * Restore the old ResponseWriter into the FacesContext.
	   * </p>
	   * 
	   * <p>
	   * Write out the after view content to the response's writer.
	   * </p>
	   * 
	   * <p>
	   * Flush the response buffer, and remove the after view content from the request scope.
	   * </p>
	   * 
	   * @param context
	   *          the <code>FacesContext</code> for the current request
	   * @param viewToRender
	   *          the view to render
	   * @throws IOException
	   *           if an error occurs rendering the view to the client
	   */
	  private void doRenderView(FacesContext context, UIViewRoot viewToRender) throws IOException,
	                                                                          FacesException
	  {
	    viewToRender.encodeAll(context);
	  }

	  private static final class StringBuilderWriter extends Writer
	  {
	    private StringBuilder       mBuilder;
	    private FacesContext        mContext;

	    // TODO: These bridge needs to use it's own constants here. This will
	    // confine
	    // us to only work with the R.I.
	    private static final String SAVESTATE_FIELD_MARKER = "~com.sun.faces.saveStateFieldMarker~";

	    public StringBuilderWriter(FacesContext context, int initialCapacity)
	    {
	      if (initialCapacity < 0)
	      {
	        throw new IllegalArgumentException();
	      }
	      mBuilder = new StringBuilder(initialCapacity);
	      mContext = context;
	    }

	    @Override
	    public void write(char[] cbuf, int off, int len) throws IOException
	    {
	      if (off < 0 || off > cbuf.length || len < 0 || off + len > cbuf.length || off + len < 0)
	      {
	        throw new IndexOutOfBoundsException();
	      }
	      else if (len == 0)
	      {
	        return;
	      }
	      mBuilder.append(cbuf, off, len);
	    }

	    @Override
	    public void flush() throws IOException
	    {
	    }

	    @Override
	    public void close() throws IOException
	    {
	    }

	    /**
	     * Write a string.
	     * 
	     * @param str
	     *          String to be written
	     */
	    @Override
	    public void write(String str)
	    {
	      mBuilder.append(str);
	    }

	    @Override
	    public void write(String str, int off, int len)
	    {
	      write(str.substring(off, off + len));
	    }

	    public StringBuilder getBuffer()
	    {
	      return mBuilder;
	    }

	    @Override
	    public String toString()
	    {
	      return mBuilder.toString();
	    }

	    public void write(Writer writer) throws IOException
	    {
	      // TODO: Buffer?
	      StateManager stateManager = mContext.getApplication().getStateManager();
	      Object stateToWrite = stateManager.saveView(mContext);
	      int markLen = SAVESTATE_FIELD_MARKER.length();
	      int pos = 0;
	      int tildeIdx = mBuilder.indexOf(SAVESTATE_FIELD_MARKER);
	      while (tildeIdx > 0)
	      {
	        writer.write(mBuilder.substring(pos, (tildeIdx - pos)));
	        stateManager.writeState(mContext, stateToWrite);
	        pos += tildeIdx + markLen;
	        tildeIdx = mBuilder.indexOf(SAVESTATE_FIELD_MARKER, pos);
	      }
	      writer.write(mBuilder.substring(pos));
	    }
	  }

	    /**
	     * Execute the target view.  If the HTTP status code range is
	     * not 2xx, then return true to indicate the response should be
	     * immediately flushed by the caller so that conditions such as 404
	     * are properly handled.
	     * @param context the <code>FacesContext</code> for the current request
	     * @param viewToExecute the view to build
	     * @return <code>true</code> if the response should be immediately flushed
	     *  to the client, otherwise <code>false</code>
	     * @throws IOException if an error occurs executing the page
	     */
	    private boolean executePageToBuildView(FacesContext context,
	            UIViewRoot viewToExecute)
	            throws IOException {
	        String requestURI = viewToExecute.getViewId();
	        
	        ExternalContext extContext = context.getExternalContext();
	        
	        // replace the response with our wrapper
	        if(hasInterweavingResponse()) {
	            // save the original response
	            Object originalResponse = extContext.getResponse();
	            
	            ViewHandlerPortletRenderResponseWrapper wrapped =
	                    new ViewHandlerPortletRenderResponseWrapper((RenderResponse)extContext.getResponse());
	            extContext.setResponse(wrapped);
	            // build the view by executing the page
	            extContext.dispatch(requestURI);
	            
	            
	            // replace the original response
	            extContext.setResponse(originalResponse);
	            
	            // Put the AFTER_VIEW_CONTENT into request scope
	            // temporarily
	            extContext.getRequestMap().put(Bridge.AFTER_VIEW_CONTENT, wrapped);
	        } else {
	            // build the view by executing the page
	            extContext.dispatch(requestURI);
	            
	        }
	        
	        return false;
	    }
	    // If the JSF RI does not contain InterweavingResponse interface skip
	    // the content Interweaving logic
	    private boolean hasInterweavingResponse() {
	        try {
	            Class.forName(INTERWEAVING_RESPONSE_INTERFACE);
	        } catch (Throwable t) {
	            return false;
	        }
	        return true;
	    }

}
