/* AuResponse.java

	Purpose:
		
	Description:
		
	History:
		Tue May 31 11:35:49     2005, Created by tomyeh

Copyright (C) 2005 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.au;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.zkoss.json.JSONArray;
import org.zkoss.json.JSONAware;
import org.zkoss.json.JSONs;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;

/**
 * A response sent from the server to the client via
 * {@link org.zkoss.zk.ui.sys.UiEngine}.
 *
 * <p>Application developers rarely need access this class and its derived
 * directly.
 * Rather, use {@link org.zkoss.zk.ui.util.Clients} instead.
 * If you prefer to use the derives directly, you can use them with
 * {@link org.zkoss.zk.ui.Execution#addAuResponse}.
 *
 * @author tomyeh
 */
public class AuResponse {
	/** ZK-Error (5501) indicating the request is out-of-sequence.
	 * ZK-Error is returned as the response header of a failed AU request.
	 * @since 5.0.4
	 */
	public static final int SC_OUT_OF_SEQUENCE = 5501;
	/** ZK-Error (5502) indicating the request is aborted, because it
	 * is waiting too long for the completion of the previous request.
	 * ZK-Error is returned as the response header of a failed AU request.
	 * @since 6.5.2
	 */
	public static final int SC_ACTIVATION_TIMEOUT = 5502;

	protected String _cmd;
	private final Object _depends;
	/** Either String or DeferredValue. */
	protected Object[] _data;

	/** Constructs a component-independent response.
	 * @since 5.0.0 (becomes public)
	 */
	public AuResponse(String cmd) {
		this(cmd, (Component) null, (Object[]) null);
	}

	/** Constructs a component-independent response.
	 *
	 * @param data the data. It can be null, String, Date,
	 * and any kind of objects that
	 * the client accepts (marshaled by JSON).
	 * @since 5.0.0 (becomes public)
	 */
	public AuResponse(String cmd, Object data) {
		this(cmd, (Component) null, data);
	}

	/** Constructs a component-independent response.
	 * @since 5.0.0 (becomes public)
	 */
	public AuResponse(String cmd, Object[] data) {
		this(cmd, (Component) null, data);
	}

	/** Constructs a response with one or zero data.
	 *
	 * @param depends specifies whether this response depends on whether
	 * the depends component.
	 * If depends is not null, this response shall be purged if the depends
	 * component is removed.
	 * If null, this response is called component-independent, and
	 * always sent to the client.
	 *
	 * <p>Note: info of the depends component doesn't send to the client.
	 * It is used only to optimize what responses to send.
	 *
	 * @param data specifies the data to be sent. If null, no data at all.
	 * @since 5.0.0 (becomes public)
	 */
	public AuResponse(String cmd, Component depends, Object data) {
		this(cmd, depends, data != null ? new Object[] { data } : null);
	}

	/** Constructs a response with multiple data.
	 * @since 5.0.0 (becomes public)
	 */
	public AuResponse(String cmd, Component depends, Object[] data) {
		if (cmd == null || cmd.length() == 0)
			throw new IllegalArgumentException("cmd");
		_cmd = cmd;
		_depends = depends;
		_data = data;
	}

	/** Constructs a response with single data.
	 * @since 5.0.0 (becomes public)
	 */
	public AuResponse(String cmd, Page depends, Object data) {
		this(cmd, depends, data != null ? new Object[] { data } : null);
	}

	/** Constructs a response with multiple data.
	 * @param data an array of data (null to ignore).
	 * Each element must be an instance of String, {@link DeferredValue}
	 * or null.
	 * @exception IllegalArgumentException if an element of data
	 * is neither String nor DeferredValue.
	 * @since 5.0.0 (becomes public)
	 */
	public AuResponse(String cmd, Page depends, Object[] data) {
		if (cmd == null || cmd.length() == 0)
			throw new IllegalArgumentException("cmd");
		_cmd = cmd;
		_depends = depends;
		_data = data;
	}

	/** Returns the command of this response (never null).
	 */
	public String getCommand() {
		return _cmd;
	}

	/** Returns the evaluated result of the associated data of
	 * this response (might be null).
	 * 
	 * <p>Note: when this method is called, {@link DeferredValue}
	 * will be evaluated. Thus, don't call it until the rendering phase.
	 * If you want to access it other than the render phase,
	 * use {@link #getRawData} instead.
	 *
	 * <p>Note: it is a copy, so any modification to it won't affect
	 * the data of this response.
	 *
	 * @see #getRawData
	 * @since 5.0.0
	 */
	public List<Object> getEncodedData() {
		if (_data == null)
			return null;

		final JSONArray encdata = new JSONArray();
		for (int j = 0; j < _data.length; ++j) {
			Object d = _data[j];
			if (d instanceof DeferredValue)
				d = ((DeferredValue) d).getValue();
			if (d instanceof Component)
				d = new JSONComponent((Component) d);
			if (d instanceof Date)
				d = new JSONDate((Date) d);
			encdata.add(d);
		}
		return encdata;
	}

	/** Returns the associated data of this response in the original
	 * format (might be null).
	 * <p>Note: it is a readonly array. Don't change its value.
	 * @see #getEncodedData
	 * @since 3.0.5
	 */
	public Object[] getRawData() {
		return _data;
	}

	/** Returns the component or page that this response depends on.
	 * If it is not null and the depends component/page is removed,
	 * this response shall be removed, too.
	 *
	 * <p>Note: the returned object is either a {@link Component} or a
	 * {@link Page}.
	 */
	public final Object getDepends() {
		return _depends;
	}

	/** Returns the override key. If null, this response will be appended.
	 * If not null, it overrides the previous response, if any, with
	 * the same override key and the same depends ({@link #getDepends}).
	 * <p>Default: return null.
	 * The derived class shall override this method if it prefers to send
	 * the last response in the same category.
	 * <p>Notice that if depends is null and the override key is not null,
	 * they are sharing a single namespace of an execution.
	 * <p>Notice that if {@link org.zkoss.zk.ui.AbstractComponent#response(String,AuResponse)}
	 * is called, this override key is ignored (and the first argument of
	 * the invocation is used).
	 * @since 5.0.2
	 */
	public String getOverrideKey() {
		return null;
	}

	//-- Object --//
	public int hashCode() {
		int hash = 1;
		if (_data != null) {
			hash = 31 * hash + Arrays.hashCode(_data);
		}
		if (_cmd != null) {
			hash = 31 * hash + _cmd.hashCode();
		}
		return hash;
	}

	public final boolean equals(Object o) { //prevent override
		return this == o;
	}

	public String toString() {
		//Don't call getCommand and getData since it causes
		//AuSetDeferredAttribute to evaluate the deferred value
		final StringBuffer sb = new StringBuffer(60).append("[cmd=").append(_cmd);
		if (_data != null && _data.length > 0) {
			sb.append(", data0=").append(_data[0]);
			if (_data.length > 1) {
				sb.append(", data1=").append(trimOutput(_data[1]));
				if (_data.length > 2)
					sb.append(", data2=").append(trimOutput(_data[2]));
			}
		}
		return sb.append(']').toString();
	}

	private static String trimOutput(Object data) {
		String s = Objects.toString(data);
		if (s == null)
			return null;
		s = s.trim();
		return s.length() <= 36 ? s : s.substring(0, 36) + "...";
	}

	protected static class JSONDate implements JSONAware {
		private final Date _d;

		public JSONDate(Date d) {
			_d = d;
		}

		public String toJSONString() {
			return "jq.j2d('" + JSONs.d2j(_d) + "')";
		}
	}

	protected static class JSONComponent implements JSONAware {
		private final Component _comp;

		public JSONComponent(Component comp) {
			_comp = comp;
		}

		public String toJSONString() {
			return _comp.getPage() == null ? "null" : "{$u:'" + _comp.getUuid() + "'}";
		}
	}
}
