package com.atlassian.bitbucket.comment;


import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Collections;
import java.util.Iterator;

import static com.google.common.collect.Iterables.transform;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.emptyIterable;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class CommentChainTest {

    @Mock
    private Comment root1;
    @Mock
    private Comment root2;
    @Mock
    private Comment root3;

    @Mock
    private Comment reply11;
    @Mock
    private Comment reply12;
    @Mock
    private Comment reply31;
    @Mock
    private Comment reply32;
    @Mock
    private Comment reply33;

    @Mock
    private Comment reply121;
    @Mock
    private Comment reply122;
    @Mock
    private Comment reply321;

    @Before
    public void setUpThread() {
        when(root1.getId()).thenReturn(1L);
        when(root2.getId()).thenReturn(2L);
        when(root3.getId()).thenReturn(3L);

        when(reply11.getId()).thenReturn(11L);
        when(reply12.getId()).thenReturn(12L);
        when(reply31.getId()).thenReturn(31L);
        when(reply32.getId()).thenReturn(32L);
        when(reply33.getId()).thenReturn(33L);

        when(reply121.getId()).thenReturn(121L);
        when(reply122.getId()).thenReturn(122L);
        when(reply321.getId()).thenReturn(321L);

        when(root1.getComments()).thenReturn(ImmutableList.of(reply11, reply12));
        when(root2.getComments()).thenReturn(Collections.<Comment>emptyList());
        when(root3.getComments()).thenReturn(ImmutableList.of(reply31, reply32, reply33));

        when(reply11.getComments()).thenReturn(Collections.<Comment>emptyList());
        when(reply12.getComments()).thenReturn(ImmutableList.of(reply121, reply122));
        when(reply31.getComments()).thenReturn(Collections.<Comment>emptyList());
        when(reply32.getComments()).thenReturn(ImmutableList.of(reply321));
        when(reply33.getComments()).thenReturn(Collections.<Comment>emptyList());
    }

    @SuppressWarnings("ConstantConditions")
    @Test(expected = NullPointerException.class)
    public void shouldNotAcceptNullRootComments() {
        new CommentChain<>(null);
    }

    @Test
    public void shouldCreateValidIteratorForEmptyList() {
        assertThat(new CommentChain<>(Collections.<Comment>emptyList()), emptyIterable());
    }

    @Test
    public void shouldTraverseRepliesDepthFirst() {
        Iterable<Long> commentIds = ImmutableList.copyOf(
                transform(createChain(), Comment::getId));
        assertThat(commentIds, contains(1L, 11L, 12L, 121L, 122L, 2L, 3L, 31L, 32L, 321L, 33L));
    }

    @Test(expected = UnsupportedOperationException.class)
    public void shouldReturnReadOnlyIterator() {
        Iterator<Comment> iterator = createChain().iterator();

        assertTrue(iterator.hasNext());
        iterator.next();
        iterator.remove();
    }

    @Test
    public void shouldReturnIndependentIterators() {
        CommentChain<Comment> chain = createChain();
        Iterator<Comment> iterator1 = chain.iterator();
        Iterator<Comment> iterator2 = chain.iterator();

        // the total number of comments is 11
        for (int i = 0; i < 11; i++) {
            assertTrue(iterator1.hasNext());
            assertTrue(iterator2.hasNext());

            assertSame(iterator1.next(), iterator2.next());
        }
    }

    @Test
    public void shouldSupportCommentSubclasses() {
        CommentExtension commentExtension = mock(CommentExtension.class);
        CommentExtension commentExtensionReply = mock(CommentExtension.class);
        when(commentExtension.getComments()).thenReturn(ImmutableList.<Comment>of(commentExtensionReply));

        assertThat(new CommentChain<>(ImmutableList.of(commentExtension)),
                contains(commentExtension, commentExtensionReply));
    }

    private CommentChain<Comment> createChain() {
        return new CommentChain<>(ImmutableList.of(root1, root2, root3));
    }

    public static interface CommentExtension extends Comment {
    }
}
