/* GenericAutowireComposer.java
	Purpose:
		
	Description:
		
	History:
		Jun 11, 2008 10:56:06 AM, Created by henrichen

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

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.util;

import java.lang.reflect.Method;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.zkoss.lang.Classes;
import org.zkoss.lang.Library;
import org.zkoss.lang.Objects;
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.IdSpace;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WebApp;
import org.zkoss.zk.ui.event.CreateEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SerializableEventListener;

/**
 * <p>A skeletal composer that you can extend and write intuitive onXxx 
 * event handler methods with "auto-wired" accessible variable objects such
 * as implicit objects, components, and external resolvable variables in a ZK 
 * zuml page. This class will registers onXxx events to the supervised 
 * component and wire all accessible variable objects to this composer by 
 * calling setXxx() method or set xxx field value directly per the variable 
 * name. Since 3.0.7, this composer has wired all implicit objects 
 * such as self, spaceOwner, page, desktop, session, application, 
 * componentScope, spaceScope, pageScope, desktopScope, sessionScope, 
 * applicationScope, and requestScope, so you can use them directly. Besides 
 * that, it also provides alert(String message) method, so you can call alert() 
 * without problems. Since 3.5.2, the composer itself would be assigned as an 
 * attribute of the supervised component per the naming convention of 
 * the component id and composer class name or of component id and "composer". 
 * e.g. If the component id is "mywin" and the composer class is org.zkoss.MyComposer, 
 * then the composer can be referenced by the variable name of "mywin$MyController" or 
 * "mywin$composer". Notice that the '$' separator can be changed to other character
 * such as '_' for Groovy or other environment that '$' is not applicable. Simply
 * extends this class and calling {@link #GenericAutowireComposer(char separator)}
 * constructor with proper separator character.</p>
 * 
 * <P>Alternatives: in most cases, you don't extend from {@link GenericAutowireComposer} directly.
 * Rather, you can extend from one of the following skeletons.
 * <dl>
 * <dt>{@link org.zkoss.zk.ui.select.SelectorComposer}</dt>
 * <dd>It supports the autowiring based on Java annotation and a CSS3-based selector.
 * If you don't know which one to use, use {@link org.zkoss.zk.ui.select.SelectorComposer}.</dd>
 * <dt>{@link GenericForwardComposer}</dt>
 * <dd>It supports the autowiring based on naming convention.
 * You don't need to specify annotations explicitly, but it is error-prone if
 * it is used properly.</dd>
 * </dl>
 *
 * <p>Notice that since this composer kept references to the components, single
 * instance composer object cannot be shared by multiple components.</p>
 *  
 * <p>The following is an example. The onOK event listener is registered into 
 * the target window, and the Textbox component with id name "mytextbox" is
 * injected into the "mytextbox" field automatically (so you can use 
 * mytextbox variable directly in onOK). The "value" property of "mytextbox" 
 * is assigned with composer's getTitle(), i.e. "ZK".</p>
 * 
 * <pre><code>
 * MyComposer.java
 * 
 * public class MyComposer extends GenericAutowireComposer {
 *     private Textbox mytextbox;
 *     
 *     public void onOK() {
 *         mytextbox.setValue("Enter Pressed");
 *         alert("Hi!");
 *     }
 *     public String getTitle() {
 *         return "ZK";
 *     }
 * }
 * 
 * test.zul
 * 
 * &lt;window id="mywin" apply="MyComposer">
 *     &lt;textbox id="mytextbox" value="${mywin$composer.title}"/>
 * &lt;/window>
 * </code></pre>
 *
 * <p>Since 5.0.8, you could name the composer by specify a custom attribute
 * called <code>composerName</code>. For example,
 *<pre><code>
 * &lt;window apply="MyComposer">
 * &lt;custom-attribute composerName="mc"/>
 *     &lt;textbox id="mytextbox" value="${mc.title}"/>
 * &lt;/window>
 * </code></pre>
 * 
 * @author henrichen
 * @since 3.0.6
 * @see ConventionWires
 */
public abstract class GenericAutowireComposer<T extends Component> extends GenericComposer<T>
		implements ComponentCloneListener, ComponentActivationListener {
	private static final long serialVersionUID = 20091006115726L;
	private static final String COMPOSER_CLONE = "COMPOSER_CLONE";
	private static final String ON_CLONE_DO_AFTER_COMPOSE = "onCLONE_DO_AFTER_COMPOSE";
	private static Logger log = LoggerFactory.getLogger(GenericAutowireComposer.class);

	/** Implicit Object; the applied component itself. 
	 * @since 3.0.7
	 */
	protected transient T self;
	/** Implicit Object; the space owner of the applied component.
	 * @since 3.0.7
	 */
	protected transient IdSpace spaceOwner;
	/** Implicit Object; the page.
	 * @since 3.0.7
	 */
	protected transient Page page;
	/** Implicit Object; the desktop.
	 * @since 3.0.7
	 */
	protected transient Desktop desktop;
	/** Implicit Object; the session.
	 * @since 3.0.7
	 */
	protected transient Session session;
	/** Implicit Object; the web application.
	 * @since 3.0.7
	 */
	protected transient WebApp application;
	/** Implicit Object; a map of attributes defined in the applied component.
	 * @since 3.0.7
	 */
	protected transient Map<String, Object> componentScope;
	/** Implicit Object; a map of attributes defined in the ID space contains the applied component.
	 * @since 3.0.7
	 */
	protected transient Map<String, Object> spaceScope;
	/** Implicit Object; a map of attributes defined in the page.
	 * @since 3.0.7
	 */
	protected transient Map<String, Object> pageScope;
	/** Implicit Object; a map of attributes defined in the desktop.
	 * @since 3.0.7
	 */
	protected transient Map<String, Object> desktopScope;
	/** Implicit Object; a map of attributes defined in the session.
	 * @since 3.0.7
	 */
	protected transient Map<String, Object> sessionScope;
	/** Implicit Object; a map of attributes defined in the web application.
	 * @since 3.0.7
	 */
	protected transient Map<String, Object> applicationScope;
	/** Implicit Object; a map of attributes defined in the request.
	 * @since 3.0.7
	 */
	protected transient Map<String, Object> requestScope;
	/** Implicit Object; the current execution.
	 * @since 3.0.7
	 */
	protected transient Execution execution;
	/** Implicit Object; the arg argument passed to the createComponents method. It is never null.
	 * @since 3.0.8
	 */
	protected transient Map<?, ?> arg;
	/** Implicit Object; the param argument passed from the http request.
	 * @since 3.6.1
	 */
	protected transient Map<String, String[]> param;

	/** The separator used to separate the component ID and event name.
	 * By default, it is '$'. For Groovy and other environment that '$'
	 * is not applicable, you can specify '_'.
	 */
	protected /*final*/ char _separator;
	/** Indicates whether to ignore variables defined in zscript when wiring
	 * a member.
	 */
	private /*final*/ boolean _ignoreZScript; //don't make it final ZK Grails depends on it
	/** Indicates whether to ignore variables defined in variable resolver
	 * ({@link Page#addVariableResolver}) when wiring a member.
	 */
	private /*final*/ boolean _ignoreXel;

	/** The default constructor.
	 * <p>It is a shortcut of <code>GenericAutowireComposer('$',
	 * !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.zscript")),
	 * !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.xel")))</code>.
	 * <p>In other words, whether to ignore variables defined in ZSCRIPT and XEL depends
	 * on the library variables called <code>org.zkoss.zk.ui.composer.autowire.zscript</code>
	 * and <code>org.zkoss.zk.ui.composer.autowire.xel</code>.
	 * Furthermore, if not specified, their values are default to <b>false</b>, i.e., 
	 * they shall <t>not</t> be wired (i.e., shall be ignored)
	 * <p>If you want to control whether to wire ZSCRIPT's or XEL's variable
	 * explicitly, you could use
	 * {@link #GenericAutowireComposer(char,boolean,boolean)} instead.
	 *
	 * <h2>Version Difference</h2>
	 * <p>ZK 5.0 and earlier, this constructor is the same as
	 * <code>GenericAutowireComposer('$', false, false)</code><br/>
	 * In other words, it is default to wire (i.e., shall <i>not</i> ignore).
	 */
	protected GenericAutowireComposer() {
		this('$');
	}

	/** Constructor with a custom separator.
	 * The separator is used to separate the component ID and event name.
	 * By default, it is '$'. For Groovy and other environment that '$'
	 * is not applicable, you can specify '_'.
	 * <p>It is a shortcut of <code>GenericAutowireComposer('$',
	 * !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.zscript")),
	 * !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.xel")))</code>.
	 * <p>In other words, whether to ignore variables defined in ZSCRIPT and XEL depends
	 * on the library variables called <code>org.zkoss.zk.ui.composer.autowire.zscript</code>
	 * and <code>org.zkoss.zk.ui.composer.autowire.xel</code>.
	 * Furthermore, if not specified, their values are default to <b>false</b>, i.e., 
	 * they shall <t>not</t> be wired (i.e., shall be ignored)
	 * <p>If you want to control whether to wire ZSCRIPT's or XEL's variable
	 * explicitly, you could use
	 * {@link #GenericAutowireComposer(char,boolean,boolean)} instead.
	 *
	 * <h2>Version Difference</h2>
	 * <p>ZK 5.0 and earlier, this constructor is the same as
	 * <code>GenericAutowireComposer('$', false, false)</code><br/>
	 * In other words, it is default to wire (i.e., shall <i>not</i> ignore).
	 * @param separator the separator used to separate the component ID and event name.
	 * Refer to {@link #_separator} for details.
	 * @since 3.6.0
	 */
	protected GenericAutowireComposer(char separator) {
		initIgnores();
		_separator = separator;
		_ignoreZScript = _sIgnoreZScript;
		_ignoreXel = _sIgnoreXel;
	}

	/** Constructors with full control, including separator, whether to
	 * search zscript and xel variables
	 * @param separator the separator used to separate the component ID and event name.
	 * Refer to {@link #_separator} for details.
	 * @param ignoreZScript whether to ignore variables defined in zscript when wiring
	 * a member.
	 * @param ignoreXel whether to ignore variables defined in variable resolver
	 * ({@link Page#addVariableResolver}) when wiring a member.
	 * @since 5.0.3
	 */
	protected GenericAutowireComposer(char separator, boolean ignoreZScript, boolean ignoreXel) {
		_separator = separator;
		_ignoreZScript = ignoreZScript;
		_ignoreXel = ignoreXel;
	}

	private void initIgnores() {
		if (!_sIgnoreChecked) {
			_sIgnoreZScript = !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.zscript"));
			_sIgnoreXel = !"true".equals(Library.getProperty("org.zkoss.zk.ui.composer.autowire.xel"));
			_sIgnoreChecked = true;
		}
	}

	private static boolean _sIgnoreChecked, _sIgnoreZScript, _sIgnoreXel;

	/** Returns the current page.
	 * @since 5.0.10
	 */
	protected Page getPage() {
		if (self != null) {
			final Page page = self.getPage();
			if (page != null)
				return page;
		}
		return super.getPage();
	}

	/**
	 * Auto wire accessible variables of the specified component into a 
	 * controller Java object; a subclass that 
	 * override this method should remember to call super.doAfterCompose(comp) 
	 * or it will not work.
	 */
	public void doAfterCompose(T comp) throws Exception {
		super.doAfterCompose(comp);

		//wire variables to reference fields (include implicit objects) ASAP
		ConventionWires.wireVariables(comp, this, _separator, _ignoreZScript, _ignoreXel);

		//register event to wire variables just before component onCreate
		comp.addEventListener(1000, "onCreate", new BeforeCreateWireListener());
	}

	private class BeforeCreateWireListener implements SerializableEventListener<CreateEvent> {
		public void onEvent(CreateEvent event) throws Exception {
			//wire variables again so some late created object can be wired in(e.g. DataBinder)
			ConventionWires.wireVariables(event.getTarget(), GenericAutowireComposer.this, _separator, _ignoreZScript,
					_ignoreXel);
			//called only once
			event.getTarget().removeEventListener("onCreate", this);
		}
	}

	/** Shortcut to call Messagebox.show(String).
	 * @since 3.0.7 
	 */
	private static Method _alert;

	protected void alert(String m) {
		if ("ajax".equals(Executions.getCurrent().getDesktop().getDeviceType())) {
			//zk.jar cannot depends on zul.jar; thus we call Messagebox.show() via
			//reflection.
			try {
				if (_alert == null) {
					final Class<?> mboxcls = Classes.forNameByThread("org.zkoss.zul.Messagebox");
					_alert = mboxcls.getMethod("show", new Class<?>[] { String.class });
				}
				_alert.invoke(null, new Object[] { m });
				return; //done
			} catch (Throwable ex) {
				log.debug("Failed to invoke org.zkoss.zul.Messagebox", ex);
				//Ignore
			}
		}

		org.zkoss.zk.ui.util.Clients.alert(m);
	}

	//ComponentCloneListener	
	/** Internal use only. Call-back method of CloneComposerListener. You shall 
	 * not call this method directly. Clone this Composer when its applied 
	 * component is cloned.
	 * @param comp the clone of the applied component
	 * @return A clone of this Composer. 
	 * @since 3.5.2
	 */
	public Object willClone(Component comp) {
		try {
			final Execution exec = Executions.getCurrent();
			final int idcode = System.identityHashCode(comp);
			Composer composerClone = (Composer) exec.getAttribute(COMPOSER_CLONE + idcode);
			if (composerClone == null) {
				composerClone = (Composer) Classes.newInstance(getClass(), null);
				exec.setAttribute(COMPOSER_CLONE + idcode, composerClone);

				//cannot call doAfterCompose directly because the clone 
				//component might not be attach to Page yet
				comp.addEventListener(ON_CLONE_DO_AFTER_COMPOSE, new CloneDoAfterCompose());
				Events.postEvent(new Event(ON_CLONE_DO_AFTER_COMPOSE, comp, composerClone));
			}
			return composerClone;
		} catch (Exception ex) {
			throw UiException.Aide.wrap(ex);
		}
	}

	//doAfterCompose, called once after clone
	private static class CloneDoAfterCompose implements SerializableEventListener<Event> {
		@SuppressWarnings("unchecked")
		public void onEvent(Event event) throws Exception {
			final Component clone = event.getTarget();
			final GenericAutowireComposer composerClone = (GenericAutowireComposer) event.getData();
			composerClone.doAfterCompose(clone);
			clone.removeEventListener(ON_CLONE_DO_AFTER_COMPOSE, this);
		}
	}

	//ComponentActivationListener
	public void didActivate(Component comp) {
		//wire variables to reference fields (include implicit objects)

		//Note: we have to check _applied because application might store
		//the composer somewhere other than the original component
		if (comp != null && Objects.equals(comp.getUuid(), _applied)) {
			if (self == null) { //Bug #2873310. didActivate only once
				ConventionWires.wireImplicit(comp, this); //Bug ZK-546. Shall re-wire transient implicit variables only
			}
		}

		//feature #ZK-2822
		ConventionWires.wireServiceCommand(comp, this);
	}

	public void willPassivate(Component comp) {
	}
}
