package com.atlassian.bitbucket.mesh.util;

import org.junit.Test;

import javax.annotation.Nonnull;
import java.util.Comparator;
import java.util.Objects;
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;

public class MoreStreamsTest {

    @Test
    public void testMergeSortedMergesTwoSortedStreamsCorrectly() {
        Stream<Integer> result = MoreStreams.mergeSorted(
                Stream.of(1, 2, 3, 8, 10),
                Stream.of(4, 5, 6, 7, 9, 11));

        assertThat(result.collect(toList()), contains(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
    }

    @Test
    public void testMergeSortedMergesTwoSortedStreamsUsingStableSort() {
        Ordered a = ord(1, "A");
        Ordered b = ord(1, "B");
        Ordered c = ord(1, "C");
        Ordered d = ord(1, "D");
        Ordered e = ord(1, "E");
        Ordered f = ord(2, "F");
        Ordered g = ord(2, "G");
        Ordered h = ord(2, "H");
        //Order is only defined by the number, when the order is equal between elements, they should appear in the same
        //order they appear in the stream
        Stream<Ordered> result = MoreStreams.mergeSorted(
                Stream.of(a, b, f),
                Stream.of(c, g),
                Stream.of(d, e, h)
        );

        assertThat(result.collect(toList()), contains(a, b, c, d, e, f, g, h));
    }

    @Test
    public void testMergeSortedWithCustomComparator() {
        NonComparable n1 = nonComp(1);
        NonComparable n2 = nonComp(2);
        NonComparable n3 = nonComp(3);
        NonComparable n4 = nonComp(4);
        NonComparable n5 = nonComp(5);
        NonComparable n6 = nonComp(6);
        NonComparable n7 = nonComp(7);
        NonComparable n8 = nonComp(8);

        Stream<NonComparable> result = MoreStreams.mergeSorted(asList(
                Stream.of(n1, n2, n5, n6),
                Stream.of(n3, n4, n7, n8)
        ), comparing(NonComparable::getOrder));

        assertThat(result.collect(toList()), contains(n1, n2, n3, n4, n5, n6, n7, n8));
    }

    private static NonComparable nonComp(int order) {
        return new NonComparable(order);
    }

    private static Ordered ord(int order, String label) {
        return new Ordered(order, label);
    }

    static class NonComparable {

        final int order;

        NonComparable(int order) {
            this.order = order;
        }

        public int getOrder() {
            return order;
        }
    }

    static class Ordered implements Comparable<Ordered> {

        static final Comparator<Ordered> COMPARATOR = comparing(Ordered::getOrder);
        final String label;
        final int order;

        Ordered(int order, String label) {
            this.order = order;
            this.label = label;
        }

        @Override
        public int compareTo(@Nonnull Ordered o) {
            return COMPARATOR.compare(this, o);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Ordered ordered = (Ordered) o;
            return order == ordered.order && Objects.equals(label, ordered.label);
        }

        public int getOrder() {
            return order;
        }

        @Override
        public int hashCode() {
            return Objects.hash(order, label);
        }
    }
}