/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.sets;

import java.util.ArrayList;
import java.util.Collection;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.BinaryOperator;

/**
 * This is extension of Set interface with support for storing values more than once.
 *
 * In some sense the whole idea looks strange, because "set" is a collection with unique elements.
 * But in practice it is usefull because it allows for duplicated values and in the same time offers all
 * performance characteristics of Set.
 *
 * It is complementary to MultiMap.
 *
 * @param <K> K
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public interface MultiSet<K> extends Set<K> {

    /**
     * Empty MultiSet
     */
    @SuppressWarnings("rawtypes")
    MultiSet EMPTY = new SetUtils.EmptyMS();

    /**
     * Returns view with all values equal to provided key
     * It supports remove operation.
     * It supports add operation (but obviously allows only inserting duplicates).
     *
     * @param key key
     * @return view
     */
    Collection<K> getAll(Object key);

    /**
     * Finds all values equal to key and merges them using fold operator.
     * For example, you can give "max" as fold operator.
     *
     * @param key key
     * @param fold fold
     * @return computed value
     */
    default K get(Object key, BinaryOperator<K> fold) {
        return getAll(key).stream().reduce(fold).orElseThrow(()->new NoSuchElementException());
    }

    /**
     * Removes all values equal to specific key.
     * Returns removed objects
     *
     * @param key key
     * @return values
     */
    default Collection<K> removeAll(Object key) {
        Collection<K> out = new ArrayList<>(getAll(key));
        for(K k : out) {
            remove(k);
        }
        return out;
    }

    /**
     * This method returns new Set with values from this collection.
     * Returned Set won't duplicates.
     *
     * Please note: returned Set in NOT a view.
     *
     * @return new set
     */
    default Set<K> uniqueSet() {
        return new OpenSet<>(this);
    }

    /**
     * Returns empty immutable MultiSet
     *
     * @param <K> K
     * @return MultiSet
     */
    @SuppressWarnings("unchecked")
    static <K> MultiSet<K> empty() {
        return EMPTY;
    }
}
