/* ListitemComparator.java

	Purpose:
		
	Description:
		
	History:
		Thu May 25 21:25:55     2006, Created by tomyeh

Copyright (C) 2006 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.util.Comparator;
import java.util.List;

/**
 * A comparator used to compare {@link Listitem}, if not live data,
 * or the data themselves, if live data.
 *
 * @author tomyeh
 */
public class ListitemComparator implements Comparator, java.io.Serializable {
	/** The listheader (optinal). */
	private final Listheader _header;
	/** Column index. */
	private int _index;
	/** Ascending. */
	private final boolean _asc;
	/** Ignore case. */
	private boolean _igcase;
	/** Compares by value (instead of label) */
	private boolean _byval;
	/** Whether to treat null as the maximum value. */
	private boolean _maxnull;

	/** Compares with {@link Listitem#getValue}.
	 *
	 * <p>It assumes the value returned by {@link Listitem#getValue}
	 * implements Comparable.
	 *
	 * <p>Note: It assumes the ascending order and case-insensitive.
	 * If not, use {@link #ListitemComparator(int, boolean, boolean)}
	 * instead.
	 */
	public ListitemComparator() {
		this(-1, true, true, false, false);
	}

	/** Compares with the column of the specified index.
	 *
	 * <p>0 for the first column, 1 for the second and so on
	 *
	 * <p>Note: -1 for {@link Listitem#getValue} and it assumes
	 * the value implements Comparable.
	 *
	 * <p>Note: It assumes the ascending order, case-insensitive and
	 * comparing the returned values of {@link Listcell#getLabel}.
	 * If not, use {@link #ListitemComparator(int, boolean, boolean, boolean)}
	 * instead.
	 *
	 * <p>A null value is considered as the minimum value.
	 *
	 * @param index which column to compare. If -1, {@link Listitem#getValue}
	 * is used.
	 */
	public ListitemComparator(int index) {
		this(index, true, true, false, false);
	}

	/** Compares with the column of the specified index.
	 *
	 * <p>0 for the first column, 1 for the second and so on
	 *
	 * <p>Note: -1 for {@link Listitem#getValue} and it assumes
	 * the value implements Comparable.
	 *
	 * <p>Note: it compares the returned value of {@link Listcell#getLabel}.
	 * If you want to compare {@link Listcell#getValue}.,
	 * use {@link #ListitemComparator(int, boolean, boolean, boolean)}
	 * instead.
	 *
	 * <p>A null value is considered as the minimum value.
	 *
	 * @param index which column to compare. If -1, {@link Listitem#getValue}
	 * is used.
	 * @param ascending whether to sort as ascending (or descending).
	 * @param ignoreCase whether to sort case-insensitive
	 */
	public ListitemComparator(int index, boolean ascending, boolean ignoreCase) {
		this(index, ascending, ignoreCase, false, false);
	}

	/** Compares with the column of the specified index.
	 *
	 * <p>0 for the first column, 1 for the second and so on
	 *
	 * <p>Note: -1 for {@link Listitem#getValue} and it assumes
	 * the value implements Comparable.
	 *
	 * <p>A null value is considered as the minimum value.
	 *
	 * @param index which column to compare. If -1, {@link Listitem#getValue}
	 * is used.
	 * @param ascending whether to sort as ascending (or descending).
	 * @param ignoreCase whether to sort case-insensitive
	 * @param byValue whether to compare {@link Listcell#getValue}.
	 * If false, it compares {@link Listcell#getLabel}.
	 * If true, it assumes the value returned by {@link Listcell#getValue}
	 * implements Comparable.
	 * It is ignored if the index is -1.
	 */
	public ListitemComparator(int index, boolean ascending, boolean ignoreCase, boolean byValue) {
		this(index, ascending, ignoreCase, byValue, false);
	}

	/** Compares with the column of the specified index.
	 *
	 * <p>0 for the first column, 1 for the second and so on
	 *
	 * <p>Note: -1 for {@link Listitem#getValue} and it assumes
	 * the value implements Comparable.
	 *
	 * @param index which column to compare. If -1, {@link Listitem#getValue}
	 * is used.
	 * @param ascending whether to sort as ascending (or descending).
	 * @param ignoreCase whether to sort case-insensitive
	 * @param byValue whether to compare {@link Listcell#getValue}.
	 * If false, it compares {@link Listcell#getLabel}.
	 * If true, it assumes the value returned by {@link Listcell#getValue}
	 * implements Comparable.
	 * It is ignored if the index is -1.
	 * @param nullAsMax whether to consider null as the maximum value.
	 * If false, null is considered as the minimum value.
	 */
	public ListitemComparator(int index, boolean ascending, boolean ignoreCase, boolean byValue, boolean nullAsMax) {
		_header = null;
		_index = index;
		_asc = ascending;
		_igcase = ignoreCase;
		_byval = byValue;
		_maxnull = nullAsMax;
	}

	/** Compares with the column which the list header is at.
	 *
	 * <p>Note: it compares the returned value of {@link Listcell#getLabel}.
	 * If you want to compare {@link Listcell#getValue}.,
	 * use {@link #ListitemComparator(Listheader, boolean, boolean, boolean)}
	 * instead.
	 *
	 * <p>A null value is considered as the minimum value.
	 *
	 * @param ascending whether to sort as ascending (or descending).
	 * @param ignoreCase whether to sort case-insensitive
	 */
	public ListitemComparator(Listheader header, boolean ascending, boolean ignoreCase) {
		this(header, ascending, ignoreCase, false, false);
	}

	/** Compares with the column which the list header is at.
	 *
	 * <p>A null value is considered as the minimum value.
	 *
	 * @param ascending whether to sort as ascending (or descending).
	 * @param ignoreCase whether to sort case-insensitive
	 * @param byValue whether to compare {@link Listcell#getValue}.
	 * If false, it compares {@link Listcell#getLabel}.
	 */
	public ListitemComparator(Listheader header, boolean ascending, boolean ignoreCase, boolean byValue) {
		this(header, ascending, ignoreCase, byValue, false);
	}

	/** Compares with the column which the list header is at.
	 *
	 * @param ascending whether to sort as ascending (or descending).
	 * @param ignoreCase whether to sort case-insensitive
	 * @param byValue whether to compare {@link Listcell#getValue}.
	 * If false, it compares {@link Listcell#getLabel}.
	 * @param nullAsMax whether to consider null as the maximum value.
	 * If false, null is considered as the minimum value.
	 */
	public ListitemComparator(Listheader header, boolean ascending, boolean ignoreCase, boolean byValue,
			boolean nullAsMax) {
		_header = header;
		_index = -1; //not decided yet
		_asc = ascending;
		_igcase = ignoreCase;
		_byval = byValue;
		_maxnull = nullAsMax;
	}

	/** Returns the listheader that this comparator is associated with, or null
	 * if not available.
	 */
	public Listheader getListheader() {
		return _header;
	}

	/** Returns whether the order is ascending.
	 */
	public boolean isAscending() {
		return _asc;
	}

	/** Returns whether to ignore case.
	 */
	public boolean shallIgnoreCase() {
		return _igcase;
	}

	/** Returns whether to compare the returned value of {@link Listcell#getValue}
	 */
	public boolean byValue() {
		return _byval;
	}

	//Comparator//
	public int compare(Object o1, Object o2) {
		final int index = _index < 0 && _header != null ? _header.getColumnIndex() : _index;

		Object v1, v2;
		if (o1 instanceof Listitem) { //not live data
			final Listitem li1 = (Listitem) o1, li2 = (Listitem) o2;
			if (index < 0) {
				v1 = handleCase((Comparable) li1.getValue());
				v2 = handleCase((Comparable) li2.getValue());
			} else {
				List lcs1 = li1.getChildren();
				if (index >= lcs1.size())
					v1 = null;
				else {
					final Listcell lc = (Listcell) lcs1.get(index);
					v1 = handleCase(_byval ? lc.getValue() : lc.getLabel());
				}
				List lcs2 = li2.getChildren();
				if (index >= lcs2.size())
					v2 = null;
				else {
					final Listcell lc = (Listcell) lcs2.get(index);
					v2 = handleCase(_byval ? lc.getValue() : lc.getLabel());
				}
			}
		} else { //live data
			v1 = handleCase(o1);
			v2 = handleCase(o2);
		}

		if (v1 == null)
			return v2 == null ? 0 : _maxnull ? 1 : -1;
		if (v2 == null)
			return _maxnull ? -1 : 1;
		final int v = compareTo((Comparable) v1, v2);
		return _asc ? v : -v;
	}

	@SuppressWarnings("unchecked")
	private static int compareTo(Comparable v1, Object v2) {
		return v1.compareTo(v2);
	}

	private Object handleCase(Object c) {
		if (_igcase) {
			if (c instanceof String)
				return ((String) c).toUpperCase();
			if (c instanceof Character)
				return new Character(Character.toUpperCase(((Character) c).charValue()));
		}
		return c;
	}

	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (!(o instanceof ListitemComparator))
			return false;
		final ListitemComparator c = (ListitemComparator) o;
		return c._index == _index && c._asc == _asc && c._igcase == _igcase;
	}

	public int hashCode() {
		return _index ^ (_asc ? 1 : 5) ^ (_igcase ? 9 : 3);
	}

	public String toString() {
		return "[Comparator " + _index + "-th col, asc:" + _asc + ']';
	}
}
