package com.atlassian.bitbucket.mesh.util;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Test;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import static org.hamcrest.Matchers.*;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.junit.Assert.*;

public class MoreCollectorsTest {

    @Test
    public void testToImmutableList() {
        List<String> list = ImmutableList.of('a', 'b', 'c').stream()
                .map(String::valueOf)
                .collect(MoreCollectors.toImmutableList());
        assertEquals(ImmutableList.of("a", "b", "c"), list);
        assertImmutable(list);
    }

    @Test
    public void testToImmutableListAllowsNull() {
        List<String> list = Stream.of("a", null, "b")
                .collect(MoreCollectors.toImmutableList());
        assertThat(list, hasSize(3));
        assertThat(list, contains(equalTo("a"), nullValue(String.class), equalTo("b")));
        assertImmutable(list);
    }

    @Test
    public void testToImmutableListParallel() {
        List<String> list = ImmutableList.of('a', 'b', 'c').stream()
                .parallel()
                .map(String::valueOf)
                .collect(MoreCollectors.toImmutableList());
        assertEquals(ImmutableList.of("a", "b", "c"), list);
        assertImmutable(list);
    }

    @Test
    public void testToImmutableMap() {
        Map<String, String> map = ImmutableList.of(Pair.of("a", "b"), Pair.of("b", "c")).stream()
                .collect(MoreCollectors.toImmutableMap(Pair::getKey, Pair::getValue));
        assertEquals(ImmutableMap.of("a", "b", "b", "c"), map);
        assertImmutable(map);
    }

    @Test
    public void testToImmutableMapAllowsNullKeys() {
        Map<String, String> map = Stream.of("a", "b")
                .collect(MoreCollectors.toImmutableMap(entry -> "a".equals(entry) ? entry : null, Function.identity()));
        assertThat(map.size(), equalTo(2));
        assertThat(map, hasEntry("a", "a"));
        assertThat(map, hasEntry(null, "b"));
        assertImmutable(map);
    }

    @Test
    public void testToImmutableMapParallel() {
        Map<String, String> map = ImmutableList.of(Pair.of("a", "b"), Pair.of("b", "c"), Pair.of("c", "d")).stream()
                .parallel()
                .collect(MoreCollectors.toImmutableMap(Pair::getKey, Pair::getValue));
        assertEquals(ImmutableMap.of("a", "b", "b", "c", "c", "d"), map);
        assertImmutable(map);
    }

    @Test
    public void testToImmutableSet() {
        Set<Character> set = ImmutableSet.of('a', 'B', 'c').stream()
                .filter(Character::isLowerCase)
                .collect(MoreCollectors.toImmutableSet());
        assertEquals(ImmutableSet.of('a', 'c'), set);
        assertEquals(Character.valueOf('a'), Iterables.get(set, 0)); //Verify the order is stable
        assertImmutable(set);
    }

    @Test
    public void testToImmutableSetAllowsNull() {
        Set<String> set = Stream.of("a", null, "b")
                .collect(MoreCollectors.toImmutableSet());
        assertThat(set, hasSize(3));
        assertThat(set, contains(equalTo("a"), nullValue(String.class), equalTo("b")));
        assertImmutable(set);
    }

    @Test
    public void testToImmutableSetParallel() {
        Set<Character> set = ImmutableSet.of('a', 'B', 'c').stream()
                .parallel()
                .filter(Character::isLowerCase)
                .collect(MoreCollectors.toImmutableSet());
        assertEquals(ImmutableSet.of('a', 'c'), set);
        assertEquals(Character.valueOf('a'), Iterables.get(set, 0)); //Verify the order is stable
        assertImmutable(set);
    }

    private static void assertImmutable(Collection<?> collection) {
        try {
            collection.clear();

            fail("The \"immutable\" collection isn't");
        } catch (UnsupportedOperationException e) {
            //Pass; we shouldn't be able to modify the collection
        }
    }

    private static void assertImmutable(Map<?, ?> map) {
        try {
            map.clear();

            fail("The \"immutable\" map isn't");
        } catch (UnsupportedOperationException e) {
            //Pass; we shouldn't be able to modify the map
        }
    }
}