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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import net.ranides.assira.collection.sets.HashSet;
import net.ranides.assira.generic.CompareUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public interface MultiMap<K,V> extends Map<K,V> {
    
    @SuppressWarnings("rawtypes")
    MultiMap EMPTY = new MapUtils.EmptyMM();
    
    Collection<V> getAll(Object key);
    
    default V get(Object key, BinaryOperator<V> fold) {
        return getAll(key).stream().reduce(fold).orElseThrow(()->new NoSuchElementException());
    }
    
    default boolean containsEntry(Object key, Object value) {
        return getAll(key).contains(value);
    }

    default void putAll(K key, Iterable<? extends V> values) {
        for(V value : values) {
            put(key, value);
        }
    }
    
    @Override
    default V replace(K key, V value) {
        V prev;
        if (((prev = get(key)) != null) || containsKey(key)) {
            removeAll(key);
            put(key, value);
        }
        return prev;
    }
    
    default Collection<V> replaceAll(K key, Collection<V> values) {
        Collection<V> prev = Collections.emptyList();
        if (containsKey(key)) {
            prev = removeAll(key);
            putAll(key, values);
        }
        return prev;
    }
    
    default boolean remove(Object key, Object value) {
        Predicate<Object> ek = CompareUtils.predicate(key);
        Predicate<Object> ev = CompareUtils.predicate(value);
        
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if( ek.test(e.getKey()) && ev.test(e.getValue()) ) {
                i.remove();
                return true;
            }
        }
        return false;
    }
    
    default Collection<V> removeAll(Object key) {
        Collection<V> prev = new ArrayList<>();
        V last;
        while(null != (last=remove(key))) {
            prev.add(last);
        }
        return prev;
    }
    
    default Set<K> uniqueKeySet() {
        return new HashSet<>(keySet());
    }

    @SuppressWarnings("unchecked")
    static <K,V> MultiMap<K,V> empty() {
        return EMPTY;
    }
    
}
