/*******************************************************************************
 * Copyright (c) 2010 Neil Bartlett.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Neil Bartlett - initial API and implementation
 *******************************************************************************/
package org.bndtools.utils.collections;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import aQute.lib.collections.MultiMap;

public class CollectionUtils {
    /**
     * Move the selected items down one position in the list.
     *
     * @param list The list of items, which will be altered in-place.
     * @param selectionIndexes The indexes of the items to be moved.
     * @return Whether any items have been moved. For example, would return false if the selected items were already at
     *         the bottom of the list.
     */
    public static <T> boolean moveDown(List<T> list, int[] selectionIndexes) {
        boolean moved = false;

        int size = list.size();
        for (int i = 0; i < size; i++) {
            T item = list.get(i);
            if (arrayContains(selectionIndexes, i)) {
                // Find next unselected item
                int nextUnselected = -1;
                int j = i + 1;
                while (j < size) {
                    if (!arrayContains(selectionIndexes, j)) {
                        nextUnselected = j;
                        break;
                    }
                    j++;
                }
                // Swap with it
                if (nextUnselected != -1) {
                    list.set(i, list.get(nextUnselected));
                    list.set(nextUnselected, item);
                    moved = true;
                }
            }
        }
        return moved;
    }

    /**
     * Move the selected items up one position in the list.
     *
     * @param list The list of items, which will be altered in-place.
     * @param selectionIndexes The indexes of the items to be moved.
     * @return Whether any items have been moved. For example, would return false if the selected items were already at
     *         the top of the list.
     */
    public static <T> boolean moveUp(List<T> list, int[] selectionIndexes) {
        boolean moved = false;

        int size = list.size();
        for (int i = size - 1; i >= 0; i--) {
            T item = list.get(i);
            if (arrayContains(selectionIndexes, i)) {
                // Find next unselected item
                int nextUnselected = -1;
                int j = i - 1;
                while (j >= 0) {
                    if (!arrayContains(selectionIndexes, j)) {
                        nextUnselected = j;
                        break;
                    }
                    j--;
                }
                // Swap with it
                if (nextUnselected != -1) {
                    list.set(i, list.get(nextUnselected));
                    list.set(nextUnselected, item);
                    moved = true;
                }
            }
        }
        return moved;
    }

    private static boolean arrayContains(int[] array, int item) {
        for (int i : array) {
            if (i == item)
                return true;
        }
        return false;
    }

    public static <K, V> MultiMap<V, K> invertMultiMap(MultiMap<K, V> input) {
        MultiMap<V, K> result = new MultiMap<V, K>();

        for (Entry<K, List<V>> inputEntry : input.entrySet()) {
            K inputKey = inputEntry.getKey();
            List<V> inputList = inputEntry.getValue();
            for (V inputVal : inputList) {
                result.add(inputVal, inputKey);
            }
        }

        return result;
    }

    public static <K, V> Map<V, Set<K>> invertMapOfCollection(Map<K, ? extends Collection<V>> mapOfCollection) {
        Map<V, Set<K>> result = new TreeMap<V, Set<K>>();

        for (Entry<K, ? extends Collection<V>> inputEntry : mapOfCollection.entrySet()) {
            K inputKey = inputEntry.getKey();
            Collection<V> inputCollection = inputEntry.getValue();

            for (V inputValue : inputCollection) {
                Set<K> resultSet = result.get(inputValue);
                if (resultSet == null) {
                    resultSet = new TreeSet<K>();
                    result.put(inputValue, resultSet);
                }
                resultSet.add(inputKey);
            }
        }

        return result;
    }

    public static <T> List<T> asList(Object[] array) {
        @SuppressWarnings("unchecked")
        List<T> list = (List<T>) Arrays.asList(array);
        return list;
    }

    public static <T> List<T> asList(Enumeration<T> enumeration) {
        List<T> result = new ArrayList<T>();
        while (enumeration.hasMoreElements())
            result.add(enumeration.nextElement());
        return result;
    }

    public static <T> List<T> newArrayList(Object[] array) {
        List<T> result = new ArrayList<T>(array.length);
        for (Object obj : array) {
            @SuppressWarnings("unchecked")
            T item = (T) obj;
            result.add(item);
        }
        return result;
    }

    public static <T> List<T> flatten(Collection<? extends Collection<T>> listList) {
        int size = 0;
        for (Collection<T> list : listList)
            size += list.size();
        List<T> result = new ArrayList<>(size);
        for (Collection<T> list : listList)
            result.addAll(list);
        return result;
    }

    public static <T> T[] flatten(List<T[]> arrayList) {
        int size = 0;
        for (T[] array : arrayList)
            size += array.length;
        @SuppressWarnings("unchecked")
        T[] result = (T[]) new Object[size];
        int copied = 0;
        for (T[] array : arrayList) {
            System.arraycopy(array, 0, result, copied, array.length);
            copied += array.length;
        }
        return result;
    }

    public static <T> List<T> append(Collection<T> first, Collection<T> second) {
        List<T> result = new ArrayList<>(first.size() + second.size());
        result.addAll(first);
        result.addAll(second);
        return result;
    }

}
