/* Listitem.java

	Purpose:
		
	Description:
		
	History:
		Wed Jun 15 17:38:52     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.zul;

import java.io.Serializable;
import java.util.HashMap;

import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.sys.BooleanPropertyAccess;
import org.zkoss.zk.ui.sys.IntegerPropertyAccess;
import org.zkoss.zk.ui.sys.PropertyAccess;
import org.zkoss.zul.impl.LoadStatus;
import org.zkoss.zul.impl.XulElement;

/**
 * A list item.
 *
 * <p>Default {@link #getZclass}: z-listitem (since 5.0.0)
 *
 * @author tomyeh
 */
public class Listitem extends XulElement {

	private transient Object _value;
	/** The index in the parent (only for implementation purpose). */
	private int _index = -1; //no parent at beginning
	private boolean _selected, _disabled, _selectable = true;
	/** whether the content of this item is loaded; used if
	 * the listbox owning this item is using a list model.
	 */
	private boolean _loaded;

	public Listitem() {
	}

	public Listitem(String label) {
		setLabel(label);
	}

	public <T> Listitem(String label, T value) {
		setLabel(label);
		setValue(value);
	}

	/** Returns the list box that it belongs to.
	 * <p>It is the same as {@link #getParent}.
	 */
	public Listbox getListbox() {
		return (Listbox) getParent();
	}

	/**
	 * Returns the listgroup that this item belongs to, or null.
	 * @since 3.5.0
	 */
	public Listgroup getListgroup() {
		final Listbox lb = getListbox();
		if (lb != null)
			return lb.getListgroupAt(getIndex());
		return null;
	}

	public String getZclass() {
		return _zclass == null ? "z-listitem" : _zclass;
	}

	/**
	 * Returns whether it is selectable.
	 * <p>Default: true.</p>
	 * @since 8.0.0
	 */
	public boolean isSelectable() {
		return _selectable;
	}

	/** Sets whether it is selectable.
	 *
	 * <p>If the listbox is in a checkmark mode, the selectable state will affect
	 * the checkable icon to display or not.</p>
	 * <p>Default: true.</p>
	 * @param selectable
	 */
	public void setSelectable(boolean selectable) {
		if (_selectable != selectable) {
			_selectable = selectable;

			// non-checkable cannot be selected
			if (!_selectable)
				setSelected(false);
			smartUpdate("selectable", selectable);
		}
	}

	/** Returns the maximal length of each item's label.
	 * It is a shortcut of getParent().getMaxlength();
	 * Thus, it works only if the listbox's mold is "select".
	 */
	public int getMaxlength() {
		final Listbox listbox = getListbox();
		return listbox != null ? listbox.getMaxlength() : 0;
	}

	/** Returns the value.
	 * <p>Default: null.
	 * <p>Note: the value is application dependent, you can place
	 * whatever value you want.
	 * <p>If you are using listitem with HTML Form (and with
	 * the name attribute), it is better to specify a String-typed
	 * value.
	 */
	@SuppressWarnings("unchecked")
	public <T> T getValue() {
		return (T) _value;
	}

	/** Sets the value.
	 * @param value the value.
	 * <p>Note: the value is application dependent, you can place
	 * whatever value you want.
	 * <p>If you are using listitem with HTML Form (and with
	 * the name attribute), it is better to specify a String-typed
	 * value.
	 */
	public <T> void setValue(T value) {
		if (!Objects.equals(_value, value)) {
			_value = value;

			final Listbox listbox = getListbox();
			if (listbox != null)
				if (listbox.inSelectMold())
					smartUpdate("value", _value);
				else if (listbox.getName() != null)
					smartUpdate("value", _value);
		}
	}

	/** Returns whether it is disabled.
	 * <p>Default: false.
	 */
	public boolean isDisabled() {
		return _disabled;
	}

	/** Sets whether it is disabled.
	 */
	public void setDisabled(boolean disabled) {
		if (_disabled != disabled) {
			_disabled = disabled;
			smartUpdate("disabled", _disabled);
		}
	}

	/** Returns whether it is selected.
	 * <p>Default: false.
	 */
	public boolean isSelected() {
		return _selected;
	}

	/** Sets whether it is selected.
	 */
	public void setSelected(boolean selected) {
		if (_selected != selected) {
			final Listbox listbox = (Listbox) getParent();
			if (listbox != null) {
				//Note: we don't update it here but let its parent does the job
				listbox.toggleItemSelection(this);
			} else {
				_selected = selected;
			}
		}
	}

	/** Returns the label of the {@link Listcell} it contains, or null
	 * if no such cell.
	 */
	public String getLabel() {
		final Listcell cell = (Listcell) getFirstChild();
		return cell != null ? cell.getLabel() : null;
	}

	/** Sets the label of the {@link Listcell} it contains.
	 *
	 * <p>If it is not created, we automatically create it.
	 */
	public void setLabel(String label) {
		autoFirstCell().setLabel(label);
	}

	private Listcell autoFirstCell() {
		Listcell cell = (Listcell) getFirstChild();
		if (cell == null) {
			cell = new Listcell();
			cell.applyProperties();
			cell.setParent(this);
		}
		return cell;
	}

	/** Returns the image of the {@link Listcell} it contains.
	 */
	public String getImage() {
		final Listcell cell = (Listcell) getFirstChild();
		return cell != null ? cell.getImage() : null;
	}

	/** Sets the image of the {@link Listcell} it contains.
	 *
	 * <p>If it is not created, we automatically create it.
	 */
	public void setImage(String image) {
		autoFirstCell().setImage(image);
	}

	/** Returns the index of this item (a.k.a., the order in the listbox).
	 */
	public int getIndex() {
		return _index;
	}

	/** Sets whether the content of this item is loaded; used if
	 * the listbox owning this item is using a list model.
	 */
	/*package*/ void setLoaded(boolean loaded) {
		if (loaded != _loaded) {
			_loaded = loaded;

			final Listbox listbox = getListbox();
			if (listbox != null && listbox.getModel() != null)
				smartUpdate("_loaded", _loaded);
		}
	}

	/** Returns whether the content of this item is loaded.
	 * It is meaningful only if {@link #getListbox} is live data,
	 * i.e., {@link Listbox#getModel} is not null.
	 *
	 * @since 2.4.0
	 */
	public boolean isLoaded() {
		return _loaded;
	}

	//-- Utilities for implementation only (called by Listbox) */
	/*package*/ void setIndexDirectly(int index) {
		setIndex(index);
	}

	protected void setIndex(int index) {
		_index = index;
	}

	/*package*/ void setSelectedDirectly(boolean selected) {
		_selected = selected;
	}

	public boolean setVisible(boolean visible) {
		if (isVisible() == visible)
			return visible;

		final boolean result = super.setVisible(visible);
		final Listbox listbox = (Listbox) getParent();
		if (listbox != null) {
			if (listbox.inSelectMold())
				listbox.invalidate();
			final Listgroup g = listbox.getListgroupAt(getIndex());
			if (g == null || g.isOpen())
				listbox.addVisibleItemCount(visible ? 1 : -1);
		}

		// Bug ZK-485, send after invoking listbox.addVisibleItemCount()
		smartUpdate("visible", visible);
		return result;
	}

	protected void smartUpdate(String name, Object value) { //make it accessible in this package
		Listbox box = getListbox();
		if (isVisible() || box == null || !box.inSelectMold())
			super.smartUpdate(name, value);
	}

	protected void smartUpdate(String name, boolean value) { //make it accessible in this package
		Listbox box = getListbox();
		if (isVisible() || box == null || !box.inSelectMold())
			super.smartUpdate(name, value);
	}

	protected void smartUpdate(String name, int value) {
		Listbox box = getListbox();
		if (isVisible() || box == null || !box.inSelectMold())
			super.smartUpdate(name, value);
	}

	public String getMold() {
		return getParent() != null ? "select".equals(getParent().getMold()) ? "select" : super.getMold()
				: super.getMold();
	}

	/**
	 * @deprecated as of release 6.0.0. To control the size of Listbox related 
	 * components, please refer to {@link Listbox} and {@link Listheader} instead.
	 */
	public void setWidth(String width) {
		// Don't need to remove this method, it's used to override super.setWidth();
	}

	/**
	 * @deprecated as of release 6.0.0. To control the size of Listbox related 
	 * components, please refer to {@link Listbox} and {@link Listheader} instead.
	 */
	public void setHflex(String flex) {
		// Don't need to remove this method, it's used to override super.setHflex();
	}

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

		render(renderer, "selected", isSelected());
		render(renderer, "disabled", isDisabled());
		render(renderer, "_loaded", _loaded ? _loaded : getListbox().getModel() == null);
		renderer.render("_index", _index);

		if (_value instanceof String) {
			render(renderer, "value", _value);
		}

		if (!isSelectable())
			renderer.render("checkable", false);
	}

	protected void addMoved(Component oldparent, Page oldpg, Page newpg) {
		if (oldparent == null || !((Listbox) oldparent).isLoadingModel()) {
			super.addMoved(oldparent, oldpg, newpg);
		}
	}

	private static HashMap<String, PropertyAccess> _properties = new HashMap<String, PropertyAccess>(2);

	static {
		_properties.put("_loaded", new BooleanPropertyAccess() {
			public void setValue(Component cmp, Boolean loaded) {
				((Listitem) cmp)._loaded = loaded;
			}

			public Boolean getValue(Component cmp) {
				return ((Listitem) cmp)._loaded;
			}
		});
		_properties.put("_index", new IntegerPropertyAccess() {
			public void setValue(Component cmp, Integer index) {
				((Listitem) cmp)._index = index;
			}

			public Integer getValue(Component cmp) {
				return ((Listitem) cmp)._index;
			}
		});
	}

	public PropertyAccess getPropertyAccess(String prop) {
		PropertyAccess pa = _properties.get(prop);
		if (pa != null)
			return pa;
		return super.getPropertyAccess(prop);
	}

	//-- Component --//
	public void beforeParentChanged(Component parent) {
		if (parent != null && !(parent instanceof Listbox))
			throw new UiException("Listitem's parent must be Listbox, not " + parent);
		super.beforeParentChanged(parent);
	}

	public void beforeChildAdded(Component child, Component refChild) {
		if (!(child instanceof Listcell))
			throw new UiException("Unsupported child for listitem: " + child);
		super.beforeChildAdded(child, refChild);
	}

	// -- Serializable --//
	private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
		s.defaultWriteObject();

		if (_value instanceof Serializable) {
			s.writeBoolean(true);
			s.writeObject(_value);
		} else {
			s.writeBoolean(false);
		}
	}

	private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
		s.defaultReadObject();
		if (s.readBoolean())
			_value = s.readObject();
	}

	//Clone//
	public Object clone() {
		final Listitem clone = (Listitem) super.clone();
		clone._index = -1;
		//note: we have to reset, since listbox.insertBefore assumes
		//that a parent-less listitem's index is -1
		return clone;
	}

	//-- ComponentCtrl --//
	public Object getExtraCtrl() {
		return new ExtraCtrl();
	}

	/** A utility class to implement {@link #getExtraCtrl}.
	 * It is used only by component developers.
	 */
	protected class ExtraCtrl extends XulElement.ExtraCtrl implements LoadStatus {
		//-- LoadStatus --//
		public boolean isLoaded() {
			return Listitem.this.isLoaded();
		}

		public void setLoaded(boolean loaded) {
			Listitem.this.setLoaded(loaded);
		}

		public void setIndex(int index) {
			Listitem.this.setIndex(index);
		}
	}

}
