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

import java.io.Serializable;
import java.util.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.function.*;
import java.util.stream.Stream;

import lombok.experimental.UtilityClass;
import net.ranides.assira.collection.ACollection;
import net.ranides.assira.collection.CollectionUtils;
import net.ranides.assira.collection.sets.SetUtils;
import net.ranides.assira.functional.covariant.ProjectionFunction;
import net.ranides.assira.generic.CompareUtils;
import net.ranides.assira.text.StringTraits;

/**
 * Static methods working with Maps and MultiMaps
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@UtilityClass
public class MapUtils {

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view does not support modifications.
     *
     * @param map source map
     * @param function function
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return read-only view
     */
    public static <K, TV, RV> Map<K,RV> valueAdapter(Map<K,TV> map, Function<? super TV, ? extends RV> function) {
        return new MapAdapter<>(map, function, null);
    }

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view supports insertions and replacements: it uses passed projection for conversion in both directions.
     *
     * @param map source map
     * @param projection projection
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return mutable view
     */
    public static <K, TV, RV> Map<K,RV> valueProjection(Map<K,TV> map, ProjectionFunction<TV, RV> projection) {
        return new MapAdapter<>(map, projection, (Serializable & BiFunction<K,RV,RV>)(k,v) -> projection.apply(map.put(k, projection.invert(v))));
    }

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view supports modifications: it uses "inserter" function to change map.
     *
     * It passes to inserter "key" and "new value".
     *
     * @param map source map
     * @param getter mapping function
     * @param inserter insertion function
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return mutable view
     */
    public static <K, TV, RV> Map<K,RV> valueProjection(Map<K,TV> map, Function<? super TV, ? extends RV> getter, BiFunction<? super K, ? super RV, ? extends RV> inserter) {
        return new MapAdapter<>(map, getter, inserter);
    }

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view supports modifications: it uses "replacer" function to change map.
     *
     * It passes to replacer "previous value" and "new value".
     *
     * @param map source map
     * @param getter mapping function
     * @param setter insertion function
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return mutable view
     */
    public static <K, TV, RV> Map<K,RV> valueDelegate(Map<K,TV> map, Function<? super TV, ? extends RV> getter, BiFunction<? super TV, ? super RV, ? extends RV> setter) {
        return new MapDelegate<>(map, getter, setter);
    }

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view does not support modifications.
     *
     * @param map source map
     * @param function mapping function
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return read-only view
     */
    @SuppressWarnings("unchecked")
    public static <K, TV, RV> MultiMap<K,RV> valueAdapter(MultiMap<K,TV> map, Function<? super TV, ? extends RV> function) {
        return new MultiMapAdapter<>(map, function, null);
    }

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view supports modifications: it uses passed projection for conversion in both directions.
     *
     * @param map source map
     * @param projection projection
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return mutable view
     */
    public static <K, TV, RV> MultiMap<K,RV> valueProjection(MultiMap<K,TV> map, ProjectionFunction<TV, RV> projection) {
        return new MultiMapAdapter<>(map, projection, (k,v) -> projection.apply(map.put(k, projection.invert(v))));
    }

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view supports modifications: it uses "inserter" function to change map.
     * It passes to inserter "key" and "new value".
     *
     * @param map source map
     * @param function mapping function
     * @param inserter inverted mapping, used for insertion
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return mutable view
     */
    public static <K, TV, RV> MultiMap<K,RV> valueProjection(MultiMap<K,TV> map, Function<? super TV, ? extends RV> function, BiFunction<? super K, ? super RV, ? extends RV> inserter) {
        return new MultiMapAdapter<>(map, function, inserter);
    }

    /**
     * Creates view which transforms values stored inside source map.
     * Returned view supports modifications: it uses "replacer" function to change map.
     *
     * It passes to replacer "previous value" and "new value".
     *
     * @param map source map
     * @param function mapping function
     * @param replacer inverted mapping, used for insertion
     * @param <K> K
     * @param <TV> source type
     * @param <RV> target type
     * @return mutable view
     */
    public static <K, TV, RV> MultiMap<K,RV> valueDelegate(MultiMap<K,TV> map, Function<? super TV, ? extends RV> function, BiFunction<? super TV, ? super RV, ? extends RV> replacer) {
        return new MultiMapDelegate<>(map, function, replacer);
    }

    /**
     * Returns read-only view which filters keys and maps them by provided function
     *
     * At access time it checks if provided key is accepted by "acceptor"
     * At access time it maps accepted key by "function" and returns value from source map.
     *
     * @param content content
     * @param acceptor acceptor
     * @param <K> K
     * @param <V> V
     * @return view
     */
    public static <K,V> Map<K,V> keyFilter(Map<K, V> content, Predicate<Object> acceptor) {
        return new KeyFilter<>(content, acceptor);
    }

    /**
     * It cretes read-only view of map which keys are created by provided functions.
     * Maps keys at access time, before lookup.
     *
     * @param map map
     * @param keyMapper keyMapper
     * @param <TK> TK
     * @param <RK> RK
     * @param <V> TV
     * @return view
     */
    public static <TK, RK, V> Map<RK, V> keyMap(Map<TK, ? extends V> map, Function<TK, RK> keyMapper) {
        return new KeyAdapter<>(map, keyMapper, Function.identity());
    }

    /**
     * It cretes read-only view of map which keys are created by provided functions.
     * Maps keys at access time, before lookup.
     *
     * @param map map
     * @param keyMapper keyMapper
     * @param <TK> TK
     * @param <RK> RK
     * @param <TV> TV
     * @param <RV> RV
     * @return view
     */
    public static <TK, RK, TV, RV> Map<RK,RV> keyMap(Map<TK, ? extends TV> map, Function<TK, RK> keyMapper, Function<TV, RV> valueMapper) {
        return new KeyAdapter<>(map, keyMapper, valueMapper);
    }

    /**
     * Returns synchronized MultiMap
     *
     * @param map map
     * @param <K> K
     * @param <V> V
     * @return MultiMap
     */
    public static <K, V> MultiMap<K, V> synchronizedMultiMap(MultiMap<K, V> map) {
        return new SynchronizedMultiMap<>(map);
    }

    /**
     * Returns flattened map inserting flat result into OpenMap
     *
     * @param source source
     * @param joiner joiner
     * @return map
     */
    public static Map<String,Object> flat(Map<?,?> source, String joiner) {
        return flat(new OpenMap<>(), source, joiner);
    }

    /**
     * Returns flattened map.
     *
     * Every key inside provided map is converted into string.
     * If source map contains any nested maps, their keys are converted to String too.
     * Nested keys are concatenated using "joiner" as separator.
     *
     * Effectivelly, converts nested maps into flat maps with keys reflecting overall structure.
     *
     * Target map should be able to store any string mappings in efficient manner.
     *
     * @param target target
     * @param source source
     * @param joiner joiner
     * @return map
     */
    public static Map<String,Object> flat(Map<String,Object> target, Map<?,?> source, String joiner) {
        return flat(target, source, "", (a,b)->{
            if(StringTraits.isEmpty(a)) {
                return String.valueOf(b);
            }
            return a + joiner + b;
        });
    }

    /**
     * Creates new map, putting all values from source map. If source contains nested maps, they are
     * flattened: "joiner" is used to generate new keys, and values from nested map are inserted directly
     * into result.
     * <p>
     * Join function is called with two arguments: prefix-key [P] and nested-key [K]. Computed ket [KP] maybe passed
     * to join function later, if the key [K] was associated with nested map with values.
     * </p><p>
     * Keys for non-nested values are generated using {@code seed} and [K].
     * </p>
     * @param source source
     * @param seed seed
     * @param joiner joiner
     * @param <K> K
     * @return map
     */
    public static <K> Map<K,Object> flat(Map<?,?> source, K seed, BiFunction<K, Object, K> joiner) {
        return flat(new OpenMap<>(), source, seed, joiner);
    }

    @SuppressWarnings({"rawtypes"})
    private static <K> Map<K,Object> flat(Map<K,Object> target, Map<?,?> source, K seed, BiFunction<K, Object, K> joiner) {
        for(Map.Entry<?,?> entry : source.entrySet()) {
            K key = joiner.apply(seed, entry.getKey());
            if(entry.getValue() instanceof Map) {
                flat(target, (Map)entry.getValue(), key, joiner);
            } else {
                target.put(key, entry.getValue());
            }
        }
        return target;
    }

    private static class MapAdapter<K, TV, RV> extends AMap<K, RV> implements Map<K, RV> {

        private static final long serialVersionUID = 1L;

        private static final BiFunction NSE = (Serializable & BiFunction)(a,b) -> {
            throw new UnsupportedOperationException();
        };

        protected final Map<K, TV> content;
        protected final Function<? super TV, ? extends RV> function;
        protected final BiFunction<? super K, ? super RV, ? extends RV> inserter;

        @SuppressWarnings("unchecked")
        public MapAdapter(Map<K, TV> content, Function<? super TV, ? extends RV> function, BiFunction<? super K, ? super RV, ? extends RV> setter) {
            this.content = content;
            this.function = function;
            this.inserter = null != setter ? setter : NSE;
        }

        @Override
        public int size() {
            return content.size();
        }

        @Override
        public boolean containsKey(Object key) {
            return content.containsKey(key);
        }

        @Override
        public boolean containsValue(Object value) {
            return content.values().stream().map(function).anyMatch(rv -> CompareUtils.equals(rv, value));
        }

        @Override
        public void clear() {
            content.clear();
        }

        @Override
        public RV remove(Object key) {
            TV prev = content.remove(key);
            return prev==null ? null : function.apply(prev);
        }

        @Override
        public Set<K> keySet() {
            return content.keySet();
        }

        @Override
        public Collection<RV> values() {
            return CollectionUtils.map(content.values(), function);
        }

        @Override
        public Set<Entry<K, RV>> entrySet() {
            return SetUtils.map(content.entrySet(), e -> new EntryAdapter(e));
        }

        @Override
        public RV get(Object key) {
            return function.apply(content.get(key));
        }

        @Override
        public RV put(K key, RV value) {
            return inserter.apply(key, value);
        }

        private final class EntryAdapter extends AEntry<K, RV> {

            private final Entry<K,TV> delegate;

            public EntryAdapter(Entry<K, TV> delegate) {
                this.delegate = delegate;
            }

            @Override
            public K getKey() {
                return delegate.getKey();
            }

            @Override
            public RV getValue() {
                return function.apply(delegate.getValue());
            }

            @Override
            public RV setValue(RV value) {
                return inserter.apply(delegate.getKey(), value);
            }

        }

    }

    private static class MapDelegate<K, TV, RV> extends AMap<K, RV> implements Map<K, RV> {

        private static final long serialVersionUID = 1L;

        protected final Map<K, TV> content;
        protected final Function<? super TV, ? extends RV> getter;
        protected final BiFunction<? super TV, ? super RV, ? extends RV> setter;

        public MapDelegate(Map<K,TV> content, Function<? super TV, ? extends RV> getter, BiFunction<? super TV, ? super RV, ? extends RV> setter) {
            this.content = content;
            this.getter = getter;
            this.setter = setter;
        }

        @Override
        public int size() {
            return content.size();
        }

        @Override
        public boolean containsKey(Object key) {
            return content.containsKey(key);
        }

        @Override
        public boolean containsValue(Object value) {
            return values().contains(value);
        }

        @Override
        public RV get(Object key) {
            TV var = content.get(key);
            return null!=var ? getter.apply(var) : null;
        }

        @Override
        public RV put(K key, RV value) {
            TV original = content.get(key);
            return null!=original ? setter.apply(original, value) : null;
        }

        @Override
        public Set<K> keySet() {
            return content.keySet();
        }

        @Override
        public Collection<RV> values() {
            return CollectionUtils.map(content.values(), getter);
        }

        @Override
        public Set<Entry<K, RV>> entrySet() {
            return SetUtils.map(content.entrySet(), e -> new EntryWrapper(e));
        }

        private final class EntryWrapper extends AEntry<K, RV> {

            private final Map.Entry<K,TV> delegate;

            public EntryWrapper(Map.Entry<K, TV> delegate) {
                this.delegate = delegate;
            }

            @Override
            public K getKey() {
                return delegate.getKey();
            }

            @Override
            public RV getValue() {
                return getter.apply(delegate.getValue());
            }

            @Override
            public RV setValue(RV value) {
                return setter.apply(delegate.getValue(), value);
            }

        }

    }

    private static class MultiMapAdapter<K, TV, RV> extends MapAdapter<K, TV, RV> implements MultiMap<K, RV> {

        private static final long serialVersionUID = 1L;

        private final MultiMap<K, TV> mcontent;

        public MultiMapAdapter(MultiMap<K, TV> mcontent, Function<? super TV, ? extends RV> function, BiFunction<? super K, ? super RV, ? extends RV> setter) {
            super(mcontent, function, setter);
            this.mcontent = mcontent;
        }

        @Override
        public Collection<RV> getAll(Object key) {
            return CollectionUtils.map(mcontent.getAll(key), function);
        }

    }

    private static class MultiMapDelegate<K, TV, RV> extends MapDelegate<K,TV,RV> implements MultiMap<K, RV> {

        private static final long serialVersionUID = 1L;

        private final MultiMap<K, TV> mcontent;

        public MultiMapDelegate(MultiMap<K, TV> content, Function<? super TV, ? extends RV> getter, BiFunction<? super TV, ? super RV, ? extends RV> setter) {
            super(content, getter, setter);
            this.mcontent = content;
        }

        @Override
        public Collection<RV> getAll(Object key) {
            return CollectionUtils.map(mcontent.getAll(key), getter);
        }

    }

    static class EmptyMultiMap<K,V> extends AMap<K,V> implements MultiMap<K, V> {
        
        private static final long serialVersionUID = 1L;

        @Override
        public Set<Map.Entry<K, V>> entrySet() {
            return Collections.emptySet();
        }

        @Override
        public int size() {
            return 0;
        }

        @Override
        public boolean containsKey(Object key) {
            return false;
        }

        @Override
        public boolean containsValue(Object value) {
            return false;
        }

        @Override
        public V get(Object key) {
            return null;
        }
        
        @Override
        public Collection<V> getAll(Object key) {
            return Collections.emptyList();
        }

    }

    private static class KeyAdapter<TK, RK, TV, RV> extends AMap<RK, RV> {

        private final Map<TK,? extends TV> content;

        private final Function<TK, RK> kmap;

        private final Function<TV, RV> vmap;

        public KeyAdapter(Map<TK,? extends TV> content, Function<TK, RK> kmap, Function<TV, RV> vmap) {
            this.content = content;
            this.kmap = kmap;
            this.vmap = vmap;
        }

        @Override
        public int size() {
            return content.size();
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean containsKey(Object key) {
            try {
                return content.containsKey(kmap.apply((TK) key));
            } catch (ClassCastException e) {
                return false;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public RV get(Object key) {
            try {
                return vmap.apply(content.get(kmap.apply((TK) key)));
            } catch (ClassCastException e) {
                return null;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean containsValue(Object value) {
            try {
                return content.containsValue(vmap.apply((TV) value));
            } catch (ClassCastException e) {
                return false;
            }
        }

        @Override
        public Set<RK> keySet() {
            return SetUtils.map(content.keySet(), kmap);
        }

        @Override
        public Collection<RV> values() {
            return CollectionUtils.map(content.values(), vmap);
        }

        @Override
        public Set<Entry<RK, RV>> entrySet() {
            return SetUtils.map(content.entrySet(), e -> new SimpleEntry<>(kmap.apply(e.getKey()), vmap.apply(e.getValue())));
        }
    }

    private static class KeyFilter<K, V> extends AMap<K, V> {

        protected final Map<K, V> content;
        protected final Predicate<Object> filter;

        KeyFilter(Map<K, V> content, Predicate<Object> acceptor) {
            this.content = content;
            this.filter = acceptor;
        }

        @Override
        public Set<Entry<K, V>> entrySet() {
            return new KeyMapEntrySet();
        }

        @Override
        public int size() {
            return content.size();
        }

        @Override
        public boolean containsValue(Object value) {
            return content.containsValue(value);
        }

        @Override
        public V get(Object key) {
            return filter.test(key) ? content.get($key(key)) : null;
        }

        @Override
        public V getOrDefault(Object key, V defaultValue) {
            return filter.test(key) ? content.getOrDefault($key(key), defaultValue) : defaultValue;
        }

        @Override
        public boolean containsKey(Object key) {
            return filter.test(key) && content.containsKey($key(key));
        }

        @Override
        public V put(K key, V value) {
            return content.put($key(key), value);
        }

        @Override
        public void putAll(Map<? extends K, ? extends V> m) {
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                content.put($key(key), value);
            }
        }

        @Override
        public V remove(Object key) {
            return filter.test(key) ? content.remove($key(key)) : null;
        }

        @Override
        public V putIfAbsent(K key, V value) {
            return content.putIfAbsent($key(key), value);
        }

        @Override
        public boolean remove(Object key, Object value) {
            return filter.test(key) && content.remove($key(key), value);
        }

        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            return content.replace($key(key), oldValue, newValue);
        }

        @Override
        public V replace(K key, V value) {
            return content.replace($key(key), value);
        }

        @Override
        public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
            return content.computeIfAbsent($key(key), mappingFunction);
        }

        @Override
        public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            return content.computeIfPresent($key(key), remappingFunction);
        }

        @Override
        public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            return content.compute($key(key), remappingFunction);
        }

        @Override
        public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            return content.merge($key(key), value, remappingFunction);
        }

        @SuppressWarnings("unchecked")
        private K $key(Object key) {
            return (K)key;
        }

        @SuppressWarnings("unchecked")
        private Entry<K, V> $toSimpleEntry(Entry<?,?> entry) {
            K k = (K)entry.getKey();
            V v = (V)entry.getValue();
            return new SimpleEntry<>(k, v);
        }

        private final class KeyMapEntrySet extends ACollection<Entry<K, V>> implements Set<Map.Entry<K, V>>, Serializable {

            private static final long serialVersionUID = 1L;

            private final Set<Entry<K, V>> entries = content.entrySet();

            @Override
            public boolean contains(Object o) {
                return o instanceof Entry<?,?> && entries.contains($toSimpleEntry((Entry<?,?>)o));
            }

            @Override
            public boolean add(Entry<K, V> kvEntry) {
                return entries.add($toSimpleEntry(kvEntry));
            }

            @Override
            public Iterator<Map.Entry<K, V>> iterator() {
                return entries.iterator();
            }

            @Override
            public int size() {
                return entries.size();
            }

            @Override
            public int hashCode() {
                return entries.hashCode();
            }

            @Override
            public boolean equals(Object obj) {
                if(obj instanceof Set) {
                    @SuppressWarnings("unchecked")
                    Set<Entry<K, V>> that = (Set<Entry<K, V>>)obj;
                    return this.size() == that.size() && this.containsAll(that);
                }
                return false;
            }

        }

    }

    private static class SynchronizedMultiMap<K, V> implements MultiMap<K, V>, Serializable {

        private final Object mutex = new Serializable(){};

        private final MultiMap<K, V> data;

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        public SynchronizedMultiMap(MultiMap<K, V> data) {
            this.data = data;
        }

        @Override
        public Collection<V> getAll(Object key) {
            synchronized (mutex) {
                return new SynchronizedCollection<>(mutex, data.getAll(key));
            }
        }

        @Override
        public int size() {
            synchronized (mutex) {
                return data.size();
            }
        }

        @Override
        public boolean isEmpty() {
            synchronized (mutex) {
                return data.isEmpty();
            }
        }

        @Override
        public boolean containsKey(Object key) {
            synchronized (mutex) {
                return data.containsKey(key);
            }
        }

        @Override
        public boolean containsValue(Object value) {
            synchronized (mutex) {
                return data.containsValue(value);
            }
        }

        @Override
        public V get(Object key) {
            synchronized (mutex) {
                return data.get(key);
            }
        }

        @Override
        public V put(K key, V value) {
            synchronized (mutex) {
                return data.put(key, value);
            }
        }

        @Override
        public V remove(Object key) {
            synchronized (mutex) {
                return data.remove(key);
            }
        }

        @Override
        public void putAll(Map<? extends K, ? extends V> m) {
            synchronized (mutex) {
                data.putAll(m);
            }
        }

        @Override
        public void clear() {
            synchronized (mutex) {
                data.clear();
            }
        }

        @Override
        public Set<K> keySet() {
            synchronized (mutex) {
                if(keySet == null) {
                    keySet = new SynchronizedSet<>(mutex, data.keySet());
                }
                return keySet;
            }
        }

        @Override
        public Collection<V> values() {
            synchronized (mutex) {
                if(values == null) {
                    values = new SynchronizedCollection<>(mutex, data.values());
                }
                return values;
            }
        }

        @Override
        public Set<Entry<K, V>> entrySet() {
            synchronized (mutex) {
                if(entrySet == null) {
                    entrySet = new SynchronizedSet<>(mutex, data.entrySet());
                }
                return entrySet;
            }
        }

        @Override
        public V get(Object key, BinaryOperator<V> fold) {
            synchronized (mutex) {
                return data.get(key, fold);
            }
        }

        @Override
        public boolean containsEntry(Object key, Object value) {
            synchronized (mutex) {
                return data.containsEntry(key, value);
            }
        }

        @Override
        public void putAll(K key, Iterable<? extends V> values) {
            synchronized (mutex) {
                data.putAll(key, values);
            }
        }

        @Override
        public V replace(K key, V value) {
            synchronized (mutex) {
                return data.replace(key, value);
            }
        }

        @Override
        public Collection<V> replaceAll(K key, Collection<V> values) {
            synchronized (mutex) {
                return data.replaceAll(key, values);
            }
        }

        @Override
        public boolean remove(Object key, Object value) {
            synchronized (mutex) {
                return data.remove(key, value);
            }
        }

        @Override
        public Collection<V> removeAll(Object key) {
            synchronized (mutex) {
                return data.removeAll(key);
            }
        }

        @Override
        public Set<K> uniqueKeySet() {
            synchronized (mutex) {
                return data.uniqueKeySet();
            }
        }

        @Override
        public V getOrDefault(Object key, V defaultValue) {
            synchronized (mutex) {
                return data.getOrDefault(key, defaultValue);
            }
        }

        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            synchronized (mutex) {
                data.forEach(action);
            }
        }

        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            synchronized (mutex) {
                data.replaceAll(function);
            }
        }

        @Override
        public V putIfAbsent(K key, V value) {
            synchronized (mutex) {
                return data.putIfAbsent(key, value);
            }
        }

        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            synchronized (mutex) {
                return data.replace(key, oldValue, newValue);
            }
        }

        @Override
        public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
            synchronized (mutex) {
                return data.computeIfAbsent(key, mappingFunction);
            }
        }

        @Override
        public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {
                return data.computeIfPresent(key, remappingFunction);
            }
        }

        @Override
        public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {
                return data.compute(key, remappingFunction);
            }
        }

        @Override
        public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {
                return data.merge(key, value, remappingFunction);
            }
        }

        @Override
        public boolean equals(Object o) {
            return (this == o) || data.equals(o);
        }

        @Override
        public int hashCode() {
            return Objects.hash(data);
        }
    }

    static class SynchronizedCollection<E> implements Collection<E> {

        final Collection<E> c;

        final Object mutex;

        SynchronizedCollection(Object mutex, Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }

        public int size() {
            synchronized (mutex) {
                return c.size();
            }
        }

        public boolean isEmpty() {
            synchronized (mutex) {
                return c.isEmpty();
            }
        }

        public boolean contains(Object o) {
            synchronized (mutex) {
                return c.contains(o);
            }
        }

        public Object[] toArray() {
            synchronized (mutex) {
                return c.toArray();
            }
        }

        public <T> T[] toArray(T[] a) {
            synchronized (mutex) {
                return c.toArray(a);
            }
        }

        public Iterator<E> iterator() {
            return c.iterator(); // Must be manually synched by user!
        }

        public boolean add(E e) {
            synchronized (mutex) {
                return c.add(e);
            }
        }

        public boolean remove(Object o) {
            synchronized (mutex) {
                return c.remove(o);
            }
        }

        public boolean containsAll(Collection<?> coll) {
            synchronized (mutex) {
                return c.containsAll(coll);
            }
        }

        public boolean addAll(Collection<? extends E> coll) {
            synchronized (mutex) {
                return c.addAll(coll);
            }
        }

        public boolean removeAll(Collection<?> coll) {
            synchronized (mutex) {
                return c.removeAll(coll);
            }
        }

        public boolean retainAll(Collection<?> coll) {
            synchronized (mutex) {
                return c.retainAll(coll);
            }
        }

        public void clear() {
            synchronized (mutex) {
                c.clear();
            }
        }

        public String toString() {
            synchronized (mutex) {
                return c.toString();
            }
        }

        @Override
        public void forEach(Consumer<? super E> consumer) {
            synchronized (mutex) {
                c.forEach(consumer);
            }
        }

        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            synchronized (mutex) {
                return c.removeIf(filter);
            }
        }

        @Override
        public Spliterator<E> spliterator() {
            return c.spliterator(); // Must be manually synched by user!
        }

        @Override
        public Stream<E> stream() {
            return c.stream(); // Must be manually synched by user!
        }

        @Override
        public Stream<E> parallelStream() {
            return c.parallelStream(); // Must be manually synched by user!
        }

    }

    static class SynchronizedSet<E> extends SynchronizedCollection<E> implements Set<E> {

        SynchronizedSet(Object mutex, Set<E> s) {
            super(mutex, s);
        }

        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            synchronized (mutex) {
                return c.equals(o);
            }
        }

        public int hashCode() {
            synchronized (mutex) {
                return c.hashCode();
            }
        }
    }

}
