/* AbstractListModelConverter.java

	Purpose:
		
	Description:
		
	History:
		2011/12/12 Created by Dennis Chen

Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.bind.converter.sys;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.zkoss.bind.BindContext;
import org.zkoss.bind.Converter;
import org.zkoss.bind.xel.zel.BindELContext;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zul.GroupsModel;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelArray;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.ListModelMap;
import org.zkoss.zul.ListModelSet;
import org.zkoss.zul.ext.Selectable;
import org.zkoss.zul.impl.GroupsListModel;

/**
 * The abstract {@link Converter} implementation for converting collection to ListModel and vice versa.
 * @author dennis
 * @since 6.0.0
 */
public abstract class AbstractListModelConverter<C extends Component>
		implements Converter<Object, Object, C>, Serializable {

	private static final long serialVersionUID = 201108171744L;

	/**
	 * @param comp the component that has listmodel
	 * @return null if no list model for the component
	 */
	protected abstract ListModel<?> getComponentModel(C comp);

	/**
	 * post processing the wrapped model. default return original one
	 * @param ctx the context
	 * @param comp the component of the converter
	 * @param model the wrapped model
	 * @return the list model
	 */
	protected ListModel<?> handleWrappedModel(BindContext ctx, C comp, ListModel<?> model) {
		return model;
	}

	/** Convert a Set, Map, List, Object[], Enum, or other kind of ListModel to associated {@link ListModel}.
	 * @param val must be instanceof Set, Map, List, Object[], Enum Class, or other kind of ListModel implementation.
	 * @param comp associated component
	 * @param ctx bind context
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Object coerceToUi(Object val, C comp, BindContext ctx) {
		if (val == null) {
			val = new ArrayList();
		}
		ListModel model = null;
		if (val instanceof ListModel) {
			BindELContext.addModel(comp, val); //ZK-758. @see AbstractRenderer#addItemReference
			return val;
		} else if (val instanceof Set) {
			model = new ListModelSet((Set) val, false); //ZK-1528, doesn't use live
		} else if (val instanceof List) {
			//ZK-1528, doesn't use live, 
			//user should use ListModel if he want to dynamically change inside element.
			model = new ListModelList((List) val, false);
		} else if (val instanceof Map) {
			model = new ListModelMap((Map) val, false); //ZK-1528, doesn't use live
		} else if (val instanceof Object[]) {
			model = new ListModelArray((Object[]) val, false); //ZK-1528, doesn't use live
		} else if ((val instanceof Class) && Enum.class.isAssignableFrom((Class) val)) {
			model = new ListModelArray((Object[]) ((Class) val).getEnumConstants(), false); //ZK-1528, doesn't use live
		} else if (val instanceof GroupsModel) { //feature#2866506: Data Binding shall support GroupsModel with Listbox/Grid
			model = GroupsListModel.toListModel((GroupsModel) val);
			return model;
		} else {
			throw new UiException(
					"Expects java.util.Set, java.util.List, java.util.Map, Object[], Enum Class, GroupsModel, or ListModel only. "
							+ val.getClass());
		}
		//		final Container container = new Container(model);

		final ListModel compModel = getComponentModel(comp);
		if (compModel instanceof Selectable) {
			Selectable selectable = ((Selectable) compModel);
			((Selectable) model).setMultiple(selectable.isMultiple());

			//Should we check the contains? it has O(m) issue and O(mn) in array case
			//if not, there is a issue if the new data doesn't contains the selected obj
			//			for(Object selected:container.contains(selectable.getSelection())){
			//				((Selectable) model).addToSelection(selected);
			//			}

			//no need to check contains, user should remove the selection if it is not in current list anymore
			//that could be done by binding to selectedItem or selectedItems
			for (Object selected : selectable.getSelection()) {
				((Selectable) model).addToSelection(selected);
			}

		}
		model = handleWrappedModel(ctx, comp, model);
		BindELContext.addModel(comp, model); //ZK-758. @see AbstractRenderer#addItemReference

		return model;
	}

	/** Convert a {@link ListModel} to Set, Map, List, or ListModel (itself).
	 * @param val must be ListModelSet, ListModelList, ListModelMap, or other kind of ListModel
	 * @param comp associated component
	 * @param ctx bind context
	 */
	@SuppressWarnings("rawtypes")
	public Object coerceToBean(Object val, C comp, BindContext ctx) {
		if (val == null) {
			throw new NullPointerException("value");
		}
		if (val instanceof ListModelSet) {
			return ((ListModelSet) val).getInnerSet();
		} else if (val instanceof ListModelList) {
			return ((ListModelList) val).getInnerList();
		} else if (val instanceof ListModelMap) {
			return ((ListModelMap) val).getInnerMap();
		} else if (val instanceof ListModelArray) {
			return ((ListModelArray) val).getInnerArray();
		} else if (val instanceof GroupsListModel) {
			return ((GroupsListModel) val).getGroupsModel();
		} else if (val instanceof ListModel) {
			return val;
		} else {
			throw new UiException(
					"Expects ListModelSet, ListModelList, ListModelMap, GroupsListModel or ListModel only."
							+ val.getClass());
		}
	}

}
