/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.ajax4jsf.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIOutput;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.component.UISelectMany;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.el.ValueBinding;
import javax.faces.model.SelectItem;

import org.ajax4jsf.Messages;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Maksim Kaszynski
 *
 */
public class SelectUtils {
	
	private static final Log log = LogFactory.getLog(SelectUtils.class);
	
	/**
	 * Gathers all select items from specified component's children
	 * @param context
	 * @param component
	 * @return list of {@link SelectItems} taken from f:selectItem and f:selectItems
	 */
	public static List<SelectItem> getSelectItems(FacesContext context, UIComponent component) {
        ArrayList<SelectItem> list = new ArrayList<SelectItem>();
		Iterator<UIComponent> kids = component.getChildren().iterator();
		while (kids.hasNext()) {
			UIComponent kid = kids.next();
			if (kid instanceof UISelectItem) {
				Object value = ((UISelectItem) kid).getValue();
				if (value == null) {
					UISelectItem item = (UISelectItem) kid;
					list.add(new SelectItem(item.getItemValue(), item.getItemLabel(), item.getItemDescription(), item.isItemDisabled()));
				} else if (value instanceof SelectItem) {
					list.add((SelectItem)value);
				} else {
					String valueClass = (value != null ? "'" + value.getClass().getName() + "'" : "");
					throw new IllegalArgumentException(Messages.getMessage(Messages.INVALID_ATTRIBUTE_VALUE, valueClass, "<selectItem>"));
				}
			} else if (kid instanceof UISelectItems && null != context) {
				Object value = ((UISelectItems) kid).getValue();
				if (value instanceof SelectItem) {
					list.add((SelectItem)value);
				} else if (value instanceof SelectItem[]) {
					SelectItem items[] = (SelectItem[]) value;
					list.addAll(Arrays.asList(items));
				} else if (value instanceof Collection) {
					list.addAll(((Collection<SelectItem>)value));
				} else if (value instanceof Map) {
					Map<Object,Object> map = (Map<Object, Object>) value;
					Set<Entry<Object,Object>> entrySet = map.entrySet();
					for (Entry<Object, Object> entry : entrySet) {
						list.add(new SelectItem(entry.getValue(), entry.getKey().toString(), null));
					}
				} else {
					String valueClass = (value != null ? "'" + value.getClass().getName() + "'" : "");
					throw new IllegalArgumentException(Messages.getMessage(Messages.INVALID_ATTRIBUTE_VALUE, valueClass, "<selectItems>"));
				}
			}
		}
        return list;

	}
	
	/**
	 * Converts UISelectMany submitted value to converted value
	 * 
	 * @author Manfred Geiler
	 * @param facesContext
	 * @param component
	 * @param submittedValue
	 * @return
	 * @throws ConverterException
	 */
	public static Object getConvertedUISelectManyValue(
			FacesContext facesContext, UISelectMany component,
			String[] submittedValue) throws ConverterException {
		// Attention!
		// This code is duplicated in jsfapi component package.
		// If you change something here please do the same in the other class!

		if (submittedValue == null)
			throw new NullPointerException("submittedValue");
		
		ELContext elContext = facesContext.getELContext();
		ValueExpression vb = component.getValueExpression("value");
		Class<?> valueType = null;
		Class<?> arrayComponentType = null;
		if (vb != null) {
			valueType = vb.getType(elContext);
			if (valueType != null && valueType.isArray()) {
				arrayComponentType = valueType.getComponentType();
			}
		}

		Converter converter = component.getConverter();
		if (converter == null) {
			if (valueType == null) {
				// No converter, and no idea of expected type
				// --> return the submitted String array
				return submittedValue;
			}

			if (List.class.isAssignableFrom(valueType)) {
				// expected type is a List
				// --> according to javadoc of UISelectMany we assume that the
				// element type
				// is java.lang.String, and copy the String array to a new List
				List<String> lst = Arrays.asList(submittedValue);
				return lst;
			}

			if (arrayComponentType == null) {
				throw new IllegalArgumentException(Messages.getMessage(Messages.VALUE_BINDING_TYPE_ERROR));
			}

			if (String.class.equals(arrayComponentType))
				return submittedValue; // No conversion needed for String type
			if (Object.class.equals(arrayComponentType))
				return submittedValue; // No conversion for Object class

			try {
				converter = facesContext.getApplication().createConverter(
						arrayComponentType);
			} catch (FacesException e) {
				log.error(Messages.getMessage(Messages.NO_CONVERTER_FOUND_ERROR, arrayComponentType.getName()), e);
				return submittedValue;
			}
		}

		// Now, we have a converter...
		if (valueType == null) {
			// ...but have no idea of expected type
			// --> so let's convert it to an Object array
			int len = submittedValue.length;
			Object[] convertedValues = (Object[]) Array.newInstance(
					arrayComponentType == null ? Object.class
							: arrayComponentType, len);
			for (int i = 0; i < len; i++) {
				convertedValues[i] = getConvertedValue(facesContext, component, submittedValue[i], converter);
			}
			return convertedValues;
		}

		if (List.class.isAssignableFrom(valueType)) {
			// Curious case: According to specs we should assume, that the
			// element type
			// of this List is java.lang.String. But there is a Converter set
			// for this
			// component. Because the user must know what he is doing, we will
			// convert the values.
			int len = submittedValue.length;
			List<Object> lst = new ArrayList<Object>(len);
			for (int i = 0; i < len; i++) {
				Object convertedValue = getConvertedValue(facesContext, component, submittedValue[i], converter);
				if (convertedValue != null) {
				    lst.add(convertedValue);
				}
			}
			return lst;
		}

		if (arrayComponentType == null) {
			throw new IllegalArgumentException(Messages.getMessage(Messages.VALUE_BINDING_TYPE_ERROR));
		}

		if (arrayComponentType.isPrimitive()) {
			// primitive array
			int len = submittedValue.length;
			Object convertedValues = Array.newInstance(arrayComponentType, len);
			for (int i = 0; i < len; i++) {
				Object convertedValue = getConvertedValue(facesContext, component, submittedValue[i], converter);
				if (convertedValue != null) {
				    Array.set(convertedValues, i, convertedValue);
				}
			}
			return convertedValues;
		} else {
			// Object array
			int len = submittedValue.length;
			ArrayList<Object> convertedValues = new ArrayList<Object>(len);
			for (int i = 0; i < len; i++) {
				Object convertedValue = getConvertedValue(facesContext, component, submittedValue[i], converter);
				if (convertedValue != null) {
				    convertedValues.add(i, convertedValue);
				} 
			}
			return convertedValues.toArray((Object[]) Array.newInstance(
					arrayComponentType, len));
		}
	}

    private static Object getConvertedValue(FacesContext context,
            UISelectMany component, String submittedValue, Converter converter) {
        try {
            return converter.getAsObject(context, component, submittedValue);
        } catch (ConverterException e) {
            addConversionErrorMessage(context, component.getClientId(context), 
                    component.getConverterMessage());
        }
        return null;
    }
	
    private static void addConversionErrorMessage(FacesContext context,
            String clientId, String converterMessage) {
        
        if (converterMessage == null) {
            return;
        }
            
        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, converterMessage, converterMessage);
        context.addMessage(clientId, message);
    }

	public static Object getConvertedUIInputValue(
			FacesContext facesContext, UIInput component,
			String submittedValue) throws ConverterException{

		Object convertedValue = null;
		
		/*
		if (submittedValue == null)
			throw new NullPointerException("submittedValue");
		*/
		if(InputUtils.EMPTY_STRING.equals(submittedValue)){
			return null;
		}
		Converter converter = getConverterForProperty(facesContext, component, "value");
		if(converter != null){
			convertedValue = converter.getAsObject(facesContext, component, submittedValue);
		} else {
			convertedValue = submittedValue;
		}
		
		return convertedValue;

	}
	/**
	 * 
	 * @param facesContext
	 * @param component
	 * @param property
	 * @return converter for specified component attribute
	 */
	public static Converter getConverterForProperty(FacesContext facesContext, UIOutput component, String property){
		Converter converter = component.getConverter();
		if(converter == null){
			ValueExpression ve = component.getValueExpression(property);
			if(ve != null){
				Class<?> valueType = ve.getType(facesContext.getELContext());
				if(valueType == null || String.class.equals(valueType) || Object.class.equals(valueType)){
					//No converter needed
				} else {
					converter = facesContext.getApplication().createConverter(valueType);
					if(converter == null){
						throw new ConverterException(Messages.getMessage(Messages.NO_CONVERTER_FOUND_ERROR, valueType.getName()));
					}
				}
			}
		} 
		return converter;
	}
	
}
