/*
 * @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.ConcurrentModificationException;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import net.ranides.assira.collection.sets.HashSet;

/**
 * 
 * @author Ranides Atterwim <ranides@gmail.com>
 * @param <K> 
 */
public interface GenericMap<K> {

    int size();

    boolean isEmpty();

    boolean containsKey(GenericKey<K,?> key);

    boolean containsValue(Object value);

    <V> V get(GenericKey<K,V> key);
    
    default <V> Optional<V> getOptional(GenericKey<K,V> key) {
        if(containsKey(key)) {
            return Optional.of(get(key));
        } else {
            return Optional.empty();
        }
    }

    <V> V put(GenericKey<K,V> key, V value);
    
    default <V> V put(GenericKey<K,V> key, Optional<V> value) {
        if(value.isPresent()) {
            return put(key, value.get());
        } else {
            return get(key);
        }
    }

    <V> V remove(GenericKey<K,V> key);

    void putAll(GenericMap<? extends K> m);

    void clear();

    Set<GenericKey<K,?>> keySet();

    Collection<?> values();

    Set<GenericEntry<K>> entrySet();

    @Override
    boolean equals(Object o);

    @Override
    int hashCode();
    
    default <V> V getOrDefault(GenericKey<K,V> key, V ddefault) {
        V v;
        return (((v = get(key)) != null) || containsKey(key)) ? v : ddefault;
    }

    default void forEach(BiConsumer<? super GenericKey<K,?>, Object> action) {
        Objects.requireNonNull(action);
        for (GenericEntry<K> entry : entrySet()) {
            GenericKey<K,?> k;
            Object v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }

    default void replaceAll(BiFunction<? super GenericKey<K,?>, Object, Object> function) {
        Objects.requireNonNull(function);
        for (GenericEntry<K> entry : entrySet()) {
            GenericKey<K,?> k;
            Object v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }

            // ise thrown from function is not a cme.
            v = function.apply(k, v);

            try {
                entry.setValue(k.type().cast(v));
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
        }
    }

    default <V> V putIfAbsent(GenericKey<K,V> key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }

        return v;
    }

    default <V> boolean remove(GenericKey<K,V> key, V value) {
        Object current = get(key);
        if (!Objects.equals(current, value) || (current == null && !containsKey(key))) {
            return false;
        }
        remove(key);
        return true;
    }

    default <V> boolean replace(GenericKey<K,V> key, V oldValue, V value) {
        Object current = get(key);
        if (!Objects.equals(current, oldValue) || (current == null && !containsKey(key))) {
            return false;
        }
        put(key, value);
        return true;
    }

    default <V> V replace(GenericKey<K,V> key, V value) {
        V current;
        if (((current = get(key)) != null) || containsKey(key)) {
            current = put(key, value);
        }
        return current;
    }

    default <V> V computeIfAbsent(GenericKey<K,V> key, Function<? super GenericKey<K,V>, ? extends V> function) {
        Objects.requireNonNull(function);
        V prev;
        if (null == (prev = get(key)) && !containsKey(key)) {
            V value = function.apply(key);
            put(key, value);
            return value;
        }
        return prev;
    }

    default <V> V computeIfPresent(GenericKey<K,V> key, BiFunction<? super GenericKey<K,?>, ? super V, ? extends V> function) {
        Objects.requireNonNull(function);
        V prev;
        if (null != (prev = get(key)) || containsKey(key)) {
            V value = function.apply(key, prev);
            put(key, value);
            return value;
        } else {
            return null;
        }
    }

    default <V> V compute(GenericKey<K,V> key, BiFunction<? super GenericKey<K,V>, ? super V, ? extends V> function) {
        Objects.requireNonNull(function);
        V value = function.apply(key, get(key));
        put(key, value);
        return value;
    }

    default <V> V merge(GenericKey<K,V> key, V value, BiFunction<? super V, ? super V, ? extends V> function) {
        Objects.requireNonNull(function);
        V prev = get(key);
        V newValue = (prev == null) ? value : function.apply(prev, value);
        put(key, newValue);
        return newValue;
    }
    
    <V> Collection<V> getAll(GenericKey<K,V> key);
    
    default <V> V get(GenericKey<K,V> key, BinaryOperator<V> fold) {
        return getAll(key).stream().reduce(fold).orElseThrow(()->new NoSuchElementException());
    }
    
    default <V> boolean containsEntry(GenericKey<K,V> key, Object value) {
        return getAll(key).contains(value);
    }

    default <V> void putAll(GenericKey<K,V> key, Iterable<? extends V> values) {
        for(V value : values) {
            put(key, value);
        }
    }

    default <V> Collection<V> removeAll(GenericKey<K,V> key) {
        Collection<V> prev = new ArrayList<>();
        V last;
        while(null != (last=remove(key))) {
            prev.add(last);
        }
        return prev;
    }
    
    default Set<GenericKey<K,?>> uniqueKeySet() {
        return new HashSet<>(keySet());
    }
}
