package org.ovirt.engine.ui.common.widget.editor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.text.shared.Renderer;
import com.google.gwt.user.client.ui.ListBox;

/**
 * See ListModelMultipleSelectListBoxEditor for an explanation of how to use the ListBox
 * @see ListModelMultipleSelectListBoxEditor
 */
public class ListModelMultipleSelectListBox<T> extends ListModelListBox<List<T>> implements TakesListValue<T> {
    /**
     * The editor.
     */
    private TakesConstrainedValueListEditor<T> editor;

    /**
     * Internal list that contains the typed values so we can look them up based on the selected indexes of the
     * {@code ListBox}.
     */
    private List<T> typedItemList = new ArrayList<>();

    /**
     * The current selected list of typed values.
     */
    private List<T> selectedList = new ArrayList<>();

    /**
     * The indexes of the acceptable items.
     */
    private List<T> itemIndex = new ArrayList<>();

    /**
     * Constructor
     * @param renderer The rendered used to render the values.
     */
    public ListModelMultipleSelectListBox(Renderer<List<T>> renderer) {
        super(renderer);
        asListBox().setMultipleSelect(true);
    }

    @Override
    public void setValue(List<T> value, boolean fireEvents) {
        List<T> originalValues = new ArrayList<>(selectedItems());
        updateSelectedList();
        if (fireEvents) {
            ValueChangeEvent.fireIfNotEqual(this, originalValues, selectedItems());
        }
    }

    @Override
    public void setListValue(List<T> value) {
        // Reset the current selected values, we will locate the right selected values below.
        selectedList.clear();
        selectedList.addAll(value);
        ListBox listBox = asListBox();
        //Unselect anything set by the 'normal' setValue
        if (listBox.getSelectedIndex() > -1) {
            listBox.setItemSelected(listBox.getSelectedIndex(), false);
        }
        for (T item: selectedList) {
            listBox.setItemSelected(itemIndex.indexOf(item), true);
        }
    }

    /**
     * Update the current selected list based on the state of the actual {@code ListBox}
     */
    private void updateSelectedList() {
        Set<Integer> indexes = new HashSet<Integer>();
        // Reset the current selected values, we will locate the right selected values below.
        selectedList.clear();
        ListBox listBox = asListBox();
        // There is no method available in a ListBox to find all the selected items if multi select
        // is enabled. The suggested method is looping over each item and checking if they are selected.
        // This attempts to optimize the loop by using the getSelectedIndex() to get the first selected
        // item and deselecting it, then at the end re-select them all. This avoids the loop.
        while (listBox.getSelectedIndex() >= 0) {
            int index = listBox.getSelectedIndex();
            listBox.setItemSelected(index, false);
            if (index >= 0 && index < typedItemList.size()) {
                T value = typedItemList.get(index);
                selectedList.add(value);
            }
            // Mark the index, so we can reselect at the end.
            indexes.add(index);
        }
        // Put back the selection.
        for (Integer index : indexes) {
            listBox.setItemSelected(index, true);
        }
    }

    /**
     * Stored the typed value in an internal data structure so we can look it up based on the index.
     * @param value The single valued list containing the value.
     */
    private void addToItems(List<T> value) {
        typedItemList.addAll(value);
    }

    @Override
    public TakesConstrainedValueListEditor<T> asEditor() {
        if (editor == null) {
            editor = TakesConstrainedValueListEditor.ofList(this, this, this);
        }
        return editor;
    }

    @Override
    public void setAcceptableValues(Collection<List<T>> newValues) {
        // Set a value in the super class, so calling setAcceptableValues doesn't add a null value and
        // potentially NPE if the renderer doesn't take kindly to getting a null value passed to it.
        super.setValue(newValues.iterator().next(), false);
        // Populate the list box.
        super.setAcceptableValues(newValues);
        // Store the rendered values so we can reverse them to find the right typed value. This will
        // break if more than one type value renders to the same string.
        itemIndex.clear();
        for (List<T> value: newValues) {
            addToItems(value);
            itemIndex.add(value.get(0));
        }
    }

    /**
     * Getter for the list of selected items.
     * @return The list of typed selected items.
     */
    public List<T> selectedItems() {
        return selectedList;
    }
}

@@ -4127,32 +4127,118 @@
 %3E%3E newValues) %7B%0A
+        if (newValues == null %7C%7C newValues.isEmpty()) %7B%0A            return;%0A        %7D%0A
         // Set a
@@ -4792,16 +4792,17 @@
 T%3E value
+ 
 : newVal

Handle null and empty list values in ListModelMultipleSelectListBox