/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.richfaces.ui.select;

import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterators;

import org.richfaces.log.Logger;
import org.richfaces.log.RichfacesLogger;
import org.richfaces.util.RendererUtils;

import javax.el.ValueExpression;
import javax.faces.application.ProjectStage;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * @author Maksim Kaszynski
 */
public final class SelectUtils {
    private static final Logger LOG = RichfacesLogger.APPLICATION.getLogger();

    private static final class GenericObjectSelectItem extends SelectItem {
        private static final long serialVersionUID = -714217221281952395L;
        private static final RendererUtils UTILS = RendererUtils.getInstance();
        private static final String VAR = "var";
        private static final String ITEM_VALUE = "itemValue";
        private static final String ITEM_LABEL = "itemLabel";
        private static final String ITEM_DESCRIPTION = "itemDescription";
        private static final String ITEM_ESCAPED = "itemLabelEscaped";
        private static final String ITEM_DISABLED = "itemDisabled";
        private static final String NO_SELECTION_OPTION = "noSelectionOption";

        private GenericObjectSelectItem() {
        }

        private void updateItem(FacesContext facesContext, UIComponent sourceComponent, Object value) {
            String var = (String) sourceComponent.getAttributes().get(VAR);

            Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
            Object oldVarValue = null;
            if (var != null) {
                oldVarValue = requestMap.put(var, value);
            }
            try {
                Map<String, Object> attrs = sourceComponent.getAttributes();
                Object itemValueResult = attrs.get(ITEM_VALUE);
                Object itemLabelResult = attrs.get(ITEM_LABEL);
                Object itemDescriptionResult = attrs.get(ITEM_DESCRIPTION);

                setValue(itemValueResult != null ? itemValueResult : value);
                setLabel(itemLabelResult != null ? itemLabelResult.toString() : value.toString());
                setDescription(itemDescriptionResult != null ? itemDescriptionResult.toString() : null);
                setEscape(UTILS.isBooleanAttribute(sourceComponent, ITEM_ESCAPED));
                setDisabled(UTILS.isBooleanAttribute(sourceComponent, ITEM_DISABLED));
                setNoSelectionOption(UTILS.isBooleanAttribute(sourceComponent, NO_SELECTION_OPTION));
            } finally {
                if (var != null) {
                    if (oldVarValue != null) {
                        requestMap.put(var, oldVarValue);
                    } else {
                        requestMap.remove(var);
                    }
                }
            }
        }
    }

    private static final class MapItemsIterator extends AbstractIterator<SelectItem> {
        private Iterator<?> data;

        public MapItemsIterator(Map<?, ?> map) {
            super();
            this.data = map.entrySet().iterator();
        }

        @Override
        protected SelectItem computeNext() {
            if (data.hasNext()) {
                Entry<?, ?> next = (Entry<?, ?>) data.next();

                return new SelectItem(next.getValue(), next.getKey().toString());
            }

            return endOfData();
        }
    }

    private static final class GenericItemsIterator extends AbstractIterator<SelectItem> {
        private FacesContext facesContext;
        private UIComponent component;
        private Iterator<?> data;

        public GenericItemsIterator(FacesContext facesContext, UIComponent component, Iterator<?> data) {
            super();
            this.facesContext = facesContext;
            this.component = component;
            this.data = data;
        }

        @Override
        protected SelectItem computeNext() {
            if (data.hasNext()) {
                Object next = data.next();

                if (next instanceof SelectItem) {
                    return (SelectItem) next;
                } else {
                    GenericObjectSelectItem genericItem = new GenericObjectSelectItem();
                    genericItem.updateItem(facesContext, component, next);

                    return genericItem;
                }
            }

            return endOfData();
        }
    }

    private static final class SelectItemsIterator extends AbstractIterator<SelectItem> {
        private Iterator<UIComponent> children;
        private Iterator<SelectItem> items = Iterators.emptyIterator();
        private FacesContext context;

        public SelectItemsIterator(FacesContext context, Iterator<UIComponent> children) {
            this.context = context;
            this.children = children;
        }

        @Override
        protected SelectItem computeNext() {
            while (items.hasNext() || children.hasNext()) {
                if (items.hasNext()) {
                    SelectItem nextItem = items.next();

                    if (!items.hasNext()) {
                        // free iterator
                        items = Iterators.emptyIterator();
                    }

                    return nextItem;
                } else {
                    items = createIterator(children.next());
                }
            }

            return endOfData();
        }

        private Iterator<SelectItem> createUISelectItemIterator(UISelectItem selectItem) {
            Object value = selectItem.getValue();

            SelectItem result = null;

            if (value == null) {
                result = new SelectItem(selectItem.getItemValue(), selectItem.getItemLabel(), selectItem.getItemDescription(),
                    selectItem.isItemDisabled(), selectItem.isItemEscaped(), selectItem.isNoSelectionOption());
            } else if (value instanceof SelectItem) {
                result = (SelectItem) value;
            } else {
                ValueExpression expression = selectItem.getValueExpression("value");
                throw new IllegalArgumentException("ValueExpression '"
                    + (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : "
                    + RichfacesLogger.getComponentPath(selectItem) + " does not reference an Object of type SelectItem");
            }

            return Iterators.singletonIterator(result);
        }

        private Iterator<SelectItem> createSelectItemsIterator(UIComponent component, Object value) {
            if (value == null) {
                return Iterators.emptyIterator();
            } else if (value instanceof SelectItem) {
                return Iterators.singletonIterator((SelectItem) value);
            } else if (value instanceof Object[]) {
                Iterator<Object> data = Iterators.forArray((Object[]) value);
                return new GenericItemsIterator(context, component, data);
            } else if (value instanceof Iterable<?>) {
                Iterator<?> data = ((Iterable<?>) value).iterator();
                return new GenericItemsIterator(context, component, data);
            } else if (value instanceof Map) {
                return new MapItemsIterator((Map<?, ?>) value);
            } else {
                Logger.Level level = Logger.Level.INFO;
                if (!context.isProjectStage(ProjectStage.Production)) {
                    level = Logger.Level.WARNING;
                }
                if (LOG.isLogEnabled(level)) {
                    ValueExpression expression = component.getValueExpression("value");
                    LOG.log(level, String.format("ValueExpression %s of UISelectItems with component-path %s"
                        + " does not reference an Object of type SelectItem," + " array, Iterable or Map, but of type: %s",
                        (expression == null ? null : expression.getExpressionString()),
                        RichfacesLogger.getComponentPath(component), (value == null ? null : value.getClass().getName())));
                }
            }

            return Iterators.emptyIterator();
        }

        private Iterator<SelectItem> createUISelectItemsIterator(UISelectItems selectItems) {
            Object value = selectItems.getValue();
            return createSelectItemsIterator(selectItems, value);

        }

        private Iterator<SelectItem> createUISelectItemsInterfaceIterator(UIComponent component) {
            Object value = ((SelectItemsInterface)component).getItemValues();
            return createSelectItemsIterator(component, value);
        }

        private Iterator<SelectItem> createIterator(UIComponent child) {
            if (!child.isRendered()) {
                return Iterators.emptyIterator();
            }

            if (child instanceof UISelectItem) {
                return createUISelectItemIterator((UISelectItem) child);
            } else if (child instanceof UISelectItems) {
                return createUISelectItemsIterator((UISelectItems) child);
            } else if (child instanceof SelectItemsInterface) {
                return createUISelectItemsInterfaceIterator(child);
            }

            return Iterators.emptyIterator();
        }
    }

    private SelectUtils() {
    }

    /**
     * Gathers all select items from specified component's children
     *
     * @param context Faces context
     * @param component UIComponent with UISelectItem or UISelectItems children
     * @return list of {@link SelectItem} taken from f:selectItem and f:selectItems
     */
    public static Iterator<SelectItem> getSelectItems(FacesContext context, UIComponent component) {
        Iterator<UIComponent> children = component.getChildren().iterator();
        if (component instanceof SelectItemsInterface) {
            Iterator<UIComponent> self = Iterators.singletonIterator(component);
            children = Iterators.concat(self, children);
        }
        Iterator<SelectItem> iterator = new SelectItemsIterator(context, children);
        return iterator;
    }
}
