package com.github.blahord.bettercollections.compatibility;

import com.github.blahord.bettercollections.map.HashMap;
import com.github.blahord.bettercollections.map.MutableMap;
import com.github.blahord.bettercollections.util.Maps;
import org.assertj.core.api.Assertions;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.*;
import java.util.function.Consumer;

import static com.github.blahord.bettercollections.compatibility.MapWrappers.asJavaUtilMap;
import static com.github.blahord.bettercollections.util.Maps.emptyMap;
import static com.github.blahord.bettercollections.util.Maps.entry;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.assertj.core.api.Java6Assertions.failBecauseExceptionWasNotThrown;

@Test(groups = "unit-tests")
public class MutableJavaMapTest {

    private MutableMap<? extends Integer, ? extends Integer,
        ? super Integer, ? super Integer> map;
    private MutableJavaMap<Integer, Integer> javaMap;

    @BeforeMethod
    public void setUp() throws Exception {
        map = HashMap.create(Maps.<Integer, Integer>asMap(entry(1, 10), entry(2, 20), entry(3, 30), entry(4, 40), entry(5, 50)));
        javaMap = new MutableJavaMap<>(map);
    }

    public void testKeySet() throws Exception {
        final java.util.Set<Integer> javaKeySet = javaMap.keySet();

        assertThat(javaKeySet).containsOnly(1, 2, 3, 4, 5);

        assertThat(javaKeySet.hashCode()).isEqualTo(1 + 2 + 3 + 4 + 5);
        assertThat(javaKeySet.equals(javaKeySet)).isTrue();
        assertThat(javaKeySet.equals(new java.util.HashSet<>(Arrays.asList(1, 2, 3, 4, 5)))).isTrue();
        assertThat(new java.util.HashSet<>(Arrays.asList(1, 2, 3, 4, 5)).equals(javaKeySet)).isTrue();

        assertThat(javaKeySet.equals(new java.util.HashSet<>(Arrays.asList(1, 2, 3, 4)))).isFalse();
        assertThat(new java.util.HashSet<>(Arrays.asList(1, 2, 3, 4)).equals(javaKeySet)).isFalse();

        assertThat(javaKeySet.size()).isEqualTo(5);
        assertThat(new MutableJavaMap<>(HashMap.create()).keySet().size()).isEqualTo(0);

        assertThat(javaKeySet.isEmpty()).isFalse();
        assertThat(new MutableJavaMap<>(HashMap.create()).keySet().isEmpty()).isTrue();

        assertThat(javaKeySet.contains(1)).isTrue();
        assertThat(javaKeySet.contains(2)).isTrue();
        assertThat(javaKeySet.contains(3)).isTrue();
        assertThat(javaKeySet.contains(4)).isTrue();
        assertThat(javaKeySet.contains(5)).isTrue();
        assertThat(javaKeySet.contains(0)).isFalse();
        assertThat(javaKeySet.contains(6)).isFalse();
        assertThat(new MutableJavaMap<>(HashMap.create()).keySet().contains(1)).isFalse();

        assertThat(javaKeySet.containsAll(Collections.emptySet())).isTrue();
        assertThat(javaKeySet.containsAll(Collections.singleton(1))).isTrue();
        assertThat(javaKeySet.containsAll(Collections.singleton(2))).isTrue();
        assertThat(javaKeySet.containsAll(Collections.singleton(3))).isTrue();
        assertThat(javaKeySet.containsAll(Collections.singleton(4))).isTrue();
        assertThat(javaKeySet.containsAll(Collections.singleton(5))).isTrue();
        assertThat(javaKeySet.containsAll(Arrays.asList(1, 2, 3, 4, 5))).isTrue();
        assertThat(javaKeySet.containsAll(Arrays.asList(1, 2, 3, 5, 4))).isTrue();
        assertThat(javaKeySet.containsAll(Arrays.asList(1, 2, 3, 3, 5))).isTrue();
        assertThat(javaKeySet.containsAll(Arrays.asList(1, 5, 3, 4, 2))).isTrue();
        assertThat(javaKeySet.containsAll(Arrays.asList(1, 2, 3, 4, 5))).isTrue();
        assertThat(javaKeySet.containsAll(Collections.singletonList(7))).isFalse();
        assertThat(javaKeySet.containsAll(Arrays.asList(7, 1))).isFalse();

        assertThat(new MutableJavaMap<>(HashMap.create()).keySet().containsAll(Collections.emptySet())).isTrue();
        assertThat(new MutableJavaMap<>(HashMap.create()).keySet().containsAll(Collections.singleton(1))).isFalse();

        assertThat(javaKeySet.iterator()).containsOnly(1, 2, 3, 4, 5);
        final java.util.Iterator<Integer> javaKeySetIterator = javaKeySet.iterator();
        while (javaKeySetIterator.next() != 1) {
        }
        javaKeySetIterator.remove();

        assertThat(javaKeySet.iterator()).containsOnly(2, 3, 4, 5);
        assertThat(javaMap).containsOnly(Assertions.entry(2, 20), Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(2, 20), Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));

        javaKeySet.remove(2);
        assertThat(javaKeySet.iterator()).containsOnly(3, 4, 5);
        assertThat(javaMap).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));

        javaKeySet.retainAll(Arrays.asList(3, 4));
        assertThat(javaKeySet.iterator()).containsOnly(3, 4);
        assertThat(javaMap).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40));

        javaKeySet.removeAll(Collections.singletonList(3));
        assertThat(javaKeySet.iterator()).containsOnly(4);
        assertThat(javaMap).containsOnly(Assertions.entry(4, 40));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(4, 40));

        checkCallUnsupported(javaKeySet, s -> s.add(4));
        checkCallUnsupported(javaKeySet, s -> s.addAll(Collections.emptySet()));

        javaKeySet.clear();

        assertThat(javaMap).isEmpty();
        assertThat(asJavaUtilMap(map)).isEmpty();
    }

    public void testValues() throws Exception {
        final java.util.Collection<Integer> javaValuesCollection = javaMap.values();

        assertThat(javaValuesCollection).containsOnly(10, 20, 30, 40, 50);

        assertThat(javaValuesCollection.size()).isEqualTo(5);
        assertThat(new MutableJavaMap<>(HashMap.create()).values().size()).isEqualTo(0);

        assertThat(javaValuesCollection.isEmpty()).isFalse();
        assertThat(new MutableJavaMap<>(HashMap.create()).values().isEmpty()).isTrue();

        assertThat(javaValuesCollection.contains(10)).isTrue();
        assertThat(javaValuesCollection.contains(20)).isTrue();
        assertThat(javaValuesCollection.contains(30)).isTrue();
        assertThat(javaValuesCollection.contains(40)).isTrue();
        assertThat(javaValuesCollection.contains(50)).isTrue();
        assertThat(javaValuesCollection.contains(0)).isFalse();
        assertThat(javaValuesCollection.contains(60)).isFalse();
        assertThat(new MutableJavaMap<>(HashMap.create()).values().contains(10)).isFalse();

        assertThat(javaValuesCollection.containsAll(Collections.emptySet())).isTrue();
        assertThat(javaValuesCollection.containsAll(Collections.singleton(10))).isTrue();
        assertThat(javaValuesCollection.containsAll(Collections.singleton(20))).isTrue();
        assertThat(javaValuesCollection.containsAll(Collections.singleton(30))).isTrue();
        assertThat(javaValuesCollection.containsAll(Collections.singleton(40))).isTrue();
        assertThat(javaValuesCollection.containsAll(Collections.singleton(50))).isTrue();
        assertThat(javaValuesCollection.containsAll(Arrays.asList(10, 20, 30, 40, 50))).isTrue();
        assertThat(javaValuesCollection.containsAll(Arrays.asList(10, 20, 30, 50, 40))).isTrue();
        assertThat(javaValuesCollection.containsAll(Arrays.asList(10, 20, 30, 30, 50))).isTrue();
        assertThat(javaValuesCollection.containsAll(Arrays.asList(10, 50, 30, 40, 20))).isTrue();
        assertThat(javaValuesCollection.containsAll(Arrays.asList(10, 20, 30, 40, 50))).isTrue();
        assertThat(javaValuesCollection.containsAll(Collections.singletonList(70))).isFalse();
        assertThat(javaValuesCollection.containsAll(Arrays.asList(70, 10))).isFalse();

        assertThat(new MutableJavaMap<>(HashMap.create()).values().containsAll(Collections.emptySet())).isTrue();
        assertThat(new MutableJavaMap<>(HashMap.create()).values().containsAll(Collections.singleton(10))).isFalse();

        assertThat(javaValuesCollection.iterator()).containsOnly(10, 20, 30, 40, 50);
        final java.util.Iterator<Integer> javaKeySetIterator = javaValuesCollection.iterator();
        while (javaKeySetIterator.next() != 10) {
        }
        javaKeySetIterator.remove();

        assertThat(javaValuesCollection.iterator()).containsOnly(20, 30, 40, 50);
        assertThat(javaMap).containsOnly(Assertions.entry(2, 20), Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(2, 20), Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));

        javaValuesCollection.remove(20);
        assertThat(javaValuesCollection.iterator()).containsOnly(30, 40, 50);
        assertThat(javaMap).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));

        javaValuesCollection.retainAll(Arrays.asList(30, 40));
        assertThat(javaValuesCollection.iterator()).containsOnly(30, 40);
        assertThat(javaMap).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40));

        javaValuesCollection.removeAll(Collections.singletonList(30));
        assertThat(javaValuesCollection.iterator()).containsOnly(40);
        assertThat(javaMap).containsOnly(Assertions.entry(4, 40));
        assertThat(asJavaUtilMap(map)).containsOnly(Assertions.entry(4, 40));

        checkCallUnsupported(javaValuesCollection, s -> s.add(40));
        checkCallUnsupported(javaValuesCollection, s -> s.addAll(Collections.emptySet()));

        javaValuesCollection.clear();

        assertThat(javaMap).isEmpty();
        assertThat(asJavaUtilMap(map)).isEmpty();
    }

    public void testEntrySet() throws Exception {
        final java.util.Set<java.util.Map.Entry<Integer, Integer>> javaEntrySet = javaMap.entrySet();

        assertThat(javaEntrySet).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20), Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaEntrySet.hashCode()).isEqualTo(
            new MapEntry<>(1, 10).hashCode() +
            new MapEntry<>(2, 20).hashCode() +
            new MapEntry<>(3, 30).hashCode() +
            new MapEntry<>(4, 40).hashCode() +
            new MapEntry<>(5, 50).hashCode()
        );

        java.util.Set<java.util.Map.Entry<Integer, Integer>> javaEntrySet2 = new HashSet<>(
            Arrays.asList(
                new MapEntry<>(1, 10),
                new MapEntry<>(2, 20),
                new MapEntry<>(3, 30),
                new MapEntry<>(4, 40),
                new MapEntry<>(5, 50)
            )
        );

        assertThat(javaEntrySet.equals(javaEntrySet)).isTrue();
        assertThat(javaEntrySet.equals(javaEntrySet2)).isTrue();
        assertThat(javaEntrySet2.equals(javaEntrySet)).isTrue();
        javaEntrySet2.remove(new MapEntry<>(1, 10));
        assertThat(javaEntrySet.equals(javaEntrySet2)).isFalse();
        assertThat(javaEntrySet2.equals(javaEntrySet)).isFalse();

        javaEntrySet.remove(new MapEntry<>(1, 10));
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(map.size()).isEqualTo(4);
        assertThat(map.get(2)).isEqualTo(20);
        assertThat(map.get(3)).isEqualTo(30);
        assertThat(map.get(4)).isEqualTo(40);
        assertThat(map.get(5)).isEqualTo(50);

        java.util.Iterator<java.util.Map.Entry<Integer, Integer>> javaEntrySetIterator = javaEntrySet.iterator();
        try {
            javaEntrySetIterator.remove();
            failBecauseExceptionWasNotThrown(IllegalStateException.class);
        } catch (IllegalStateException e) {
        }
        assertThat(javaEntrySetIterator.next()).isEqualTo(Assertions.entry(2, 20));
        javaEntrySetIterator.remove();
        assertThat(javaMap).containsOnly(
            Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(map.size()).isEqualTo(3);
        assertThat(map.get(3)).isEqualTo(30);
        assertThat(map.get(4)).isEqualTo(40);
        assertThat(map.get(5)).isEqualTo(50);

        assertThat(javaEntrySetIterator.next()).isEqualTo(Assertions.entry(3, 30));
        assertThat(javaEntrySetIterator.next()).isEqualTo(Assertions.entry(4, 40));
        assertThat(javaEntrySetIterator.next()).isEqualTo(Assertions.entry(5, 50));
        try {
            javaEntrySetIterator.next();
            failBecauseExceptionWasNotThrown(NoSuchElementException.class);
        } catch (NoSuchElementException e) {
        }

        assertThat(javaEntrySet.size()).isEqualTo(3);
        assertThat(javaEntrySet.contains(new MapEntry<>(3, 30))).isTrue();
        assertThat(javaEntrySet.contains(new MapEntry<>(3, 40))).isFalse();
        assertThat(javaEntrySet.contains(new MapEntry<>(2, 20))).isFalse();

        assertThat(javaEntrySet.toArray()).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));
        assertThat(javaEntrySet.toArray(new java.util.Map.Entry[3])).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));
        assertThat(javaEntrySet.toArray(new java.util.Map.Entry[1])).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), Assertions.entry(5, 50));

        assertThat(javaEntrySet.isEmpty()).isFalse();
        assertThat(new MutableJavaMap<>(HashMap.create(emptyMap())).entrySet().isEmpty()).isTrue();

        assertThat(javaEntrySet.containsAll(Collections.emptySet())).isTrue();
        assertThat(javaEntrySet.containsAll(Collections.singleton(new MapEntry<>(3, 30)))).isTrue();
        assertThat(javaEntrySet.containsAll(Collections.singleton(new MapEntry<>(4, 40)))).isTrue();
        assertThat(javaEntrySet.containsAll(Collections.singleton(new MapEntry<>(5, 50)))).isTrue();
        assertThat(javaEntrySet.containsAll(Arrays.asList(new MapEntry<>(3, 30), new MapEntry<>(4, 40), new MapEntry<>(5, 50)))).isTrue();
        assertThat(javaEntrySet.containsAll(Arrays.asList(new MapEntry<>(3, 40), new MapEntry<>(4, 40), new MapEntry<>(5, 50)))).isFalse();
        assertThat(javaEntrySet.containsAll(Arrays.asList(new MapEntry<>(3, 40), new MapEntry<>(4, 50), new MapEntry<>(5, 60)))).isFalse();

        javaEntrySet.retainAll(Arrays.asList(new MapEntry<>(3, 30), new MapEntry<>(4, 40)));
        assertThat(javaEntrySet.toArray()).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40));
        assertThat(javaEntrySet.toArray(new java.util.Map.Entry[3])).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40), null);
        assertThat(javaEntrySet.toArray(new java.util.Map.Entry[1])).containsOnly(Assertions.entry(3, 30), Assertions.entry(4, 40));

        javaEntrySet.removeAll(Arrays.asList(new MapEntry<>(3, 30)));
        assertThat(javaEntrySet.toArray()).containsOnly(Assertions.entry(4, 40));
        assertThat(javaEntrySet.toArray(new java.util.Map.Entry[3])).containsOnly(Assertions.entry(4, 40), null);
        assertThat(javaEntrySet.toArray(new java.util.Map.Entry[1])).containsOnly(Assertions.entry(4, 40));

        javaEntrySet.iterator().next().setValue(33);
        assertThat(javaMap).containsOnly(
            Assertions.entry(4, 33)
        );
        assertThat(map.size()).isEqualTo(1);
        assertThat(map.get(4)).isEqualTo(33);

        assertThat(javaEntrySet.iterator().next().toString()).isEqualTo("4 -> 33");

        checkCallUnsupported(javaEntrySet, e -> e.add(new MapEntry<>(6, 60)));
        checkCallUnsupported(javaEntrySet, e -> e.addAll(Collections.emptySet()));

        javaEntrySet.clear();
        assertThat(javaMap).isEmpty();
        assertThat(map.isEmpty()).isTrue();
    }

    public void testRemove() throws Exception {
        assertThat(javaMap.remove(1)).isEqualTo(10);
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.remove(1)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(map.remove(2)).isEqualTo(20);
        assertThat(javaMap).containsOnly(
                Assertions.entry(3, 30),
                Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
                Assertions.entry(3, 30),
                Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(map.remove(2)).isNull();
        assertThat(javaMap).containsOnly(
                Assertions.entry(3, 30),
                Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
                Assertions.entry(3, 30),
                Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
    }

    public void testClear() throws Exception {
        javaMap.clear();
        assertThat(javaMap).isEmpty();
        assertThat(map.isEmpty()).isTrue();

        map.put(1, 10);
        map.put(2, 20);
        map.put(3, 30);
        map.put(4, 40);
        map.put(5, 50);

        map.clear();
        assertThat(javaMap).isEmpty();
        assertThat(map.isEmpty()).isTrue();
    }

    public void testPut() throws Exception {
        assertThat(javaMap.put(4, 44)).isEqualTo(40);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.put(6, 60)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 50), Assertions.entry(6, 60)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 50), Assertions.entry(6, 60)
        );
    }

    public void testPutAll() throws Exception {
        java.util.Map<Integer, Integer> newMap = new java.util.HashMap<>();
        newMap.put(4, 44);
        newMap.put(5, 55);

        javaMap.putAll(newMap);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 55)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 55)
        );

        newMap = new java.util.HashMap<>();
        newMap.put(4, 44);
        newMap.put(6, 60);
        javaMap.putAll(newMap);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 55), Assertions.entry(6, 60)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 55), Assertions.entry(6, 60)
        );

        newMap = new java.util.HashMap<>();
        newMap.put(7, 70);
        newMap.put(8, 80);
        javaMap.putAll(newMap);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 55), Assertions.entry(6, 60),
            Assertions.entry(7, 70), Assertions.entry(8, 80)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 44),
            Assertions.entry(5, 55), Assertions.entry(6, 60),
            Assertions.entry(7, 70), Assertions.entry(8, 80)
        );
    }

    public void testReplaceAll() throws Exception {
        javaMap.replaceAll((k, v) -> k + v);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 22),
            Assertions.entry(3, 33), Assertions.entry(4, 44),
            Assertions.entry(5, 55)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 22),
            Assertions.entry(3, 33), Assertions.entry(4, 44),
            Assertions.entry(5, 55)
        );
    }

    public void testPutIfAbsent() throws Exception {
        assertThat(javaMap.putIfAbsent(1, 44)).isEqualTo(10);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.putIfAbsent(6, 60)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50), Assertions.entry(6, 60)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50), Assertions.entry(6, 60)
        );
    }

    public void testRemove1() throws Exception {
        assertThat(javaMap.remove(1, 10)).isTrue();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.remove(2, 22)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.remove(6, 20)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.remove(6, 22)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.remove(4, 30)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
    }

    public void testReplace() throws Exception {
        assertThat(javaMap.replace(1, 10, 11)).isTrue();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.replace(1, 10, 11)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.replace(6, 11, 66)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.replace(6, 66, 99)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.replace(3, 20, 66)).isFalse();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
    }

    public void testReplace1() throws Exception {
        assertThat(javaMap.replace(1, 11)).isEqualTo(10);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.replace(6, 10)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
    }

    public void testCompute() throws Exception {
        assertThat(javaMap.compute(1, (k, v) -> k + v)).isEqualTo(11);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.compute(1, (k, v) -> null)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.compute(1, (k, v) -> null)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.compute(1, (k, v) -> 10 * k)).isEqualTo(10);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
    }

    public void testComputeIfAbsent() throws Exception {
        assertThat(javaMap.computeIfAbsent(1, k -> 10000 * k)).isEqualTo(10);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.computeIfAbsent(6, k -> 10 * k)).isEqualTo(60);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50), Assertions.entry(6, 60)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 10), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50), Assertions.entry(6, 60)
        );
    }

    public void testComputeIfPresent() throws Exception {
        assertThat(javaMap.computeIfPresent(1, (k, v) -> k + v)).isEqualTo(11);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 11), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.computeIfPresent(1, (k, v) -> null)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.computeIfPresent(1, (k, v) -> null)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
    }

    public void testMerge() throws Exception {
        assertThat(javaMap.merge(1, 101, (v1, v2) -> v1 + v2)).isEqualTo(111);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 111), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 111), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );

        assertThat(javaMap.merge(1, 101, (v1, v2) -> null)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.merge(1, null, (v1, v2) -> null)).isNull();
        assertThat(javaMap).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(2, 20), Assertions.entry(3, 30),
            Assertions.entry(4, 40), Assertions.entry(5, 50)
        );

        assertThat(javaMap.merge(1, 101, (v1, v2) -> v1 + v2)).isEqualTo(101);
        assertThat(javaMap).containsOnly(
            Assertions.entry(1, 101), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
        assertThat(asJavaUtilMap(map)).containsOnly(
            Assertions.entry(1, 101), Assertions.entry(2, 20),
            Assertions.entry(3, 30), Assertions.entry(4, 40),
            Assertions.entry(5, 50)
        );
    }

    private <T> void checkCallUnsupported(T subject, Consumer<? super T> action) {
        try {
            action.accept(subject);
            failBecauseExceptionWasNotThrown(UnsupportedOperationException.class);
        } catch (UnsupportedOperationException e) {
        }
    }

    private static class MapEntry<K, V> implements java.util.Map.Entry<K, V> {

        private final K key;
        private final V value;

        public MapEntry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public K getKey() {
            return key;
        }

        @Override
        public V getValue() {
            return value;
        }

        @Override
        public V setValue(V value) {
            throw new UnsupportedOperationException("");
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || !(o instanceof java.util.Map.Entry)) return false;
            final java.util.Map.Entry<?, ?> mapEntry = (java.util.Map.Entry<?, ?>) o;
            return Objects.equals(key, mapEntry.getKey()) &&
                   Objects.equals(value, mapEntry.getValue());
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        @Override
        public String toString() {
            return key + " -> " + value;
        }
    }
}