/* A.java

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		Sep 22, 2009 4:05:38 PM , Created by jumperchen
}}IS_NOTE

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

{{IS_RIGHT
}}IS_RIGHT
*/
package org.zkoss.zul;

import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.impl.LabelImageElement;

/**
 * The same as HTML A tag.
 * <p>Default {@link #getZclass}: z-a.
 * @author jumperchen
 * @since 5.0.0
 */
public class A extends LabelImageElement implements org.zkoss.zk.ui.ext.Disable {
	private AuxInfo _auxinf;

	static {
		addClientEvent(A.class, Events.ON_FOCUS, CE_DUPLICATE_IGNORE);
		addClientEvent(A.class, Events.ON_BLUR, CE_DUPLICATE_IGNORE);
	}

	public A() {
	}

	public A(String label) {
		super(label);
	}

	public A(String label, String image) {
		super(label, image);
	}

	/** Returns whether it is disabled.
	 * <p>Default: false.
	 */
	public boolean isDisabled() {
		return _auxinf != null && _auxinf.disabled;
	}

	/** Sets whether it is disabled.
	 */
	public void setDisabled(boolean disabled) {
		if ((_auxinf != null && _auxinf.disabled) != disabled) {
			initAuxInfoForA().disabled = disabled;
			smartUpdate("disabled", isDisabled());
		}
	}

	/** Returns a list of component IDs that shall be disabled when the user
	 * clicks this anchor.
	 * @since 7.0.2
	 */
	public String getAutodisable() {
		return _auxinf != null ? _auxinf.autodisable : null;
	}

	/** Sets a list of component IDs that shall be disabled when the user
	 * clicks this anchor.
	 *
	 * <p>To represent the anchor itself, the developer can specify <code>self</code>.
	 * For example, <code>&lt;a id="ok" autodisable="self,cancel"/&gt;</code>
	 * is the same as <code>&lt;a id="ok" autodisable="ok,cancel"/&gt;</code>
	 * that will disable
	 * both the ok and cancel anchor when an user clicks it.
	 *
	 * <p>The anchor being disabled will be enabled automatically
	 * once the client receives a response from the server.
	 * In other words, the server doesn't notice if a anchor is disabled
	 * with this method.
	 *
	 * <p>However, if you prefer to enable them later manually, you can
	 * prefix with '+'. For example,
	 * <code>&lt;a id="ok" autodisable="+self,+cancel"/&gt;</code>
	 *
	 * <p>Then, you have to enable them manually such as
	 * <pre><code>if (something_happened){
	 *  ok.setDisabled(false);
	 *  cancel.setDisabled(false);
	 *</code></pre>
	 *
	 * <p>Default: null.
	 * @since 7.0.2
	 */
	public void setAutodisable(String autodisable) {
		if (!Objects.equals(_auxinf != null ? _auxinf.autodisable : null, autodisable)) {
			initAuxInfoForA().autodisable = autodisable;
			smartUpdate("autodisable", getAutodisable());
		}
	}

	/** Returns the direction.
	 * <p>Default: "normal".
	 */
	public String getDir() {
		return _auxinf != null ? _auxinf.dir : "normal";
	}

	/** Sets the direction to layout with image.
	 * @param dir either "normal" or "reverse".
	 */
	public void setDir(String dir) throws WrongValueException {
		if (!"normal".equals(dir) && !"reverse".equals(dir))
			throw new WrongValueException(dir);

		if (!Objects.equals(_auxinf != null ? _auxinf.dir : "normal", dir)) {
			initAuxInfoForA().dir = dir;
			smartUpdate("dir", getDir());
		}
	}

	/** Returns the href that the browser shall jump to, if an user clicks
	 * this button.
	 * <p>Default: null. If null, the button has no function unless you
	 * specify the onClick event listener.
	 * <p>If it is not null, the onClick event won't be sent.
	 */
	public String getHref() {
		return _auxinf != null ? _auxinf.href : null;
	}

	/** Sets the href.
	 */
	public void setHref(String href) {
		if (href != null && href.length() == 0)
			href = null;
		if (!Objects.equals(_auxinf != null ? _auxinf.href : null, href)) {
			initAuxInfoForA().href = href;
			smartUpdate("href", new EncodedHref()); //Bug 1850895
		}
	}

	/** Returns the target frame or window.
	 *
	 * <p>Note: it is useful only if href ({@link #setHref}) is specified
	 * (i.e., use the onClick listener).
	 *
	 * <p>Default: null.
	 */
	public String getTarget() {
		return _auxinf != null ? _auxinf.target : null;
	}

	/** Sets the target frame or window.
	 * @param target the name of the frame or window to hyperlink.
	 */
	public void setTarget(String target) {
		if (target != null && target.length() == 0)
			target = null;

		if (!Objects.equals(_auxinf != null ? _auxinf.target : null, target)) {
			initAuxInfoForA().target = target;
			smartUpdate("target", getTarget());
		}
	}

	private String getEncodedHref() {
		final Desktop dt = getDesktop();
		return _auxinf != null && _auxinf.href != null && dt != null ? dt.getExecution().encodeURL(_auxinf.href) : null;
		//if desktop is null, it doesn't belong to any execution
	}

	//-- super --//
	protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws java.io.IOException {
		super.renderProperties(renderer);

		String s;
		if (!"normal".equals(s = getDir()))
			render(renderer, "dir", s);

		render(renderer, "disabled", isDisabled());
		render(renderer, "autodisable", getAutodisable());
		final String href;
		render(renderer, "href", href = getEncodedHref());
		render(renderer, "target", getTarget());

		org.zkoss.zul.impl.Utils.renderCrawlableA(href, getLabel());
	}

	@Override
	protected void renderCrawlable(String label) throws java.io.IOException {
		//does nothing since generated in renderProperties
	}

	private AuxInfo initAuxInfoForA() {
		if (_auxinf == null)
			_auxinf = new AuxInfo();
		return _auxinf;
	}

	//Component//
	@Override
	public String getZclass() {
		return _zclass != null ? _zclass : "z-a";
	}

	private class EncodedHref implements org.zkoss.zk.au.DeferredValue {
		public Object getValue() {
			return getEncodedHref();
		}
	}

	private static class AuxInfo implements java.io.Serializable, Cloneable {
		private String dir = "normal";
		private String href, target;
		private String autodisable;
		protected String upload;
		private boolean disabled;

		public Object clone() {
			try {
				return super.clone();
			} catch (CloneNotSupportedException e) {
				throw new InternalError();
			}
		}
	}
}
