/**
 * 
 */
package javax.portlet.faces;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.WindowState;


/**
 * JSR 301 generic faces pottlet implementation.
 * 
 * @author asmirnov
 * 
 */
public class GenericFacesPortlet extends GenericPortlet {

	private static final String BRIDGE_SERVICE_CLASSPATH = "META-INF/services/javax.portlet.faces.Bridge";
	public static final String BRIDGE_CLASS = "javax.portlet.faces.BridgeClassName";
	
	private volatile String bridgeClassName = null;
	private volatile Bridge facesPortletBrige = null;

	public void init(PortletConfig config) throws PortletException {
		super.init(config);
		bridgeClassName = this.getPortletContext().getInitParameter(
				BRIDGE_CLASS);
		if (bridgeClassName == null) {
			ClassLoader loader = getClassLoader();
			InputStream stream = loader
					.getResourceAsStream(BRIDGE_SERVICE_CLASSPATH);
			if (null != stream) {
				try {
					BufferedReader reader = null;
					try {
						reader = new BufferedReader(new InputStreamReader(
								stream, "UTF-8"));
					} catch (UnsupportedEncodingException e) {
						reader = new BufferedReader(new InputStreamReader(
								stream));
					}
					bridgeClassName = reader.readLine();
					if (null != bridgeClassName) {
						bridgeClassName.trim();
					}
				} catch (IOException e) {
					// Ignore
				} catch (SecurityException e) {
					// Ignore
				} finally {
					if (null != stream) {
						try {
							stream.close();
						} catch (IOException e) {
							// Ignore
						}
					}
				}
			}
		}
		if (null == bridgeClassName) {
			throw new PortletException("Can't detect bridge implementation class name");
		}
        String renderPolicy = 
            this.getPortletConfig().getInitParameter(Bridge.BRIDGE_PACKAGE_PREFIX + Bridge.RENDER_POLICY);
        if (renderPolicy != null)        
            this.getPortletContext().setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + 
                                                  this.getPortletName() + "." + Bridge.RENDER_POLICY,
                                                  Bridge.BridgeRenderPolicy.valueOf(renderPolicy));
        String preserveActionParams = 
            this.getPortletConfig().getInitParameter(Bridge.BRIDGE_PACKAGE_PREFIX + Bridge.PRESERVE_ACTION_PARAMS);
        if (preserveActionParams != null)        
            this.getPortletContext().setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + 
                                                  this.getPortletName() + "." + Bridge.PRESERVE_ACTION_PARAMS,
                                                  Boolean.valueOf(preserveActionParams));
	}

	protected void doDispatch(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		PortletMode mode = request.getPortletMode();
		if (mode == PortletMode.VIEW || mode == PortletMode.EDIT
				|| mode == PortletMode.HELP) {
			super.doDispatch(request, response);
		} else {
			doFacesDispatch(request, response);
		}

	}

	protected void doEdit(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		doFacesDispatch(request, response);
	}

	protected void doHelp(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		doFacesDispatch(request, response);
	}

	protected void doView(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		doFacesDispatch(request, response);
	}

	public void processAction(ActionRequest request, ActionResponse response)
			throws PortletException, IOException {
		String defaultViewId = getDefaultViewId(request, request
				.getPortletMode());
		Bridge brige = getFacesPortletBrige();
		request.setAttribute(Bridge.DEFAULT_VIEWID, defaultViewId);
		try {
			brige.doFacesRequest(request, response);

		} catch (BridgeException e) {
			throw new PortletException("Error process faces request", e);
		}
	}

	void doFacesDispatch(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		String defaultViewId = getDefaultViewId(request, request
				.getPortletMode());
		if (null != defaultViewId
				&& !request.getWindowState().equals(WindowState.MINIMIZED)) {
			Bridge brige = getFacesPortletBrige();
			request.setAttribute(Bridge.DEFAULT_VIEWID, defaultViewId);
			try {
				brige.doFacesRequest(request, response);

			} catch (BridgeException e) {
				throw new PortletException("Error process faces request", e);
			}
		}
	}

	/**
	 * JSR 301 API method
	 * 
	 * @return
	 */
	public String getBrigeClassName() {
		return bridgeClassName;
	}

	private ClassLoader getClassLoader() {
		ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();
		if (null == classLoader) {
			classLoader = this.getClass().getClassLoader();
		}
		return classLoader;
	}

	public String getDefaultViewId(PortletRequest request, PortletMode mode) {
		return this.getPortletConfig().getInitParameter(
				Bridge.DEFAULT_VIEWID + "." + mode.toString());
	}

	public void destroy() {
		bridgeClassName = null;
		// If brige was initialized, destroy it.
		if (null != facesPortletBrige) {
			facesPortletBrige.destroy();
			facesPortletBrige = null;
		}
		super.destroy();
	}

	/**
	 * @return the facesPortletBrige
	 * @throws PortletException
	 */
	@SuppressWarnings("unchecked")
	protected Bridge getFacesPortletBrige() throws PortletException {
		if (null == facesPortletBrige) {
			String brigeClassName = getBrigeClassName();
			try {
				Class bridgeClass = getClassLoader().loadClass(brigeClassName);
				facesPortletBrige = (Bridge) bridgeClass.newInstance();
				facesPortletBrige.init(getPortletConfig());
			} catch (ClassNotFoundException e) {
				throw new PortletException(
						"Class for a JSF Portlet Bridge not found", e);
			} catch (InstantiationException e) {
				throw new PortletException(
						"Error on create instance of a JSF Portlet Bridge", e);
			} catch (IllegalAccessException e) {
				throw new PortletException(
						"IllegalAccess on create instance of a JSF Portlet Bridge",
						e);
			}
		}
		return facesPortletBrige;
	}
}
