/* ListModelELResolver.java

	Purpose:
		
	Description:
		
	History:
		Jan 12, 2012 3:18:34 PM, Created by henrichen

Copyright (C) 2012 Potix Corporation. All Rights Reserved.
*/

package org.zkoss.bind.xel.zel;

import java.beans.FeatureDescriptor;
import java.util.Arrays;
import java.util.Iterator;

import org.zkoss.zel.ELContext;
import org.zkoss.zel.ELException;
import org.zkoss.zel.ELResolver;
import org.zkoss.zel.PropertyNotFoundException;
import org.zkoss.zel.PropertyNotWritableException;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelArray;
import org.zkoss.zul.ListModelList;

/**
 * ELResolver for {@link ListModel}.
 * @author henrichen
 * @since 6.0.0
 */
public class ListModelELResolver extends ELResolver {

	public Object getValue(ELContext context, Object base, Object property)
			throws NullPointerException, PropertyNotFoundException, ELException {
		if (context == null) {
			throw new NullPointerException();
		}

		if (base instanceof ListModel<?>) {
			ListModel<?> listmodel = (ListModel<?>) base;
			Integer idx = coerce(property);
			if (idx == null) { // property is not a legal number format
				return null; // unresolved null
			}
			context.setPropertyResolved(true);
			if (idx >= 0 && idx < listmodel.getSize()) {
				return listmodel.getElementAt(idx);
			}
			//out of range, a resolved null
		}

		return null;
	}

	public Class<?> getType(ELContext context, Object base, Object property)
			throws NullPointerException, PropertyNotFoundException, ELException {
		if (context == null) {
			throw new NullPointerException();
		}

		if (base instanceof ListModel<?>) {
			ListModel<?> listmodel = (ListModel<?>) base;
			Integer idx = coerce(property);
			if (idx == null)
				return null;
			if (idx < 0 || idx >= listmodel.getSize()) {
				throw new PropertyNotFoundException(new ArrayIndexOutOfBoundsException(idx).getMessage());
			}
			context.setPropertyResolved(true);
			return Object.class;
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	public void setValue(ELContext context, Object base, Object property, Object value)
			throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException {
		if (context == null) {
			throw new NullPointerException();
		}

		if (base instanceof ListModel<?>) {
			ListModel<?> listmodel = (ListModel<?>) base;
			Integer idx = coerce(property);
			if (idx == null) { // property is not a legal number format
				return; // unresolved null
			}
			context.setPropertyResolved(true);

			//ZK-1960 save back to listmodel
			if (idx >= 0 && idx < listmodel.getSize()) {
				if (base instanceof ListModelArray) {
					((ListModelArray<Object>) base).set(idx, value);
				} else if (base instanceof ListModelList<?>) {
					((ListModelList<Object>) base).set(idx, value);
				} else {
					throw new PropertyNotWritableException(
							"can't write property " + property + " to ListModel:" + base);
				}
			} else {
				//out of range, should ignore to compatible with old version(when we didn't implement save) or throw exception?
			}
		}
	}

	public boolean isReadOnly(ELContext context, Object base, Object property)
			throws NullPointerException, PropertyNotFoundException, ELException {
		return true;
	}

	public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
		if (base instanceof ListModel<?>) {
			FeatureDescriptor[] descs = new FeatureDescriptor[((ListModel<?>) base).getSize()];
			for (int i = 0; i < descs.length; i++) {
				descs[i] = new FeatureDescriptor();
				descs[i].setDisplayName("[" + i + "]");
				descs[i].setExpert(false);
				descs[i].setHidden(false);
				descs[i].setName("" + i);
				descs[i].setPreferred(true);
				descs[i].setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.FALSE);
				descs[i].setValue(TYPE, Integer.class);
			}
			return Arrays.asList(descs).iterator();
		}
		return null;
	}

	public Class<?> getCommonPropertyType(ELContext context, Object base) {
		if (base instanceof ListModel<?>) { // implies base != null
			return Integer.class;
		}
		return null;
	}

	private static final Integer coerce(Object property) {
		//should only handle a property that is possible a number
		if (property instanceof Number) {
			return ((Number) property).intValue();
		}
		if (property instanceof Character) {
			return (int) ((Character) property).charValue();
		}
		if (property instanceof Boolean) {
			return (((Boolean) property).booleanValue() ? 1 : 0);
		}
		if (property instanceof String) {
			//follow EL spec.: String in number format for Array is number
			try {
				return Integer.parseInt((String) property);
			} catch (NumberFormatException ex) {
				//ignore
			}
		}
		//just ignore other types (especially a string)
		return null;
	}
}
