/* Renders.java

	Purpose:

	Description:

	History:
		Tue Aug 10 08:48:59 TST 2010, Created by tomyeh

Copyright (C) 2010 Potix Corporation. All Rights Reserved.

*/
package org.zkoss.zkplus.embed;

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

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.owasp.encoder.Encode;

import org.zkoss.web.servlet.http.Https;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.GenericRichlet;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.Richlet;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.WebApp;
import org.zkoss.zk.ui.http.ExecutionImpl;
import org.zkoss.zk.ui.http.I18Ns;
import org.zkoss.zk.ui.http.WebManager;
import org.zkoss.zk.ui.impl.Attributes;
import org.zkoss.zk.ui.impl.RequestInfoImpl;
import org.zkoss.zk.ui.metainfo.PageDefinitions;
import org.zkoss.zk.ui.sys.HtmlPageRenders;
import org.zkoss.zk.ui.sys.RequestInfo;
import org.zkoss.zk.ui.sys.SessionCtrl;
import org.zkoss.zk.ui.sys.UiFactory;
import org.zkoss.zk.ui.sys.WebAppCtrl;

/**
 * Utilities to embed ZK component(s) as a native JSF component, a JSP tag, Zimlet or others.
 * It allows application developers to use the native element without knowing the existence of ZK.
 * For example, ZK Spreadsheet for JSF is a native JSF component made in this way.
 *
 * <p>Example:
<pre><code>
	Calendar cal = new Calendar();
	Renders.render(config.getServletContext(), request, response, cal, null, out);
</code></pre>
 *
 * <p>See also <a href="http://books.zkoss.org/wiki/ZK_Developer%27s_Reference/Integration/Embed_ZK_Component_in_Foreign_Framework">Embed ZK Component in Foreign Framework</a>.
 *
 * @author tomyeh
 * @since 5.0.5
 */
public class Renders {
	/** Outputs the HTML tags of the given component to the given writer.
	 * @param comp the component to output (never null). It might have child components.
	 * @param path the request path. If null, the servlet path is assumed.
	 * @param out the output (never null).
	 * @since 5.0.5
	 */
	public static final void render(ServletContext ctx, HttpServletRequest request, HttpServletResponse response,
			Component comp, String path, Writer out) throws ServletException, IOException {
		if (comp == null)
			throw new IllegalArgumentException();
		render(ctx, request, response, new EmbedRichlet(comp), path, false, out);
	}

	/** Outputs the HTML tags of the given component to the given writer.
	 * It is the same as <code>render(ctx, request, response, richlet, path, false, out)</code>.
	 *
	 * @param path the request path. If null, the servlet path is assumed.
	 * @param out the output (never null).
	 * @param richlet the richlet to run. If you have only one component to show and no need
	 * process it under an execution, you could use
	 * {@link #render(ServletContext, HttpServletRequest, HttpServletResponse, Component, String, Writer)}
	 * instead.
	 * @since 5.0.5
	 */
	public static final void render(ServletContext ctx, HttpServletRequest request, HttpServletResponse response,
			Richlet richlet, String path, Writer out) throws ServletException, IOException {
		render(ctx, request, response, richlet, path, false, out);
	}

	/** Outputs the HTML tags of the given component to the given writer with
	 * additional control.
	 * @param path the request path. If null, the servlet path is assumed.
	 * @param out the output (never null).
	 * @param richlet the richlet to run. If you have only one component to show and no need
	 * process it under an execution, you could use
	 * {@link #render(ServletContext, HttpServletRequest, HttpServletResponse, Component, String, Writer)}
	 * instead.
	 * @param pageDOM whether to generate the DOM element to represent the page.
	 * In other words, if true is specified, the content will be enclosed with
	 * an additional DIV element representing the tag.
	 * @since 5.0.8
	 */
	public static final void render(ServletContext ctx, HttpServletRequest request, HttpServletResponse response,
			Richlet richlet, String path, boolean pageDOM, Writer out) throws ServletException, IOException {
		if (path == null)
			path = Https.getThisServletPath(request);

		final WebManager webman = WebManager.getWebManager(ctx);
		final Session sess = WebManager.getSession(ctx, request);
		final WebApp wapp = sess.getWebApp();
		final WebAppCtrl wappc = (WebAppCtrl) wapp;
		final Object old = I18Ns.setup(sess, request, response, wapp.getConfiguration().getResponseCharset());
		Execution exec = null;
		try {
			final Desktop desktop = webman.getDesktop(sess, request, response, path, true);
			if (desktop == null) //forward or redirect
				return;

			final RequestInfo ri = new RequestInfoImpl(wapp, sess, desktop, request,
					PageDefinitions.getLocator(wapp, path));
			((SessionCtrl) sess).notifyClientRequest(true);

			final UiFactory uf = wappc.getUiFactory();
			final Page page = WebManager.newPage(uf, ri, richlet, response, path);
			exec = new ExecutionImpl(ctx, request, response, desktop, page);
			exec.setAttribute(Attributes.PAGE_REDRAW_CONTROL, "page");
			exec.setAttribute(Attributes.PAGE_RENDERER, new PageRenderer(exec, pageDOM));

			wappc.getUiEngine().execNewPage(exec, richlet, page, out);
			//no need to set device type here, since UiEngine will do it later
		} finally {
			I18Ns.cleanup(request, old);
			if (exec != null) {
				exec.removeAttribute(Attributes.PAGE_REDRAW_CONTROL);
				exec.removeAttribute(Attributes.PAGE_RENDERER);
			}
		}
	}

	//Supporting classes//
	private static class EmbedRichlet extends GenericRichlet {
		private final Component _comp;

		private EmbedRichlet(Component comp) {
			_comp = comp;
		}

		public void service(Page page) {
			_comp.setPage(page);
		}
	}

	/**
	 * A special page renderer that renders a page without generating
	 * the HTML tag of the page.
	 * In other words, it generates all components directly.
	 * @author tomyeh
	 * @since 5.0.4
	 */
	public static class PageRenderer implements org.zkoss.zk.ui.sys.PageRenderer {
		private final Execution _exec;
		private final boolean _pageDOM;

		/** Default constructor.
		 * It is the same as <code>PageRenderer(Executions.getCurrent())</code>.
		 */
		public PageRenderer() {
			this(Executions.getCurrent(), false);
		}

		public PageRenderer(Execution exec) {
			this(exec, false);
		}

		/**
		 * @param pageDOM whether to generate the DOM element to represent the page.
		 * In other words, if true is specified, the content will be enclosed with
		 * an additional DIV element representing the tag.
		 * @since 5.0.8
		 */
		public PageRenderer(Execution exec, boolean pageDOM) {
			_exec = exec;
			_pageDOM = pageDOM;
		}

		public void render(Page page, Writer out) throws IOException {
			out.write(HtmlPageRenders.outLangStyleSheets(_exec, null, null));
			out.write(HtmlPageRenders.outLangJavaScripts(_exec, null, null));

			if (_pageDOM) {
				HtmlPageRenders.outPageContent(_exec, page, out, false);
				return;
			}

			final Desktop desktop = _exec.getDesktop();
			out.write("<script class=\"z-runonce\" type=\"text/javascript\">zkpb('");
			out.write(page.getUuid());
			out.write("','");
			out.write(desktop.getId());
			out.write("','");
			out.write(Encode.forJavaScript(getContextURI()));
			out.write("','");
			out.write(Encode.forJavaScript(desktop.getUpdateURI(null)));
			out.write("','");
			out.write(Encode.forJavaScript(desktop.getResourceURI(null)));
			out.write("','");
			out.write(Encode.forJavaScript(desktop.getRequestPath()));
			out.write('\'');

			String style = page.getStyle();
			if (style != null && style.length() > 0) {
				out.write(",{style:'");
				out.write(style);
				out.write("'}");
			}

			out.write(");zkpe();</script>\n");

			for (Component root = page.getFirstRoot(); root != null; root = root.getNextSibling()) {
				HtmlPageRenders.outStandalone(_exec, root, out);
			}
		}

		private String getContextURI() {
			if (_exec != null) {
				String s = _exec.getContextURI();
				int j = s.lastIndexOf('/'); //might have jsessionid=...
				return j >= 0 ? s.substring(0, j) + s.substring(j + 1) : s;
			}
			return "";
		}
	}
}
