/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.collection;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;

import androidx.arch.core.internal.SafeIterableMap;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map.Entry;

@RunWith(JUnit4.class)
public class SafeIterableMapTest {

    @Test
    public void testToString() {
        SafeIterableMap<Integer, String> map = from(1, 2, 3, 4).to("a", "b", "c", "d");
        assertThat(map.toString(), is("[1=a, 2=b, 3=c, 4=d]"));
    }

    @Test
    public void testEmptyToString() {
        SafeIterableMap<Integer, Boolean> map = mapOf();
        assertThat(map.toString(), is("[]"));
    }

    @Test
    public void testOneElementToString() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1);
        assertThat(map.toString(), is("[1=true]"));
    }


    @Test
    public void testEquality1() {
        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(10, 20, 30, 40);
        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(10, 20, 30, 40);
        assertThat(map1.equals(map2), is(true));
    }

    @Test
    public void testEquality2() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        //noinspection ObjectEqualsNull
        assertThat(map.equals(null), is(false));
    }

    @Test
    public void testEquality3() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        //noinspection EqualsBetweenInconvertibleTypes
        assertThat(map.equals(new ArrayList<>()), is(false));
    }

    @Test
    public void testEquality4() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        assertThat(map.equals(new SafeIterableMap<Integer, Boolean>()), is(false));
    }

    @Test
    public void testEquality5() {
        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
        SafeIterableMap<Integer, Boolean> map2 = mapOf(1);
        assertThat(map1.equals(map2), is(false));
    }

    @Test
    public void testEquality6() {
        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 2, 3, 5);
        assertThat(map1.equals(map2), is(false));
    }

    @Test
    public void testEquality7() {
        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(1, 2, 3, 4);
        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(1, 2, 3, 5);
        assertThat(map1.equals(map2), is(false));
    }


    @Test
    public void testEquality8() {
        SafeIterableMap<Integer, Boolean> map1 = mapOf();
        SafeIterableMap<Integer, Boolean> map2 = mapOf();
        assertThat(map1.equals(map2), is(true));
    }

    @Test
    public void testEqualityRespectsOrder() {
        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 3, 2, 4);
        assertThat(map1.equals(map2), is(false));
    }

    @Test
    public void testPut() {
        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
        assertThat(map.putIfAbsent(5, 10), is((Integer) null));
        assertThat(map, is(from(1, 2, 3, 4, 5).to(10, 20, 30, 40, 10)));
    }

    @Test
    public void testAddExisted() {
        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 261, 40);
        assertThat(map.putIfAbsent(3, 239), is(261));
        assertThat(map, is(from(1, 2, 3, 4).to(10, 20, 261, 40)));
    }

    @Test
    public void testRemoveLast() {
        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
        assertThat(map.remove(4), is(40));
        assertThat(map, is(from(1, 2, 3).to(10, 20, 30)));
    }

    @Test
    public void testRemoveFirst() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        assertThat(map.remove(1), is(true));
        assertThat(map, is(mapOf(2, 3, 4)));
    }

    @Test
    public void testRemoveMiddle() {
        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
        assertThat(map.remove(2), is(20));
        assertThat(map.remove(3), is(30));
        assertThat(map, is(from(1, 4).to(10, 40)));
    }

    @Test
    public void testRemoveNotExisted() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        assertThat(map.remove(5), is((Boolean) null));
        assertThat(map, is(mapOf(1, 2, 3, 4)));
    }

    @Test
    public void testRemoveSole() {
        SafeIterableMap<Integer, Integer> map = from(1).to(261);
        assertThat(map.remove(1), is(261));
        assertThat(map, is(new SafeIterableMap<Integer, Integer>()));
    }

    @Test
    public void testRemoveDuringIteration1() {
        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
        int index = 0;
        int[] expected = new int[]{1, 4};
        for (Entry<Integer, Integer> i : map) {
            assertThat(i.getKey(), is(expected[index++]));
            if (index == 1) {
                assertThat(map.remove(2), is(20));
                assertThat(map.remove(3), is(30));
            }
        }
    }

    @Test
    public void testRemoveDuringIteration2() {
        SafeIterableMap<Integer, Integer> map = from(1, 2).to(10, 20);
        Iterator<Entry<Integer, Integer>> iter = map.iterator();
        assertThat(map.remove(2), is(20));
        assertThat(map.remove(1), is(10));
        assertThat(iter.hasNext(), is(false));
    }

    @Test
    public void testRemoveDuringIteration3() {
        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
        int index = 0;
        Iterator<Entry<Integer, Integer>> iter = map.iterator();
        assertThat(map.remove(1), is(10));
        assertThat(map.remove(2), is(20));
        int[] expected = new int[]{3, 4};
        while (iter.hasNext()) {
            assertThat(iter.next().getKey(), is(expected[index++]));
        }
    }

    @Test
    public void testRemoveDuringIteration4() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2);
        int[] expected = new int[]{1, 2};
        int index = 0;
        for (Entry<Integer, Boolean> entry : map) {
            assertThat(entry.getKey(), is(expected[index++]));
            if (index == 1) {
                map.remove(1);
            }
        }
        assertThat(index, is(2));
    }

    @Test
    public void testAdditionDuringIteration() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{1, 2, 3, 4};
        int index = 0;
        for (Entry<Integer, Boolean> entry : map) {
            assertThat(entry.getKey(), is(expected[index++]));
            if (index == 1) {
                map.putIfAbsent(5, true);
            }
        }
    }

    @Test
    public void testReAdditionDuringIteration() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{1, 2, 4};
        int index = 0;
        for (Entry<Integer, Boolean> entry : map) {
            assertThat(entry.getKey(), is(expected[index++]));
            if (index == 1) {
                map.remove(3);
                map.putIfAbsent(3, true);
            }
        }
    }

    @Test
    public void testSize() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        assertThat(map.size(), is(4));
        map.putIfAbsent(5, true);
        map.putIfAbsent(6, true);
        assertThat(map.size(), is(6));
        map.remove(5);
        map.remove(5);
        assertThat(map.size(), is(5));
        map.remove(1);
        map.remove(2);
        map.remove(4);
        map.remove(3);
        map.remove(6);
        assertThat(map.size(), is(0));
        map.putIfAbsent(4, true);
        assertThat(map.size(), is(1));
        assertThat(mapOf().size(), is(0));
    }

    @Test
    public void testIteratorWithAdditions1() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{1, 2, 3, 5};
        int index = 0;
        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
        while (iterator.hasNext()) {
            Entry<Integer, Boolean> entry = iterator.next();
            assertThat(entry.getKey(), is(expected[index++]));
            if (index == 3) {
                map.remove(4);
                map.putIfAbsent(5, true);
            }
        }
    }

    @Test
    public void testIteratorWithAdditions2() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1);
        int[] expected = new int[]{1, 2, 3};
        int index = 0;
        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
        while (iterator.hasNext()) {
            Entry<Integer, Boolean> entry = iterator.next();
            assertThat(entry.getKey(), is(expected[index++]));
            if (index == 1) {
                map.putIfAbsent(2, true);
                map.putIfAbsent(3, true);
            }
        }
        assertThat(index, is(3));
    }


    @Test
    public void testIteratorWithAdditions3() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3);
        int[] expected = new int[]{1};
        int index = 0;
        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
        while (iterator.hasNext()) {
            Entry<Integer, Boolean> entry = iterator.next();
            assertThat(entry.getKey(), is(expected[index++]));
            map.remove(2);
            map.remove(3);
        }
        assertThat(index, is(1));
    }

    @Test
    public void testIteratorWithAdditions4() {
        SafeIterableMap<Integer, Boolean> map = mapOf();
        int[] expected = new int[]{1, 2, 3};
        int index = 0;
        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
        map.putIfAbsent(1, true);
        while (iterator.hasNext()) {
            Entry<Integer, Boolean> entry = iterator.next();
            assertThat(entry.getKey(), is(expected[index++]));
            if (index == 1) {
                map.putIfAbsent(2, false);
            }
            if (index == 2) {
                map.putIfAbsent(3, false);
            }
        }
        assertThat(index, is(3));
    }

    @Test
    public void testIteratorWithAddition5() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2);
        int[] expected = new int[]{1, 2};
        int index = 0;
        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
        while (iterator.hasNext()) {
            Entry<Integer, Boolean> entry = iterator.next();
            assertThat(entry.getKey(), is(expected[index++]));
            if (index == 1) {
                map.remove(1);
            }
        }
        assertThat(index, is(2));
    }

    @Test
    public void testDescendingIteration() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{4, 3, 2, 1};
        int index = 0;
        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
            assertThat(iter.next().getKey(), is(expected[index++]));
        }
        assertThat(index, is(4));
    }

    @Test
    public void testDescendingIterationRemove1() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{4, 3, 2};
        int index = 0;
        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
            if (index == 1) {
                map.remove(1);
            }
            assertThat(iter.next().getKey(), is(expected[index++]));
        }
        assertThat(index, is(3));
        assertThat(map.size(), is(3));
    }

    @Test
    public void testDescendingIterationRemove2() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{3, 2, 1};
        int index = 0;
        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
            if (index == 0) {
                map.remove(4);
            }
            assertThat(iter.next().getKey(), is(expected[index++]));
        }
        assertThat(index, is(3));
        assertThat(map.size(), is(3));
    }

    @Test
    public void testDescendingIterationRemove3() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{4, 1};
        int index = 0;
        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
            if (index == 1) {
                map.remove(3);
                map.remove(2);
            }
            assertThat(iter.next().getKey(), is(expected[index++]));
        }
        assertThat(index, is(2));
        assertThat(map.size(), is(2));
    }

    @Test
    public void testDescendingIterationAddition() {
        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
        int[] expected = new int[]{4, 3, 2, 1};
        int index = 0;
        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
            if (index == 0) {
                map.putIfAbsent(5, false);
            }
            assertThat(iter.next().getKey(), is(expected[index++]));
        }
        assertThat(index, is(4));
        assertThat(map.size(), is(5));
    }

    @Test
    public void testDescendingIteratorEmpty() {
        SafeIterableMap<Integer, Boolean> map = mapOf();
        Iterator<Entry<Integer, Boolean>> iterator = map.descendingIterator();
        assertThat(iterator.hasNext(), is(false));
    }

    @Test
    public void testIteratorEmpty() {
        SafeIterableMap<Integer, Boolean> map = mapOf();
        Iterator<Entry<Integer, Boolean>> iterator = map.iterator();
        assertThat(iterator.hasNext(), is(false));
    }

    @Test
    public void testIteratorWithAdditionEmpty() {
        SafeIterableMap<Integer, Boolean> map = mapOf();
        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
        assertThat(iterator.hasNext(), is(false));
    }

    @Test
    public void testEldest() {
        SafeIterableMap<Integer, Boolean> map = mapOf();
        assertThat(map.eldest(), nullValue());
        map.putIfAbsent(1, false);
        assertThat(map.eldest().getKey(), is(1));
        map.putIfAbsent(2, false);
        assertThat(map.eldest().getKey(), is(1));
        map.remove(1);
        assertThat(map.eldest().getKey(), is(2));
        map.remove(2);
        assertThat(map.eldest(), nullValue());
    }

    @Test
    public void testNewest() {
        SafeIterableMap<Integer, Boolean> map = mapOf();
        assertThat(map.newest(), nullValue());
        map.putIfAbsent(1, false);
        assertThat(map.newest().getKey(), is(1));
        map.putIfAbsent(2, false);
        assertThat(map.newest().getKey(), is(2));
        map.remove(2);
        assertThat(map.eldest().getKey(), is(1));
        map.remove(1);
        assertThat(map.newest(), nullValue());
    }


    // for most operations we don't care about values, so we create map from key to true
    @SafeVarargs
    private static <K> SafeIterableMap<K, Boolean> mapOf(K... keys) {
        SafeIterableMap<K, Boolean> map = new SafeIterableMap<>();
        for (K key : keys) {
            map.putIfAbsent(key, true);
        }
        return map;
    }

    @SafeVarargs
    private static <K> MapBuilder<K> from(K... keys) {
        return new MapBuilder<>(keys);
    }

    private static class MapBuilder<K> {
        final K[] mKeys;

        MapBuilder(K[] keys) {
            this.mKeys = keys;
        }

        @SafeVarargs
        public final <V> SafeIterableMap<K, V> to(V... values) {
            assertThat("Failed to build Map", mKeys.length, is(values.length));
            SafeIterableMap<K, V> map = new SafeIterableMap<>();
            for (int i = 0; i < mKeys.length; i++) {
                map.putIfAbsent(mKeys[i], values[i]);
            }
            return map;
        }
    }
}


